From 01fd227088171bb30820a44acc25563dad6e0207 Mon Sep 17 00:00:00 2001 From: amit bezalel Date: Sat, 15 Jul 2017 07:52:23 +0300 Subject: [PATCH] some more house cleaning, updated license & docs --- LICENSE | 27 +++++++++ README.md | 24 ++++++-- architecture/plantUml.wsd | 35 ++++++++++++ architecture/player-arch.png | Bin 0 -> 9162 bytes architecture/proxy-arch.png | Bin 0 -> 14017 bytes client/README.md | 16 ------ client/client-conn.go | 13 ----- main.go | 14 +++-- player/player_test.go | 23 ++------ proxy/proxy.go | 4 +- server/server-conn.go | 56 ++----------------- server/server.go | 54 ++++-------------- server/server_test.go | 2 - server/ws-server-gorilla.go | 104 ----------------------------------- 14 files changed, 111 insertions(+), 261 deletions(-) create mode 100644 architecture/plantUml.wsd create mode 100644 architecture/player-arch.png create mode 100644 architecture/proxy-arch.png delete mode 100644 client/README.md delete mode 100644 server/ws-server-gorilla.go diff --git a/LICENSE b/LICENSE index 9641ea7..ba552f8 100644 --- a/LICENSE +++ b/LICENSE @@ -19,3 +19,30 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Based on code collected from various go-vnc implementations: +------------------------------------------------------------ +MIT License + +Copyright (c) 2013 Mitchell Hashimoto +Copyright (c) 2016-2017 Kate Ward +Copyright (c) 2017 Vasiliy Tolstov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 2aff624..c7d26d9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,23 @@ # VncProxy -A RFB proxy, written in go that can save and replay RBS files +An RFB proxy, written in go that can save and replay FBS files +* supports all modern encodings +* supports regular and sockified (noVnc) server connnections +* produces FBS files compatible with tightvnc player +* can also be used as: + * a screen recorder vnc-client + * a replay server to show fbs recordings to connecting clients -Still a work in progress, but the client side already includes all the required encodings -understanding the RemoteFrameBuffer protocol is important since we need to save the frameResponses with timestamps +This is still a work in progress, and requires some error handling and general tidying up, +but the code is already working (see server_test, proxy_test & player_test) +- tested on tight encoding with: tightvnc (client+server), noVnc(web client), chickenOfTheVnc(client), vineVnc(server), tigerVnc(client) -some server code will be added shortly, with the proxying logics. +## **Architecture** +**Proxy** + +![Image of Arch](https://www.planttext.com/plantuml/img/TP5D2i8m44RtSufSe5UGYk2wM75Vaz46OYBJQLN4kzkVj9E2xcQ-n_kIaBpXYhYzEO2ZPOVgvFMTmlEbjgHhowYv9OIIAnvPCR1tt7VEekUYBuX1YTGXZK4p1ljpSq0To231HrecKVR9Km0BKndPQytP9ksKKMKEBmELCgcPMN8z6QLu4LOqkdzEdTsaUcMRyF0zJf-TwZymG8xU31_m1G00) + +**Player** + +![Image of Arch](https://www.planttext.com/plantuml/img/ut8eBaaiAYdDpU7Y2iaioKbL2CjBBYZAhwXKS4igLWZ8IQnCBU8ABaai0Si4W6IgkQ02mQb5PQb50K3zNCLW0Q0Mg8vQX1xddCpKl18kE4j1DoSrhKJN3baxWgcWMvIPdW6IHcZbWfkhe5jQWAhJ8JKl1UXw0000) + +The code is based on several implementations of go-vnc including the original one by Mitchell Hashimoto, and the very active fork which belongs to Vasiliy Tolstov. \ No newline at end of file diff --git a/architecture/plantUml.wsd b/architecture/plantUml.wsd new file mode 100644 index 0000000..c5dc448 --- /dev/null +++ b/architecture/plantUml.wsd @@ -0,0 +1,35 @@ +@startuml +Title Vncproxy Architecture +State VncProxy{ +State ClientPart +State ServerPart +state FbsRecorder +} +State VncClient +State VncServer + +VncClient -right-> ServerPart +ServerPart -left-> VncClient +VncServer -left-> ClientPart +ClientPart -right-> VncServer +ClientPart -left-> ServerPart +ServerPart -right-> ClientPart + +ClientPart -down-> FbsRecorder +ServerPart -down-> FbsRecorder +@enduml + + +@startuml +Title Vncproxy Fbs Player +State VncProxy{ + +State ServerPart +state FbsPlayer +} +State VncClient +VncClient -left-> ServerPart +ServerPart -right-> VncClient + +FbsPlayer -up-> ServerPart +@enduml \ No newline at end of file diff --git a/architecture/player-arch.png b/architecture/player-arch.png new file mode 100644 index 0000000000000000000000000000000000000000..d28af8b801691bec14a54e02bf1057e1f25f8bfa GIT binary patch literal 9162 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 P;HUgtUA|Vb~c!-yd;1G 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