| /* |
| * Copyright (c) 2010 ARM Limited |
| * All rights reserved |
| * |
| * The license below extends only to copyright in the software and shall |
| * not be construed as granting a license to any other intellectual |
| * property including but not limited to intellectual property relating |
| * to a hardware implementation of the functionality of the software |
| * licensed hereunder. You may use the software subject to the license |
| * terms below provided that you ensure that this notice is replicated |
| * unmodified and in its entirety in all distributions of the software, |
| * modified or unmodified, in source code or in binary form. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer; |
| * redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution; |
| * neither the name of the copyright holders nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * Authors: Ali Saidi |
| * William Wang |
| */ |
| |
| /** @file |
| * Implementiation of a VNC server |
| */ |
| |
| #include <sys/ioctl.h> |
| #include <sys/stat.h> |
| #include <sys/termios.h> |
| #include <sys/types.h> |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <unistd.h> |
| |
| #include <cerrno> |
| #include <cstdio> |
| |
| #include "base/vnc/vncserver.hh" |
| #include "base/atomicio.hh" |
| #include "base/bitmap.hh" |
| #include "base/misc.hh" |
| #include "base/output.hh" |
| #include "base/socket.hh" |
| #include "base/trace.hh" |
| #include "debug/VNC.hh" |
| #include "sim/byteswap.hh" |
| #include "sim/core.hh" |
| |
| using namespace std; |
| |
| /** @file |
| * Implementiation of a VNC server |
| */ |
| |
| /** |
| * Poll event for the listen socket |
| */ |
| VncServer::ListenEvent::ListenEvent(VncServer *vs, int fd, int e) |
| : PollEvent(fd, e), vncserver(vs) |
| { |
| } |
| |
| void |
| VncServer::ListenEvent::process(int revent) |
| { |
| vncserver->accept(); |
| } |
| |
| /** |
| * Poll event for the data socket |
| */ |
| VncServer::DataEvent::DataEvent(VncServer *vs, int fd, int e) |
| : PollEvent(fd, e), vncserver(vs) |
| { |
| } |
| |
| void |
| VncServer::DataEvent::process(int revent) |
| { |
| if (revent & POLLIN) |
| vncserver->data(); |
| else if (revent & POLLNVAL) |
| vncserver->detach(); |
| } |
| |
| /** |
| * VncServer |
| */ |
| VncServer::VncServer(const Params *p) |
| : VncInput(p), listenEvent(NULL), dataEvent(NULL), number(p->number), |
| dataFd(-1), sendUpdate(false) |
| { |
| if (p->port) |
| listen(p->port); |
| |
| curState = WaitForProtocolVersion; |
| |
| // currently we only support this one pixel format |
| // unpacked 32bit rgb (rgb888 + 8 bits of nothing/alpha) |
| // keep it around for telling the client and making |
| // sure the client cooperates |
| pixelFormat.bpp = 32; |
| pixelFormat.depth = 24; |
| pixelFormat.bigendian = 0; |
| pixelFormat.truecolor = 1; |
| pixelFormat.redmax = 0xff; |
| pixelFormat.greenmax = 0xff; |
| pixelFormat.bluemax = 0xff; |
| pixelFormat.redshift = 16; |
| pixelFormat.greenshift = 8; |
| pixelFormat.blueshift = 0; |
| |
| DPRINTF(VNC, "Vnc server created at port %d\n", p->port); |
| } |
| |
| VncServer::~VncServer() |
| { |
| if (dataFd != -1) |
| ::close(dataFd); |
| |
| if (listenEvent) |
| delete listenEvent; |
| |
| if (dataEvent) |
| delete dataEvent; |
| } |
| |
| |
| //socket creation and vnc client attach |
| void |
| VncServer::listen(int port) |
| { |
| if (ListenSocket::allDisabled()) { |
| warn_once("Sockets disabled, not accepting vnc client connections"); |
| return; |
| } |
| |
| while (!listener.listen(port, true)) { |
| DPRINTF(VNC, |
| "can't bind address vnc server port %d in use PID %d\n", |
| port, getpid()); |
| port++; |
| } |
| |
| int p1, p2; |
| p2 = name().rfind('.') - 1; |
| p1 = name().rfind('.', p2); |
| ccprintf(cerr, "Listening for %s connection on port %d\n", |
| name().substr(p1 + 1, p2 - p1), port); |
| |
| listenEvent = new ListenEvent(this, listener.getfd(), POLLIN); |
| pollQueue.schedule(listenEvent); |
| } |
| |
| // attach a vnc client |
| void |
| VncServer::accept() |
| { |
| if (!listener.islistening()) |
| panic("%s: cannot accept a connection if not listening!", name()); |
| |
| int fd = listener.accept(true); |
| if (dataFd != -1) { |
| char message[] = "vnc server already attached!\n"; |
| atomic_write(fd, message, sizeof(message)); |
| ::close(fd); |
| return; |
| } |
| |
| dataFd = fd; |
| |
| // Send our version number to the client |
| write((uint8_t*)vncVersion(), strlen(vncVersion())); |
| |
| // read the client response |
| dataEvent = new DataEvent(this, dataFd, POLLIN); |
| pollQueue.schedule(dataEvent); |
| |
| inform("VNC client attached\n"); |
| } |
| |
| // data called by data event |
| void |
| VncServer::data() |
| { |
| // We have new data, see if we can handle it |
| size_t len; |
| DPRINTF(VNC, "Vnc client message recieved\n"); |
| |
| switch (curState) { |
| case WaitForProtocolVersion: |
| checkProtocolVersion(); |
| break; |
| case WaitForSecurityResponse: |
| checkSecurity(); |
| break; |
| case WaitForClientInit: |
| // Don't care about shared, just need to read it out of the socket |
| uint8_t shared; |
| len = read(&shared); |
| assert(len == 1); |
| |
| // Send our idea of the frame buffer |
| sendServerInit(); |
| |
| break; |
| case NormalPhase: |
| uint8_t message_type; |
| len = read(&message_type); |
| if (!len) { |
| detach(); |
| return; |
| } |
| assert(len == 1); |
| |
| switch (message_type) { |
| case ClientSetPixelFormat: |
| setPixelFormat(); |
| break; |
| case ClientSetEncodings: |
| setEncodings(); |
| break; |
| case ClientFrameBufferUpdate: |
| requestFbUpdate(); |
| break; |
| case ClientKeyEvent: |
| recvKeyboardInput(); |
| break; |
| case ClientPointerEvent: |
| recvPointerInput(); |
| break; |
| case ClientCutText: |
| recvCutText(); |
| break; |
| default: |
| panic("Unimplemented message type recv from client: %d\n", |
| message_type); |
| break; |
| } |
| break; |
| default: |
| panic("Unknown vnc server state\n"); |
| } |
| } |
| |
| |
| // read from socket |
| size_t |
| VncServer::read(uint8_t *buf, size_t len) |
| { |
| if (dataFd < 0) |
| panic("vnc not properly attached.\n"); |
| |
| size_t ret; |
| do { |
| ret = ::read(dataFd, buf, len); |
| } while (ret == -1 && errno == EINTR); |
| |
| |
| if (ret <= 0){ |
| DPRINTF(VNC, "Read failed.\n"); |
| detach(); |
| return 0; |
| } |
| |
| return ret; |
| } |
| |
| size_t |
| VncServer::read1(uint8_t *buf, size_t len) |
| { |
| size_t read_len M5_VAR_USED; |
| read_len = read(buf + 1, len - 1); |
| assert(read_len == len - 1); |
| return read_len; |
| } |
| |
| |
| template<typename T> |
| size_t |
| VncServer::read(T* val) |
| { |
| return read((uint8_t*)val, sizeof(T)); |
| } |
| |
| // write to socket |
| size_t |
| VncServer::write(const uint8_t *buf, size_t len) |
| { |
| if (dataFd < 0) |
| panic("Vnc client not properly attached.\n"); |
| |
| ssize_t ret; |
| ret = atomic_write(dataFd, buf, len); |
| |
| if (ret < len) |
| detach(); |
| |
| return ret; |
| } |
| |
| template<typename T> |
| size_t |
| VncServer::write(T* val) |
| { |
| return write((uint8_t*)val, sizeof(T)); |
| } |
| |
| size_t |
| VncServer::write(const char* str) |
| { |
| return write((uint8_t*)str, strlen(str)); |
| } |
| |
| // detach a vnc client |
| void |
| VncServer::detach() |
| { |
| if (dataFd != -1) { |
| ::close(dataFd); |
| dataFd = -1; |
| } |
| |
| if (!dataEvent || !dataEvent->queued()) |
| return; |
| |
| pollQueue.remove(dataEvent); |
| delete dataEvent; |
| dataEvent = NULL; |
| curState = WaitForProtocolVersion; |
| |
| inform("VNC client detached\n"); |
| DPRINTF(VNC, "detach vnc client %d\n", number); |
| } |
| |
| void |
| VncServer::sendError(const char* error_msg) |
| { |
| uint32_t len = strlen(error_msg); |
| write(&len); |
| write(error_msg); |
| } |
| |
| void |
| VncServer::checkProtocolVersion() |
| { |
| assert(curState == WaitForProtocolVersion); |
| |
| size_t len M5_VAR_USED; |
| char version_string[13]; |
| |
| // Null terminate the message so it's easier to work with |
| version_string[12] = 0; |
| |
| len = read((uint8_t*)version_string, 12); |
| assert(len == 12); |
| |
| uint32_t major, minor; |
| |
| // Figure out the major/minor numbers |
| if (sscanf(version_string, "RFB %03d.%03d\n", &major, &minor) != 2) { |
| warn(" Malformed protocol version %s\n", version_string); |
| sendError("Malformed protocol version\n"); |
| detach(); |
| } |
| |
| DPRINTF(VNC, "Client request protocol version %d.%d\n", major, minor); |
| |
| // If it's not 3.X we don't support it |
| if (major != 3 || minor < 2) { |
| warn("Unsupported VNC client version... disconnecting\n"); |
| uint8_t err = AuthInvalid; |
| write(&err); |
| detach(); |
| } |
| // Auth is different based on version number |
| if (minor < 7) { |
| uint32_t sec_type = htobe((uint32_t)AuthNone); |
| write(&sec_type); |
| } else { |
| uint8_t sec_cnt = 1; |
| uint8_t sec_type = htobe((uint8_t)AuthNone); |
| write(&sec_cnt); |
| write(&sec_type); |
| } |
| |
| // Wait for client to respond |
| curState = WaitForSecurityResponse; |
| } |
| |
| void |
| VncServer::checkSecurity() |
| { |
| assert(curState == WaitForSecurityResponse); |
| |
| uint8_t security_type; |
| size_t len M5_VAR_USED = read(&security_type); |
| |
| assert(len == 1); |
| |
| if (security_type != AuthNone) { |
| warn("Unknown VNC security type\n"); |
| sendError("Unknown security type\n"); |
| } |
| |
| DPRINTF(VNC, "Sending security auth OK\n"); |
| |
| uint32_t success = htobe(VncOK); |
| write(&success); |
| curState = WaitForClientInit; |
| } |
| |
| void |
| VncServer::sendServerInit() |
| { |
| ServerInitMsg msg; |
| |
| DPRINTF(VNC, "Sending server init message to client\n"); |
| |
| msg.fbWidth = htobe(videoWidth()); |
| msg.fbHeight = htobe(videoHeight()); |
| |
| msg.px.bpp = htobe(pixelFormat.bpp); |
| msg.px.depth = htobe(pixelFormat.depth); |
| msg.px.bigendian = htobe(pixelFormat.bigendian); |
| msg.px.truecolor = htobe(pixelFormat.truecolor); |
| msg.px.redmax = htobe(pixelFormat.redmax); |
| msg.px.greenmax = htobe(pixelFormat.greenmax); |
| msg.px.bluemax = htobe(pixelFormat.bluemax); |
| msg.px.redshift = htobe(pixelFormat.redshift); |
| msg.px.greenshift = htobe(pixelFormat.greenshift); |
| msg.px.blueshift = htobe(pixelFormat.blueshift); |
| memset(msg.px.padding, 0, 3); |
| msg.namelen = 2; |
| msg.namelen = htobe(msg.namelen); |
| memcpy(msg.name, "M5", 2); |
| |
| write(&msg); |
| curState = NormalPhase; |
| } |
| |
| void |
| VncServer::setPixelFormat() |
| { |
| DPRINTF(VNC, "Received pixel format from client message\n"); |
| |
| PixelFormatMessage pfm; |
| read1((uint8_t*)&pfm, sizeof(PixelFormatMessage)); |
| |
| DPRINTF(VNC, " -- bpp = %d; depth = %d; be = %d\n", pfm.px.bpp, |
| pfm.px.depth, pfm.px.bigendian); |
| DPRINTF(VNC, " -- true color = %d red,green,blue max = %d,%d,%d\n", |
| pfm.px.truecolor, betoh(pfm.px.redmax), betoh(pfm.px.greenmax), |
| betoh(pfm.px.bluemax)); |
| DPRINTF(VNC, " -- red,green,blue shift = %d,%d,%d\n", pfm.px.redshift, |
| pfm.px.greenshift, pfm.px.blueshift); |
| |
| if (betoh(pfm.px.bpp) != pixelFormat.bpp || |
| betoh(pfm.px.depth) != pixelFormat.depth || |
| betoh(pfm.px.bigendian) != pixelFormat.bigendian || |
| betoh(pfm.px.truecolor) != pixelFormat.truecolor || |
| betoh(pfm.px.redmax) != pixelFormat.redmax || |
| betoh(pfm.px.greenmax) != pixelFormat.greenmax || |
| betoh(pfm.px.bluemax) != pixelFormat.bluemax || |
| betoh(pfm.px.redshift) != pixelFormat.redshift || |
| betoh(pfm.px.greenshift) != pixelFormat.greenshift || |
| betoh(pfm.px.blueshift) != pixelFormat.blueshift) |
| fatal("VNC client doesn't support true color raw encoding\n"); |
| } |
| |
| void |
| VncServer::setEncodings() |
| { |
| DPRINTF(VNC, "Received supported encodings from client\n"); |
| |
| PixelEncodingsMessage pem; |
| read1((uint8_t*)&pem, sizeof(PixelEncodingsMessage)); |
| |
| pem.num_encodings = betoh(pem.num_encodings); |
| |
| DPRINTF(VNC, " -- %d encoding present\n", pem.num_encodings); |
| supportsRawEnc = supportsResizeEnc = false; |
| |
| for (int x = 0; x < pem.num_encodings; x++) { |
| int32_t encoding; |
| size_t len M5_VAR_USED; |
| len = read(&encoding); |
| assert(len == sizeof(encoding)); |
| DPRINTF(VNC, " -- supports %d\n", betoh(encoding)); |
| |
| switch (betoh(encoding)) { |
| case EncodingRaw: |
| supportsRawEnc = true; |
| break; |
| case EncodingDesktopSize: |
| supportsResizeEnc = true; |
| break; |
| } |
| } |
| |
| if (!supportsRawEnc) |
| fatal("VNC clients must always support raw encoding\n"); |
| } |
| |
| void |
| VncServer::requestFbUpdate() |
| { |
| DPRINTF(VNC, "Received frame buffer update request from client\n"); |
| |
| FrameBufferUpdateReq fbr; |
| read1((uint8_t*)&fbr, sizeof(FrameBufferUpdateReq)); |
| |
| fbr.x = betoh(fbr.x); |
| fbr.y = betoh(fbr.y); |
| fbr.width = betoh(fbr.width); |
| fbr.height = betoh(fbr.height); |
| |
| DPRINTF(VNC, " -- x = %d y = %d w = %d h = %d\n", fbr.x, fbr.y, fbr.width, |
| fbr.height); |
| |
| sendFrameBufferUpdate(); |
| } |
| |
| void |
| VncServer::recvKeyboardInput() |
| { |
| DPRINTF(VNC, "Received keyboard input from client\n"); |
| KeyEventMessage kem; |
| read1((uint8_t*)&kem, sizeof(KeyEventMessage)); |
| |
| kem.key = betoh(kem.key); |
| DPRINTF(VNC, " -- received key code %d (%s)\n", kem.key, kem.down_flag ? |
| "down" : "up"); |
| |
| if (keyboard) |
| keyboard->keyPress(kem.key, kem.down_flag); |
| } |
| |
| void |
| VncServer::recvPointerInput() |
| { |
| DPRINTF(VNC, "Received pointer input from client\n"); |
| PointerEventMessage pem; |
| |
| read1((uint8_t*)&pem, sizeof(PointerEventMessage));; |
| |
| pem.x = betoh(pem.x); |
| pem.y = betoh(pem.y); |
| DPRINTF(VNC, " -- pointer at x = %d y = %d buttons = %#x\n", pem.x, pem.y, |
| pem.button_mask); |
| |
| if (mouse) |
| mouse->mouseAt(pem.x, pem.y, pem.button_mask); |
| } |
| |
| void |
| VncServer::recvCutText() |
| { |
| DPRINTF(VNC, "Received client copy buffer message\n"); |
| |
| ClientCutTextMessage cct; |
| read1((uint8_t*)&cct, sizeof(ClientCutTextMessage)); |
| |
| char str[1025]; |
| size_t data_len = betoh(cct.length); |
| DPRINTF(VNC, "String length %d\n", data_len); |
| while (data_len > 0) { |
| size_t len; |
| size_t bytes_to_read = data_len > 1024 ? 1024 : data_len; |
| len = read((uint8_t*)&str, bytes_to_read); |
| str[bytes_to_read] = 0; |
| data_len -= len; |
| assert(data_len >= 0); |
| DPRINTF(VNC, "Buffer: %s\n", str); |
| } |
| |
| } |
| |
| |
| void |
| VncServer::sendFrameBufferUpdate() |
| { |
| |
| if (!fbPtr || dataFd <= 0 || curState != NormalPhase || !sendUpdate) { |
| DPRINTF(VNC, "NOT sending framebuffer update\n"); |
| return; |
| } |
| |
| assert(vc); |
| |
| // The client will request data constantly, unless we throttle it |
| sendUpdate = false; |
| |
| DPRINTF(VNC, "Sending framebuffer update\n"); |
| |
| FrameBufferUpdate fbu; |
| FrameBufferRect fbr; |
| |
| fbu.type = ServerFrameBufferUpdate; |
| fbu.num_rects = 1; |
| fbr.x = 0; |
| fbr.y = 0; |
| fbr.width = videoWidth(); |
| fbr.height = videoHeight(); |
| fbr.encoding = EncodingRaw; |
| |
| // fix up endian |
| fbu.num_rects = htobe(fbu.num_rects); |
| fbr.x = htobe(fbr.x); |
| fbr.y = htobe(fbr.y); |
| fbr.width = htobe(fbr.width); |
| fbr.height = htobe(fbr.height); |
| fbr.encoding = htobe(fbr.encoding); |
| |
| // send headers to client |
| write(&fbu); |
| write(&fbr); |
| |
| assert(fbPtr); |
| |
| uint8_t *tmp = vc->convert(fbPtr); |
| write(tmp, videoWidth() * videoHeight() * sizeof(uint32_t)); |
| delete [] tmp; |
| |
| } |
| |
| void |
| VncServer::sendFrameBufferResized() |
| { |
| assert(fbPtr && dataFd > 0 && curState == NormalPhase); |
| DPRINTF(VNC, "Sending framebuffer resize\n"); |
| |
| FrameBufferUpdate fbu; |
| FrameBufferRect fbr; |
| |
| fbu.type = ServerFrameBufferUpdate; |
| fbu.num_rects = 1; |
| fbr.x = 0; |
| fbr.y = 0; |
| fbr.width = videoWidth(); |
| fbr.height = videoHeight(); |
| fbr.encoding = EncodingDesktopSize; |
| |
| // fix up endian |
| fbu.num_rects = htobe(fbu.num_rects); |
| fbr.x = htobe(fbr.x); |
| fbr.y = htobe(fbr.y); |
| fbr.width = htobe(fbr.width); |
| fbr.height = htobe(fbr.height); |
| fbr.encoding = htobe(fbr.encoding); |
| |
| // send headers to client |
| write(&fbu); |
| write(&fbr); |
| |
| // No actual data is sent in this message |
| } |
| |
| void |
| VncServer::setFrameBufferParams(VideoConvert::Mode mode, uint16_t width, |
| uint16_t height) |
| { |
| VncInput::setFrameBufferParams(mode, width, height); |
| |
| if (mode != videoMode || width != videoWidth() || height != videoHeight()) { |
| if (dataFd > 0 && fbPtr && curState == NormalPhase) { |
| if (supportsResizeEnc) |
| sendFrameBufferResized(); |
| else |
| // The frame buffer changed size and we can't update the client |
| detach(); |
| } |
| } |
| } |
| |
| // create the VNC server object |
| VncServer * |
| VncServerParams::create() |
| { |
| return new VncServer(this); |
| } |
| |