├── go.mod ├── .gitignore ├── go.sum ├── go ├── operators │ ├── equal.go │ └── equal_test.go └── metrics │ ├── metrics_test.go │ └── metrics.go ├── buttons ├── buttons.go ├── button_string.go └── example_test.go ├── encodings ├── encodings.go └── encoding_string.go ├── rfbflags ├── rfbflag_string.go └── rfbflags.go ├── .github ├── workflows │ └── go.yml └── copilot-instructions.md ├── messages ├── servermessage_string.go ├── clientmessage_string.go └── messages.go ├── LICENSE ├── encodings_test.go ├── doc.go ├── common.go ├── common_test.go ├── initialization.go ├── security.go ├── logging └── logging.go ├── security_test.go ├── keys ├── example_test.go ├── keys_test.go ├── keys.go └── key_string.go ├── initialization_test.go ├── pixel_format.go ├── pixel_format_test.go ├── vncclient_test.go ├── encodings.go ├── handshake.go ├── README.md ├── client.go ├── server_test.go ├── client_test.go ├── vncclient.go ├── handshake_test.go └── server.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kward/go-vnc 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | golang.org/x/mod v0.29.0 // indirect 7 | golang.org/x/sync v0.17.0 // indirect 8 | golang.org/x/tools v0.38.0 // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | # OS X files 27 | .DS_Store 28 | ._* 29 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= 2 | golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= 3 | golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= 4 | golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 5 | golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= 6 | golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= 7 | -------------------------------------------------------------------------------- /go/operators/equal.go: -------------------------------------------------------------------------------- 1 | /* 2 | The operators package provides additional operators that aren't part of the 3 | base Go installation. 4 | */ 5 | 6 | package operators 7 | 8 | // EqualSlicesOfByte compares two byte slices for equality. 9 | func EqualSlicesOfByte(x, y []byte) bool { 10 | // Special cases. 11 | switch { 12 | case len(x) != len(y): 13 | return false 14 | } 15 | for i, v := range x { 16 | if v != y[i] { 17 | return false 18 | } 19 | } 20 | return true 21 | } 22 | -------------------------------------------------------------------------------- /buttons/buttons.go: -------------------------------------------------------------------------------- 1 | // Package buttons describes the supported button masks. 2 | package buttons 3 | 4 | // Button represents a mask of pointer presses/releases. 5 | type Button uint8 6 | 7 | //go:generate stringer -type=Button 8 | 9 | // All available button mask components. 10 | const ( 11 | Left Button = 1 << iota 12 | Middle 13 | Right 14 | Four 15 | Five 16 | Six 17 | Seven 18 | Eight 19 | None Button = 0 20 | ) 21 | 22 | func Mask(button Button) uint8 { 23 | return uint8(button) 24 | } 25 | -------------------------------------------------------------------------------- /encodings/encodings.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package encodings provides constants for the known VNC encoding types. 3 | https://tools.ietf.org/html/rfc6143#section-7.7 4 | */ 5 | package encodings 6 | 7 | // Encoding represents a known VNC encoding type. 8 | type Encoding int32 9 | 10 | //go:generate stringer -type=Encoding 11 | 12 | const ( 13 | Raw Encoding = 0 14 | CopyRect Encoding = 1 15 | RRE Encoding = 2 16 | Hextile Encoding = 5 17 | TRLE Encoding = 15 18 | ZRLE Encoding = 16 19 | ColorPseudo Encoding = -239 20 | DesktopSizePseudo Encoding = -223 21 | ) 22 | -------------------------------------------------------------------------------- /rfbflags/rfbflag_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=RFBFlag"; DO NOT EDIT. 2 | 3 | package rfbflags 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[RFBFalse-0] 12 | _ = x[RFBTrue-1] 13 | } 14 | 15 | const _RFBFlag_name = "RFBFalseRFBTrue" 16 | 17 | var _RFBFlag_index = [...]uint8{0, 8, 15} 18 | 19 | func (i RFBFlag) String() string { 20 | idx := int(i) - 0 21 | if i < 0 || idx >= len(_RFBFlag_index)-1 { 22 | return "RFBFlag(" + strconv.FormatInt(int64(i), 10) + ")" 23 | } 24 | return _RFBFlag_name[_RFBFlag_index[idx]:_RFBFlag_index[idx+1]] 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | go-version: [ '1.21.x', '1.22.x' ] 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: ${{ matrix.go-version }} 21 | - name: Install staticcheck 22 | run: go install honnef.co/go/tools/cmd/staticcheck@latest 23 | - name: Staticcheck 24 | run: staticcheck ./... 25 | - name: Go vet 26 | run: go vet ./... 27 | - name: Run tests 28 | run: go test ./... 29 | -------------------------------------------------------------------------------- /go/operators/equal_test.go: -------------------------------------------------------------------------------- 1 | package operators 2 | 3 | import "testing" 4 | 5 | func TestEqualSlicesOfByte(t *testing.T) { 6 | // Equal. 7 | if !EqualSlicesOfByte(nil, nil) { 8 | t.Errorf("EqualSlicesOfByte([]byte): nil == nil") 9 | } 10 | if !EqualSlicesOfByte([]byte{}, []byte{}) { 11 | t.Errorf("EqualSlicesOfByte([]byte): [] == []") 12 | } 13 | if !EqualSlicesOfByte([]byte{1, 2, 3}, []byte{1, 2, 3}) { 14 | t.Errorf("EqualSlicesOfByte([]byte): [1, 2, 3] == [1, 2, 3]") 15 | } 16 | // Not equal. 17 | if EqualSlicesOfByte([]byte{1, 2, 3}, nil) { 18 | t.Errorf("EqualSlicesOfByte([]byte): [1, 2, 3] != nil") 19 | } 20 | if EqualSlicesOfByte([]byte{1, 2, 3}, []byte{}) { 21 | t.Errorf("EqualSlicesOfByte([]byte): [1, 2, 3] != []") 22 | } 23 | if EqualSlicesOfByte([]byte{1, 2, 3}, []byte{4, 5, 6}) { 24 | t.Errorf("EqualSlicesOfByte([]byte): [1, 2, 3] != [4, 5, 6]") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rfbflags/rfbflags.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package rfbflags provides constants for the RFB flag values. 3 | */ 4 | package rfbflags 5 | 6 | // RFBFlag represents a RFB Flag value. 7 | type RFBFlag uint8 8 | 9 | //go:generate stringer -type=RFBFlag 10 | 11 | // RFB flag values. 12 | const ( 13 | RFBFalse RFBFlag = iota 14 | RFBTrue 15 | ) 16 | 17 | func BoolToRFBFlag(b bool) RFBFlag { 18 | if b { 19 | return RFBTrue 20 | } 21 | return RFBFalse 22 | } 23 | 24 | // To returns true if the flag value != RFBFalse. 25 | func ToBool(f RFBFlag) bool { 26 | // Using != as any non-zero RFBFlag value equates to true. 27 | return f != RFBFalse 28 | } 29 | 30 | // IsBigEndian returns true if the flag value != RFBFalse. 31 | func IsBigEndian(f RFBFlag) bool { 32 | return ToBool(f) 33 | } 34 | 35 | // IsTrueColor returns true if the flag value != RFBFalse. 36 | func IsTrueColor(f RFBFlag) bool { 37 | return ToBool(f) 38 | } 39 | -------------------------------------------------------------------------------- /messages/servermessage_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=ServerMessage"; DO NOT EDIT. 2 | 3 | package messages 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[FramebufferUpdate-0] 12 | _ = x[SetColorMapEntries-1] 13 | _ = x[Bell-2] 14 | _ = x[ServerCutText-3] 15 | } 16 | 17 | const _ServerMessage_name = "FramebufferUpdateSetColorMapEntriesBellServerCutText" 18 | 19 | var _ServerMessage_index = [...]uint8{0, 17, 35, 39, 52} 20 | 21 | func (i ServerMessage) String() string { 22 | idx := int(i) - 0 23 | if i < 0 || idx >= len(_ServerMessage_index)-1 { 24 | return "ServerMessage(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | return _ServerMessage_name[_ServerMessage_index[idx]:_ServerMessage_index[idx+1]] 27 | } 28 | -------------------------------------------------------------------------------- /messages/clientmessage_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=ClientMessage"; DO NOT EDIT. 2 | 3 | package messages 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[SetPixelFormat-0] 12 | _ = x[SetEncodings-2] 13 | _ = x[FramebufferUpdateRequest-3] 14 | _ = x[KeyEvent-4] 15 | _ = x[PointerEvent-5] 16 | _ = x[ClientCutText-6] 17 | } 18 | 19 | const ( 20 | _ClientMessage_name_0 = "SetPixelFormat" 21 | _ClientMessage_name_1 = "SetEncodingsFramebufferUpdateRequestKeyEventPointerEventClientCutText" 22 | ) 23 | 24 | var ( 25 | _ClientMessage_index_1 = [...]uint8{0, 12, 36, 44, 56, 69} 26 | ) 27 | 28 | func (i ClientMessage) String() string { 29 | switch { 30 | case i == 0: 31 | return _ClientMessage_name_0 32 | case 2 <= i && i <= 6: 33 | i -= 2 34 | return _ClientMessage_name_1[_ClientMessage_index_1[i]:_ClientMessage_index_1[i+1]] 35 | default: 36 | return "ClientMessage(" + strconv.FormatInt(int64(i), 10) + ")" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Mitchell Hashimoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /messages/messages.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package messages provides constants for the client and server messages. 3 | */ 4 | package messages 5 | 6 | //----------------------------------------------------------------------------- 7 | // Client messages 8 | // 9 | // Client-to-Server: https://tools.ietf.org/html/rfc6143#section-7.5 10 | 11 | // ClientMessage represents a Client-to-Server RFB message type. 12 | type ClientMessage uint8 13 | 14 | //go:generate stringer -type=ClientMessage 15 | 16 | // Client-to-Server message types. 17 | const ( 18 | SetPixelFormat ClientMessage = iota 19 | _ 20 | SetEncodings 21 | FramebufferUpdateRequest 22 | KeyEvent 23 | PointerEvent 24 | ClientCutText 25 | ) 26 | 27 | //----------------------------------------------------------------------------- 28 | // Server messages 29 | // 30 | // Server-to-Client: https://tools.ietf.org/html/rfc6143#section-7.6 31 | 32 | // ServerMessage represents a Server-to-Client RFB message type. 33 | type ServerMessage uint8 34 | 35 | //go:generate stringer -type=ServerMessage 36 | 37 | // Server-to-Client message types. 38 | const ( 39 | FramebufferUpdate ServerMessage = iota 40 | SetColorMapEntries 41 | Bell 42 | ServerCutText 43 | ) 44 | -------------------------------------------------------------------------------- /buttons/button_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Button"; DO NOT EDIT. 2 | 3 | package buttons 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[Left-1] 12 | _ = x[Middle-2] 13 | _ = x[Right-4] 14 | _ = x[Four-8] 15 | _ = x[Five-16] 16 | _ = x[Six-32] 17 | _ = x[Seven-64] 18 | _ = x[Eight-128] 19 | _ = x[None-0] 20 | } 21 | 22 | const ( 23 | _Button_name_0 = "NoneLeftMiddle" 24 | _Button_name_1 = "Right" 25 | _Button_name_2 = "Four" 26 | _Button_name_3 = "Five" 27 | _Button_name_4 = "Six" 28 | _Button_name_5 = "Seven" 29 | _Button_name_6 = "Eight" 30 | ) 31 | 32 | var ( 33 | _Button_index_0 = [...]uint8{0, 4, 8, 14} 34 | ) 35 | 36 | func (i Button) String() string { 37 | switch { 38 | case i <= 2: 39 | return _Button_name_0[_Button_index_0[i]:_Button_index_0[i+1]] 40 | case i == 4: 41 | return _Button_name_1 42 | case i == 8: 43 | return _Button_name_2 44 | case i == 16: 45 | return _Button_name_3 46 | case i == 32: 47 | return _Button_name_4 48 | case i == 64: 49 | return _Button_name_5 50 | case i == 128: 51 | return _Button_name_6 52 | default: 53 | return "Button(" + strconv.FormatInt(int64(i), 10) + ")" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /encodings/encoding_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Encoding"; DO NOT EDIT. 2 | 3 | package encodings 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[Raw-0] 12 | _ = x[CopyRect-1] 13 | _ = x[RRE-2] 14 | _ = x[Hextile-5] 15 | _ = x[TRLE-15] 16 | _ = x[ZRLE-16] 17 | _ = x[ColorPseudo - -239] 18 | _ = x[DesktopSizePseudo - -223] 19 | } 20 | 21 | const ( 22 | _Encoding_name_0 = "ColorPseudo" 23 | _Encoding_name_1 = "DesktopSizePseudo" 24 | _Encoding_name_2 = "RawCopyRectRRE" 25 | _Encoding_name_3 = "Hextile" 26 | _Encoding_name_4 = "TRLEZRLE" 27 | ) 28 | 29 | var ( 30 | _Encoding_index_2 = [...]uint8{0, 3, 11, 14} 31 | _Encoding_index_4 = [...]uint8{0, 4, 8} 32 | ) 33 | 34 | func (i Encoding) String() string { 35 | switch { 36 | case i == -239: 37 | return _Encoding_name_0 38 | case i == -223: 39 | return _Encoding_name_1 40 | case 0 <= i && i <= 2: 41 | return _Encoding_name_2[_Encoding_index_2[i]:_Encoding_index_2[i+1]] 42 | case i == 5: 43 | return _Encoding_name_3 44 | case 15 <= i && i <= 16: 45 | i -= 15 46 | return _Encoding_name_4[_Encoding_index_4[i]:_Encoding_index_4[i+1]] 47 | default: 48 | return "Encoding(" + strconv.FormatInt(int64(i), 10) + ")" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /encodings_test.go: -------------------------------------------------------------------------------- 1 | package vnc 2 | 3 | // TODO(kward): Fully test the encodings. 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/kward/go-vnc/encodings" 9 | "github.com/kward/go-vnc/go/operators" 10 | ) 11 | 12 | func TestEncoding_Marshal(t *testing.T) { 13 | encs := Encodings{&RawEncoding{}} 14 | bytes, err := encs.Marshal() 15 | if err != nil { 16 | t.Errorf("unexpected error; %s", err) 17 | } 18 | if got, want := bytes, []byte{0, 0, 0, 0}; !operators.EqualSlicesOfByte(got, want) { 19 | t.Errorf("incorrect result; got = %v, want = %v", got, want) 20 | } 21 | } 22 | 23 | func TestRawEncoding_Type(t *testing.T) { 24 | e := &RawEncoding{} 25 | if got, want := e.Type(), encodings.Raw; got != want { 26 | t.Errorf("incorrect encoding; got = %s, want = %s", got, want) 27 | } 28 | } 29 | 30 | func TestRawEncoding_Marshal(t *testing.T) { 31 | for _, tt := range []struct { 32 | desc string 33 | e *RawEncoding 34 | data []byte 35 | }{ 36 | {"empty data", 37 | &RawEncoding{[]Color{}}, 38 | []byte{}}, 39 | {"single color", 40 | &RawEncoding{[]Color{ 41 | Color{&PixelFormat16bit, &ColorMap{}, 0, 127, 7, 0}}}, 42 | []byte{0, 127}}, 43 | {"multiple colors", 44 | &RawEncoding{[]Color{ 45 | Color{&PixelFormat16bit, &ColorMap{}, 0, 127, 7, 0}, 46 | Color{&PixelFormat16bit, &ColorMap{}, 0, 32767, 2047, 127}}}, 47 | []byte{0, 127, 127, 255}}, 48 | } { 49 | data, err := tt.e.Marshal() 50 | if err != nil { 51 | t.Errorf("%s: unexpected error: %s", tt.desc, err) 52 | continue 53 | } 54 | if got, want := data, tt.data; !operators.EqualSlicesOfByte(got, want) { 55 | t.Errorf("%s: incorrect result; got = %v, want = %v", tt.desc, got, want) 56 | continue 57 | } 58 | } 59 | } 60 | 61 | func TestRawEncoding_Read(t *testing.T) {} 62 | 63 | func TestDesktopSizePseudoEncoding_Type(t *testing.T) { 64 | e := &DesktopSizePseudoEncoding{} 65 | if got, want := e.Type(), encodings.DesktopSizePseudo; got != want { 66 | t.Errorf("incorrect encoding; got = %s, want = %s", got, want) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /go/metrics/metrics_test.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | ) 7 | 8 | func TestCounter(t *testing.T) { 9 | reset() 10 | 11 | c := NewCounter("test") 12 | if got, want := c.Value(), uint64(0); got != want { 13 | t.Errorf("initial value incorrect; got = %v, want = %v", got, want) 14 | } 15 | 16 | c.Increment() 17 | if got, want := c.Value(), uint64(1); got != want { 18 | t.Errorf("incremented value incorrect; got = %v, want = %v", got, want) 19 | } 20 | 21 | c.Reset() 22 | if got, want := c.Value(), uint64(0); got != want { 23 | t.Errorf("reset value incorrect; got = %v, want = %v", got, want) 24 | } 25 | 26 | if got, want := c.Name(), "test"; got != want { 27 | t.Errorf("name incorrect; got = %v, want = %v", got, want) 28 | } 29 | } 30 | 31 | func TestGauge(t *testing.T) { 32 | reset() 33 | 34 | c := NewGauge("test") 35 | if got, want := c.Value(), uint64(0); got != want { 36 | t.Errorf("initial value incorrect; got = %v, want = %v", got, want) 37 | } 38 | 39 | c.Adjust(123) 40 | if got, want := c.Value(), uint64(123); got != want { 41 | t.Errorf("incremented value incorrect; got = %v, want = %v", got, want) 42 | } 43 | 44 | c.Adjust(-23) 45 | if got, want := c.Value(), uint64(100); got != want { 46 | t.Errorf("decremented value incorrect; got = %v, want = %v", got, want) 47 | } 48 | 49 | c.Adjust(-456) 50 | if got, want := c.Value(), uint64(0); got != want { 51 | t.Errorf("minimum value incorrect; got = %v, want = %v", got, want) 52 | } 53 | 54 | c.Adjust(math.MaxInt64) 55 | c.Adjust(math.MaxInt64) 56 | c.Adjust(math.MaxInt64) 57 | if got, want := c.Value(), uint64(math.MaxUint64); got != want { 58 | t.Errorf("maximum value incorrect; got = %v, want = %v", got, want) 59 | } 60 | 61 | c.Reset() 62 | if got, want := c.Value(), uint64(0); got != want { 63 | t.Errorf("reset value incorrect; got = %v, want = %v", got, want) 64 | } 65 | 66 | if got, want := c.Name(), "test"; got != want { 67 | t.Errorf("name incorrect; got = %v, want = %v", got, want) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package vnc provides VNC client implementation. 3 | 4 | This package implements The Remote Framebuffer Protocol as documented in 5 | [RFC 6143](http://tools.ietf.org/html/rfc6143). 6 | 7 | A basic VNC client can be created like this. Replace the IP on the net.Dial line 8 | with something more appropriate for your setup. 9 | 10 | package main 11 | 12 | import ( 13 | "context" 14 | "log" 15 | "net" 16 | "time" 17 | 18 | "github.com/kward/go-vnc" 19 | "github.com/kward/go-vnc/messages" 20 | "github.com/kward/go-vnc/rfbflags" 21 | ) 22 | 23 | func main() { 24 | // Establish TCP connection to VNC server. 25 | nc, err := net.Dial("tcp", "127.0.0.1:5900") 26 | if err != nil { 27 | log.Fatalf("Error connecting to VNC host. %v", err) 28 | } 29 | 30 | // Negotiate connection with the server. 31 | vcc := vnc.NewClientConfig("some_password") 32 | vc, err := vnc.Connect(context.Background(), nc, vcc) 33 | if err != nil { 34 | log.Fatalf("Error negotiating connection to VNC host. %v", err) 35 | } 36 | 37 | // Periodically request framebuffer updates. 38 | go func() { 39 | w, h := vc.FramebufferWidth(), vc.FramebufferHeight() 40 | for { 41 | if err := vc.FramebufferUpdateRequest(rfbflags.RFBTrue, 0, 0, w, h); err != nil { 42 | log.Printf("error requesting framebuffer update: %v", err) 43 | } 44 | time.Sleep(1 * time.Second) 45 | } 46 | }() 47 | 48 | // Listen and handle server messages. 49 | go vc.ListenAndHandle() 50 | 51 | // Process messages coming in on the ServerMessage channel. 52 | for { 53 | msg := <-vcc.ServerMessageCh 54 | switch msg.Type() { 55 | case messages.FramebufferUpdate: 56 | log.Println("Received FramebufferUpdate message.") 57 | default: 58 | log.Printf("Received message type:%v msg:%v\n", msg.Type(), msg) 59 | } 60 | } 61 | } 62 | 63 | This example will connect to a VNC server running on the localhost. It will 64 | periodically request updates from the server, and listen for and handle 65 | incoming FramebufferUpdate messages coming from the server. 66 | */ 67 | package vnc 68 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | // Common things that aren't part of the RFB protocol. 2 | 3 | package vnc 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | "fmt" 9 | "time" 10 | ) 11 | 12 | // VNCError implements error interface. 13 | type VNCError struct { 14 | desc string 15 | } 16 | 17 | // NewVNCError returns a custom VNCError error. 18 | func NewVNCError(desc string) error { 19 | return &VNCError{desc} 20 | } 21 | 22 | // Error returns an VNCError as a string. 23 | func (e VNCError) Error() string { 24 | return e.desc 25 | } 26 | 27 | func Errorf(format string, a ...interface{}) error { 28 | return &VNCError{ 29 | desc: fmt.Sprintf(format, a...), 30 | } 31 | } 32 | 33 | var settleDuration = 25 * time.Millisecond 34 | 35 | // Settle returns the UI settle duration. 36 | func Settle() time.Duration { 37 | return settleDuration 38 | } 39 | 40 | // SetSettle changes the UI settle duration. 41 | func SetSettle(s time.Duration) { 42 | settleDuration = s 43 | } 44 | 45 | // settleUI allows the UI to "settle" before the next UI change is made. 46 | func settleUI() { 47 | time.Sleep(settleDuration) 48 | } 49 | 50 | type Buffer struct { 51 | buf *bytes.Buffer // byte stream 52 | } 53 | 54 | func NewBuffer(b []byte) *Buffer { 55 | return &Buffer{buf: bytes.NewBuffer(b)} 56 | } 57 | 58 | func (b *Buffer) Bytes() []byte { 59 | return b.buf.Bytes() 60 | } 61 | 62 | func (b *Buffer) Read(data interface{}) error { 63 | return binary.Read(b.buf, binary.BigEndian, data) 64 | } 65 | 66 | func (b *Buffer) Write(data interface{}) error { 67 | return binary.Write(b.buf, binary.BigEndian, data) 68 | } 69 | 70 | func (b *Buffer) WriteByte(c byte) error { 71 | return b.buf.WriteByte(c) 72 | } 73 | 74 | // Marshaler is the interface satisfied for marshaling messages. 75 | type Marshaler interface { 76 | // Marshal returns the wire encoding of a message. 77 | Marshal() ([]byte, error) 78 | } 79 | 80 | // Unarshaler is the interface satisfied for unmarshaling messages. 81 | type Unmarshaler interface { 82 | // Unmarshal parses a wire format message into a message. 83 | Unmarshal(data []byte) error 84 | } 85 | 86 | // MarshalerUnmarshaler satisfies both the Marshaler and Unmarshaler interfaces. 87 | type MarshalerUnmarshaler interface { 88 | Marshaler 89 | Unmarshaler 90 | } 91 | -------------------------------------------------------------------------------- /buttons/example_test.go: -------------------------------------------------------------------------------- 1 | package buttons_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kward/go-vnc/buttons" 7 | ) 8 | 9 | // ExampleButton demonstrates using individual mouse button constants. 10 | func ExampleButton() { 11 | // Individual buttons 12 | fmt.Printf("Left button: %s (0x%x)\n", buttons.Left, uint8(buttons.Left)) 13 | fmt.Printf("Middle button: %s (0x%x)\n", buttons.Middle, uint8(buttons.Middle)) 14 | fmt.Printf("Right button: %s (0x%x)\n", buttons.Right, uint8(buttons.Right)) 15 | 16 | // Additional buttons 17 | fmt.Printf("Button 4: %s (0x%x)\n", buttons.Four, uint8(buttons.Four)) 18 | fmt.Printf("Button 5: %s (0x%x)\n", buttons.Five, uint8(buttons.Five)) 19 | 20 | // Output: 21 | // Left button: Left (0x1) 22 | // Middle button: Middle (0x2) 23 | // Right button: Right (0x4) 24 | // Button 4: Four (0x8) 25 | // Button 5: Five (0x10) 26 | } 27 | 28 | // Example_buttonMask demonstrates combining multiple buttons into a button mask. 29 | // VNC PointerEvent messages use a button mask to indicate which buttons are pressed. 30 | func Example_buttonMask() { 31 | // Single button press 32 | mask := uint8(buttons.Left) 33 | fmt.Printf("Left click: mask=0x%x\n", mask) 34 | 35 | // Multiple buttons pressed simultaneously (e.g., left+right) 36 | mask = uint8(buttons.Left | buttons.Right) 37 | fmt.Printf("Left+Right: mask=0x%x\n", mask) 38 | 39 | // All mouse buttons pressed 40 | mask = uint8(buttons.Left | buttons.Middle | buttons.Right) 41 | fmt.Printf("All buttons: mask=0x%x\n", mask) 42 | 43 | // Output: 44 | // Left click: mask=0x1 45 | // Left+Right: mask=0x5 46 | // All buttons: mask=0x7 47 | } 48 | 49 | // Example_checkButton demonstrates checking if a specific button is pressed 50 | // in a button mask. 51 | func Example_checkButton() { 52 | // Simulate a mask with left and middle buttons pressed 53 | mask := uint8(buttons.Left | buttons.Middle) 54 | 55 | // Check individual buttons 56 | leftPressed := (mask & uint8(buttons.Left)) != 0 57 | middlePressed := (mask & uint8(buttons.Middle)) != 0 58 | rightPressed := (mask & uint8(buttons.Right)) != 0 59 | 60 | fmt.Printf("Button mask: 0x%x\n", mask) 61 | fmt.Printf("Left pressed: %v\n", leftPressed) 62 | fmt.Printf("Middle pressed: %v\n", middlePressed) 63 | fmt.Printf("Right pressed: %v\n", rightPressed) 64 | 65 | // Output: 66 | // Button mask: 0x3 67 | // Left pressed: true 68 | // Middle pressed: true 69 | // Right pressed: false 70 | } 71 | -------------------------------------------------------------------------------- /common_test.go: -------------------------------------------------------------------------------- 1 | package vnc 2 | 3 | import ( 4 | "bytes" 5 | "net" 6 | "testing" 7 | "time" 8 | 9 | "github.com/kward/go-vnc/go/operators" 10 | ) 11 | 12 | func TestBuffer_Read(t *testing.T) { 13 | var ( 14 | buf *Buffer 15 | tbyte byte 16 | tint32 int32 17 | ) 18 | 19 | buf = NewBuffer(nil) 20 | if err := buf.Read(&tint32); err == nil { 21 | t.Error("expected error") 22 | } 23 | 24 | buf = NewBuffer([]byte{123}) 25 | if err := buf.Read(&tbyte); err != nil { 26 | t.Errorf("unexpected error: %v", err) 27 | } 28 | if got, want := tbyte, byte(123); got != want { 29 | t.Errorf("incorrect result; got = %v, want = %v", got, want) 30 | } 31 | 32 | buf = NewBuffer([]byte{0, 18, 214, 135}) 33 | if err := buf.Read(&tint32); err != nil { 34 | t.Errorf("unexpected error: %v", err) 35 | } 36 | if got, want := tint32, int32(1234567); got != want { 37 | t.Errorf("incorrect result; got = %v, want = %v", got, want) 38 | } 39 | } 40 | 41 | func TestBuffer_Write(t *testing.T) { 42 | var buf *Buffer 43 | 44 | buf = NewBuffer(nil) 45 | if err := buf.Write(byte(234)); err != nil { 46 | t.Errorf("unexpected error: %v", err) 47 | } 48 | if got, want := buf.Bytes(), []byte{234}; !operators.EqualSlicesOfByte(got, want) { 49 | t.Errorf("incorrect result; got = %v, want = %v", got, want) 50 | } 51 | 52 | buf = NewBuffer(nil) 53 | if err := buf.Write(int32(23637)); err != nil { 54 | t.Errorf("unexpected error: %v", err) 55 | } 56 | if got, want := buf.Bytes(), []byte{0, 0, 92, 85}; !operators.EqualSlicesOfByte(got, want) { 57 | t.Errorf("incorrect result; got = %v, want = %v", got, want) 58 | } 59 | } 60 | 61 | // MockConn implements the net.Conn interface. 62 | type MockConn struct { 63 | b bytes.Buffer 64 | } 65 | 66 | func (m *MockConn) Read(b []byte) (int, error) { 67 | return m.b.Read(b) 68 | } 69 | func (m *MockConn) Write(b []byte) (int, error) { 70 | return m.b.Write(b) 71 | } 72 | func (m *MockConn) Close() error { return nil } 73 | func (m *MockConn) LocalAddr() net.Addr { return nil } 74 | func (m *MockConn) RemoteAddr() net.Addr { return nil } 75 | func (m *MockConn) SetDeadline(t time.Time) error { return nil } 76 | func (m *MockConn) SetReadDeadline(t time.Time) error { return nil } 77 | func (m *MockConn) SetWriteDeadline(t time.Time) error { return nil } 78 | 79 | // Implement additional buffer.Buffer functions. 80 | func (m *MockConn) Reset() { 81 | m.b.Reset() 82 | } 83 | -------------------------------------------------------------------------------- /initialization.go: -------------------------------------------------------------------------------- 1 | // Implementation of RFC 6143 §7.3 Initialization Messages. 2 | 3 | package vnc 4 | 5 | import ( 6 | "io" 7 | 8 | "github.com/kward/go-vnc/logging" 9 | "github.com/kward/go-vnc/rfbflags" 10 | ) 11 | 12 | // clientInit implements §7.3.1 ClientInit. 13 | func (c *ClientConn) clientInit() error { 14 | if logging.V(logging.FnDeclLevel) { 15 | logging.Infof("%s", logging.FnName()) 16 | } 17 | 18 | sharedFlag := rfbflags.BoolToRFBFlag(!c.config.Exclusive) 19 | if logging.V(logging.ResultLevel) { 20 | logging.Infof("sharedFlag: %d", sharedFlag) 21 | } 22 | if err := c.send(sharedFlag); err != nil { 23 | return err 24 | } 25 | 26 | // TODO(kward)20170226): VENUE responds with some sort of shared flag 27 | // response, which includes the VENUE name and IPs. Handle this? 28 | 29 | return nil 30 | } 31 | 32 | // ServerInit message sent after server receives a ClientInit message. 33 | // https://tools.ietf.org/html/rfc6143#section-7.3.2 34 | type ServerInit struct { 35 | FBWidth, FBHeight uint16 36 | PixelFormat PixelFormat 37 | NameLength uint32 38 | // Name is of variable length, and must be read separately. 39 | } 40 | 41 | const serverInitLen = 24 // Not including Name. 42 | 43 | // Verify that interfaces are honored. 44 | var _ Unmarshaler = (*ServerInit)(nil) 45 | 46 | // Read implements 47 | func (m *ServerInit) Read(r io.Reader) error { 48 | buf := make([]byte, serverInitLen) 49 | if _, err := io.ReadAtLeast(r, buf, serverInitLen); err != nil { 50 | return err 51 | } 52 | return m.Unmarshal(buf) 53 | } 54 | 55 | func (m *ServerInit) Unmarshal(data []byte) error { 56 | buf := NewBuffer(data) 57 | var msg ServerInit 58 | if err := buf.Read(&msg); err != nil { 59 | return err 60 | } 61 | *m = msg 62 | return nil 63 | } 64 | 65 | // serverInit implements §7.3.2 ServerInit. 66 | func (c *ClientConn) serverInit() error { 67 | if logging.V(logging.FnDeclLevel) { 68 | logging.Infof("%s", logging.FnName()) 69 | } 70 | 71 | var msg ServerInit 72 | if err := msg.Read(c.c); err != nil { 73 | return Errorf("failure reading ServerInit message; %v", err) 74 | } 75 | if logging.V(logging.ResultLevel) { 76 | logging.Infof("ServerInit message: %v", msg) 77 | } 78 | 79 | c.setFramebufferWidth(msg.FBWidth) 80 | c.setFramebufferHeight(msg.FBHeight) 81 | c.pixelFormat = msg.PixelFormat 82 | 83 | name := make([]uint8, msg.NameLength) 84 | if err := c.receive(&name); err != nil { 85 | return err 86 | } 87 | c.setDesktopName(string(name)) 88 | 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /security.go: -------------------------------------------------------------------------------- 1 | // Implementation of RFC 6143 §7.2 Security Types. 2 | 3 | package vnc 4 | 5 | import ( 6 | "crypto/des" 7 | 8 | "github.com/kward/go-vnc/logging" 9 | ) 10 | 11 | const ( 12 | secTypeInvalid = uint8(0) 13 | secTypeNone = uint8(1) 14 | secTypeVNCAuth = uint8(2) 15 | ) 16 | 17 | // ClientAuth implements a method of authenticating with a remote server. 18 | type ClientAuth interface { 19 | // SecurityType returns the byte identifier sent by the server to 20 | // identify this authentication scheme. 21 | SecurityType() uint8 22 | 23 | // Handshake is called when the authentication handshake should be 24 | // performed, as part of the general RFB handshake. (see 7.2.1) 25 | Handshake(*ClientConn) error 26 | } 27 | 28 | // ClientAuthNone is the "none" authentication. See 7.2.1. 29 | type ClientAuthNone struct{} 30 | 31 | func (*ClientAuthNone) SecurityType() uint8 { 32 | return secTypeNone 33 | } 34 | 35 | func (*ClientAuthNone) Handshake(conn *ClientConn) error { 36 | if logging.V(logging.FnDeclLevel) { 37 | logging.Infof("ClientAuthNone.%s", logging.FnName()) 38 | } 39 | 40 | return nil 41 | } 42 | 43 | // ClientAuthVNC is the standard password authentication. See 7.2.2. 44 | type ClientAuthVNC struct { 45 | Password string 46 | } 47 | 48 | type vncAuthChallenge [16]byte 49 | 50 | func (*ClientAuthVNC) SecurityType() uint8 { 51 | return secTypeVNCAuth 52 | } 53 | 54 | func (auth *ClientAuthVNC) Handshake(conn *ClientConn) error { 55 | if logging.V(logging.FnDeclLevel) { 56 | logging.Infof("ClientAuthVNC.%s", logging.FnName()) 57 | } 58 | 59 | if auth.Password == "" { 60 | return NewVNCError("Security Handshake failed; no password provided for VNCAuth") 61 | } 62 | 63 | // Read challenge block 64 | var challenge vncAuthChallenge 65 | if err := conn.receive(&challenge); err != nil { 66 | return err 67 | } 68 | 69 | auth.encode(&challenge) 70 | 71 | // Send the encrypted challenge back to server 72 | if err := conn.send(challenge); err != nil { 73 | return err 74 | } 75 | 76 | return nil 77 | } 78 | 79 | func (auth *ClientAuthVNC) encode(ch *vncAuthChallenge) error { 80 | // Copy password string to 8 byte 0-padded slice 81 | key := make([]byte, 8) 82 | copy(key, auth.Password) 83 | 84 | // Each byte of the password needs to be reversed. This is a 85 | // non RFC-documented behaviour of VNC clients and servers 86 | for i := range key { 87 | key[i] = (key[i]&0x55)<<1 | (key[i]&0xAA)>>1 // Swap adjacent bits 88 | key[i] = (key[i]&0x33)<<2 | (key[i]&0xCC)>>2 // Swap adjacent pairs 89 | key[i] = (key[i]&0x0F)<<4 | (key[i]&0xF0)>>4 // Swap the 2 halves 90 | } 91 | 92 | // Encrypt challenge with key. 93 | cipher, err := des.NewCipher(key) 94 | if err != nil { 95 | return err 96 | } 97 | for i := 0; i < len(ch); i += cipher.BlockSize() { 98 | cipher.Encrypt(ch[i:i+cipher.BlockSize()], ch[i:i+cipher.BlockSize()]) 99 | } 100 | 101 | return nil 102 | } 103 | -------------------------------------------------------------------------------- /logging/logging.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package logging provides a small facade over Go's slog with repo-specific helpers. 3 | */ 4 | package logging 5 | 6 | import ( 7 | "fmt" 8 | "log/slog" 9 | "os" 10 | "runtime" 11 | "strings" 12 | "sync/atomic" 13 | ) 14 | 15 | // Verbosity levels used throughout the repo; higher is more verbose. 16 | const ( 17 | FlowLevel = 2 18 | FnDeclLevel = 3 19 | ResultLevel = 4 20 | SpamLevel = 5 21 | CrazySpamLevel = 6 22 | ) 23 | 24 | var verbosity atomic.Int32 // if verbosity >= level, V(level) is true 25 | 26 | // SetVerbosity sets the global verbosity level (e.g., 0 disables V checks, 5 enables spam-level logs). 27 | func SetVerbosity(v int) { verbosity.Store(int32(v)) } 28 | 29 | // V reports whether logs guarded at the given level should be emitted. 30 | func V(level int) bool { return verbosity.Load() >= int32(level) } 31 | 32 | var logger atomic.Value // holds *slog.Logger 33 | 34 | func init() { 35 | logger.Store(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))) 36 | } 37 | 38 | // SetLogger overrides the global logger used by this package. 39 | func SetLogger(l *slog.Logger) { 40 | if l == nil { 41 | l = slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo})) 42 | } 43 | logger.Store(l) 44 | } 45 | 46 | func get() *slog.Logger { return logger.Load().(*slog.Logger) } 47 | 48 | // Structured logging helpers (prefer when you have keyvals). 49 | func Info(msg string, attrs ...any) { get().Info(msg, attrs...) } 50 | func Debug(msg string, attrs ...any) { get().Debug(msg, attrs...) } 51 | func Warn(msg string, attrs ...any) { get().Warn(msg, attrs...) } 52 | func Error(msg string, attrs ...any) { get().Error(msg, attrs...) } 53 | 54 | // Infof logs an informational message. 55 | func Infof(format string, args ...interface{}) { get().Info(fmt.Sprintf(format, args...)) } 56 | 57 | // Debugf logs a debug message. 58 | func Debugf(format string, args ...interface{}) { get().Debug(fmt.Sprintf(format, args...)) } 59 | 60 | // Warnf logs a warning message. 61 | func Warnf(format string, args ...interface{}) { get().Warn(fmt.Sprintf(format, args...)) } 62 | 63 | // Errorf logs an error message. 64 | func Errorf(format string, args ...interface{}) { get().Error(fmt.Sprintf(format, args...)) } 65 | 66 | // FnName returns the calling function name, e.g. "SomeFunction()". 67 | func FnName() string { 68 | pc := make([]uintptr, 10) // At least 1 entry needed. 69 | runtime.Callers(2, pc) 70 | name := runtime.FuncForPC(pc[0]).Name() 71 | return name[strings.LastIndex(name, ".")+1:] + "()" 72 | } 73 | 74 | // FnNameWithArgs returns the calling function name with supplied argument values embedded. 75 | func FnNameWithArgs(format string, args ...interface{}) string { 76 | pc := make([]uintptr, 10) // At least 1 entry needed. 77 | runtime.Callers(2, pc) 78 | name := runtime.FuncForPC(pc[0]).Name() 79 | a := []interface{}{name[strings.LastIndex(name, ".")+1:]} 80 | a = append(a, args...) 81 | return fmt.Sprintf("%s("+format+")", a...) 82 | } 83 | -------------------------------------------------------------------------------- /security_test.go: -------------------------------------------------------------------------------- 1 | //lint:file-ignore S1021 stylistic suggestions not necessary for tests 2 | package vnc 3 | 4 | import ( 5 | "encoding/hex" 6 | "io" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestClientAuthNone_Impl(t *testing.T) { 12 | var raw interface{} 13 | raw = new(ClientAuthNone) 14 | if _, ok := raw.(ClientAuth); !ok { 15 | t.Fatal("ClientAuthNone doesn't implement ClientAuth") 16 | } 17 | } 18 | 19 | func TestClientAuthVNC_Impl(t *testing.T) { 20 | var raw interface{} 21 | raw = new(ClientAuthVNC) 22 | if _, ok := raw.(ClientAuth); !ok { 23 | t.Fatal("ClientAuthVNC doesn't implement ClientAuth") 24 | } 25 | } 26 | 27 | // wiresharkToChallenge converts VNC authentication challenge and response 28 | // values captured with Wireshark (https://www.wireshark.org) into usable byte 29 | // streams. 30 | func wiresharkToChallenge(h string) vncAuthChallenge { 31 | var ch vncAuthChallenge 32 | r := strings.NewReplacer(":", "") 33 | b, err := hex.DecodeString(r.Replace(h)) 34 | if err != nil { 35 | return ch 36 | } 37 | copy(ch[:], b) 38 | return ch 39 | } 40 | 41 | type clientAuthVNCTest struct { 42 | pw, ch, res string 43 | } 44 | 45 | var clientAuthVNCTests []clientAuthVNCTest = []clientAuthVNCTest{ 46 | {".", "7f:e2:e1:3d:a4:ae:10:9c:54:c5:5f:52:74:aa:db:31", "1d:86:92:71:1f:00:24:35:02:d3:91:ef:e9:bc:c5:d5"}, 47 | {"12345678", "13:8e:a4:2e:0e:66:f3:ad:2d:f3:08:c3:04:cd:c4:2a", "5b:e1:56:fa:49:49:ef:56:d3:f8:44:97:73:27:95:9f"}, 48 | {"abc123", "c6:30:45:d2:57:9e:e7:f2:f9:0c:62:3e:52:40:86:c6", "a3:63:59:e4:28:c8:7f:b3:45:2c:d7:e0:ca:d6:70:3e"}, 49 | } 50 | 51 | func TestClientAuthVNC_Handshake(t *testing.T) { 52 | mockConn := &MockConn{} 53 | conn := NewClientConn(mockConn, &ClientConfig{}) 54 | 55 | for _, tt := range clientAuthVNCTests { 56 | mockConn.Reset() 57 | 58 | // Send challenge. 59 | ch := wiresharkToChallenge(tt.ch) 60 | if err := conn.send(ch); err != nil { 61 | t.Errorf("error sending challenge: %v", err) 62 | continue 63 | } 64 | 65 | // Perform handshake. 66 | auth := ClientAuthVNC{tt.pw} 67 | if err := auth.Handshake(conn); err != nil { 68 | t.Errorf("error performing handshake: %v", err) 69 | } 70 | 71 | // Validate response. 72 | var res vncAuthChallenge 73 | if err := conn.receive(&res); err != nil { 74 | t.Errorf("error reading response: %v", err) 75 | } 76 | if got, want := res, wiresharkToChallenge(tt.res); got != want { 77 | t.Errorf("incorrect response; got = %v, want = %v", got, want) 78 | } 79 | 80 | // Ensure nothing extra was sent. 81 | var buf []byte 82 | if err := conn.receiveN(&buf, 1024); err != io.EOF { 83 | t.Errorf("expected EOF; got = %v", err) 84 | } 85 | } 86 | } 87 | 88 | func TestClientAuthVNC_encode(t *testing.T) { 89 | for i, tt := range clientAuthVNCTests { 90 | ch := wiresharkToChallenge(tt.ch) 91 | a := ClientAuthVNC{tt.pw} 92 | if err := a.encode(&ch); err != nil { 93 | t.Errorf("%v: error encoding response: %v", i, err) 94 | } 95 | res := wiresharkToChallenge(tt.res) 96 | if got, want := ch, res; got != want { 97 | t.Errorf("%v: encode failed; got = %v, want = %v", i, got, want) 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /go/metrics/metrics.go: -------------------------------------------------------------------------------- 1 | /* 2 | The metrics package provides support for tracking various metrics. 3 | */ 4 | package metrics 5 | 6 | import ( 7 | "fmt" 8 | "log" 9 | "math" 10 | "net/http" 11 | ) 12 | 13 | // TODO(kward): Add the following stats: 14 | // - MultiLevel 15 | // - MinuteHour 16 | // - VariableMap 17 | // TODO(kward): Consider locking. 18 | 19 | type Metric interface { 20 | // Adjust increments or decrements the metric value. 21 | Adjust(int64) 22 | 23 | // Increment increases the metric value by one. 24 | Increment() 25 | 26 | // Name returns the varz name. 27 | Name() string 28 | 29 | // Reset the metric. 30 | Reset() 31 | 32 | // Value returns the current metric value. 33 | Value() uint64 34 | } 35 | 36 | var metrics map[string]Metric 37 | 38 | func init() { 39 | reset() 40 | 41 | http.Handle("/varz", http.HandlerFunc(Varz)) 42 | } 43 | 44 | func add(metric Metric) error { 45 | if _, ok := metrics[metric.Name()]; ok { 46 | return fmt.Errorf("Metric %v already exists", metric.Name()) 47 | } 48 | metrics[metric.Name()] = metric 49 | return nil 50 | } 51 | 52 | func reset() { 53 | metrics = map[string]Metric{} 54 | } 55 | 56 | func Adjust(name string, val int64) { 57 | m, ok := metrics[name] 58 | if ok { 59 | m.Adjust(val) 60 | } 61 | } 62 | 63 | func Varz(w http.ResponseWriter, r *http.Request) { 64 | w.Header().Set("Content-Type", "text/plain; charset=utf-8") 65 | for _, m := range metrics { 66 | fmt.Fprintf(w, "%v\n", m.Value()) 67 | } 68 | } 69 | 70 | // Counter provides a simple monotonically incrementing counter. 71 | type Counter struct { 72 | name string 73 | val uint64 74 | } 75 | 76 | func NewCounter(name string) *Counter { 77 | c := &Counter{name: name} 78 | if err := add(c); err != nil { 79 | return nil 80 | } 81 | return c 82 | } 83 | 84 | func (c *Counter) Adjust(val int64) { 85 | log.Fatal("A Counter metric cannot be adjusted") 86 | } 87 | 88 | func (c *Counter) Increment() { 89 | c.val++ 90 | } 91 | 92 | func (c *Counter) Name() string { 93 | return c.name 94 | } 95 | 96 | func (c *Counter) Reset() { 97 | c.val = 0 98 | } 99 | 100 | func (c *Counter) Value() uint64 { 101 | return c.val 102 | } 103 | 104 | // The Gauge type represents a non-negative integer, which may increase or 105 | // decrease, but shall never exceed the maximum value. 106 | type Gauge struct { 107 | name string 108 | val uint64 109 | } 110 | 111 | func NewGauge(name string) *Gauge { 112 | g := &Gauge{name: name} 113 | if err := add(g); err != nil { 114 | return nil 115 | } 116 | return g 117 | } 118 | 119 | // Adjust allows one to increase or decrease a metric. 120 | func (g *Gauge) Adjust(val int64) { 121 | // The value is positive. 122 | if val > 0 { 123 | if g.val == math.MaxUint64 { 124 | return 125 | } 126 | v := g.val + uint64(val) 127 | if v > g.val { 128 | g.val = v 129 | return 130 | } 131 | // The value wrapped, so set to maximum allowed value. 132 | g.val = math.MaxUint64 133 | return 134 | } 135 | 136 | // The value is negative. 137 | v := g.val - uint64(-val) 138 | if v < g.val { 139 | g.val = v 140 | return 141 | } 142 | // The value wrapped, so set to zero. 143 | g.val = 0 144 | } 145 | 146 | func (g *Gauge) Increment() { 147 | log.Fatal("A Gauge metric cannot be adjusted") 148 | } 149 | 150 | func (g *Gauge) Name() string { 151 | return g.name 152 | } 153 | 154 | func (g *Gauge) Reset() { 155 | g.val = 0 156 | } 157 | 158 | func (g *Gauge) Value() uint64 { 159 | return g.val 160 | } 161 | -------------------------------------------------------------------------------- /keys/example_test.go: -------------------------------------------------------------------------------- 1 | package keys_test 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/kward/go-vnc/keys" 7 | ) 8 | 9 | // ExampleFromRune demonstrates converting individual runes to Key values. 10 | // FromRune handles printable ASCII, extended Latin-1, and common control characters. 11 | func ExampleFromRune() { 12 | // Printable ASCII characters 13 | k, ok := keys.FromRune('A') 14 | if ok { 15 | fmt.Printf("'A' -> %s (0x%x)\n", k, uint32(k)) 16 | } 17 | 18 | // Control character 19 | k, ok = keys.FromRune('\n') 20 | if ok { 21 | fmt.Printf("'\\n' -> %s (0x%x)\n", k, uint32(k)) 22 | } 23 | 24 | // Unsupported character 25 | _, ok = keys.FromRune('😀') 26 | fmt.Printf("'😀' supported: %v\n", ok) 27 | 28 | // Output: 29 | // 'A' -> A (0x41) 30 | // '\n' -> Linefeed (0xff0a) 31 | // '😀' supported: false 32 | } 33 | 34 | // ExampleTextToKeys demonstrates converting a string to a slice of Key values. 35 | // This is useful for simulating typing text via VNC. 36 | func ExampleTextToKeys() { 37 | // Convert a simple string to keys 38 | ks, err := keys.TextToKeys("Hello") 39 | if err != nil { 40 | fmt.Printf("Error: %v\n", err) 41 | return 42 | } 43 | fmt.Printf("'Hello' -> %d keys\n", len(ks)) 44 | fmt.Printf("First key: %s\n", ks[0]) 45 | 46 | // String with control characters 47 | ks, err = keys.TextToKeys("line1\nline2") 48 | if err != nil { 49 | fmt.Printf("Error: %v\n", err) 50 | return 51 | } 52 | fmt.Printf("'line1\\nline2' -> %d keys (includes Linefeed)\n", len(ks)) 53 | 54 | // Unsupported characters produce an error 55 | _, err = keys.TextToKeys("test😀") 56 | fmt.Printf("Error with emoji: %v\n", err != nil) 57 | 58 | // Output: 59 | // 'Hello' -> 5 keys 60 | // First key: H 61 | // 'line1\nline2' -> 11 keys (includes Linefeed) 62 | // Error with emoji: true 63 | } 64 | 65 | // ExampleIntToKeys demonstrates converting an integer to Key values. 66 | // This is useful for typing numbers via VNC. 67 | func ExampleIntToKeys() { 68 | // Positive number 69 | ks := keys.IntToKeys(123) 70 | fmt.Printf("123 -> %d keys:", len(ks)) 71 | for _, k := range ks { 72 | fmt.Printf(" %s", k) 73 | } 74 | fmt.Println() 75 | 76 | // Negative number (includes minus sign) 77 | ks = keys.IntToKeys(-42) 78 | fmt.Printf("-42 -> %d keys:", len(ks)) 79 | for _, k := range ks { 80 | fmt.Printf(" %s", k) 81 | } 82 | fmt.Println() 83 | 84 | // Zero 85 | ks = keys.IntToKeys(0) 86 | fmt.Printf("0 -> %d keys: %s\n", len(ks), ks[0]) 87 | 88 | // Output: 89 | // 123 -> 3 keys: Digit1 Digit2 Digit3 90 | // -42 -> 3 keys: Minus Digit4 Digit2 91 | // 0 -> 1 keys: Digit0 92 | } 93 | 94 | // ExampleKey demonstrates direct Key constant usage for special keys. 95 | func ExampleKey() { 96 | // Special keys are available as constants 97 | fmt.Printf("Enter key: %s (0x%x)\n", keys.Return, uint32(keys.Return)) 98 | fmt.Printf("Escape key: %s (0x%x)\n", keys.Escape, uint32(keys.Escape)) 99 | fmt.Printf("Tab key: %s (0x%x)\n", keys.Tab, uint32(keys.Tab)) 100 | 101 | // Function keys 102 | fmt.Printf("F1 key: %s (0x%x)\n", keys.F1, uint32(keys.F1)) 103 | 104 | // Modifier keys 105 | fmt.Printf("Left Shift: %s (0x%x)\n", keys.ShiftLeft, uint32(keys.ShiftLeft)) 106 | fmt.Printf("Left Control: %s (0x%x)\n", keys.ControlLeft, uint32(keys.ControlLeft)) 107 | 108 | // Output: 109 | // Enter key: Return (0xff0d) 110 | // Escape key: Escape (0xff1b) 111 | // Tab key: Tab (0xff09) 112 | // F1 key: F1 (0xffbe) 113 | // Left Shift: ShiftLeft (0xffe1) 114 | // Left Control: ControlLeft (0xffe3) 115 | } 116 | -------------------------------------------------------------------------------- /initialization_test.go: -------------------------------------------------------------------------------- 1 | package vnc 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | ) 7 | 8 | func TestClientInit(t *testing.T) { 9 | tests := []struct { 10 | exclusive bool 11 | shared uint8 12 | }{ 13 | {true, 0}, 14 | {false, 1}, 15 | } 16 | 17 | mockConn := &MockConn{} 18 | conn := NewClientConn(mockConn, &ClientConfig{}) 19 | 20 | for _, tt := range tests { 21 | mockConn.Reset() 22 | 23 | // Send client initialization. 24 | conn.config.Exclusive = tt.exclusive 25 | if err := conn.clientInit(); err != nil { 26 | t.Fatalf("unexpected error; %s", err) 27 | } 28 | 29 | // Validate server reception. 30 | var shared uint8 31 | if err := conn.receive(&shared); err != nil { 32 | t.Errorf("error receiving client init; %s", err) 33 | continue 34 | } 35 | if got, want := shared, tt.shared; got != want { 36 | t.Errorf("incorrect shared-flag: got = %d, want = %d", got, want) 37 | continue 38 | } 39 | 40 | // Ensure nothing extra was sent by client. 41 | var buf []byte 42 | if err := conn.receiveN(&buf, 1024); err != io.EOF { 43 | t.Errorf("expected EOF; %s", err) 44 | continue 45 | } 46 | } 47 | } 48 | 49 | func TestServerInit(t *testing.T) { 50 | const ( 51 | none = iota 52 | fbw 53 | fbh 54 | pf 55 | dn 56 | ) 57 | tests := []struct { 58 | eof int 59 | fbWidth, fbHeight uint16 60 | pixelFormat PixelFormat 61 | desktopName string 62 | }{ 63 | // Valid protocol. 64 | {dn, 100, 200, NewPixelFormat(16), "foo"}, 65 | // Invalid protocol (missing fields). 66 | {eof: none}, 67 | {eof: fbw, fbWidth: 1}, 68 | {eof: fbh, fbWidth: 2, fbHeight: 1}, 69 | {eof: pf, fbWidth: 3, fbHeight: 2, pixelFormat: NewPixelFormat(16)}, 70 | } 71 | 72 | mockConn := &MockConn{} 73 | conn := NewClientConn(mockConn, &ClientConfig{}) 74 | 75 | for i, tt := range tests { 76 | mockConn.Reset() 77 | if tt.eof >= fbw { 78 | if err := conn.send(tt.fbWidth); err != nil { 79 | t.Fatal(err) 80 | } 81 | } 82 | if tt.eof >= fbh { 83 | if err := conn.send(tt.fbHeight); err != nil { 84 | t.Fatal(err) 85 | } 86 | } 87 | if tt.eof >= pf { 88 | pfBytes, err := tt.pixelFormat.Marshal() 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | if err := conn.send(pfBytes); err != nil { 93 | t.Fatal(err) 94 | } 95 | } 96 | if tt.eof >= dn { 97 | if err := conn.send(uint32(len(tt.desktopName))); err != nil { 98 | t.Fatal(err) 99 | } 100 | if err := conn.send([]byte(tt.desktopName)); err != nil { 101 | t.Fatal(err) 102 | } 103 | } 104 | 105 | // Validate server message handling. 106 | err := conn.serverInit() 107 | if tt.eof < dn && err == nil { 108 | t.Fatalf("%v: expected error", i) 109 | } 110 | if tt.eof < dn { 111 | // The protocol was incomplete; no point in checking values. 112 | continue 113 | } 114 | if err != nil { 115 | t.Fatalf("%v: unexpected error %v", i, err) 116 | } 117 | if conn.fbWidth != tt.fbWidth { 118 | t.Errorf("FramebufferWidth: got = %v, want = %v", conn.fbWidth, tt.fbWidth) 119 | } 120 | if conn.fbHeight != tt.fbHeight { 121 | t.Errorf("FramebufferHeight: got = %v, want = %v", conn.fbHeight, tt.fbHeight) 122 | } 123 | if !equalPixelFormat(conn.pixelFormat, tt.pixelFormat) { 124 | t.Errorf("PixelFormat: got = %v, want = %v", conn.pixelFormat, tt.pixelFormat) 125 | } 126 | if conn.DesktopName() != tt.desktopName { 127 | t.Errorf("DesktopName: got = %v, want = %v", conn.DesktopName(), tt.desktopName) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # Copilot instructions for go-vnc 2 | 3 | Purpose: This repo is a VNC client library that implements RFC 6143. Files are organized to mirror the spec sections and expose a small API for negotiating a connection and sending client messages. 4 | 5 | ## Big picture architecture 6 | - Connection flow: `Connect(ctx, net.Conn, *ClientConfig) (*ClientConn, error)` in `vncclient.go` runs, in order: protocol version (§7.1.1), security (§7.1.2/7.1.3), client init (§7.3.1), server init (§7.3.2), then sends `SetEncodings` and `SetPixelFormat`. 7 | - Message routing: `ClientConn.ListenAndHandle()` reads a `messages.ServerMessage` byte, looks up a prototype in `ClientConfig.ServerMessages`, calls `Read(*ClientConn)` on it, then pushes the parsed message onto `ServerMessageCh`. 8 | - Encodings: Server-to-client rectangles are represented by `Rectangle` with an `Encoding` strategy (see `encodings.go`). Raw is always supported; other encodings must be included in `ClientConn.encodings`. 9 | - Pixel format and color: `PixelFormat` describes wire pixel layout; `Color` and `ColorMap` translate wire values. True-color vs color-mapped behavior is handled in `Color.Unmarshal`. 10 | 11 | ## Key files and how to extend 12 | - Handshake and init: `handshake.go`, `security.go`, `initialization.go` (map to RFC §7.1–7.3). 13 | - Client->Server messages: `client.go` (§7.5). Example: `FramebufferUpdateRequest`, `KeyEvent`, `PointerEvent`. 14 | - Server->Client messages: `server.go` (§7.6). Includes `FramebufferUpdate`, `SetColorMapEntries`, `Bell`, `ServerCutText`. 15 | - Encodings: `encodings.go` defines the `Encoding` interface and implementations like `RawEncoding`. To add one: implement `Encoding` (Type, Read, Marshal), add a constant in `encodings/encodings.go`, and ensure `ClientConn.Encodable` can return it (include in `ClientConn.encodings`). 16 | - Messages enums: `messages/messages.go` defines wire message ids, used across the codebase. 17 | 18 | ## Conventions and patterns specific to this repo 19 | - Files mirror RFC sections; wire structs embed padding fields to match on-the-wire layout and use big-endian via `Buffer` helpers. 20 | - Default encodings include only Raw. If your client must handle desktop resizes, include `DesktopSizePseudoEncoding` in `ClientConn.encodings` before calling `SetEncodings`. 21 | - Logging uses `logging.V(level) && logging.Infof(...)` patterns backed by Go's slog. Treat logs as optional; do not introduce mandatory flag parsing in library code. Configure with `logging.SetVerbosity(level)` and optionally provide a custom slog logger via `logging.SetLogger(...)`. 22 | - A small UI settle delay is applied after client input (`KeyEvent`, `PointerEvent`, `ClientCutText`). Tests disable it via `SetSettle(0)`. 23 | - Context tuning: the `Connect` path honors ctx value `"vnc_max_proto_version"` with values "3.3" or "3.8". 24 | 25 | ## Developer workflows 26 | - Build/test (modules): `go test ./...` from repo root. No external services required; tests use an in-memory `MockConn`. 27 | - Examples: See `doc.go` for a minimal client that dials a TCP VNC server, calls `Connect`, and then uses `ListenAndHandle` plus periodic `FramebufferUpdateRequest`. 28 | - Adding a server message: implement `ServerMessage` (Type, Read). Add an instance to `ClientConfig.ServerMessages`; `ListenAndHandle` dispatches by Type(). 29 | - Adding a client message: follow the pattern in `client.go`—define a wire struct with explicit field sizes and call `c.send(...)`. 30 | 31 | ## Gotchas (repo-specific) 32 | - Metrics accounting in `send/receive` is approximate; don’t rely on it for exact byte counts. 33 | - If you need alpha in images, note `colorsToImage` sets alpha to 1 (nearly transparent). Adjust if you depend on visibility. 34 | - Keep wire-level structs unexported when possible; expose higher-level helpers for users. 35 | -------------------------------------------------------------------------------- /keys/keys_test.go: -------------------------------------------------------------------------------- 1 | package keys 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestIntToKeys(t *testing.T) { 9 | for _, tt := range []struct { 10 | val int 11 | keys Keys 12 | }{ 13 | {-1234, Keys{Minus, Digit1, Digit2, Digit3, Digit4}}, 14 | {0, Keys{Digit0}}, 15 | {5678, Keys{Digit5, Digit6, Digit7, Digit8}}, 16 | } { 17 | if got, want := IntToKeys(tt.val), tt.keys; !reflect.DeepEqual(got, want) { 18 | t.Errorf("IntToKeys(%d) = %v, want %v", tt.val, got, want) 19 | continue 20 | } 21 | } 22 | } 23 | 24 | func TestASCIIMappingToKeyConstants(t *testing.T) { 25 | // Digits '0'..'9' 26 | for r, k := range map[rune]Key{ 27 | '0': Digit0, '1': Digit1, '2': Digit2, '3': Digit3, '4': Digit4, 28 | '5': Digit5, '6': Digit6, '7': Digit7, '8': Digit8, '9': Digit9, 29 | '-': Minus, ' ': Space, 30 | 'A': A, 'Z': Z, 31 | 'a': SmallA, 'z': SmallZ, 32 | } { 33 | if got := Key(r); got != k { 34 | t.Fatalf("Key(%q) = %v, want %v", r, got, k) 35 | } 36 | } 37 | } 38 | 39 | func TestControlKeyValues(t *testing.T) { 40 | // Anchor a few well-known control keys to expected codes. 41 | // These mirror X11 KeySym values used by RFB. 42 | tests := []struct { 43 | name string 44 | key Key 45 | want Key 46 | }{ 47 | {"BackSpace", BackSpace, 0xff08}, 48 | {"Tab", Tab, 0xff09}, 49 | {"Return", Return, 0xff0d}, 50 | {"Escape", Escape, 0xff1b}, 51 | {"Delete", Delete, 0xffff}, 52 | } 53 | for _, tt := range tests { 54 | if tt.key != tt.want { 55 | t.Errorf("%s = 0x%x, want 0x%x", tt.name, uint32(tt.key), uint32(tt.want)) 56 | } 57 | } 58 | } 59 | 60 | func TestFromRune(t *testing.T) { 61 | tests := []struct { 62 | name string 63 | r rune 64 | want Key 65 | ok bool 66 | }{ 67 | // Printable ASCII 68 | {"space", ' ', Space, true}, 69 | {"digit0", '0', Digit0, true}, 70 | {"digit9", '9', Digit9, true}, 71 | {"minus", '-', Minus, true}, 72 | {"A", 'A', A, true}, 73 | {"Z", 'Z', Z, true}, 74 | {"a", 'a', SmallA, true}, 75 | {"z", 'z', SmallZ, true}, 76 | {"tilde", '~', AsciiTilde, true}, 77 | // Control characters 78 | {"newline", '\n', Linefeed, true}, 79 | {"tab", '\t', Tab, true}, 80 | {"backspace", '\b', BackSpace, true}, 81 | {"return", '\r', Return, true}, 82 | // Extended Latin-1 83 | {"non-breaking space", '\u00A0', Key(0xA0), true}, 84 | {"yen", '¥', Key(0xA5), true}, 85 | // Unsupported 86 | {"emoji", '😀', 0, false}, 87 | {"high unicode", '\u2013', 0, false}, 88 | {"control below 0x20", '\x01', 0, false}, 89 | } 90 | for _, tt := range tests { 91 | t.Run(tt.name, func(t *testing.T) { 92 | got, ok := FromRune(tt.r) 93 | if ok != tt.ok { 94 | t.Errorf("FromRune(%q) ok = %v, want %v", tt.r, ok, tt.ok) 95 | } 96 | if ok && got != tt.want { 97 | t.Errorf("FromRune(%q) = %v, want %v", tt.r, got, tt.want) 98 | } 99 | }) 100 | } 101 | } 102 | 103 | func TestTextToKeys(t *testing.T) { 104 | tests := []struct { 105 | name string 106 | text string 107 | want Keys 108 | wantErr bool 109 | }{ 110 | {"empty", "", Keys{}, false}, 111 | {"digits", "123", Keys{Digit1, Digit2, Digit3}, false}, 112 | {"mixed", "A0-z", Keys{A, Digit0, Minus, SmallZ}, false}, 113 | {"with newline", "hi\n", Keys{SmallH, SmallI, Linefeed}, false}, 114 | {"with tab", "a\tb", Keys{SmallA, Tab, SmallB}, false}, 115 | {"emoji fail", "test😀", nil, true}, 116 | {"high unicode fail", "test—more", nil, true}, 117 | } 118 | for _, tt := range tests { 119 | t.Run(tt.name, func(t *testing.T) { 120 | got, err := TextToKeys(tt.text) 121 | if (err != nil) != tt.wantErr { 122 | t.Errorf("TextToKeys(%q) error = %v, wantErr %v", tt.text, err, tt.wantErr) 123 | return 124 | } 125 | if !tt.wantErr && !reflect.DeepEqual(got, tt.want) { 126 | t.Errorf("TextToKeys(%q) = %v, want %v", tt.text, got, tt.want) 127 | } 128 | }) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /pixel_format.go: -------------------------------------------------------------------------------- 1 | // Implementation of RFC 6143 §7.4 Pixel Format Data Structure. 2 | 3 | package vnc 4 | 5 | import ( 6 | "encoding/binary" 7 | "fmt" 8 | "io" 9 | 10 | "github.com/kward/go-vnc/rfbflags" 11 | ) 12 | 13 | var ( 14 | PixelFormat8bit PixelFormat = NewPixelFormat(8) 15 | PixelFormat16bit PixelFormat = NewPixelFormat(16) 16 | PixelFormat32bit PixelFormat = NewPixelFormat(32) 17 | ) 18 | 19 | // PixelFormat describes the way a pixel is formatted for a VNC connection. 20 | type PixelFormat struct { 21 | BPP uint8 // bits-per-pixel 22 | Depth uint8 // depth 23 | BigEndian rfbflags.RFBFlag // big-endian-flag 24 | TrueColor rfbflags.RFBFlag // true-color-flag 25 | RedMax, GreenMax, BlueMax uint16 // red-, green-, blue-max (2^BPP-1) 26 | RedShift, GreenShift, BlueShift uint8 // red-, green-, blue-shift 27 | _ [3]byte // padding 28 | } 29 | 30 | const pixelFormatLen = 16 31 | 32 | // Verify that interfaces are honored. 33 | var _ fmt.Stringer = (*PixelFormat)(nil) 34 | var _ MarshalerUnmarshaler = (*PixelFormat)(nil) 35 | 36 | // NewPixelFormat returns a populated PixelFormat structure. 37 | func NewPixelFormat(bpp uint8) PixelFormat { 38 | bigEndian := rfbflags.RFBTrue 39 | // Avoid float rounding/overflow; cap at 0xFFFF since fields are uint16. 40 | var rgbMax uint16 41 | if bpp >= 16 { 42 | rgbMax = 0xFFFF 43 | } else { 44 | rgbMax = uint16((1 << bpp) - 1) 45 | } 46 | var ( 47 | tc = rfbflags.RFBTrue 48 | rs, gs, bs uint8 49 | ) 50 | switch bpp { 51 | case 8: 52 | tc = rfbflags.RFBFalse 53 | rs, gs, bs = 0, 0, 0 54 | case 16: 55 | rs, gs, bs = 0, 4, 8 56 | case 32: 57 | rs, gs, bs = 0, 8, 16 58 | } 59 | return PixelFormat{bpp, bpp, bigEndian, tc, rgbMax, rgbMax, rgbMax, rs, gs, bs, [3]byte{}} 60 | } 61 | 62 | // Marshal implements the Marshaler interface. 63 | func (pf PixelFormat) Marshal() ([]byte, error) { 64 | // Validation checks. 65 | switch pf.BPP { 66 | case 8, 16, 32: 67 | default: 68 | return nil, NewVNCError(fmt.Sprintf("Invalid BPP value %v; must be 8, 16, or 32", pf.BPP)) 69 | } 70 | 71 | if pf.Depth < pf.BPP { 72 | return nil, NewVNCError(fmt.Sprintf("Invalid Depth value %v; cannot be < BPP", pf.Depth)) 73 | } 74 | switch pf.Depth { 75 | case 8, 16, 32: 76 | default: 77 | return nil, NewVNCError(fmt.Sprintf("Invalid Depth value %v; must be 8, 16, or 32", pf.Depth)) 78 | } 79 | 80 | // Create the slice of bytes 81 | buf := NewBuffer(nil) 82 | if err := buf.Write(&pf); err != nil { 83 | return nil, err 84 | } 85 | 86 | return buf.Bytes(), nil 87 | } 88 | 89 | // Read reads from an io.Reader, and populates the PixelFormat. 90 | func (pf *PixelFormat) Read(r io.Reader) error { 91 | buf := make([]byte, pixelFormatLen) 92 | if _, err := io.ReadAtLeast(r, buf, pixelFormatLen); err != nil { 93 | return err 94 | } 95 | return pf.Unmarshal(buf) 96 | } 97 | 98 | // Unmarshal implements the Unmarshaler interface. 99 | func (pf *PixelFormat) Unmarshal(data []byte) error { 100 | buf := NewBuffer(data) 101 | 102 | var msg PixelFormat 103 | if err := buf.Read(&msg); err != nil { 104 | return err 105 | } 106 | if rfbflags.IsTrueColor(msg.TrueColor) { 107 | msg.TrueColor = rfbflags.RFBTrue // Use our constant value. 108 | } 109 | *pf = msg 110 | 111 | return nil 112 | } 113 | 114 | // String implements the fmt.Stringer interface. 115 | func (pf PixelFormat) String() string { 116 | return fmt.Sprintf("{ bpp: %d depth: %d big-endian: %s true-color: %s red-max: %d green-max: %d blue-max: %d red-shift: %d green-shift: %d blue-shift: %d }", 117 | pf.BPP, pf.Depth, pf.BigEndian, pf.TrueColor, pf.RedMax, pf.GreenMax, pf.BlueMax, pf.RedShift, pf.GreenShift, pf.BlueShift) 118 | } 119 | 120 | func (pf PixelFormat) order() binary.ByteOrder { 121 | if rfbflags.IsBigEndian(pf.BigEndian) { 122 | return binary.BigEndian 123 | } 124 | return binary.LittleEndian 125 | } 126 | -------------------------------------------------------------------------------- /pixel_format_test.go: -------------------------------------------------------------------------------- 1 | package vnc 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/kward/go-vnc/go/operators" 9 | "github.com/kward/go-vnc/rfbflags" 10 | ) 11 | 12 | const ( 13 | // Shadow the RFBFlag constants. 14 | RFBFalse = rfbflags.RFBFalse 15 | RFBTrue = rfbflags.RFBTrue 16 | ) 17 | 18 | func TestPixelFormat_Marshal(t *testing.T) { 19 | tests := []struct { 20 | pf PixelFormat 21 | b []byte 22 | ok bool 23 | }{ 24 | // 25 | // Valid PixelFormats. 26 | // 27 | {PixelFormat{BPP: 8, Depth: 8, BigEndian: RFBTrue, TrueColor: RFBFalse}, 28 | []uint8{8, 8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true}, 29 | {PixelFormat{BPP: 8, Depth: 16, BigEndian: RFBTrue, TrueColor: RFBFalse}, 30 | []uint8{8, 16, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, true}, 31 | {NewPixelFormat(16), 32 | []uint8{16, 16, 1, 1, 255, 255, 255, 255, 255, 255, 0, 4, 8, 0, 0, 0}, true}, 33 | // 34 | // Invalid PixelFormats. 35 | // 36 | // BPP invalid 37 | {PixelFormat{BPP: 1, Depth: 1, BigEndian: RFBTrue, TrueColor: RFBFalse}, 38 | []uint8{}, false}, 39 | // Depth invalid 40 | {PixelFormat{BPP: 8, Depth: 1, BigEndian: RFBTrue, TrueColor: RFBFalse}, 41 | []uint8{}, false}, 42 | // BPP > Depth 43 | {PixelFormat{BPP: 16, Depth: 8, BigEndian: RFBTrue, TrueColor: RFBFalse}, 44 | []uint8{}, false}, 45 | } 46 | 47 | for _, tt := range tests { 48 | pf := tt.pf 49 | b, err := pf.Marshal() 50 | if err == nil && !tt.ok { 51 | t.Error("expected error") 52 | } 53 | if err != nil { 54 | if verr, ok := err.(*VNCError); !ok { 55 | t.Errorf("unexpected %v error: %v", reflect.TypeOf(err), verr) 56 | } 57 | } 58 | if !tt.ok { 59 | continue 60 | } 61 | if got, want := b, tt.b; !operators.EqualSlicesOfByte(got, want) { 62 | t.Errorf("invalid pixel-format; got = %v, want = %v", got, want) 63 | } 64 | } 65 | } 66 | 67 | func TestPixelFormat_Unmarshal(t *testing.T) { 68 | tests := []struct { 69 | b []byte 70 | pf PixelFormat 71 | ok bool 72 | }{ 73 | // 74 | // Valid PixelFormats. 75 | // 76 | {[]uint8{8, 8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 77 | PixelFormat{BPP: 8, Depth: 8, BigEndian: RFBTrue, TrueColor: RFBFalse}, 78 | true}, 79 | {[]uint8{8, 16, 1, 1, 255, 255, 255, 255, 255, 255, 0, 4, 8, 0, 0, 0}, 80 | PixelFormat{ 81 | BPP: 8, Depth: 16, 82 | BigEndian: RFBTrue, TrueColor: RFBTrue, 83 | RedMax: 65535, GreenMax: 65535, BlueMax: 65535, 84 | RedShift: 0, GreenShift: 4, BlueShift: 8}, 85 | true}, 86 | {[]uint8{16, 16, 1, 1, 255, 255, 255, 255, 255, 255, 0, 4, 8, 0, 0, 0}, 87 | NewPixelFormat(16), true}, 88 | {[]uint8{32, 32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 89 | PixelFormat{BPP: 32, Depth: 32, BigEndian: RFBTrue, TrueColor: RFBFalse}, 90 | true}, 91 | // 92 | // Invalid PixelFormats. 93 | // 94 | } 95 | 96 | for _, tt := range tests { 97 | var buf bytes.Buffer 98 | buf.Write(tt.b) 99 | 100 | var pf PixelFormat 101 | err := pf.Unmarshal(buf.Bytes()) 102 | if err == nil && !tt.ok { 103 | t.Error("expected error") 104 | } 105 | if err != nil { 106 | if verr, ok := err.(*VNCError); !ok { 107 | t.Errorf("unexpected %v error: %v", reflect.TypeOf(err), verr) 108 | } 109 | } 110 | if !tt.ok { 111 | continue 112 | } 113 | if got, want := pf, tt.pf; !equalPixelFormat(got, want) { 114 | t.Errorf("invalid pixel-format; got = %v, want = %v", pf, tt.pf) 115 | } 116 | } 117 | } 118 | 119 | func TestPixelFormat_String(t *testing.T) { 120 | for _, tt := range []struct { 121 | desc string 122 | pf PixelFormat 123 | str string 124 | }{ 125 | {"8bpp-8depth", 126 | PixelFormat{BPP: 8, Depth: 8, BigEndian: RFBTrue, TrueColor: RFBFalse}, 127 | "{ bpp: 8 depth: 8 big-endian: RFBTrue true-color: RFBFalse red-max: 0 green-max: 0 blue-max: 0 red-shift: 0 green-shift: 0 blue-shift: 0 }"}, 128 | } { 129 | if got, want := tt.pf.String(), tt.str; got != want { 130 | t.Errorf("%s: string() = %q, want = %q", tt.desc, got, want) 131 | } 132 | } 133 | } 134 | 135 | func equalPixelFormat(g, w PixelFormat) bool { 136 | got, err := g.Marshal() 137 | if err != nil { 138 | return false 139 | } 140 | want, err := w.Marshal() 141 | if err != nil { 142 | return false 143 | } 144 | return operators.EqualSlicesOfByte(got, want) 145 | } 146 | -------------------------------------------------------------------------------- /vncclient_test.go: -------------------------------------------------------------------------------- 1 | package vnc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "log" 8 | "net" 9 | "reflect" 10 | "testing" 11 | 12 | "context" 13 | ) 14 | 15 | func newMockServer(t *testing.T, version string) string { 16 | ln, err := net.Listen("tcp", "127.0.0.1:0") 17 | if err != nil { 18 | t.Fatalf("error listening: %s", err) 19 | } 20 | 21 | go func() { 22 | defer ln.Close() 23 | c, err := ln.Accept() 24 | if err != nil { 25 | // Avoid calling testing.T from a goroutine; log and return. 26 | log.Printf("test server: error accepting conn: %v", err) 27 | return 28 | } 29 | defer c.Close() 30 | 31 | if _, err = c.Write([]byte(fmt.Sprintf("RFB %s\n", version))); err != nil { 32 | // Avoid calling testing.T from a goroutine; log and return. 33 | log.Printf("test server: failed writing version: %v", err) 34 | return 35 | } 36 | }() 37 | 38 | return ln.Addr().String() 39 | } 40 | 41 | func TestLowMajorVersion(t *testing.T) { 42 | nc, err := net.Dial("tcp", newMockServer(t, "002.009")) 43 | if err != nil { 44 | t.Fatalf("error connecting to mock server: %s", err) 45 | } 46 | 47 | _, err = Connect(context.Background(), nc, &ClientConfig{}) 48 | if err == nil { 49 | t.Fatal("error expected") 50 | } 51 | if err != nil { 52 | if verr, ok := err.(*VNCError); !ok { 53 | t.Errorf("Client() unexpected %v error: %v", reflect.TypeOf(err), verr) 54 | } 55 | } 56 | } 57 | 58 | func TestLowMinorVersion(t *testing.T) { 59 | nc, err := net.Dial("tcp", newMockServer(t, "003.002")) 60 | if err != nil { 61 | t.Fatalf("error connecting to mock server: %s", err) 62 | } 63 | 64 | _, err = Connect(context.Background(), nc, &ClientConfig{}) 65 | if err == nil { 66 | t.Fatal("error expected") 67 | } 68 | if err != nil { 69 | if verr, ok := err.(*VNCError); !ok { 70 | t.Errorf("Client() unexpected %v error: %v", reflect.TypeOf(err), verr) 71 | } 72 | } 73 | } 74 | 75 | func TestClientConn(t *testing.T) { 76 | conn := &ClientConn{} 77 | 78 | if got, want := conn.DesktopName(), ""; got != want { 79 | t.Errorf("DesktopName() failed; got = %v, want = %v", got, want) 80 | } 81 | if got, want := conn.FramebufferHeight(), uint16(0); got != want { 82 | t.Errorf("FramebufferHeight() failed; got = %v, want = %v", got, want) 83 | } 84 | if got, want := conn.FramebufferWidth(), uint16(0); got != want { 85 | t.Errorf("FramebufferWidth() failed; got = %v, want = %v", got, want) 86 | } 87 | } 88 | 89 | func TestReceiveN(t *testing.T) { 90 | tests := []struct { 91 | data interface{} 92 | }{ 93 | {[]uint8{10, 11, 12}}, 94 | {[]int32{20, 21, 22}}, 95 | {bytes.NewBuffer([]byte{30, 31, 32})}, 96 | } 97 | 98 | mockConn := &MockConn{} 99 | conn := NewClientConn(mockConn, &ClientConfig{}) 100 | 101 | for _, tt := range tests { 102 | mockConn.Reset() 103 | 104 | // Place data in buffer. 105 | var d interface{} 106 | switch tt.data.(type) { 107 | case *bytes.Buffer: 108 | d = tt.data.(*bytes.Buffer).Bytes() 109 | default: 110 | d = tt.data 111 | } 112 | if err := binary.Write(conn.c, binary.BigEndian, d); err != nil { 113 | t.Errorf("unexpected error: %v", err) 114 | } 115 | 116 | // Read data from buffer. 117 | switch tt.data.(type) { 118 | case []uint8: 119 | var data []uint8 120 | n := len(tt.data.([]uint8)) 121 | if err := conn.receiveN(&data, n); err != nil { 122 | t.Errorf("error receiving data: %v", err) 123 | } 124 | if got, want := len(data), n; got != want { 125 | t.Errorf("incorrect amount of data received; got = %v, want = %v", got, want) 126 | } 127 | case []int32: 128 | var data []int32 129 | n := len(tt.data.([]int32)) 130 | if err := conn.receiveN(&data, n); err != nil { 131 | t.Errorf("error receiving data: %v", err) 132 | } 133 | if got, want := len(data), n; got != want { 134 | t.Errorf("incorrect amount of data received; got = %v, want = %v", got, want) 135 | } 136 | case *bytes.Buffer: 137 | var data bytes.Buffer 138 | n := len(tt.data.(*bytes.Buffer).Bytes()) 139 | if err := conn.receiveN(&data, n); err != nil { 140 | t.Errorf("error receiving data: %v", err) 141 | } 142 | if got, want := data.Len(), n; got != want { 143 | t.Errorf("incorrect amount of data received; got = %v, want = %v", got, want) 144 | } 145 | default: 146 | t.Fatalf("unimplemented for %v", reflect.TypeOf(tt.data)) 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /encodings.go: -------------------------------------------------------------------------------- 1 | /* 2 | Implementation of RFC 6143 §7.7 Encodings. 3 | https://tools.ietf.org/html/rfc6143#section-7.7 4 | */ 5 | package vnc 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | 11 | "github.com/kward/go-vnc/encodings" 12 | ) 13 | 14 | //============================================================================= 15 | // Encodings 16 | 17 | // An Encoding implements a method for encoding pixel data that is 18 | // sent by the server to the client. 19 | type Encoding interface { 20 | fmt.Stringer 21 | Marshaler 22 | 23 | // Read the contents of the encoded pixel data from the reader. 24 | // This should return a new Encoding implementation that contains 25 | // the proper data. 26 | Read(*ClientConn, *Rectangle) (Encoding, error) 27 | 28 | // The number that uniquely identifies this encoding type. 29 | Type() encodings.Encoding 30 | } 31 | 32 | // Encodings describes a slice of Encoding. 33 | type Encodings []Encoding 34 | 35 | // Verify that interfaces are honored. 36 | var _ Marshaler = (*Encodings)(nil) 37 | 38 | // Marshal implements the Marshaler interface. 39 | func (e Encodings) Marshal() ([]byte, error) { 40 | buf := NewBuffer(nil) 41 | for _, enc := range e { 42 | if err := buf.Write(enc.Type()); err != nil { 43 | return nil, err 44 | } 45 | } 46 | return buf.Bytes(), nil 47 | } 48 | 49 | //----------------------------------------------------------------------------- 50 | // Raw Encoding 51 | // 52 | // Raw encoding is the simplest encoding type, which is raw pixel data. 53 | // 54 | // See RFC 6143 §7.7.1. 55 | // https://tools.ietf.org/html/rfc6143#section-7.7.1 56 | 57 | // RawEncoding holds raw encoded rectangle data. 58 | type RawEncoding struct { 59 | Colors []Color 60 | } 61 | 62 | // Verify that interfaces are honored. 63 | var _ Encoding = (*RawEncoding)(nil) 64 | 65 | // Marshal implements the Encoding interface. 66 | func (e *RawEncoding) Marshal() ([]byte, error) { 67 | buf := NewBuffer(nil) 68 | 69 | for _, c := range e.Colors { 70 | bytes, err := c.Marshal() 71 | if err != nil { 72 | return nil, err 73 | } 74 | if err := buf.Write(bytes); err != nil { 75 | return nil, err 76 | } 77 | } 78 | 79 | return buf.Bytes(), nil 80 | } 81 | 82 | // Read implements the Encoding interface. 83 | func (*RawEncoding) Read(c *ClientConn, rect *Rectangle) (Encoding, error) { 84 | var buf bytes.Buffer 85 | bytesPerPixel := int(c.pixelFormat.BPP / 8) 86 | n := rect.Area() * bytesPerPixel 87 | if err := c.receiveN(&buf, n); err != nil { 88 | return nil, fmt.Errorf("unable to read rectangle with raw encoding: %s", err) 89 | } 90 | 91 | colors := make([]Color, rect.Area()) 92 | for y := uint16(0); y < rect.Height; y++ { 93 | for x := uint16(0); x < rect.Width; x++ { 94 | color := NewColor(&c.pixelFormat, &c.colorMap) 95 | if err := color.Unmarshal(buf.Next(bytesPerPixel)); err != nil { 96 | return nil, err 97 | } 98 | colors[int(y)*int(rect.Width)+int(x)] = *color 99 | } 100 | } 101 | 102 | return &RawEncoding{colors}, nil 103 | } 104 | 105 | // String implements the fmt.Stringer interface. 106 | func (*RawEncoding) String() string { return "RawEncoding" } 107 | 108 | // Type implements the Encoding interface. 109 | func (*RawEncoding) Type() encodings.Encoding { return encodings.Raw } 110 | 111 | //============================================================================= 112 | // Pseudo-Encodings 113 | // 114 | // Rectangles with a "pseudo-encoding" allow a server to send data to the 115 | // client. The interpretation of the data depends on the pseudo-encoding. 116 | // 117 | // See RFC 6143 §7.8. 118 | // https://tools.ietf.org/html/rfc6143#section-7.8 119 | 120 | //----------------------------------------------------------------------------- 121 | // DesktopSize Pseudo-Encoding 122 | // 123 | // When a client requests DesktopSize pseudo-encoding, it is indicating to the 124 | // server that it can handle changes to the framebuffer size. If this encoding 125 | // received, the client must resize its framebuffer, and drop all existing 126 | // information stored in the framebuffer. 127 | // 128 | // See RFC 6143 §7.8.2. 129 | // https://tools.ietf.org/html/rfc6143#section-7.8.2 130 | 131 | // DesktopSizePseudoEncoding represents a desktop size message from the server. 132 | type DesktopSizePseudoEncoding struct{} 133 | 134 | // Verify that interfaces are honored. 135 | var _ Encoding = (*DesktopSizePseudoEncoding)(nil) 136 | 137 | // Marshal implements the Marshaler interface. 138 | func (e *DesktopSizePseudoEncoding) Marshal() ([]byte, error) { 139 | return []byte{}, nil 140 | } 141 | 142 | // Read implements the Encoding interface. 143 | func (*DesktopSizePseudoEncoding) Read(c *ClientConn, rect *Rectangle) (Encoding, error) { 144 | c.fbWidth = rect.Width 145 | c.fbHeight = rect.Height 146 | 147 | return &DesktopSizePseudoEncoding{}, nil 148 | } 149 | 150 | // String implements the fmt.Stringer interface. 151 | func (e *DesktopSizePseudoEncoding) String() string { return "DesktopSizePseudoEncoding" } 152 | 153 | // Type implements the Encoding interface. 154 | func (*DesktopSizePseudoEncoding) Type() encodings.Encoding { return encodings.DesktopSizePseudo } 155 | -------------------------------------------------------------------------------- /keys/keys.go: -------------------------------------------------------------------------------- 1 | // Package keys provides constants for keyboard inputs (X11 KeySyms as used by 2 | // the RFB protocol per RFC 6143 §7.5.4). The constants in this package mirror 3 | // ASCII/Latin-1 where possible: 4 | // 5 | // - Printable ASCII U+0020..U+007E are sequential and map directly; for a 6 | // printable rune r in that range, Key(r) equals the corresponding constant 7 | // (e.g., Key('A') == A, Key('a') == SmallA, Key('0') == Digit0, Key('-') == Minus). 8 | // - Extended Latin-1 U+0080..U+00FF are also represented in the low 8 bits for 9 | // convenience. 10 | // - Special and control keys live in the 0xFFxx range (e.g., BackSpace 0xFF08, 11 | // Tab 0xFF09, Return 0xFF0D, Escape 0xFF1B), matching X11 KeySym values. 12 | package keys 13 | 14 | import ( 15 | "fmt" 16 | "strconv" 17 | ) 18 | 19 | // Key represents a VNC key press (an X11 KeySym value on the wire). 20 | type Key uint32 21 | 22 | //go:generate stringer -type=Key 23 | 24 | // Keys is a convenience slice of Key values. 25 | type Keys []Key 26 | 27 | // FromRune converts a rune to a Key. It handles printable ASCII (U+0020..U+007E), 28 | // extended Latin-1 (U+0080..U+00FF), and common control characters (\n, \t, \b, \r). 29 | // Returns (Key, true) on success or (0, false) for unsupported runes. 30 | func FromRune(r rune) (Key, bool) { 31 | switch r { 32 | case '\n': 33 | return Linefeed, true 34 | case '\t': 35 | return Tab, true 36 | case '\b': 37 | return BackSpace, true 38 | case '\r': 39 | return Return, true 40 | } 41 | // Printable ASCII and extended Latin-1 map directly. 42 | if r >= 0x20 && r <= 0xFF { 43 | return Key(r), true 44 | } 45 | return 0, false 46 | } 47 | 48 | // TextToKeys converts a string to Keys by mapping each rune via FromRune. 49 | // Returns an error if any rune is unsupported. 50 | func TextToKeys(s string) (Keys, error) { 51 | ks := make(Keys, 0, len(s)) 52 | for _, r := range s { 53 | k, ok := FromRune(r) 54 | if !ok { 55 | return nil, fmt.Errorf("unsupported rune %q (U+%04X)", r, r) 56 | } 57 | ks = append(ks, k) 58 | } 59 | return ks, nil 60 | } 61 | 62 | // IntToKeys returns Keys that represent the key presses required to type an int 63 | // using ASCII digits and an optional leading minus sign. Digits and minus map 64 | // directly since they're in the printable ASCII range. 65 | func IntToKeys(v int) Keys { 66 | s := strconv.Itoa(v) 67 | ks := make(Keys, 0, len(s)) 68 | for _, r := range s { 69 | ks = append(ks, Key(r)) 70 | } 71 | return ks 72 | } 73 | 74 | // Latin 1 (byte 3 = 0) 75 | // ISO/IEC 8859-1 = Unicode U+0020..U+00FF 76 | const ( 77 | Space Key = iota + 0x0020 78 | Exclaim // exclamation mark 79 | QuoteDbl 80 | NumberSign 81 | Dollar 82 | Percent 83 | Ampersand 84 | Apostrophe 85 | ParenLeft 86 | ParenRight 87 | Asterisk 88 | Plus 89 | Comma 90 | Minus 91 | Period 92 | Slash 93 | Digit0 94 | Digit1 95 | Digit2 96 | Digit3 97 | Digit4 98 | Digit5 99 | Digit6 100 | Digit7 101 | Digit8 102 | Digit9 103 | Colon 104 | Semicolon 105 | Less 106 | Equal 107 | Greater 108 | Question 109 | At 110 | A 111 | B 112 | C 113 | D 114 | E 115 | F 116 | G 117 | H 118 | I 119 | J 120 | K 121 | L 122 | M 123 | N 124 | O 125 | P 126 | Q 127 | R 128 | S 129 | T 130 | U 131 | V 132 | W 133 | X 134 | Y 135 | Z 136 | BracketLeft 137 | Backslash 138 | BracketRight 139 | AsciiCircum 140 | Underscore 141 | Grave 142 | SmallA 143 | SmallB 144 | SmallC 145 | SmallD 146 | SmallE 147 | SmallF 148 | SmallG 149 | SmallH 150 | SmallI 151 | SmallJ 152 | SmallK 153 | SmallL 154 | SmallM 155 | SmallN 156 | SmallO 157 | SmallP 158 | SmallQ 159 | SmallR 160 | SmallS 161 | SmallT 162 | SmallU 163 | SmallV 164 | SmallW 165 | SmallX 166 | SmallY 167 | SmallZ 168 | BraceLeft 169 | Bar 170 | BraceRight 171 | AsciiTilde 172 | ) 173 | const ( 174 | BackSpace Key = iota + 0xff08 175 | Tab 176 | Linefeed 177 | Clear 178 | _ 179 | Return 180 | ) 181 | const ( 182 | Pause Key = iota + 0xff13 183 | ScrollLock 184 | SysReq 185 | Escape Key = 0xff1b 186 | Delete Key = 0xffff 187 | ) 188 | const ( // Cursor control & motion. 189 | Home Key = iota + 0xff50 190 | Left 191 | Up 192 | Right 193 | Down 194 | PageUp 195 | PageDown 196 | End 197 | Begin 198 | ) 199 | const ( // Misc functions. 200 | Select Key = 0xff60 201 | Print 202 | Execute 203 | Insert 204 | Undo 205 | Redo 206 | Menu 207 | Find 208 | Cancel 209 | Help 210 | Break 211 | ModeSwitch Key = 0xff7e 212 | NumLock Key = 0xff7f 213 | ) 214 | const ( // Keypad functions. 215 | KeypadSpace Key = 0xff80 216 | KeypadTab Key = 0xff89 217 | KeypadEnter Key = 0xff8d 218 | ) 219 | const ( // Keypad functions cont. 220 | KeypadF1 Key = iota + 0xff91 221 | KeypadF2 222 | KeypadF3 223 | KeypadF4 224 | KeypadHome 225 | KeypadLeft 226 | KeypadUp 227 | KeypadRight 228 | KeypadDown 229 | KeypadPrior 230 | KeypadPageUp 231 | KeypadNext 232 | KeypadPageDown 233 | KeypadEnd 234 | KeypadBegin 235 | KeypadInsert 236 | KeypadDelete 237 | KeypadMultiply 238 | KeypadAdd 239 | KeypadSeparator 240 | KeypadSubtract 241 | KeypadDecimal 242 | KeypadDivide 243 | Keypad0 244 | Keypad1 245 | Keypad2 246 | Keypad3 247 | Keypad4 248 | Keypad5 249 | Keypad6 250 | Keypad7 251 | Keypad8 252 | Keypad9 253 | KeypadEqual Key = 0xffbd 254 | ) 255 | const ( 256 | F1 Key = iota + 0xffbe 257 | F2 258 | F3 259 | F4 260 | F5 261 | F6 262 | F7 263 | F8 264 | F9 265 | F10 266 | F11 267 | F12 268 | ) 269 | const ( 270 | ShiftLeft Key = iota + 0xffe1 271 | ShiftRight 272 | ControlLeft 273 | ControlRight 274 | CapsLock 275 | ShiftLock 276 | MetaLeft 277 | MetaRight 278 | AltLeft 279 | AltRight 280 | SuperLeft 281 | SuperRight 282 | HyperLeft 283 | HyperRight 284 | ) 285 | -------------------------------------------------------------------------------- /handshake.go: -------------------------------------------------------------------------------- 1 | // Implementation of RFC 6143 §7.1 Handshake Messages. 2 | 3 | package vnc 4 | 5 | import ( 6 | "fmt" 7 | 8 | "github.com/kward/go-vnc/logging" 9 | 10 | "context" 11 | ) 12 | 13 | const pvLen = 12 // ProtocolVersion message length. 14 | 15 | func parseProtocolVersion(pv []byte) (uint, uint, error) { 16 | var major, minor uint 17 | 18 | if len(pv) < pvLen { 19 | return 0, 0, fmt.Errorf("ProtocolVersion message too short (%v < %v)", len(pv), pvLen) 20 | } 21 | 22 | l, err := fmt.Sscanf(string(pv), "RFB %d.%d\n", &major, &minor) 23 | if l != 2 { 24 | return 0, 0, fmt.Errorf("error parsing ProtocolVersion") 25 | } 26 | if err != nil { 27 | return 0, 0, err 28 | } 29 | 30 | return major, minor, nil 31 | } 32 | 33 | const ( 34 | // Client ProtocolVersions. 35 | PROTO_VERS_UNSUP = "UNSUPPORTED" 36 | PROTO_VERS_3_3 = "RFB 003.003\n" 37 | PROTO_VERS_3_8 = "RFB 003.008\n" 38 | ) 39 | 40 | // protocolVersionHandshake implements §7.1.1 ProtocolVersion Handshake. 41 | func (c *ClientConn) protocolVersionHandshake(ctx context.Context) error { 42 | if logging.V(logging.FnDeclLevel) { 43 | logging.Infof("%s", logging.FnName()) 44 | } 45 | 46 | var protocolVersion [pvLen]byte 47 | 48 | // Read the ProtocolVersion message sent by the server. 49 | if err := c.receive(&protocolVersion); err != nil { 50 | return err 51 | } 52 | if logging.V(logging.ResultLevel) { 53 | logging.Infof("protocolVersion: %s", protocolVersion) 54 | } 55 | 56 | major, minor, err := parseProtocolVersion(protocolVersion[:]) 57 | if err != nil { 58 | return err 59 | } 60 | pv := PROTO_VERS_UNSUP 61 | if major == 3 { 62 | if minor >= 8 { 63 | pv = PROTO_VERS_3_8 64 | } else if minor >= 3 { 65 | pv = PROTO_VERS_3_3 66 | } 67 | } 68 | if pv == PROTO_VERS_UNSUP { 69 | return NewVNCError(fmt.Sprintf("ProtocolVersion handshake failed; unsupported version '%v'", string(protocolVersion[:]))) 70 | } 71 | 72 | if mpv := ctx.Value("vnc_max_proto_version"); mpv != nil && mpv != "" { 73 | switch mpv { 74 | case "3.3": 75 | pv = PROTO_VERS_3_3 76 | case "3.8": 77 | pv = PROTO_VERS_3_8 78 | } 79 | } 80 | 81 | if logging.V(logging.ResultLevel) { 82 | logging.Infof("supported protocolVersion: %s", pv) 83 | } 84 | c.protocolVersion = pv 85 | 86 | // Respond with the version we will support 87 | if err = c.send([]byte(pv)); err != nil { 88 | return err 89 | } 90 | 91 | return nil 92 | } 93 | 94 | // securityHandshake implements §7.1.2 Security Handshake. 95 | func (c *ClientConn) securityHandshake() error { 96 | if logging.V(logging.FnDeclLevel) { 97 | logging.Infof("%s", logging.FnName()) 98 | } 99 | 100 | switch c.protocolVersion { 101 | case PROTO_VERS_3_3: 102 | if err := c.securityHandshake33(); err != nil { 103 | return err 104 | } 105 | case PROTO_VERS_3_8: 106 | if err := c.securityHandshake38(); err != nil { 107 | return err 108 | } 109 | default: 110 | return NewVNCError("Security handshake failed; unsupported protocol") 111 | } 112 | 113 | return nil 114 | } 115 | 116 | func (c *ClientConn) securityHandshake33() error { 117 | if logging.V(logging.FnDeclLevel) { 118 | logging.Infof("%s", logging.FnName()) 119 | } 120 | 121 | var secType uint32 122 | if err := c.receive(&secType); err != nil { 123 | return err 124 | } 125 | 126 | var auth ClientAuth 127 | switch uint8(secType) { // 3.3 uses uint32, but 3.8 uses uint8. Unify on 3.8. 128 | case secTypeInvalid: // Connection failed. 129 | reason, err := c.readErrorReason() 130 | if err != nil { 131 | return err 132 | } 133 | return NewVNCError(fmt.Sprintf("Security handshake failed; connection failed: %s", reason)) 134 | case secTypeNone: 135 | auth = &ClientAuthNone{} 136 | case secTypeVNCAuth: 137 | auth = &ClientAuthVNC{c.config.Password} 138 | default: 139 | return NewVNCError(fmt.Sprintf("Security handshake failed; invalid security type: %v", secType)) 140 | } 141 | c.config.secType = auth.SecurityType() 142 | if err := auth.Handshake(c); err != nil { 143 | return err 144 | } 145 | 146 | return nil 147 | } 148 | 149 | func (c *ClientConn) securityHandshake38() error { 150 | if logging.V(logging.FnDeclLevel) { 151 | logging.Infof("%s", logging.FnName()) 152 | } 153 | 154 | // Determine server supported security types. 155 | var numSecurityTypes uint8 156 | if err := c.receive(&numSecurityTypes); err != nil { 157 | return err 158 | } 159 | if numSecurityTypes == 0 { 160 | reason, err := c.readErrorReason() 161 | if err != nil { 162 | return err 163 | } 164 | return NewVNCError(fmt.Sprintf("Security handshake failed; no security types: %v", reason)) 165 | } 166 | securityTypes := make([]uint8, numSecurityTypes) 167 | if err := c.receive(&securityTypes); err != nil { 168 | return err 169 | } 170 | if logging.V(logging.ResultLevel) { 171 | logging.Infof("securityTypes: %v", securityTypes) 172 | } 173 | 174 | // Choose client security type. 175 | // TODO(kward): try "better" security types first. 176 | var auth ClientAuth 177 | FindAuth: 178 | for _, securityType := range securityTypes { 179 | for _, a := range c.config.Auth { 180 | if a.SecurityType() == securityType { 181 | // We use the first matching supported authentication. 182 | auth = a 183 | break FindAuth 184 | } 185 | } 186 | } 187 | if auth == nil { 188 | return NewVNCError(fmt.Sprintf("Security handshake failed; no suitable auth schemes found; server supports: %#v", securityTypes)) 189 | } 190 | if err := c.send(auth.SecurityType()); err != nil { 191 | return err 192 | } 193 | c.config.secType = auth.SecurityType() 194 | if err := auth.Handshake(c); err != nil { 195 | return err 196 | } 197 | return nil 198 | } 199 | 200 | // securityResultHandshake implements §7.1.3 SecurityResult Handshake. 201 | func (c *ClientConn) securityResultHandshake() error { 202 | if logging.V(logging.FnDeclLevel) { 203 | logging.Infof("%s", logging.FnName()) 204 | } 205 | 206 | if c.config.secType == secTypeNone { 207 | return nil 208 | } 209 | 210 | var securityResult uint32 211 | if err := c.receive(&securityResult); err != nil { 212 | return err 213 | } 214 | switch securityResult { 215 | case 0: 216 | case 1: 217 | reason, err := c.readErrorReason() 218 | if err != nil { 219 | return err 220 | } 221 | return NewVNCError(fmt.Sprintf("SecurityResult handshake failed: %s", reason)) 222 | default: 223 | return NewVNCError(fmt.Sprintf("Invalid SecurityResult status: %v", securityResult)) 224 | } 225 | 226 | return nil 227 | } 228 | 229 | // TODO(kward): need a context for timeout 230 | func (c *ClientConn) readErrorReason() (string, error) { 231 | if logging.V(logging.FnDeclLevel) { 232 | logging.Infof("%s", logging.FnName()) 233 | } 234 | 235 | var reasonLen uint32 236 | if err := c.receive(&reasonLen); err != nil { 237 | return "", err 238 | } 239 | 240 | reason := make([]uint8, reasonLen) 241 | if err := c.receive(&reason); err != nil { 242 | return "", err 243 | } 244 | 245 | return string(reason), nil 246 | } 247 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VNC Library for Go 2 | go-vnc is a VNC client library for Go. 3 | 4 | This library implements [RFC 6143][RFC6143] -- The Remote Framebuffer Protocol 5 | -- the protocol used by VNC. 6 | 7 | ## Project links 8 | * CI: [![CI][CIStatus]][CIProject] 9 | * Documentation: [![GoDoc][GoDocStatus]][GoDoc] 10 | 11 | ## Setup (Go modules) 12 | 1. Add to your module and run tests. 13 | 14 | ``` 15 | $ go get github.com/kward/go-vnc@latest 16 | $ go test ./... 17 | ``` 18 | 19 | ## Usage 20 | Sample code usage is available in the GoDoc. 21 | 22 | - Connect and listen to server messages: 23 | - Client input examples (keys, pointer, clipboard, updates): see [Client input examples](#client-input-examples) 24 | - Package examples on pkg.go.dev: [keys examples](https://pkg.go.dev/github.com/kward/go-vnc/keys#pkg-examples), [buttons examples](https://pkg.go.dev/github.com/kward/go-vnc/buttons#pkg-examples) 25 | 26 | ### Client input examples 27 | 28 | Below are small, focused examples showing how to send client event messages to a VNC server using this library. They assume you have an active connection `vc *vnc.ClientConn` from `vnc.Connect(...)`. 29 | 30 | Key press/release (type characters or use special keys): 31 | 32 | ```go 33 | import ( 34 | "github.com/kward/go-vnc" 35 | "github.com/kward/go-vnc/keys" 36 | ) 37 | 38 | // Press and release a printable ASCII key (using a Key constant). 39 | _ = vc.KeyEvent(keys.A, vnc.PressKey) 40 | _ = vc.KeyEvent(keys.A, vnc.ReleaseKey) 41 | 42 | // Or convert from a rune for general text input. 43 | if k, ok := keys.FromRune('!'); ok { 44 | _ = vc.KeyEvent(k, vnc.PressKey) 45 | _ = vc.KeyEvent(k, vnc.ReleaseKey) 46 | } 47 | 48 | // Special keys (X11 KeySyms in the 0xFFxx range) are also constants. 49 | _ = vc.KeyEvent(keys.Return, vnc.PressKey) 50 | _ = vc.KeyEvent(keys.Return, vnc.ReleaseKey) 51 | ``` 52 | 53 | Pointer/mouse events (move and button masks): 54 | 55 | ```go 56 | import ( 57 | "github.com/kward/go-vnc" 58 | "github.com/kward/go-vnc/buttons" 59 | ) 60 | 61 | // Move the pointer to absolute coordinates (x, y). 62 | _ = vc.PointerEvent(buttons.None, 100, 200) 63 | 64 | // Left click: press then release. 65 | _ = vc.PointerEvent(buttons.Left, 100, 200) 66 | _ = vc.PointerEvent(buttons.None, 100, 200) 67 | 68 | // Multiple buttons can be combined as a mask. 69 | _ = vc.PointerEvent(buttons.Left|buttons.Right, 120, 220) 70 | ``` 71 | 72 | Send clipboard/cut text (Latin-1 only; CRs are stripped per RFC 6143 §7.5.6): 73 | 74 | ```go 75 | import "github.com/kward/go-vnc" 76 | 77 | // Newlines (\n) are fine; carriage returns (\r) are removed by the client. 78 | _ = vc.ClientCutText("Line 1\r\nLine 2\n") 79 | 80 | // Note: Only Latin-1 characters are allowed. Emojis will return an error. 81 | if err := vc.ClientCutText("hello 😀"); err != nil { 82 | // handle non-Latin-1 error 83 | } 84 | ``` 85 | 86 | Request framebuffer updates (poll or event-driven): 87 | 88 | ```go 89 | import ( 90 | "time" 91 | "github.com/kward/go-vnc" 92 | "github.com/kward/go-vnc/rfbflags" 93 | ) 94 | 95 | // Periodically request incremental updates for the full desktop. 96 | go func() { 97 | w, h := vc.FramebufferWidth(), vc.FramebufferHeight() 98 | for { 99 | _ = vc.FramebufferUpdateRequest(rfbflags.RFBTrue, 0, 0, w, h) 100 | time.Sleep(100 * time.Millisecond) 101 | } 102 | }() 103 | ``` 104 | 105 | Notes: 106 | - A small UI settle delay is applied after client input (KeyEvent, PointerEvent, ClientCutText) to avoid overwhelming remote UIs. In tests you can disable it with: 107 | 108 | ```go 109 | import "github.com/kward/go-vnc" 110 | 111 | vnc.SetSettle(0) 112 | ``` 113 | 114 | - For building text input, see helpers in `keys`: 115 | - `keys.FromRune(r rune) (keys.Key, bool)` 116 | - `keys.TextToKeys(s string) (keys.Keys, error)` 117 | - `keys.IntToKeys(n int) keys.Keys` 118 | 119 | The source code is laid out such that the files match the document sections: 120 | 121 | - [7.1] handshake.go 122 | - [7.2] security.go 123 | - [7.3] initialization.go 124 | - [7.4] pixel_format.go 125 | - [7.5] client.go 126 | - [7.6] server.go 127 | - [7.7] encodings.go 128 | 129 | There are two additional files that provide everything else: 130 | 131 | - vncclient.go -- code for instantiating a VNC client 132 | - common.go -- common stuff not related to the RFB protocol 133 | 134 | 135 | 136 | [RFC6143]: http://tools.ietf.org/html/rfc6143 137 | 138 | [CIProject]: https://github.com/kward/go-vnc/actions/workflows/go.yml 139 | [CIStatus]: https://github.com/kward/go-vnc/actions/workflows/go.yml/badge.svg?branch=master 140 | 141 | [GoDoc]: https://pkg.go.dev/github.com/kward/go-vnc 142 | [GoDocStatus]: https://pkg.go.dev/badge/github.com/kward/go-vnc.svg 143 | 144 | ## Logging 145 | 146 | This library uses a small facade over Go's slog for internal logging. You can: 147 | 148 | - Gate verbose logs via verbosity levels: 149 | 150 | ```go 151 | import "github.com/kward/go-vnc/logging" 152 | 153 | // Enable result-level logs (and below). 0 disables V checks entirely. 154 | logging.SetVerbosity(logging.ResultLevel) 155 | ``` 156 | 157 | - Provide your own slog logger/handler (JSON or text) and level: 158 | 159 | ```go 160 | package main 161 | 162 | import ( 163 | "log/slog" 164 | "os" 165 | 166 | "github.com/kward/go-vnc/logging" 167 | ) 168 | 169 | func init() { 170 | logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo})) 171 | logging.SetLogger(logger) 172 | logging.SetVerbosity(logging.ResultLevel) // optional: enable verbose gated logs 173 | } 174 | ``` 175 | 176 | - Optionally emit structured logs from your code using the facade helpers: 177 | 178 | ```go 179 | logging.Info("connecting", "addr", addr) 180 | logging.Debug("frame", "w", w, "h", h) 181 | ``` 182 | 183 | See also: 184 | 185 | - Package docs for logging, verbosity levels, and helpers: 186 | - APIs referenced above: `logging.SetVerbosity`, `logging.SetLogger`, and structured helpers `logging.Info/Debug/Warn/Error` 187 | 188 | ## Code generation (stringer) 189 | 190 | This repo uses the Go stringer tool to generate String() methods for enums (e.g., Button, Key, Encoding, RFBFlag, ClientMessage, ServerMessage). To update generated files, install stringer and run go generate: 191 | 192 | - Install stringer (Go 1.17+): 193 | 194 | ```bash 195 | go install golang.org/x/tools/cmd/stringer@latest 196 | ``` 197 | 198 | Ensure your GOBIN is on PATH. By default, binaries are installed to $(go env GOPATH)/bin (or $(go env GOBIN) if set). For example, you can add this to your shell profile: 199 | 200 | ```bash 201 | export PATH="$(go env GOPATH)/bin:$PATH" 202 | ``` 203 | 204 | - Regenerate code from the repo root: 205 | 206 | ```bash 207 | go generate ./... 208 | ``` 209 | 210 | Notes: 211 | - The go:generate directives are embedded in source files (e.g., `//go:generate stringer -type=Button`). 212 | - Generated files have names like `*_string.go` and should be committed to the repo. 213 | 214 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | // Implementation of §7.5 Client-to-Server Messages. 2 | 3 | package vnc 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "unicode" 9 | 10 | "github.com/kward/go-vnc/buttons" 11 | "github.com/kward/go-vnc/encodings" 12 | "github.com/kward/go-vnc/keys" 13 | "github.com/kward/go-vnc/logging" 14 | "github.com/kward/go-vnc/messages" 15 | "github.com/kward/go-vnc/rfbflags" 16 | ) 17 | 18 | // SetPixelFormatMessage holds the wire format message. 19 | type SetPixelFormatMessage struct { 20 | Msg messages.ClientMessage // message-type 21 | _ [3]byte // padding 22 | PF PixelFormat // pixel-format 23 | } 24 | 25 | // SetPixelFormat sets the format in which pixel values should be sent 26 | // in FramebufferUpdate messages from the server. 27 | // 28 | // See RFC 6143 Section 7.5.1 29 | func (c *ClientConn) SetPixelFormat(pf PixelFormat) error { 30 | if logging.V(logging.FnDeclLevel) { 31 | logging.Infof("ClientConn.%s", logging.FnNameWithArgs("%s", pf)) 32 | } 33 | 34 | msg := SetPixelFormatMessage{ 35 | Msg: messages.SetPixelFormat, 36 | PF: pf, 37 | } 38 | if err := c.send(msg); err != nil { 39 | return err 40 | } 41 | 42 | // Invalidate the color map. 43 | if !rfbflags.IsTrueColor(pf.TrueColor) { 44 | c.colorMap = [256]Color{} 45 | } 46 | 47 | c.pixelFormat = pf 48 | return nil 49 | } 50 | 51 | // SetEncodingsMessage holds the wire format message, sans encoding-type field. 52 | type SetEncodingsMessage struct { 53 | Msg messages.ClientMessage // message-type 54 | _ [1]byte // padding 55 | NumEncs uint16 // number-of-encodings 56 | } 57 | 58 | // SetEncodings sets the encoding types in which the pixel data can be sent 59 | // from the server. After calling this method, the encs slice given should not 60 | // be modified. 61 | // 62 | // TODO(kward:20170306) Fix bad practice of mixing of protocol and internal 63 | // state here. 64 | // 65 | // See RFC 6143 Section 7.5.2 66 | func (c *ClientConn) SetEncodings(encs Encodings) error { 67 | if logging.V(logging.FnDeclLevel) { 68 | logging.Infof("ClientConn.%s", logging.FnNameWithArgs("%s", encs)) 69 | } 70 | 71 | // Make sure RawEncoding is supported. 72 | haveRaw := false 73 | for _, v := range encs { 74 | if v.Type() == encodings.Raw { 75 | haveRaw = true 76 | break 77 | } 78 | } 79 | if !haveRaw { 80 | encs = append(encs, &RawEncoding{}) 81 | } 82 | 83 | buf := NewBuffer(nil) 84 | 85 | // Prepare message. 86 | msg := SetEncodingsMessage{ 87 | Msg: messages.SetEncodings, 88 | NumEncs: uint16(len(encs)), 89 | } 90 | if err := buf.Write(msg); err != nil { 91 | return err 92 | } 93 | bytes, err := encs.Marshal() 94 | if err != nil { 95 | return err 96 | } 97 | if err := buf.Write(bytes); err != nil { 98 | return err 99 | } 100 | 101 | // Send message. 102 | if err := c.send(buf.Bytes()); err != nil { 103 | return err 104 | } 105 | 106 | c.encodings = encs 107 | return nil 108 | } 109 | 110 | // FramebufferUpdateRequestMessage holds the wire format message. 111 | type FramebufferUpdateRequestMessage struct { 112 | Msg messages.ClientMessage // message-type 113 | Inc rfbflags.RFBFlag // incremental 114 | X, Y uint16 // x-, y-position 115 | Width, Height uint16 // width, height 116 | } 117 | 118 | // Requests a framebuffer update from the server. There may be an indefinite 119 | // time between the request and the actual framebuffer update being received. 120 | // 121 | // See RFC 6143 Section 7.5.3 122 | func (c *ClientConn) FramebufferUpdateRequest(inc rfbflags.RFBFlag, x, y, w, h uint16) error { 123 | msg := FramebufferUpdateRequestMessage{messages.FramebufferUpdateRequest, inc, x, y, w, h} 124 | return c.send(&msg) 125 | } 126 | 127 | // KeyEventMessage holds the wire format message. 128 | type KeyEventMessage struct { 129 | Msg messages.ClientMessage // message-type 130 | DownFlag rfbflags.RFBFlag // down-flag 131 | _ [2]byte // padding 132 | Key keys.Key // key 133 | } 134 | 135 | const ( 136 | PressKey = true 137 | ReleaseKey = false 138 | ) 139 | 140 | // KeyEvent indicates a key press or release and sends it to the server. 141 | // The key is indicated using the X Window System "keysym" value. Constants are 142 | // provided in `keys/keys.go`. To simulate a key press, you must send a key with 143 | // both a true and false down event. 144 | // 145 | // See RFC 6143 Section 7.5.4. 146 | func (c *ClientConn) KeyEvent(key keys.Key, down bool) error { 147 | if logging.V(logging.FnDeclLevel) { 148 | logging.Infof("ClientConnt.%s", logging.FnNameWithArgs("%s, %t", key, down)) 149 | } 150 | 151 | msg := KeyEventMessage{messages.KeyEvent, rfbflags.BoolToRFBFlag(down), [2]byte{}, key} 152 | if err := c.send(msg); err != nil { 153 | return err 154 | } 155 | 156 | settleUI() 157 | return nil 158 | } 159 | 160 | // PointerEventMessage holds the wire format message. 161 | type PointerEventMessage struct { 162 | Msg messages.ClientMessage // message-type 163 | Mask uint8 // button-mask 164 | X, Y uint16 // x-, y-position 165 | } 166 | 167 | // PointerEvent indicates that pointer movement or a pointer button 168 | // press or release. 169 | // 170 | // The `button` is a bitwise mask of various Button values. When a button 171 | // is set, it is pressed, when it is unset, it is released. 172 | // 173 | // See RFC 6143 Section 7.5.5 174 | func (c *ClientConn) PointerEvent(button buttons.Button, x, y uint16) error { 175 | if logging.V(logging.FnDeclLevel) { 176 | logging.Infof("%s", logging.FnNameWithArgs("%s, %d, %d", button, x, y)) 177 | } 178 | 179 | msg := PointerEventMessage{messages.PointerEvent, uint8(button), x, y} 180 | if err := c.send(msg); err != nil { 181 | return err 182 | } 183 | 184 | settleUI() 185 | return nil 186 | } 187 | 188 | // ClientCutTextMessage holds the wire format message, sans the text field. 189 | type ClientCutTextMessage struct { 190 | Msg messages.ClientMessage // message-type 191 | _ [3]byte // padding 192 | Length uint32 // length 193 | } 194 | 195 | // ClientCutText tells the server that the client has new text in its cut buffer. 196 | // The text string MUST only contain Latin-1 characters. This encoding 197 | // is compatible with Go's native string format, but can only use up to 198 | // unicode.MaxLatin1 values. 199 | // 200 | // See RFC 6143 Section 7.5.6 201 | func (c *ClientConn) ClientCutText(text string) error { 202 | if logging.V(logging.FnDeclLevel) { 203 | logging.Infof("%s", logging.FnNameWithArgs("%s", text)) 204 | } 205 | 206 | for _, char := range text { 207 | if char > unicode.MaxLatin1 { 208 | return NewVNCError(fmt.Sprintf("Character %q is not valid Latin-1", char)) 209 | } 210 | } 211 | 212 | // Strip carriage-return (0x0d) chars. 213 | // From RFC: "Ends of lines are represented by the newline character (0x0a) 214 | // alone. No carriage-return (0x0d) is used." 215 | text = strings.Join(strings.Split(text, "\r"), "") 216 | 217 | msg := ClientCutTextMessage{ 218 | Msg: messages.ClientCutText, 219 | Length: uint32(len(text)), 220 | } 221 | if err := c.send(msg); err != nil { 222 | return err 223 | } 224 | if err := c.send([]byte(text)); err != nil { 225 | return err 226 | } 227 | 228 | settleUI() 229 | return nil 230 | } 231 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | //lint:file-ignore S1021 stylistic suggestions not necessary for tests 2 | package vnc 3 | 4 | import ( 5 | "testing" 6 | 7 | "github.com/kward/go-vnc/encodings" 8 | "github.com/kward/go-vnc/go/operators" 9 | ) 10 | 11 | func TestRectangle_Marshal(t *testing.T) { 12 | var ( 13 | bytes []byte 14 | rect *Rectangle 15 | ) 16 | 17 | mockConn := &MockConn{} 18 | conn := NewClientConn(mockConn, &ClientConfig{}) 19 | 20 | rect = &Rectangle{1, 2, 3, 4, &RawEncoding{}, conn.Encodable} 21 | bytes, err := rect.Marshal() 22 | if err != nil { 23 | t.Errorf("unexpected error: %v", err) 24 | } 25 | if got, want := bytes, []byte{0, 1, 0, 2, 0, 3, 0, 4, 0, 0, 0, 0}; !operators.EqualSlicesOfByte(got, want) { 26 | t.Errorf("incorrect result; got = %v, want = %v", got, want) 27 | } 28 | } 29 | 30 | func TestRectangle_Unmarshal(t *testing.T) { 31 | var rect *Rectangle 32 | 33 | rect = &Rectangle{} 34 | if err := rect.Unmarshal([]byte{0, 2, 0, 3, 0, 4, 0, 5, 0, 0, 0, 0}); err != nil { 35 | t.Errorf("unexpected error: %v", err) 36 | } 37 | if got, want := rect.X, uint16(2); got != want { 38 | t.Errorf("incorrect x-position; got = %v, want = %v", got, want) 39 | } 40 | if got, want := rect.Y, uint16(3); got != want { 41 | t.Errorf("incorrect y-position; got = %v, want = %v", got, want) 42 | } 43 | if got, want := rect.Width, uint16(4); got != want { 44 | t.Errorf("incorrect width; got = %v, want = %v", got, want) 45 | } 46 | if got, want := rect.Height, uint16(5); got != want { 47 | t.Errorf("incorrect height; got = %v, want = %v", got, want) 48 | } 49 | if rect.Enc == nil { 50 | t.Errorf("empty encoding") 51 | return 52 | } 53 | if got, want := rect.Enc.Type(), encodings.Raw; got != want { 54 | t.Errorf("incorrect encoding-type; got = %v, want = %v", got, want) 55 | } 56 | } 57 | 58 | // TODO(kward): need to read encodings in addition to rectangles. 59 | func TestFramebufferUpdate(t *testing.T) { 60 | mockConn := &MockConn{} 61 | conn := NewClientConn(mockConn, &ClientConfig{}) 62 | // Use empty PixelFormat so that the BPP is zero, and rects won't be read. 63 | // TODO(kward): give some real rectangles so this hack isn't necessary. 64 | conn.pixelFormat = PixelFormat{} 65 | 66 | for _, tt := range []struct { 67 | desc string 68 | rects []Rectangle 69 | ok bool 70 | }{ 71 | {"single raw encoded rect", 72 | []Rectangle{{1, 2, 3, 4, &RawEncoding{}, conn.Encodable}}, true}, 73 | } { 74 | mockConn.Reset() 75 | 76 | // Send the message. 77 | msg := newFramebufferUpdate(tt.rects) 78 | bytes, err := msg.Marshal() 79 | if err != nil { 80 | t.Errorf("%s: failed to marshal; %s", tt.desc, err) 81 | continue 82 | } 83 | if err := conn.send(bytes); err != nil { 84 | t.Errorf("%s: failed to send; %s", tt.desc, err) 85 | continue 86 | } 87 | 88 | // Validate message handling. 89 | var messageType uint8 90 | if err := conn.receive(&messageType); err != nil { 91 | t.Fatal(err) 92 | } 93 | fu := &FramebufferUpdate{} 94 | parsedFU, err := fu.Read(conn) 95 | if err != nil { 96 | t.Fatalf("%s: failed to read; %s", tt.desc, err) 97 | } 98 | rects := parsedFU.(*FramebufferUpdate).Rects 99 | 100 | // Validate the message. 101 | if got, want := len(rects), len(tt.rects); got != want { 102 | t.Errorf("%s: incorrect number-of-rectangles; got %d, want %d", tt.desc, got, want) 103 | continue 104 | } 105 | for i, r := range tt.rects { 106 | if got, want := rects[i].X, r.X; got != want { 107 | t.Errorf("%s: rect[%d] incorrect x-position; got %d, want %d", tt.desc, i, got, want) 108 | } 109 | if got, want := rects[i].Y, r.Y; got != want { 110 | t.Errorf("%s: rect[%d] incorrect y-position; got %d, want %d", tt.desc, i, got, want) 111 | } 112 | if got, want := rects[i].Width, r.Width; got != want { 113 | t.Errorf("%s: rect[%d] incorrect width; got %d, want %d", tt.desc, i, got, want) 114 | } 115 | if got, want := rects[i].Height, r.Height; got != want { 116 | t.Errorf("%s: rect[%d] incorrect height; got %d, want %d", tt.desc, i, got, want) 117 | } 118 | if rects[i].Enc == nil { 119 | t.Errorf("%s: rect[%d] has empty encoding", tt.desc, i) 120 | continue 121 | } 122 | if got, want := rects[i].Enc.Type(), r.Enc.Type(); got != want { 123 | t.Errorf("%s: rect[%d] incorrect encoding-type; got %d, want %d", tt.desc, i, got, want) 124 | } 125 | } 126 | } 127 | } 128 | 129 | func TestColor_Marshal(t *testing.T) { 130 | cm := ColorMap{} 131 | for i := 0; i < len(cm); i++ { 132 | cm[i] = Color{R: uint16(i), G: uint16(i << 4), B: uint16(i << 8)} 133 | } 134 | 135 | tests := []struct { 136 | c *Color 137 | data []byte 138 | }{ 139 | // 8 BPP, with ColorMap 140 | {&Color{&PixelFormat8bit, &cm, 0, 0, 0, 0}, []byte{0}}, 141 | {&Color{&PixelFormat8bit, &cm, 127, 127, 2032, 32512}, []byte{127}}, 142 | {&Color{&PixelFormat8bit, &cm, 255, 255, 4080, 65280}, []byte{255}}, 143 | // 16 BPP 144 | {&Color{&PixelFormat16bit, &ColorMap{}, 0, 0, 0, 0}, []byte{0, 0}}, 145 | {&Color{&PixelFormat16bit, &ColorMap{}, 0, 127, 7, 0}, []byte{0, 127}}, 146 | {&Color{&PixelFormat16bit, &ColorMap{}, 0, 32767, 2047, 127}, []byte{127, 255}}, 147 | {&Color{&PixelFormat16bit, &ColorMap{}, 0, 65535, 4095, 255}, []byte{255, 255}}, 148 | // 32 BPP 149 | {&Color{&PixelFormat32bit, &ColorMap{}, 0, 0, 0, 0}, []byte{0, 0, 0, 0}}, 150 | {&Color{&PixelFormat32bit, &ColorMap{}, 0, 127, 0, 0}, []byte{0, 0, 0, 127}}, 151 | {&Color{&PixelFormat32bit, &ColorMap{}, 0, 32767, 127, 0}, []byte{0, 0, 127, 255}}, 152 | {&Color{&PixelFormat32bit, &ColorMap{}, 0, 65535, 32767, 127}, []byte{0, 127, 255, 255}}, 153 | {&Color{&PixelFormat32bit, &ColorMap{}, 0, 65535, 65535, 32767}, []byte{127, 255, 255, 255}}, 154 | {&Color{&PixelFormat32bit, &ColorMap{}, 0, 65535, 65535, 65535}, []byte{255, 255, 255, 255}}, 155 | } 156 | 157 | for i, tt := range tests { 158 | data, err := tt.c.Marshal() 159 | if err != nil { 160 | t.Errorf("%v: unexpected error: %v", i, err) 161 | continue 162 | } 163 | if got, want := data, tt.data; !operators.EqualSlicesOfByte(got, want) { 164 | t.Errorf("%v: incorrect result; got = %v, want = %v", i, got, want) 165 | } 166 | } 167 | } 168 | 169 | func TestColor_Unmarshal(t *testing.T) { 170 | var cm ColorMap 171 | for i := 0; i < len(cm); i++ { 172 | cm[i] = Color{R: uint16(i), G: uint16(i << 4), B: uint16(i << 8)} 173 | } 174 | 175 | tests := []struct { 176 | data []byte 177 | pf *PixelFormat 178 | cm *ColorMap 179 | cmIndex uint32 180 | R, G, B uint16 181 | }{ 182 | // 8 BPP, with ColorMap 183 | {[]byte{0}, &PixelFormat8bit, &cm, 0, 0, 0, 0}, 184 | {[]byte{127}, &PixelFormat8bit, &cm, 127, 127, 2032, 32512}, 185 | {[]byte{255}, &PixelFormat8bit, &cm, 255, 255, 4080, 65280}, 186 | // 16 BPP 187 | {[]byte{0, 0}, &PixelFormat16bit, &ColorMap{}, 0, 0, 0, 0}, 188 | {[]byte{0, 127}, &PixelFormat16bit, &ColorMap{}, 0, 127, 7, 0}, 189 | {[]byte{127, 255}, &PixelFormat16bit, &ColorMap{}, 0, 32767, 2047, 127}, 190 | {[]byte{255, 255}, &PixelFormat16bit, &ColorMap{}, 0, 65535, 4095, 255}, 191 | // 32 BPP 192 | {[]byte{0, 0, 0, 0}, &PixelFormat32bit, &ColorMap{}, 0, 0, 0, 0}, 193 | {[]byte{0, 0, 0, 127}, &PixelFormat32bit, &ColorMap{}, 0, 127, 0, 0}, 194 | {[]byte{0, 0, 127, 255}, &PixelFormat32bit, &ColorMap{}, 0, 32767, 127, 0}, 195 | {[]byte{0, 127, 255, 255}, &PixelFormat32bit, &ColorMap{}, 0, 65535, 32767, 127}, 196 | {[]byte{127, 255, 255, 255}, &PixelFormat32bit, &ColorMap{}, 0, 65535, 65535, 32767}, 197 | {[]byte{255, 255, 255, 255}, &PixelFormat32bit, &ColorMap{}, 0, 65535, 65535, 65535}, 198 | } 199 | 200 | for i, tt := range tests { 201 | color := NewColor(tt.pf, tt.cm) 202 | if err := color.Unmarshal(tt.data); err != nil { 203 | t.Errorf("%v: unexpected error: %v", i, err) 204 | continue 205 | } 206 | if got, want := color.cmIndex, tt.cmIndex; got != want { 207 | t.Errorf("%v: incorrect cmIndex value; got = %v, want = %v", i, got, want) 208 | } 209 | if got, want := color.R, tt.R; got != want { 210 | t.Errorf("%v: incorrect R value; got = %v, want = %v", i, got, want) 211 | } 212 | if got, want := color.G, tt.G; got != want { 213 | t.Errorf("%v: incorrect G value; got = %v, want = %v", i, got, want) 214 | } 215 | if got, want := color.B, tt.B; got != want { 216 | t.Errorf("%v: incorrect B value; got = %v, want = %v", i, got, want) 217 | } 218 | } 219 | } 220 | 221 | func TestSetColorMapEntries(t *testing.T) {} 222 | 223 | func TestBell(t *testing.T) {} 224 | 225 | func TestServerCutText(t *testing.T) {} 226 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | package vnc 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "net" 7 | "reflect" 8 | "testing" 9 | "time" 10 | 11 | "github.com/kward/go-vnc/buttons" 12 | "github.com/kward/go-vnc/encodings" 13 | "github.com/kward/go-vnc/go/operators" 14 | "github.com/kward/go-vnc/keys" 15 | "github.com/kward/go-vnc/messages" 16 | "github.com/kward/go-vnc/rfbflags" 17 | "context" 18 | ) 19 | 20 | func TestSetPixelFormat(t *testing.T) { 21 | tests := []struct { 22 | pf PixelFormat 23 | msg SetPixelFormatMessage 24 | }{ 25 | { 26 | PixelFormat{}, 27 | SetPixelFormatMessage{ 28 | Msg: messages.SetPixelFormat, 29 | }}, 30 | { 31 | NewPixelFormat(16), 32 | SetPixelFormatMessage{ 33 | Msg: messages.SetPixelFormat, 34 | PF: PixelFormat{ 35 | BPP: 16, 36 | Depth: 16, 37 | BigEndian: rfbflags.RFBTrue, 38 | TrueColor: rfbflags.RFBTrue, 39 | RedMax: uint16(math.Exp2(16)) - 1, 40 | GreenMax: uint16(math.Exp2(16)) - 1, 41 | BlueMax: uint16(math.Exp2(16)) - 1, 42 | RedShift: 0, 43 | GreenShift: 4, 44 | BlueShift: 8, 45 | }, 46 | }}, 47 | } 48 | 49 | mockConn := &MockConn{} 50 | conn := NewClientConn(mockConn, &ClientConfig{}) 51 | 52 | for _, tt := range tests { 53 | mockConn.Reset() 54 | 55 | // Send request. 56 | if err := conn.SetPixelFormat(tt.pf); err != nil { 57 | t.Errorf("unexpected error: %v", err) 58 | continue 59 | } 60 | 61 | // Read back in. 62 | req := SetPixelFormatMessage{} 63 | if err := conn.receive(&req); err != nil { 64 | t.Error(err) 65 | continue 66 | } 67 | 68 | // Validate the request. 69 | if got, want := req.Msg, messages.SetPixelFormat; got != want { 70 | t.Errorf("incorrect message-type; got = %v, want = %v", got, want) 71 | } 72 | if got, want := req.PF.BPP, tt.msg.PF.BPP; got != want { 73 | t.Errorf("incorrect pixel-format bits-per-pixel; got = %v, want = %v", got, want) 74 | } 75 | if got, want := req.PF.BlueShift, tt.msg.PF.BlueShift; got != want { 76 | t.Errorf("incorrect pixel-format blue-shift; got = %v, want = %v", got, want) 77 | } 78 | } 79 | } 80 | 81 | func TestSetEncodings(t *testing.T) { 82 | tests := []struct { 83 | encs Encodings 84 | encTypes []encodings.Encoding 85 | }{ 86 | {Encodings{&RawEncoding{}}, []encodings.Encoding{0}}, 87 | } 88 | 89 | mockConn := &MockConn{} 90 | conn := NewClientConn(mockConn, &ClientConfig{}) 91 | 92 | for _, tt := range tests { 93 | mockConn.Reset() 94 | 95 | // Send request. 96 | if err := conn.SetEncodings(tt.encs); err != nil { 97 | t.Errorf("unexpected error: %v", err) 98 | continue 99 | } 100 | 101 | // Read back in. 102 | req := SetEncodingsMessage{} 103 | if err := conn.receive(&req); err != nil { 104 | t.Error(err) 105 | continue 106 | } 107 | var encs []int32 // Can't use the request struct. 108 | if err := conn.receiveN(&encs, int(req.NumEncs)); err != nil { 109 | t.Error(err) 110 | continue 111 | } 112 | 113 | // Validate the request. 114 | if got, want := req.Msg, messages.SetEncodings; got != want { 115 | t.Errorf("incorrect message-type; got = %v, want = %v", got, want) 116 | continue 117 | } 118 | if got, want := req.NumEncs, uint16(len(tt.encs)); got != want { 119 | t.Errorf("incorrect number-of-encodings; got = %v, want = %v", got, want) 120 | continue 121 | } 122 | if got, want := len(encs), len(tt.encs); got != want { 123 | t.Errorf("lengths of encodings don't match; got = %v, want = %v", got, want) 124 | continue 125 | } 126 | for i := 0; i < len(tt.encs); i++ { 127 | if got, want := encodings.Encoding(encs[i]), tt.encs[i].Type(); got != want { 128 | t.Errorf("incorrect encoding-type [%v]; got = %v, want = %v", i, got, want) 129 | } 130 | } 131 | } 132 | } 133 | 134 | func TestFramebufferUpdateRequest(t *testing.T) { 135 | tests := []struct { 136 | inc rfbflags.RFBFlag 137 | x, y, w, h uint16 138 | }{ 139 | {rfbflags.RFBFalse, 10, 20, 30, 40}, 140 | {rfbflags.RFBTrue, 11, 21, 31, 41}, 141 | } 142 | 143 | mockConn := &MockConn{} 144 | conn := NewClientConn(mockConn, &ClientConfig{}) 145 | 146 | for _, tt := range tests { 147 | mockConn.Reset() 148 | 149 | // Send request. 150 | err := conn.FramebufferUpdateRequest(tt.inc, tt.x, tt.y, tt.w, tt.h) 151 | if err != nil { 152 | t.Errorf("unexpected error: %v", err) 153 | continue 154 | } 155 | 156 | // Read back in. 157 | req := FramebufferUpdateRequestMessage{} 158 | if err := conn.receive(&req); err != nil { 159 | t.Error(err) 160 | continue 161 | } 162 | 163 | // Validate the request. 164 | if req.Msg != messages.FramebufferUpdateRequest { 165 | t.Errorf("incorrect message-type; got = %v, want = %v", req.Msg, messages.FramebufferUpdateRequest) 166 | } 167 | if req.Inc != tt.inc { 168 | t.Errorf("incorrect incremental; got = %v, want = %v", req.Inc, tt.inc) 169 | } 170 | if req.X != tt.x { 171 | t.Errorf("incorrect x-position; got = %v, want = %v", req.X, tt.x) 172 | } 173 | if req.Y != tt.y { 174 | t.Errorf("incorrect y-position; got = %v, want = %v", req.Y, tt.y) 175 | } 176 | if req.Width != tt.w { 177 | t.Errorf("incorrect width; got = %v, want = %v", req.Width, tt.w) 178 | } 179 | if req.Height != tt.h { 180 | t.Errorf("incorrect height; got = %v, want = %v", req.Height, tt.h) 181 | } 182 | } 183 | } 184 | 185 | func ExampleClientConn_KeyEvent() { 186 | // Establish TCP connection. 187 | nc, err := net.DialTimeout("tcp", "127.0.0.1:5900", 10*time.Second) 188 | if err != nil { 189 | panic(fmt.Sprintf("Error connecting to host: %v\n", err)) 190 | } 191 | 192 | // Negotiate VNC connection. 193 | vc, err := Connect(context.Background(), nc, NewClientConfig("somepass")) 194 | if err != nil { 195 | panic(fmt.Sprintf("Could not negotiate a VNC connection: %v\n", err)) 196 | } 197 | 198 | // Press and release the return key. 199 | vc.KeyEvent(keys.Return, true) 200 | vc.KeyEvent(keys.Return, false) 201 | 202 | // Close VNC connection. 203 | vc.Close() 204 | } 205 | 206 | func TestKeyEvent(t *testing.T) { 207 | tests := []struct { 208 | key keys.Key 209 | down bool 210 | }{ 211 | {keys.Digit0, PressKey}, 212 | {keys.Digit1, ReleaseKey}, 213 | } 214 | 215 | mockConn := &MockConn{} 216 | conn := NewClientConn(mockConn, &ClientConfig{}) 217 | 218 | SetSettle(0) // Disable UI settling for tests. 219 | for _, tt := range tests { 220 | mockConn.Reset() 221 | 222 | // Send request. 223 | err := conn.KeyEvent(tt.key, tt.down) 224 | if err != nil { 225 | t.Errorf("unexpected error: %v", err) 226 | continue 227 | } 228 | 229 | // Read back in. 230 | var req KeyEventMessage 231 | if err := conn.receive(&req); err != nil { 232 | t.Error(err) 233 | continue 234 | } 235 | 236 | // Validate the request. 237 | if got, want := req.Msg, messages.KeyEvent; got != want { 238 | t.Errorf("incorrect message-type; got = %v, want = %v", got, want) 239 | } 240 | down := rfbflags.ToBool(req.DownFlag) 241 | if got, want := down, tt.down; got != want { 242 | t.Errorf("incorrect down-flag; got = %v, want = %v", got, want) 243 | } 244 | if got, want := req.Key, tt.key; got != want { 245 | t.Errorf("incorrect key; got = %v, want = %v", got, want) 246 | } 247 | } 248 | } 249 | 250 | func ExampleClientConn_PointerEvent() { 251 | // Establish TCP connection. 252 | nc, err := net.DialTimeout("tcp", "127.0.0.1:5900", 10*time.Second) 253 | if err != nil { 254 | panic(fmt.Sprintf("Error connecting to host: %v\n", err)) 255 | } 256 | 257 | // Negotiate VNC connection. 258 | vc, err := Connect(context.Background(), nc, NewClientConfig("somepass")) 259 | if err != nil { 260 | panic(fmt.Sprintf("Could not negotiate a VNC connection: %v\n", err)) 261 | } 262 | 263 | // Move mouse to x=100, y=200. 264 | x, y := uint16(100), uint16(200) 265 | vc.PointerEvent(buttons.None, x, y) 266 | 267 | // Click and release the left mouse button. 268 | vc.PointerEvent(buttons.Left, x, y) 269 | vc.PointerEvent(buttons.None, x, y) 270 | 271 | // Close connection. 272 | vc.Close() 273 | } 274 | 275 | func TestPointerEvent(t *testing.T) { 276 | tests := []struct { 277 | button buttons.Button 278 | x, y uint16 279 | }{ 280 | {buttons.None, 0, 0}, 281 | {buttons.Left | buttons.Right, 123, 456}, 282 | } 283 | 284 | mockConn := &MockConn{} 285 | conn := NewClientConn(mockConn, &ClientConfig{}) 286 | 287 | SetSettle(0) // Disable UI settling for tests. 288 | for _, tt := range tests { 289 | mockConn.Reset() 290 | 291 | // Send request. 292 | err := conn.PointerEvent(tt.button, tt.x, tt.y) 293 | if err != nil { 294 | t.Errorf("unexpected error: %v", err) 295 | continue 296 | } 297 | 298 | // Read back in. 299 | req := PointerEventMessage{} 300 | if err := conn.receive(&req); err != nil { 301 | t.Error(err) 302 | continue 303 | } 304 | 305 | // Validate the request. 306 | if got, want := req.Msg, messages.PointerEvent; got != want { 307 | t.Errorf("incorrect message-type; got = %v, want = %v", got, want) 308 | } 309 | if got, want := req.Mask, buttons.Mask(tt.button); got != want { 310 | t.Errorf("incorrect button-mask; got = %v, want = %v", got, want) 311 | } 312 | if got, want := req.X, tt.x; got != want { 313 | t.Errorf("incorrect x-position; got = %v, want = %v", got, want) 314 | } 315 | if got, want := req.Y, tt.y; got != want { 316 | t.Errorf("incorrect y-position; got = %v, want = %v", got, want) 317 | } 318 | } 319 | } 320 | 321 | func TestClientCutText(t *testing.T) { 322 | tests := []struct { 323 | text string 324 | sent []byte 325 | ok bool 326 | }{ 327 | {"abc123", []byte("abc123"), true}, 328 | {"foo\r\nbar", []byte("foo\nbar"), true}, 329 | {"", []byte{}, true}, 330 | {"ɹɐqooɟ", []byte{}, false}, 331 | } 332 | 333 | mockConn := &MockConn{} 334 | conn := NewClientConn(mockConn, &ClientConfig{}) 335 | 336 | SetSettle(0) // Disable UI settling for tests. 337 | for _, tt := range tests { 338 | mockConn.Reset() 339 | 340 | // Send request. 341 | err := conn.ClientCutText(tt.text) 342 | if err == nil && !tt.ok { 343 | t.Errorf("expected error") 344 | } 345 | if err != nil { 346 | if verr, ok := err.(*VNCError); !ok { 347 | t.Errorf("unexpected %v error: %v", reflect.TypeOf(err), verr) 348 | } 349 | } 350 | if !tt.ok { 351 | continue 352 | } 353 | 354 | // Read back in. 355 | req := ClientCutTextMessage{} 356 | if err := conn.receive(&req); err != nil { 357 | t.Error(err) 358 | continue 359 | } 360 | var text []byte 361 | if err := conn.receiveN(&text, int(req.Length)); err != nil { 362 | t.Error(err) 363 | continue 364 | } 365 | 366 | // Validate the request. 367 | if got, want := req.Msg, messages.ClientCutText; got != want { 368 | t.Errorf("incorrect message-type; got = %v, want = %v", got, want) 369 | } 370 | if got, want := req.Length, uint32(len(tt.sent)); got != want { 371 | t.Errorf("incorrect length; got = %v, want = %v", got, want) 372 | } 373 | if got, want := text, tt.sent; !operators.EqualSlicesOfByte(got, want) { 374 | t.Errorf("incorrect text; got = %v, want = %v", got, want) 375 | } 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /vncclient.go: -------------------------------------------------------------------------------- 1 | // VNC client implementation. 2 | 3 | package vnc 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | "fmt" 9 | "log" 10 | "net" 11 | "reflect" 12 | 13 | "context" 14 | 15 | "github.com/kward/go-vnc/go/metrics" 16 | "github.com/kward/go-vnc/logging" 17 | "github.com/kward/go-vnc/messages" 18 | ) 19 | 20 | // Connect negotiates a connection to a VNC server. 21 | func Connect(ctx context.Context, c net.Conn, cfg *ClientConfig) (*ClientConn, error) { 22 | conn := NewClientConn(c, cfg) 23 | 24 | if err := conn.processContext(ctx); err != nil { 25 | log.Fatalf("invalid context; %s", err) 26 | } 27 | 28 | if err := conn.protocolVersionHandshake(ctx); err != nil { 29 | conn.Close() 30 | return nil, err 31 | } 32 | if err := conn.securityHandshake(); err != nil { 33 | conn.Close() 34 | return nil, err 35 | } 36 | if err := conn.securityResultHandshake(); err != nil { 37 | conn.Close() 38 | return nil, err 39 | } 40 | if err := conn.clientInit(); err != nil { 41 | conn.Close() 42 | return nil, err 43 | } 44 | if err := conn.serverInit(); err != nil { 45 | conn.Close() 46 | return nil, err 47 | } 48 | 49 | // Send client-to-server messages. 50 | encs := conn.encodings 51 | if err := conn.SetEncodings(encs); err != nil { 52 | conn.Close() 53 | return nil, Errorf("failure calling SetEncodings; %s", err) 54 | } 55 | pf := conn.pixelFormat 56 | if err := conn.SetPixelFormat(pf); err != nil { 57 | conn.Close() 58 | return nil, Errorf("failure calling SetPixelFormat; %s", err) 59 | } 60 | 61 | return conn, nil 62 | } 63 | 64 | // A ClientConfig structure is used to configure a ClientConn. After 65 | // one has been passed to initialize a connection, it must not be modified. 66 | type ClientConfig struct { 67 | secType uint8 // The negotiated security type. 68 | 69 | // A slice of ClientAuth methods. Only the first instance that is 70 | // suitable by the server will be used to authenticate. 71 | Auth []ClientAuth 72 | 73 | // Password for servers that require authentication. 74 | Password string 75 | 76 | // Exclusive determines whether the connection is shared with other 77 | // clients. If true, then all other clients connected will be 78 | // disconnected when a connection is established to the VNC server. 79 | Exclusive bool 80 | 81 | // The channel that all messages received from the server will be 82 | // sent on. If the channel blocks, then the goroutine reading data 83 | // from the VNC server may block indefinitely. It is up to the user 84 | // of the library to ensure that this channel is properly read. 85 | // If this is not set, then all messages will be discarded. 86 | ServerMessageCh chan ServerMessage 87 | 88 | // A slice of supported messages that can be read from the server. 89 | // This only needs to contain NEW server messages, and doesn't 90 | // need to explicitly contain the RFC-required messages. 91 | ServerMessages []ServerMessage 92 | } 93 | 94 | // NewClientConfig returns a populated ClientConfig. 95 | func NewClientConfig(p string) *ClientConfig { 96 | return &ClientConfig{ 97 | Auth: []ClientAuth{ 98 | &ClientAuthNone{}, 99 | &ClientAuthVNC{p}, 100 | }, 101 | Password: p, 102 | ServerMessages: []ServerMessage{ 103 | &FramebufferUpdate{}, 104 | &SetColorMapEntries{}, 105 | &Bell{}, 106 | &ServerCutText{}, 107 | }, 108 | } 109 | } 110 | 111 | // The ClientConn type holds client connection information. 112 | type ClientConn struct { 113 | c net.Conn 114 | config *ClientConfig 115 | protocolVersion string 116 | 117 | // If the pixel format uses a color map, then this is the color 118 | // map that is used. This should not be modified directly, since 119 | // the data comes from the server. 120 | // Definition in §5 - Representation of Pixel Data. 121 | colorMap ColorMap 122 | 123 | // Name associated with the desktop, sent from the server. 124 | desktopName string 125 | 126 | // Encodings supported by the client. This should not be modified 127 | // directly. Instead, SetEncodings() should be used. 128 | encodings Encodings 129 | 130 | // Height of the frame buffer in pixels, sent from the server. 131 | fbHeight uint16 132 | 133 | // Width of the frame buffer in pixels, sent from the server. 134 | fbWidth uint16 135 | 136 | // The pixel format associated with the connection. This shouldn't 137 | // be modified. If you wish to set a new pixel format, use the 138 | // SetPixelFormat method. 139 | pixelFormat PixelFormat 140 | 141 | // Track metrics on system performance. 142 | metrics map[string]metrics.Metric 143 | } 144 | 145 | func NewClientConn(c net.Conn, cfg *ClientConfig) *ClientConn { 146 | return &ClientConn{ 147 | c: c, 148 | config: cfg, 149 | encodings: Encodings{&RawEncoding{}}, 150 | pixelFormat: PixelFormat32bit, 151 | metrics: map[string]metrics.Metric{ 152 | "bytes-received": &metrics.Gauge{}, 153 | "bytes-sent": &metrics.Gauge{}, 154 | }, 155 | } 156 | } 157 | 158 | // Close a connection to a VNC server. 159 | func (c *ClientConn) Close() error { 160 | log.Print("VNC Client connection closed.") 161 | return c.c.Close() 162 | } 163 | 164 | // DesktopName returns the server provided desktop name. 165 | func (c *ClientConn) DesktopName() string { 166 | return c.desktopName 167 | } 168 | 169 | // setDesktopName stores the server provided desktop name. 170 | func (c *ClientConn) setDesktopName(name string) { 171 | if logging.V(logging.ResultLevel) { 172 | logging.Infof("desktopName: %s", name) 173 | } 174 | c.desktopName = name 175 | } 176 | 177 | // Encodings returns the server provided encodings. 178 | func (c *ClientConn) Encodings() Encodings { 179 | return c.encodings 180 | } 181 | 182 | // FramebufferHeight returns the server provided framebuffer height. 183 | func (c *ClientConn) FramebufferHeight() uint16 { 184 | return c.fbHeight 185 | } 186 | 187 | // setFramebufferHeight stores the server provided framebuffer height. 188 | func (c *ClientConn) setFramebufferHeight(height uint16) { 189 | if logging.V(logging.ResultLevel) { 190 | logging.Infof("height: %d", height) 191 | } 192 | c.fbHeight = height 193 | } 194 | 195 | // FramebufferWidth returns the server provided framebuffer width. 196 | func (c *ClientConn) FramebufferWidth() uint16 { 197 | return c.fbWidth 198 | } 199 | 200 | // setFramebufferWidth stores the server provided framebuffer width. 201 | func (c *ClientConn) setFramebufferWidth(width uint16) { 202 | if logging.V(logging.ResultLevel) { 203 | logging.Infof("width: %d", width) 204 | } 205 | c.fbWidth = width 206 | } 207 | 208 | // ListenAndHandle listens to a VNC server and handles server messages. 209 | func (c *ClientConn) ListenAndHandle() error { 210 | if logging.V(logging.FnDeclLevel) { 211 | logging.Infof("%s", logging.FnName()) 212 | } 213 | defer c.Close() 214 | 215 | if c.config.ServerMessages == nil { 216 | return NewVNCError("Client config error: ServerMessages undefined") 217 | } 218 | serverMessages := make(map[messages.ServerMessage]ServerMessage) 219 | for _, m := range c.config.ServerMessages { 220 | serverMessages[m.Type()] = m 221 | } 222 | 223 | for { 224 | var messageType messages.ServerMessage 225 | if err := c.receive(&messageType); err != nil { 226 | log.Print("error: reading from server") 227 | break 228 | } 229 | if logging.V(logging.ResultLevel) { 230 | logging.Infof("message-type: %s", messageType) 231 | } 232 | 233 | msg, ok := serverMessages[messageType] 234 | if !ok { 235 | // Unsupported message type! Bad! 236 | log.Printf("error unsupported message-type: %v", messageType) 237 | break 238 | } 239 | 240 | parsedMsg, err := msg.Read(c) 241 | if err != nil { 242 | log.Printf("error parsing message; %v", err) 243 | break 244 | } 245 | 246 | if c.config.ServerMessageCh == nil { 247 | log.Print("ignoring message; no server message channel") 248 | continue 249 | } 250 | 251 | c.config.ServerMessageCh <- parsedMsg 252 | } 253 | 254 | return nil 255 | } 256 | 257 | // receive a packet from the network. 258 | func (c *ClientConn) receive(data interface{}) error { 259 | if err := binary.Read(c.c, binary.BigEndian, data); err != nil { 260 | return err 261 | } 262 | c.metrics["bytes-received"].Adjust(int64(binary.Size(data))) 263 | return nil 264 | } 265 | 266 | // receiveN receives N packets from the network. 267 | func (c *ClientConn) receiveN(data interface{}, n int) error { 268 | if logging.V(logging.FnDeclLevel) { 269 | logging.Infof("ClientConn.%s", logging.FnName()) 270 | } 271 | if n == 0 { 272 | return nil 273 | } 274 | 275 | switch v := data.(type) { 276 | case *[]uint8: 277 | var b uint8 278 | for i := 0; i < n; i++ { 279 | if err := binary.Read(c.c, binary.BigEndian, &b); err != nil { 280 | return err 281 | } 282 | *v = append(*v, b) 283 | } 284 | case *[]int32: 285 | var x int32 286 | for i := 0; i < n; i++ { 287 | if err := binary.Read(c.c, binary.BigEndian, &x); err != nil { 288 | return err 289 | } 290 | *v = append(*v, x) 291 | } 292 | case *bytes.Buffer: 293 | var b byte 294 | for i := 0; i < n; i++ { 295 | if err := binary.Read(c.c, binary.BigEndian, &b); err != nil { 296 | return err 297 | } 298 | v.WriteByte(b) 299 | } 300 | default: 301 | return NewVNCError(fmt.Sprintf("unrecognized data type %v", reflect.TypeOf(data))) 302 | } 303 | c.metrics["bytes-received"].Adjust(int64(binary.Size(data))) 304 | return nil 305 | } 306 | 307 | // send a packet to the network. 308 | func (c *ClientConn) send(data interface{}) error { 309 | if logging.V(logging.SpamLevel) { 310 | logging.Infof("ClientConn.%s", logging.FnNameWithArgs("%v", data)) 311 | } 312 | if err := binary.Write(c.c, binary.BigEndian, data); err != nil { 313 | return err 314 | } 315 | c.metrics["bytes-sent"].Adjust(int64(binary.Size(data))) 316 | return nil 317 | } 318 | 319 | // sendN sends N packets to the network. 320 | // func (c *ClientConn) sendN(data interface{}, n int) error { 321 | // var buf bytes.Buffer 322 | // switch data := data.(type) { 323 | // case []uint8: 324 | // for _, d := range data { 325 | // if err := binary.Write(&buf, binary.BigEndian, &d); err != nil { 326 | // return err 327 | // } 328 | // } 329 | // case []int32: 330 | // for _, d := range data { 331 | // if err := binary.Write(&buf, binary.BigEndian, &d); err != nil { 332 | // return err 333 | // } 334 | // } 335 | // default: 336 | // return NewVNCError(fmt.Sprintf("unable to send data; unrecognized data type %v", reflect.TypeOf(data))) 337 | // } 338 | // if err := binary.Write(c.c, binary.BigEndian, buf.Bytes()); err != nil { 339 | // return err 340 | // } 341 | // c.metrics["bytes-sent"].Adjust(int64(binary.Size(data))) 342 | // return nil 343 | // } 344 | 345 | func (c *ClientConn) processContext(ctx context.Context) error { 346 | if mpv := ctx.Value("vnc_max_proto_version"); mpv != nil && mpv != "" { 347 | log.Printf("vnc_max_proto_version: %v", mpv) 348 | vers := []string{"3.3", "3.8"} 349 | valid := false 350 | for _, v := range vers { 351 | if mpv == v { 352 | valid = true 353 | break 354 | } 355 | } 356 | if !valid { 357 | return fmt.Errorf("invalid max protocol version %v; supported versions are %v", mpv, vers) 358 | } 359 | } 360 | 361 | return nil 362 | } 363 | 364 | func (c *ClientConn) DebugMetrics() { 365 | log.Println("Metrics:") 366 | for name, metric := range c.metrics { 367 | log.Printf(" %v: %v", name, metric.Value()) 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /keys/key_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Key"; DO NOT EDIT. 2 | 3 | package keys 4 | 5 | import "strconv" 6 | 7 | func _() { 8 | // An "invalid array index" compiler error signifies that the constant values have changed. 9 | // Re-run the stringer command to generate them again. 10 | var x [1]struct{} 11 | _ = x[Space-32] 12 | _ = x[Exclaim-33] 13 | _ = x[QuoteDbl-34] 14 | _ = x[NumberSign-35] 15 | _ = x[Dollar-36] 16 | _ = x[Percent-37] 17 | _ = x[Ampersand-38] 18 | _ = x[Apostrophe-39] 19 | _ = x[ParenLeft-40] 20 | _ = x[ParenRight-41] 21 | _ = x[Asterisk-42] 22 | _ = x[Plus-43] 23 | _ = x[Comma-44] 24 | _ = x[Minus-45] 25 | _ = x[Period-46] 26 | _ = x[Slash-47] 27 | _ = x[Digit0-48] 28 | _ = x[Digit1-49] 29 | _ = x[Digit2-50] 30 | _ = x[Digit3-51] 31 | _ = x[Digit4-52] 32 | _ = x[Digit5-53] 33 | _ = x[Digit6-54] 34 | _ = x[Digit7-55] 35 | _ = x[Digit8-56] 36 | _ = x[Digit9-57] 37 | _ = x[Colon-58] 38 | _ = x[Semicolon-59] 39 | _ = x[Less-60] 40 | _ = x[Equal-61] 41 | _ = x[Greater-62] 42 | _ = x[Question-63] 43 | _ = x[At-64] 44 | _ = x[A-65] 45 | _ = x[B-66] 46 | _ = x[C-67] 47 | _ = x[D-68] 48 | _ = x[E-69] 49 | _ = x[F-70] 50 | _ = x[G-71] 51 | _ = x[H-72] 52 | _ = x[I-73] 53 | _ = x[J-74] 54 | _ = x[K-75] 55 | _ = x[L-76] 56 | _ = x[M-77] 57 | _ = x[N-78] 58 | _ = x[O-79] 59 | _ = x[P-80] 60 | _ = x[Q-81] 61 | _ = x[R-82] 62 | _ = x[S-83] 63 | _ = x[T-84] 64 | _ = x[U-85] 65 | _ = x[V-86] 66 | _ = x[W-87] 67 | _ = x[X-88] 68 | _ = x[Y-89] 69 | _ = x[Z-90] 70 | _ = x[BracketLeft-91] 71 | _ = x[Backslash-92] 72 | _ = x[BracketRight-93] 73 | _ = x[AsciiCircum-94] 74 | _ = x[Underscore-95] 75 | _ = x[Grave-96] 76 | _ = x[SmallA-97] 77 | _ = x[SmallB-98] 78 | _ = x[SmallC-99] 79 | _ = x[SmallD-100] 80 | _ = x[SmallE-101] 81 | _ = x[SmallF-102] 82 | _ = x[SmallG-103] 83 | _ = x[SmallH-104] 84 | _ = x[SmallI-105] 85 | _ = x[SmallJ-106] 86 | _ = x[SmallK-107] 87 | _ = x[SmallL-108] 88 | _ = x[SmallM-109] 89 | _ = x[SmallN-110] 90 | _ = x[SmallO-111] 91 | _ = x[SmallP-112] 92 | _ = x[SmallQ-113] 93 | _ = x[SmallR-114] 94 | _ = x[SmallS-115] 95 | _ = x[SmallT-116] 96 | _ = x[SmallU-117] 97 | _ = x[SmallV-118] 98 | _ = x[SmallW-119] 99 | _ = x[SmallX-120] 100 | _ = x[SmallY-121] 101 | _ = x[SmallZ-122] 102 | _ = x[BraceLeft-123] 103 | _ = x[Bar-124] 104 | _ = x[BraceRight-125] 105 | _ = x[AsciiTilde-126] 106 | _ = x[BackSpace-65288] 107 | _ = x[Tab-65289] 108 | _ = x[Linefeed-65290] 109 | _ = x[Clear-65291] 110 | _ = x[Return-65293] 111 | _ = x[Pause-65299] 112 | _ = x[ScrollLock-65300] 113 | _ = x[SysReq-65301] 114 | _ = x[Escape-65307] 115 | _ = x[Delete-65535] 116 | _ = x[Home-65360] 117 | _ = x[Left-65361] 118 | _ = x[Up-65362] 119 | _ = x[Right-65363] 120 | _ = x[Down-65364] 121 | _ = x[PageUp-65365] 122 | _ = x[PageDown-65366] 123 | _ = x[End-65367] 124 | _ = x[Begin-65368] 125 | _ = x[Select-65376] 126 | _ = x[Print-65376] 127 | _ = x[Execute-65376] 128 | _ = x[Insert-65376] 129 | _ = x[Undo-65376] 130 | _ = x[Redo-65376] 131 | _ = x[Menu-65376] 132 | _ = x[Find-65376] 133 | _ = x[Cancel-65376] 134 | _ = x[Help-65376] 135 | _ = x[Break-65376] 136 | _ = x[ModeSwitch-65406] 137 | _ = x[NumLock-65407] 138 | _ = x[KeypadSpace-65408] 139 | _ = x[KeypadTab-65417] 140 | _ = x[KeypadEnter-65421] 141 | _ = x[KeypadF1-65425] 142 | _ = x[KeypadF2-65426] 143 | _ = x[KeypadF3-65427] 144 | _ = x[KeypadF4-65428] 145 | _ = x[KeypadHome-65429] 146 | _ = x[KeypadLeft-65430] 147 | _ = x[KeypadUp-65431] 148 | _ = x[KeypadRight-65432] 149 | _ = x[KeypadDown-65433] 150 | _ = x[KeypadPrior-65434] 151 | _ = x[KeypadPageUp-65435] 152 | _ = x[KeypadNext-65436] 153 | _ = x[KeypadPageDown-65437] 154 | _ = x[KeypadEnd-65438] 155 | _ = x[KeypadBegin-65439] 156 | _ = x[KeypadInsert-65440] 157 | _ = x[KeypadDelete-65441] 158 | _ = x[KeypadMultiply-65442] 159 | _ = x[KeypadAdd-65443] 160 | _ = x[KeypadSeparator-65444] 161 | _ = x[KeypadSubtract-65445] 162 | _ = x[KeypadDecimal-65446] 163 | _ = x[KeypadDivide-65447] 164 | _ = x[Keypad0-65448] 165 | _ = x[Keypad1-65449] 166 | _ = x[Keypad2-65450] 167 | _ = x[Keypad3-65451] 168 | _ = x[Keypad4-65452] 169 | _ = x[Keypad5-65453] 170 | _ = x[Keypad6-65454] 171 | _ = x[Keypad7-65455] 172 | _ = x[Keypad8-65456] 173 | _ = x[Keypad9-65457] 174 | _ = x[KeypadEqual-65469] 175 | _ = x[F1-65470] 176 | _ = x[F2-65471] 177 | _ = x[F3-65472] 178 | _ = x[F4-65473] 179 | _ = x[F5-65474] 180 | _ = x[F6-65475] 181 | _ = x[F7-65476] 182 | _ = x[F8-65477] 183 | _ = x[F9-65478] 184 | _ = x[F10-65479] 185 | _ = x[F11-65480] 186 | _ = x[F12-65481] 187 | _ = x[ShiftLeft-65505] 188 | _ = x[ShiftRight-65506] 189 | _ = x[ControlLeft-65507] 190 | _ = x[ControlRight-65508] 191 | _ = x[CapsLock-65509] 192 | _ = x[ShiftLock-65510] 193 | _ = x[MetaLeft-65511] 194 | _ = x[MetaRight-65512] 195 | _ = x[AltLeft-65513] 196 | _ = x[AltRight-65514] 197 | _ = x[SuperLeft-65515] 198 | _ = x[SuperRight-65516] 199 | _ = x[HyperLeft-65517] 200 | _ = x[HyperRight-65518] 201 | } 202 | 203 | const _Key_name = "SpaceExclaimQuoteDblNumberSignDollarPercentAmpersandApostropheParenLeftParenRightAsteriskPlusCommaMinusPeriodSlashDigit0Digit1Digit2Digit3Digit4Digit5Digit6Digit7Digit8Digit9ColonSemicolonLessEqualGreaterQuestionAtABCDEFGHIJKLMNOPQRSTUVWXYZBracketLeftBackslashBracketRightAsciiCircumUnderscoreGraveSmallASmallBSmallCSmallDSmallESmallFSmallGSmallHSmallISmallJSmallKSmallLSmallMSmallNSmallOSmallPSmallQSmallRSmallSSmallTSmallUSmallVSmallWSmallXSmallYSmallZBraceLeftBarBraceRightAsciiTildeBackSpaceTabLinefeedClearReturnPauseScrollLockSysReqEscapeHomeLeftUpRightDownPageUpPageDownEndBeginSelectModeSwitchNumLockKeypadSpaceKeypadTabKeypadEnterKeypadF1KeypadF2KeypadF3KeypadF4KeypadHomeKeypadLeftKeypadUpKeypadRightKeypadDownKeypadPriorKeypadPageUpKeypadNextKeypadPageDownKeypadEndKeypadBeginKeypadInsertKeypadDeleteKeypadMultiplyKeypadAddKeypadSeparatorKeypadSubtractKeypadDecimalKeypadDivideKeypad0Keypad1Keypad2Keypad3Keypad4Keypad5Keypad6Keypad7Keypad8Keypad9KeypadEqualF1F2F3F4F5F6F7F8F9F10F11F12ShiftLeftShiftRightControlLeftControlRightCapsLockShiftLockMetaLeftMetaRightAltLeftAltRightSuperLeftSuperRightHyperLeftHyperRightDelete" 204 | 205 | var _Key_map = map[Key]string{ 206 | 32: _Key_name[0:5], 207 | 33: _Key_name[5:12], 208 | 34: _Key_name[12:20], 209 | 35: _Key_name[20:30], 210 | 36: _Key_name[30:36], 211 | 37: _Key_name[36:43], 212 | 38: _Key_name[43:52], 213 | 39: _Key_name[52:62], 214 | 40: _Key_name[62:71], 215 | 41: _Key_name[71:81], 216 | 42: _Key_name[81:89], 217 | 43: _Key_name[89:93], 218 | 44: _Key_name[93:98], 219 | 45: _Key_name[98:103], 220 | 46: _Key_name[103:109], 221 | 47: _Key_name[109:114], 222 | 48: _Key_name[114:120], 223 | 49: _Key_name[120:126], 224 | 50: _Key_name[126:132], 225 | 51: _Key_name[132:138], 226 | 52: _Key_name[138:144], 227 | 53: _Key_name[144:150], 228 | 54: _Key_name[150:156], 229 | 55: _Key_name[156:162], 230 | 56: _Key_name[162:168], 231 | 57: _Key_name[168:174], 232 | 58: _Key_name[174:179], 233 | 59: _Key_name[179:188], 234 | 60: _Key_name[188:192], 235 | 61: _Key_name[192:197], 236 | 62: _Key_name[197:204], 237 | 63: _Key_name[204:212], 238 | 64: _Key_name[212:214], 239 | 65: _Key_name[214:215], 240 | 66: _Key_name[215:216], 241 | 67: _Key_name[216:217], 242 | 68: _Key_name[217:218], 243 | 69: _Key_name[218:219], 244 | 70: _Key_name[219:220], 245 | 71: _Key_name[220:221], 246 | 72: _Key_name[221:222], 247 | 73: _Key_name[222:223], 248 | 74: _Key_name[223:224], 249 | 75: _Key_name[224:225], 250 | 76: _Key_name[225:226], 251 | 77: _Key_name[226:227], 252 | 78: _Key_name[227:228], 253 | 79: _Key_name[228:229], 254 | 80: _Key_name[229:230], 255 | 81: _Key_name[230:231], 256 | 82: _Key_name[231:232], 257 | 83: _Key_name[232:233], 258 | 84: _Key_name[233:234], 259 | 85: _Key_name[234:235], 260 | 86: _Key_name[235:236], 261 | 87: _Key_name[236:237], 262 | 88: _Key_name[237:238], 263 | 89: _Key_name[238:239], 264 | 90: _Key_name[239:240], 265 | 91: _Key_name[240:251], 266 | 92: _Key_name[251:260], 267 | 93: _Key_name[260:272], 268 | 94: _Key_name[272:283], 269 | 95: _Key_name[283:293], 270 | 96: _Key_name[293:298], 271 | 97: _Key_name[298:304], 272 | 98: _Key_name[304:310], 273 | 99: _Key_name[310:316], 274 | 100: _Key_name[316:322], 275 | 101: _Key_name[322:328], 276 | 102: _Key_name[328:334], 277 | 103: _Key_name[334:340], 278 | 104: _Key_name[340:346], 279 | 105: _Key_name[346:352], 280 | 106: _Key_name[352:358], 281 | 107: _Key_name[358:364], 282 | 108: _Key_name[364:370], 283 | 109: _Key_name[370:376], 284 | 110: _Key_name[376:382], 285 | 111: _Key_name[382:388], 286 | 112: _Key_name[388:394], 287 | 113: _Key_name[394:400], 288 | 114: _Key_name[400:406], 289 | 115: _Key_name[406:412], 290 | 116: _Key_name[412:418], 291 | 117: _Key_name[418:424], 292 | 118: _Key_name[424:430], 293 | 119: _Key_name[430:436], 294 | 120: _Key_name[436:442], 295 | 121: _Key_name[442:448], 296 | 122: _Key_name[448:454], 297 | 123: _Key_name[454:463], 298 | 124: _Key_name[463:466], 299 | 125: _Key_name[466:476], 300 | 126: _Key_name[476:486], 301 | 65288: _Key_name[486:495], 302 | 65289: _Key_name[495:498], 303 | 65290: _Key_name[498:506], 304 | 65291: _Key_name[506:511], 305 | 65293: _Key_name[511:517], 306 | 65299: _Key_name[517:522], 307 | 65300: _Key_name[522:532], 308 | 65301: _Key_name[532:538], 309 | 65307: _Key_name[538:544], 310 | 65360: _Key_name[544:548], 311 | 65361: _Key_name[548:552], 312 | 65362: _Key_name[552:554], 313 | 65363: _Key_name[554:559], 314 | 65364: _Key_name[559:563], 315 | 65365: _Key_name[563:569], 316 | 65366: _Key_name[569:577], 317 | 65367: _Key_name[577:580], 318 | 65368: _Key_name[580:585], 319 | 65376: _Key_name[585:591], 320 | 65406: _Key_name[591:601], 321 | 65407: _Key_name[601:608], 322 | 65408: _Key_name[608:619], 323 | 65417: _Key_name[619:628], 324 | 65421: _Key_name[628:639], 325 | 65425: _Key_name[639:647], 326 | 65426: _Key_name[647:655], 327 | 65427: _Key_name[655:663], 328 | 65428: _Key_name[663:671], 329 | 65429: _Key_name[671:681], 330 | 65430: _Key_name[681:691], 331 | 65431: _Key_name[691:699], 332 | 65432: _Key_name[699:710], 333 | 65433: _Key_name[710:720], 334 | 65434: _Key_name[720:731], 335 | 65435: _Key_name[731:743], 336 | 65436: _Key_name[743:753], 337 | 65437: _Key_name[753:767], 338 | 65438: _Key_name[767:776], 339 | 65439: _Key_name[776:787], 340 | 65440: _Key_name[787:799], 341 | 65441: _Key_name[799:811], 342 | 65442: _Key_name[811:825], 343 | 65443: _Key_name[825:834], 344 | 65444: _Key_name[834:849], 345 | 65445: _Key_name[849:863], 346 | 65446: _Key_name[863:876], 347 | 65447: _Key_name[876:888], 348 | 65448: _Key_name[888:895], 349 | 65449: _Key_name[895:902], 350 | 65450: _Key_name[902:909], 351 | 65451: _Key_name[909:916], 352 | 65452: _Key_name[916:923], 353 | 65453: _Key_name[923:930], 354 | 65454: _Key_name[930:937], 355 | 65455: _Key_name[937:944], 356 | 65456: _Key_name[944:951], 357 | 65457: _Key_name[951:958], 358 | 65469: _Key_name[958:969], 359 | 65470: _Key_name[969:971], 360 | 65471: _Key_name[971:973], 361 | 65472: _Key_name[973:975], 362 | 65473: _Key_name[975:977], 363 | 65474: _Key_name[977:979], 364 | 65475: _Key_name[979:981], 365 | 65476: _Key_name[981:983], 366 | 65477: _Key_name[983:985], 367 | 65478: _Key_name[985:987], 368 | 65479: _Key_name[987:990], 369 | 65480: _Key_name[990:993], 370 | 65481: _Key_name[993:996], 371 | 65505: _Key_name[996:1005], 372 | 65506: _Key_name[1005:1015], 373 | 65507: _Key_name[1015:1026], 374 | 65508: _Key_name[1026:1038], 375 | 65509: _Key_name[1038:1046], 376 | 65510: _Key_name[1046:1055], 377 | 65511: _Key_name[1055:1063], 378 | 65512: _Key_name[1063:1072], 379 | 65513: _Key_name[1072:1079], 380 | 65514: _Key_name[1079:1087], 381 | 65515: _Key_name[1087:1096], 382 | 65516: _Key_name[1096:1106], 383 | 65517: _Key_name[1106:1115], 384 | 65518: _Key_name[1115:1125], 385 | 65535: _Key_name[1125:1131], 386 | } 387 | 388 | func (i Key) String() string { 389 | if str, ok := _Key_map[i]; ok { 390 | return str 391 | } 392 | return "Key(" + strconv.FormatInt(int64(i), 10) + ")" 393 | } 394 | -------------------------------------------------------------------------------- /handshake_test.go: -------------------------------------------------------------------------------- 1 | package vnc 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "reflect" 7 | "testing" 8 | 9 | "context" 10 | ) 11 | 12 | func TestParseProtocolVersion(t *testing.T) { 13 | tests := []struct { 14 | proto []byte 15 | major, minor uint 16 | ok bool 17 | }{ 18 | //-- Valid ProtocolVersion messages. 19 | // RFB 003.008\n 20 | {[]byte{82, 70, 66, 32, 48, 48, 51, 46, 48, 48, 56, 10}, 3, 8, true}, 21 | // RFB 003.889\n -- OS X 10.10.3 22 | {[]byte{82, 70, 66, 32, 48, 48, 51, 46, 56, 56, 57, 10}, 3, 889, true}, 23 | // RFB 000.0000\n 24 | {[]byte{82, 70, 66, 32, 48, 48, 48, 46, 48, 48, 48, 10}, 0, 0, true}, 25 | // RFB 003.006\n -- Avid VENUE 26 | {[]byte{0x52, 0x46, 0x42, 0x20, 0x30, 0x30, 0x33, 0x2e, 0x30, 0x30, 0x36, 0x0a}, 3, 6, true}, 27 | //-- Invalid messages. 28 | // RFB 3.8\n -- too short; not zero padded 29 | {[]byte{82, 70, 66, 32, 51, 46, 56, 10}, 0, 0, false}, 30 | // RFB\n -- too short 31 | {[]byte{82, 70, 66, 10}, 0, 0, false}, 32 | // (empty) -- too short 33 | {[]byte{}, 0, 0, false}, 34 | } 35 | 36 | for i, tt := range tests { 37 | major, minor, err := parseProtocolVersion(tt.proto) 38 | if err == nil && !tt.ok { 39 | t.Errorf("%d: expected error", i) 40 | continue 41 | } 42 | if err != nil && tt.ok { 43 | t.Errorf("%d: unexpected error %v", i, err) 44 | continue 45 | } 46 | if !tt.ok { 47 | continue 48 | } 49 | // TODO(kward): validate VNCError thrown. 50 | if got, want := major, tt.major; got != want { 51 | t.Errorf("%d: incorrect major version; got = %v, want = %v", i, got, want) 52 | continue 53 | } 54 | if got, want := minor, tt.minor; got != want { 55 | t.Errorf("%d: incorrect minor version; got = %v, want = %v", i, got, want) 56 | continue 57 | } 58 | } 59 | } 60 | 61 | func TestProtocolVersionHandshake(t *testing.T) { 62 | tests := []struct { 63 | server string 64 | client string 65 | ok bool 66 | }{ 67 | // Supported versions. 68 | {"RFB 003.003\n", "RFB 003.003\n", true}, 69 | {"RFB 003.006\n", "RFB 003.003\n", true}, 70 | {"RFB 003.008\n", "RFB 003.008\n", true}, 71 | {"RFB 003.389\n", "RFB 003.008\n", true}, 72 | // Unsupported versions. 73 | {server: "RFB 002.009\n", ok: false}, 74 | } 75 | 76 | mockConn := &MockConn{} 77 | conn := NewClientConn(mockConn, &ClientConfig{}) 78 | 79 | for _, tt := range tests { 80 | mockConn.Reset() 81 | 82 | // Send server version. 83 | if err := conn.send([]byte(tt.server)); err != nil { 84 | t.Fatal(err) 85 | } 86 | 87 | // Perform protocol version handshake. 88 | err := conn.protocolVersionHandshake(context.Background()) 89 | if err == nil && !tt.ok { 90 | t.Fatalf("protocolVersionHandshake() expected error for server protocol version %v", tt.server) 91 | } 92 | if err != nil { 93 | if verr, ok := err.(*VNCError); !ok { 94 | t.Errorf("protocolVersionHandshake() unexpected %v error: %v", reflect.TypeOf(err), verr) 95 | } 96 | } 97 | 98 | // Validate client response. 99 | var client [pvLen]byte 100 | err = conn.receive(&client) 101 | if err == nil && !tt.ok { 102 | t.Fatalf("protocolVersionHandshake() unexpected error: %v", err) 103 | } 104 | if string(client[:]) != tt.client && tt.ok { 105 | t.Errorf("protocolVersionHandshake() client version: got = %v, want = %v", string(client[:]), tt.client) 106 | } 107 | 108 | // Ensure nothing extra was sent. 109 | var buf []byte 110 | if err := conn.receiveN(&buf, 1024); err != io.EOF { 111 | t.Errorf("expected EOF; got = %v", err) 112 | } 113 | } 114 | } 115 | 116 | func writeVNCAuthChallenge(w io.Writer) error { 117 | var ch vncAuthChallenge = vncAuthChallenge{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 118 | return binary.Write(w, binary.BigEndian, ch) 119 | } 120 | 121 | func readVNCAuthResponse(r io.Reader) error { 122 | var ch vncAuthChallenge 123 | return binary.Read(r, binary.BigEndian, &ch) 124 | } 125 | 126 | func TestSecurityHandshake33(t *testing.T) { 127 | tests := []struct { 128 | secType uint32 // 3.3 uses uint32, but 3.8 uses uint8. Unified on 3.8. 129 | ok bool 130 | reason string 131 | recv, sent uint64 132 | }{ 133 | //-- Supported security types. -- 134 | // Server supports None. 135 | {uint32(secTypeNone), true, "", 4, 0}, 136 | // Server supports VNCAuth. 137 | {uint32(secTypeVNCAuth), true, "", 20, 16}, 138 | //-- Unsupported security types. -- 139 | { 140 | secType: uint32(secTypeInvalid), 141 | reason: "some reason"}, 142 | {secType: 255}, 143 | } 144 | 145 | mockConn := &MockConn{} 146 | conn := NewClientConn(mockConn, NewClientConfig(".")) 147 | conn.protocolVersion = PROTO_VERS_3_3 148 | 149 | for i, tt := range tests { 150 | mockConn.Reset() 151 | 152 | // Send server message. 153 | if err := conn.send(tt.secType); err != nil { 154 | t.Fatalf("error sending security-type: %v", err) 155 | } 156 | if len(tt.reason) > 0 { 157 | if err := conn.send(uint32(len(tt.reason))); err != nil { 158 | t.Fatalf("error sending reason-length: %v", err) 159 | } 160 | if err := conn.send([]byte(tt.reason)); err != nil { 161 | t.Fatalf("error sending reason-string: %v", err) 162 | } 163 | } 164 | if tt.secType == uint32(secTypeVNCAuth) { 165 | if err := writeVNCAuthChallenge(conn.c); err != nil { 166 | t.Fatalf("error sending VNCAuth challenge: %v", err) 167 | } 168 | } 169 | 170 | // Perform security handshake. 171 | for _, m := range conn.metrics { 172 | m.Reset() 173 | } 174 | err := conn.securityHandshake() 175 | if err == nil && !tt.ok { 176 | t.Fatalf("%v: expected error for security-type %v", i, tt.secType) 177 | } 178 | if err != nil { 179 | if verr, ok := err.(*VNCError); !ok { 180 | t.Errorf("%v: unexpected %v error: %v", i, reflect.TypeOf(err), verr) 181 | } 182 | } 183 | if !tt.ok { 184 | continue 185 | } 186 | 187 | // Check bytes sent/received. 188 | if got, want := conn.metrics["bytes-received"].Value(), tt.recv; got != want { 189 | t.Errorf("%v: incorrect number of bytes received; got = %v, want = %v", i, got, want) 190 | } 191 | if got, want := conn.metrics["bytes-sent"].Value(), tt.sent; got != want { 192 | t.Errorf("%v: incorrect number of bytes sent; got = %v, want = %v", i, got, want) 193 | } 194 | 195 | // Validate client response. 196 | if tt.secType == uint32(secTypeVNCAuth) { 197 | if err := readVNCAuthResponse(conn.c); err != nil { 198 | t.Fatalf("%v: error reading VNCAuth response: %v", i, err) 199 | } 200 | } 201 | 202 | // Ensure nothing extra was sent by client. 203 | var buf []byte 204 | if err := conn.receiveN(&buf, 1024); err != io.EOF { 205 | t.Errorf("%v, expected EOF; got = %v", i, err) 206 | } 207 | } 208 | } 209 | 210 | func TestSecurityHandshake38(t *testing.T) { 211 | tests := []struct { 212 | secTypes []uint8 213 | client []ClientAuth 214 | secType uint8 215 | ok bool 216 | reason string 217 | recv, sent uint64 218 | }{ 219 | //-- Supported security types -- 220 | // Server and client support None. 221 | {[]uint8{secTypeNone}, []ClientAuth{&ClientAuthNone{}}, secTypeNone, true, "", 2, 1}, 222 | // Server and client support VNCAuth. 223 | {[]uint8{secTypeVNCAuth}, []ClientAuth{&ClientAuthVNC{"."}}, secTypeVNCAuth, true, "", 18, 17}, 224 | // Server and client both support VNCAuth and None. 225 | {[]uint8{secTypeVNCAuth, secTypeNone}, []ClientAuth{&ClientAuthVNC{"."}, &ClientAuthNone{}}, secTypeVNCAuth, true, "", 19, 17}, 226 | // Server supports unknown #255, VNCAuth and None. 227 | {[]uint8{255, secTypeVNCAuth, secTypeNone}, []ClientAuth{&ClientAuthVNC{"."}, &ClientAuthNone{}}, secTypeVNCAuth, true, "", 20, 17}, 228 | { // No security types provided. 229 | secTypes: []uint8{}, 230 | client: []ClientAuth{}, 231 | secType: secTypeInvalid, 232 | reason: "no security types"}, 233 | //-- Unsupported security types -- 234 | { // Server provided no valid security types. 235 | secTypes: []uint8{secTypeInvalid}, 236 | client: []ClientAuth{}, 237 | secType: secTypeInvalid, 238 | reason: "invalid security type"}, 239 | { // Client and server don't support same security types. 240 | secTypes: []uint8{secTypeVNCAuth}, 241 | client: []ClientAuth{&ClientAuthNone{}}, 242 | secType: secTypeInvalid}, 243 | { // Server supports only unknown #255. 244 | secTypes: []uint8{255}, 245 | client: []ClientAuth{&ClientAuthNone{}}, 246 | secType: secTypeInvalid}, 247 | } 248 | 249 | mockConn := &MockConn{} 250 | conn := NewClientConn(mockConn, &ClientConfig{}) 251 | conn.protocolVersion = PROTO_VERS_3_8 252 | 253 | for i, tt := range tests { 254 | mockConn.Reset() 255 | 256 | // Send server message. 257 | if err := conn.send(uint8(len(tt.secTypes))); err != nil { 258 | t.Fatal(err) 259 | } 260 | if err := conn.send(tt.secTypes); err != nil { 261 | t.Fatal(err) 262 | } 263 | if !tt.ok { 264 | if err := conn.send(uint32(len(tt.reason))); err != nil { 265 | t.Fatal(err) 266 | } 267 | if err := conn.send([]byte(tt.reason)); err != nil { 268 | t.Fatal(err) 269 | } 270 | } 271 | if tt.secType == secTypeVNCAuth { 272 | if err := writeVNCAuthChallenge(conn.c); err != nil { 273 | t.Fatalf("error sending VNCAuth challenge: %s", err) 274 | } 275 | } 276 | conn.config.Auth = tt.client 277 | 278 | // Perform Security Handshake. 279 | for _, m := range conn.metrics { 280 | m.Reset() 281 | } 282 | err := conn.securityHandshake() 283 | if err != nil && tt.ok { 284 | if verr, ok := err.(*VNCError); !ok { 285 | t.Fatalf("%d: unexpected %v error: %s", i, reflect.TypeOf(err), verr) 286 | } 287 | } 288 | if err == nil && !tt.ok { 289 | t.Fatalf("%d: expected error for server auth %v", i, tt.secTypes) 290 | } 291 | if !tt.ok { 292 | continue 293 | } 294 | 295 | // Check bytes sent/received. 296 | if got, want := conn.metrics["bytes-received"].Value(), tt.recv; got != want { 297 | t.Errorf("%d: incorrect number of bytes received; got = %v, want = %v", i, got, want) 298 | } 299 | if got, want := conn.metrics["bytes-sent"].Value(), tt.sent; got != want { 300 | t.Errorf("%d: incorrect number of bytes sent; got = %v, want = %v", i, got, want) 301 | } 302 | 303 | // Validate client response. 304 | var secType uint8 305 | if err := conn.receive(&secType); err != nil { 306 | t.Fatalf("%d: error receiving security-type: %v", i, err) 307 | } 308 | if got, want := secType, tt.secType; got != want { 309 | t.Errorf("%d: incorrect security-type; got = %v, want = %v", i, got, want) 310 | } 311 | if got, want := conn.config.secType, secType; got != want { 312 | t.Errorf("%d: secType not stored; got = %v, want = %v", i, got, want) 313 | } 314 | if tt.secType == secTypeVNCAuth { 315 | if err := readVNCAuthResponse(conn.c); err != nil { 316 | t.Fatalf("%d: error reading VNCAuth response: %s", i, err) 317 | } 318 | } 319 | 320 | // Ensure nothing extra was sent by client. 321 | var buf []byte 322 | if err := conn.receiveN(&buf, 1024); err != io.EOF { 323 | t.Errorf("%v: expected EOF; got = %v", i, err) 324 | } 325 | 326 | } 327 | } 328 | 329 | func TestSecurityResultHandshake(t *testing.T) { 330 | tests := []struct { 331 | result uint32 332 | ok bool 333 | reason string 334 | }{ 335 | {0, true, ""}, 336 | {1, false, "SecurityResult error"}, 337 | } 338 | 339 | mockConn := &MockConn{} 340 | conn := NewClientConn(mockConn, &ClientConfig{}) 341 | 342 | for _, tt := range tests { 343 | mockConn.Reset() 344 | 345 | // Send server message. 346 | if err := conn.send(tt.result); err != nil { 347 | t.Fatal(err) 348 | } 349 | if !tt.ok { 350 | if err := conn.send(uint32(len(tt.reason))); err != nil { 351 | t.Fatal(err) 352 | } 353 | if err := conn.send([]byte(tt.reason)); err != nil { 354 | t.Fatal(err) 355 | } 356 | } 357 | 358 | // Process SecurityResult message. 359 | err := conn.securityResultHandshake() 360 | if err == nil && !tt.ok { 361 | t.Fatalf("expected error for result %v", tt.result) 362 | } 363 | if err != nil { 364 | if verr, ok := err.(*VNCError); !ok { 365 | t.Errorf("securityResultHandshake() unexpected %v error: %v", reflect.TypeOf(err), verr) 366 | } 367 | if got, want := err.Error(), "SecurityResult handshake failed: "+tt.reason; got != want { 368 | t.Errorf("incorrect reason") 369 | } 370 | } 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Implementation of RFC 6143 §7.6 Server-to-Client Messages. 3 | https://tools.ietf.org/html/rfc6143#section-7.6 4 | */ 5 | package vnc 6 | 7 | import ( 8 | "fmt" 9 | "image" 10 | 11 | "github.com/kward/go-vnc/encodings" 12 | "github.com/kward/go-vnc/logging" 13 | "github.com/kward/go-vnc/messages" 14 | "github.com/kward/go-vnc/rfbflags" 15 | ) 16 | 17 | // ServerMessage is the interface satisfied by server messages. 18 | type ServerMessage interface { 19 | // The type of the message that is sent down on the wire. 20 | Type() messages.ServerMessage 21 | 22 | // Read reads the contents of the message from the reader. At the point 23 | // this is called, the message type has already been read from the reader. 24 | // This should return a new ServerMessage that is the appropriate type. 25 | Read(*ClientConn) (ServerMessage, error) 26 | } 27 | 28 | //----------------------------------------------------------------------------- 29 | // A framebuffer update consists of a sequence of rectangles of pixel data that 30 | // the client should put into its framebuffer. 31 | // 32 | // See RFC 6143 Section 7.6.1. 33 | // https://tools.ietf.org/html/rfc6143#section-7.6.1 34 | 35 | // FramebufferUpdate holds a FramebufferUpdate wire format message. 36 | type FramebufferUpdate struct { 37 | NumRect uint16 // number-of-rectangles 38 | Rects []Rectangle // rectangles 39 | } 40 | 41 | // Verify that interfaces are honored. 42 | var _ ServerMessage = (*FramebufferUpdate)(nil) 43 | var _ MarshalerUnmarshaler = (*FramebufferUpdate)(nil) 44 | 45 | func newFramebufferUpdate(rects []Rectangle) *FramebufferUpdate { 46 | return &FramebufferUpdate{ 47 | NumRect: uint16(len(rects)), 48 | Rects: rects, 49 | } 50 | } 51 | 52 | // Type implements the ServerMessage interface. 53 | func (m *FramebufferUpdate) Type() messages.ServerMessage { return messages.FramebufferUpdate } 54 | 55 | // Read implements the ServerMessage interface. 56 | func (m *FramebufferUpdate) Read(c *ClientConn) (ServerMessage, error) { 57 | if logging.V(logging.FnDeclLevel) { 58 | logging.Infof("FramebufferUpdate.%s", logging.FnName()) 59 | } 60 | 61 | // Build the map of supported encodings. 62 | // encs := make(map[int32]Encoding) 63 | // for _, e := range c.Encodings() { 64 | // encs[e.Type()] = e 65 | // } 66 | // encs[Raw] = &RawEncoding{} // Raw encoding support required. 67 | 68 | // Read packet. 69 | var pad [1]byte 70 | if err := c.receive(&pad); err != nil { 71 | return nil, err 72 | } 73 | if logging.V(logging.ResultLevel) { 74 | logging.Infof("pad: %v", pad) 75 | } 76 | 77 | var numRects uint16 78 | if err := c.receive(&numRects); err != nil { 79 | return nil, err 80 | } 81 | if logging.V(logging.ResultLevel) { 82 | logging.Infof("numRects: %d", numRects) 83 | } 84 | 85 | // Extract rectangles. 86 | rects := make([]Rectangle, numRects) 87 | for i := 0; i < int(numRects); i++ { 88 | rect := NewRectangle(c.Encodable) 89 | if err := rect.Read(c); err != nil { 90 | return nil, err 91 | } 92 | rects[i] = *rect 93 | } 94 | 95 | return newFramebufferUpdate(rects), nil 96 | } 97 | 98 | // Marshal implements the Marshaler interface. 99 | func (m *FramebufferUpdate) Marshal() ([]byte, error) { 100 | if logging.V(logging.FnDeclLevel) { 101 | logging.Infof("FramebufferUpdate.%s", logging.FnName()) 102 | } 103 | 104 | buf := NewBuffer(nil) 105 | msg := struct { 106 | msg messages.ServerMessage // message-type 107 | _ [1]byte // padding 108 | numRects uint16 // number-of-rectangles 109 | }{ 110 | msg: messages.FramebufferUpdate, 111 | numRects: m.NumRect, 112 | } 113 | if err := buf.Write(msg); err != nil { 114 | return nil, err 115 | } 116 | for _, rect := range m.Rects { 117 | bytes, err := rect.Marshal() 118 | if err != nil { 119 | return nil, err 120 | } 121 | buf.Write(bytes) 122 | } 123 | 124 | return buf.Bytes(), nil 125 | } 126 | 127 | // Unmarshal implements the Unmarshaler interface. 128 | func (m *FramebufferUpdate) Unmarshal(_ []byte) error { 129 | if logging.V(logging.FnDeclLevel) { 130 | logging.Infof("FramebufferUpdate.%s", logging.FnName()) 131 | } 132 | return fmt.Errorf("Unmarshal() unimplemented") 133 | } 134 | 135 | // EncodableFunc describes the function for encoding a Rectangle. 136 | type EncodableFunc func(enc encodings.Encoding) (Encoding, bool) 137 | 138 | // Encodable returns the Encoding that can be used to encode a Rectangle, or 139 | // false if the encoding isn't recognized. 140 | func (c *ClientConn) Encodable(enc encodings.Encoding) (Encoding, bool) { 141 | if logging.V(logging.FnDeclLevel) { 142 | logging.Infof("ClientConn.%s", logging.FnName()) 143 | } 144 | for _, e := range c.encodings { 145 | if e.Type() == enc { 146 | return e, true 147 | } 148 | } 149 | return nil, false 150 | } 151 | 152 | // rectangleMessage holds a Rectangle wire format message. 153 | type rectangleMessage struct { 154 | X, Y uint16 // x-, y-position 155 | W, H uint16 // width, height 156 | E encodings.Encoding // encoding-type 157 | } 158 | 159 | // Rectangle represents a rectangle of pixel data. 160 | type Rectangle struct { 161 | X, Y uint16 162 | Width, Height uint16 163 | Enc Encoding 164 | encFn EncodableFunc 165 | } 166 | 167 | // Verify that interfaces are honored. 168 | var _ fmt.Stringer = (*Rectangle)(nil) 169 | var _ MarshalerUnmarshaler = (*Rectangle)(nil) 170 | 171 | // NewRectangle returns a new Rectangle object. 172 | func NewRectangle(fn EncodableFunc) *Rectangle { 173 | return &Rectangle{encFn: fn} 174 | } 175 | 176 | // Read a rectangle message from ClientConn c. 177 | func (r *Rectangle) Read(c *ClientConn) error { 178 | if logging.V(logging.FnDeclLevel) { 179 | logging.Infof("Rectangle.%s", logging.FnName()) 180 | } 181 | 182 | var msg rectangleMessage 183 | if err := c.receive(&msg); err != nil { 184 | return err 185 | } 186 | r.X, r.Y, r.Width, r.Height = msg.X, msg.Y, msg.W, msg.H 187 | 188 | encImpl, ok := r.encFn(msg.E) 189 | if !ok { 190 | return fmt.Errorf("unsupported encoding type: %d", msg.E) 191 | } 192 | 193 | enc, err := encImpl.Read(c, r) 194 | if err != nil { 195 | return fmt.Errorf("error reading rectangle encoding: %s", err) 196 | } 197 | 198 | r.Enc = enc 199 | return nil 200 | } 201 | 202 | // Marshal implements the Marshaler interface. 203 | func (r *Rectangle) Marshal() ([]byte, error) { 204 | if logging.V(logging.FnDeclLevel) { 205 | logging.Infof("Rectangle.%s", logging.FnName()) 206 | } 207 | 208 | buf := NewBuffer(nil) 209 | 210 | var msg rectangleMessage 211 | msg.X, msg.Y, msg.W, msg.H = r.X, r.Y, r.Width, r.Height 212 | msg.E = r.Enc.Type() 213 | if err := buf.Write(msg); err != nil { 214 | return nil, err 215 | } 216 | 217 | bytes, err := r.Enc.Marshal() 218 | if err != nil { 219 | return nil, err 220 | } 221 | if err := buf.Write(bytes); err != nil { 222 | return nil, err 223 | } 224 | 225 | return buf.Bytes(), nil 226 | } 227 | 228 | // Unmarshal implements the Unmarshaler interface. 229 | func (r *Rectangle) Unmarshal(data []byte) error { 230 | if logging.V(logging.FnDeclLevel) { 231 | logging.Infof("Rectangle.%s", logging.FnName()) 232 | } 233 | 234 | buf := NewBuffer(data) 235 | 236 | var msg rectangleMessage 237 | if err := buf.Read(&msg); err != nil { 238 | return err 239 | } 240 | r.X, r.Y, r.Width, r.Height = msg.X, msg.Y, msg.W, msg.H 241 | 242 | switch msg.E { 243 | case encodings.Raw: 244 | r.Enc = &RawEncoding{} 245 | default: 246 | return fmt.Errorf("unable to unmarshal encoding %v", msg.E) 247 | } 248 | return nil 249 | } 250 | 251 | // String implements the fmt.Stringer interface. 252 | func (r *Rectangle) String() string { 253 | return fmt.Sprintf("{ x: %d y: %d, w: %d, h: %d, enc: %v }", r.X, r.Y, r.Width, r.Height, r.Enc) 254 | } 255 | 256 | // Area returns the total area in pixels of the Rectangle. 257 | func (r *Rectangle) Area() int { return int(r.Width) * int(r.Height) } 258 | 259 | //----------------------------------------------------------------------------- 260 | // SetColorMapEntries is sent by the server to set values into 261 | // the color map. This message will automatically update the color map 262 | // for the associated connection, but contains the color change data 263 | // if the consumer wants to read it. 264 | // 265 | // See RFC 6143 Section 7.6.2 266 | // https://tools.ietf.org/html/rfc6143#section-7.6.2 267 | 268 | // SetColorMapEntries holds a SetColorMapEntries wire format message, sans 269 | // message-type and padding. 270 | type SetColorMapEntries struct { 271 | FirstColor uint16 272 | Colors []Color 273 | } 274 | 275 | // Verify that interfaces are honored. 276 | var _ ServerMessage = (*SetColorMapEntries)(nil) 277 | 278 | // Type implements the ServerMessage interface. 279 | func (*SetColorMapEntries) Type() messages.ServerMessage { return messages.SetColorMapEntries } 280 | 281 | // Read implements the ServerMessage interface. 282 | func (*SetColorMapEntries) Read(c *ClientConn) (ServerMessage, error) { 283 | if logging.V(logging.FnDeclLevel) { 284 | logging.Infof("SetColorMapEntries.%s", logging.FnName()) 285 | } 286 | 287 | // Read off the padding 288 | var padding [1]byte 289 | if err := c.receive(&padding); err != nil { 290 | return nil, err 291 | } 292 | 293 | var result SetColorMapEntries 294 | if err := c.receive(&result.FirstColor); err != nil { 295 | return nil, err 296 | } 297 | 298 | var numColors uint16 299 | if err := c.receive(&numColors); err != nil { 300 | return nil, err 301 | } 302 | 303 | result.Colors = make([]Color, numColors) 304 | for i := uint16(0); i < numColors; i++ { 305 | color := &result.Colors[i] 306 | if err := c.receive(&color); err != nil { 307 | return nil, err 308 | } 309 | 310 | // Update the connection's color map 311 | c.colorMap[result.FirstColor+i] = *color 312 | } 313 | 314 | return &result, nil 315 | } 316 | 317 | // Color represents a single color in a color map. 318 | type Color struct { 319 | pf *PixelFormat 320 | cm *ColorMap 321 | cmIndex uint32 // Only valid if pf.TrueColor is false. 322 | R, G, B uint16 323 | } 324 | 325 | // Verify that interfaces are honored. 326 | var _ MarshalerUnmarshaler = (*Color)(nil) 327 | 328 | // ColorMap represents a translation map of colors. 329 | type ColorMap [256]Color 330 | 331 | // NewColor returns a new Color object. 332 | func NewColor(pf *PixelFormat, cm *ColorMap) *Color { 333 | return &Color{ 334 | pf: pf, 335 | cm: cm, 336 | } 337 | } 338 | 339 | // Marshal implements the Marshaler interface. 340 | func (c *Color) Marshal() ([]byte, error) { 341 | if logging.V(logging.FnDeclLevel) { 342 | logging.Infof("Color.%s", logging.FnName()) 343 | } 344 | 345 | order := c.pf.order() 346 | pixel := c.cmIndex 347 | if rfbflags.IsTrueColor(c.pf.TrueColor) { 348 | pixel = uint32(c.R) << c.pf.RedShift 349 | pixel |= uint32(c.G) << c.pf.GreenShift 350 | pixel |= uint32(c.B) << c.pf.BlueShift 351 | } 352 | 353 | var bytes []byte 354 | switch c.pf.BPP { 355 | case 8: 356 | bytes = make([]byte, 1) 357 | bytes[0] = byte(pixel) 358 | case 16: 359 | bytes = make([]byte, 2) 360 | order.PutUint16(bytes, uint16(pixel)) 361 | case 32: 362 | bytes = make([]byte, 4) 363 | order.PutUint32(bytes, pixel) 364 | } 365 | 366 | return bytes, nil 367 | } 368 | 369 | // Unmarshal implements the Unmarshaler interface. 370 | func (c *Color) Unmarshal(data []byte) error { 371 | if logging.V(logging.CrazySpamLevel) { 372 | logging.Infof("Color.%s", logging.FnName()) 373 | } 374 | 375 | if len(data) == 0 { 376 | return nil 377 | } 378 | 379 | order := c.pf.order() 380 | 381 | var pixel uint32 382 | switch c.pf.BPP { 383 | case 8: 384 | pixel = uint32(data[0]) 385 | case 16: 386 | pixel = uint32(order.Uint16(data)) 387 | case 32: 388 | pixel = order.Uint32(data) 389 | } 390 | 391 | if rfbflags.IsTrueColor(c.pf.TrueColor) { 392 | c.R = uint16((pixel >> c.pf.RedShift) & uint32(c.pf.RedMax)) 393 | c.G = uint16((pixel >> c.pf.GreenShift) & uint32(c.pf.GreenMax)) 394 | c.B = uint16((pixel >> c.pf.BlueShift) & uint32(c.pf.BlueMax)) 395 | } else { 396 | *c = c.cm[pixel] 397 | c.cmIndex = pixel 398 | } 399 | 400 | return nil 401 | } 402 | 403 | //lint:ignore U1000 helper for potential future image conversions; currently unused 404 | func colorsToImage(x, y, width, height uint16, colors []Color) *image.RGBA64 { 405 | rect := image.Rect(int(x), int(y), int(x+width), int(y+height)) 406 | rgba := image.NewRGBA64(rect) 407 | a := uint16(1) 408 | for i, color := range colors { 409 | rgba.Pix[4*i+0] = uint8(color.R >> 8) 410 | rgba.Pix[4*i+1] = uint8(color.R) 411 | rgba.Pix[4*i+2] = uint8(color.G >> 8) 412 | rgba.Pix[4*i+3] = uint8(color.G) 413 | rgba.Pix[4*i+4] = uint8(color.B >> 8) 414 | rgba.Pix[4*i+5] = uint8(color.B) 415 | rgba.Pix[4*i+6] = uint8(a >> 8) 416 | rgba.Pix[4*i+7] = uint8(a) 417 | } 418 | return rgba 419 | } 420 | 421 | //----------------------------------------------------------------------------- 422 | // Bell signals that an audible bell should be made on the client. 423 | // 424 | // See RFC 6143 Section 7.6.3 425 | // https://tools.ietf.org/html/rfc6143#section-7.6.3 426 | 427 | // Bell represents the wire format message, sans message-type. 428 | type Bell struct{} 429 | 430 | // Verify that interfaces are honored. 431 | var _ ServerMessage = (*Bell)(nil) 432 | 433 | // Type implements the ServerMessage interface. 434 | func (*Bell) Type() messages.ServerMessage { return messages.Bell } 435 | 436 | // Read implements the ServerMessage interface. 437 | func (*Bell) Read(c *ClientConn) (ServerMessage, error) { 438 | if logging.V(logging.FnDeclLevel) { 439 | logging.Infof("Bell.%s", logging.FnName()) 440 | } 441 | return &Bell{}, nil 442 | } 443 | 444 | //----------------------------------------------------------------------------- 445 | // ServerCutText indicates the server has new text in the cut buffer. 446 | // 447 | // See RFC 6143 Section 7.6.4 448 | // https://tools.ietf.org/html/rfc6143#section-7.6.4 449 | 450 | // ServerCutText represents the wire format message, sans message-type and 451 | // padding. 452 | type ServerCutText struct { 453 | Text string 454 | } 455 | 456 | // Verify that interfaces are honored. 457 | var _ ServerMessage = (*ServerCutText)(nil) 458 | 459 | // Type implements the ServerMessage interface. 460 | func (*ServerCutText) Type() messages.ServerMessage { return messages.ServerCutText } 461 | 462 | // Read implements the ServerMessage interface. 463 | func (*ServerCutText) Read(c *ClientConn) (ServerMessage, error) { 464 | if logging.V(logging.FnDeclLevel) { 465 | logging.Infof("ServerCutText.%s", logging.FnName()) 466 | } 467 | 468 | // Read off the padding 469 | var padding [1]byte 470 | if err := c.receive(&padding); err != nil { 471 | return nil, err 472 | } 473 | 474 | var textLength uint32 475 | if err := c.receive(&textLength); err != nil { 476 | return nil, err 477 | } 478 | 479 | textBytes := make([]uint8, textLength) 480 | if err := c.receive(&textBytes); err != nil { 481 | return nil, err 482 | } 483 | 484 | return &ServerCutText{string(textBytes)}, nil 485 | } 486 | --------------------------------------------------------------------------------