diff --git a/console-proxy/.classpath b/console-proxy/.classpath index ae9ae833bc2..6dd6176144b 100644 --- a/console-proxy/.classpath +++ b/console-proxy/.classpath @@ -1,8 +1,8 @@ - - - - - - - - + + + + + + + + diff --git a/console-proxy/src/com/cloud/consoleproxy/util/ITileScanListener.java b/console-proxy/src/com/cloud/consoleproxy/util/ITileScanListener.java new file mode 100644 index 00000000000..06fe44d8c18 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/util/ITileScanListener.java @@ -0,0 +1,27 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.consoleproxy.util; + +import java.awt.Rectangle; +import java.util.List; + +public interface ITileScanListener { + boolean onTileChange(Rectangle rowMergedRect, int row, int col); + void onRegionChange(List regionList); +} diff --git a/console-proxy/src/com/cloud/consoleproxy/util/Logger.java b/console-proxy/src/com/cloud/consoleproxy/util/Logger.java new file mode 100644 index 00000000000..32bf02d9a19 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/util/Logger.java @@ -0,0 +1,225 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.consoleproxy.util; + +// logger facility for dynamic switch between console logger used in Applet and log4j based logger +public class Logger { + private static LoggerFactory factory = null; + + public static final int LEVEL_TRACE = 1; + public static final int LEVEL_DEBUG = 2; + public static final int LEVEL_INFO = 3; + public static final int LEVEL_WARN = 4; + public static final int LEVEL_ERROR = 5; + + private Class clazz; + private Logger logger; + + private static int level = LEVEL_INFO; + + public static Logger getLogger(Class clazz) { + return new Logger(clazz); + } + + public static void setFactory(LoggerFactory f) { + factory = f; + } + + public static void setLevel(int l) { + level = l; + } + + public Logger(Class clazz) { + this.clazz = clazz; + } + + protected Logger() { + } + + public boolean isTraceEnabled() { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + return logger.isTraceEnabled(); + } + return level <= LEVEL_TRACE; + } + + public boolean isDebugEnabled() { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + return logger.isDebugEnabled(); + } + return level <= LEVEL_DEBUG; + } + + public boolean isInfoEnabled() { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + return logger.isInfoEnabled(); + } + return level <= LEVEL_INFO; + } + + public void trace(Object message) { + + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + logger.trace(message); + } else { + if(level <= LEVEL_TRACE) + System.out.println(message); + } + } + + public void trace(Object message, Throwable exception) { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + logger.trace(message, exception); + } else { + if(level <= LEVEL_TRACE) { + System.out.println(message); + if (exception != null) { + exception.printStackTrace(System.out); + } + } + } + } + + public void info(Object message) { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + logger.info(message); + } else { + if(level <= LEVEL_INFO) + System.out.println(message); + } + } + + public void info(Object message, Throwable exception) { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + logger.info(message, exception); + } else { + if(level <= LEVEL_INFO) { + System.out.println(message); + if (exception != null) { + exception.printStackTrace(System.out); + } + } + } + } + + public void debug(Object message) { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + logger.debug(message); + } else { + if(level <= LEVEL_DEBUG) + System.out.println(message); + } + } + + public void debug(Object message, Throwable exception) { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + logger.debug(message, exception); + } else { + if(level <= LEVEL_DEBUG) { + System.out.println(message); + if (exception != null) { + exception.printStackTrace(System.out); + } + } + } + } + + public void warn(Object message) { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + logger.warn(message); + } else { + if(level <= LEVEL_WARN) + System.out.println(message); + } + } + + public void warn(Object message, Throwable exception) { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + logger.warn(message, exception); + } else { + if(level <= LEVEL_WARN) { + System.out.println(message); + if (exception != null) { + exception.printStackTrace(System.out); + } + } + } + } + + public void error(Object message) { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + logger.error(message); + } else { + if(level <= LEVEL_ERROR) + System.out.println(message); + } + } + + public void error(Object message, Throwable exception) { + if(factory != null) { + if(logger == null) + logger = factory.getLogger(clazz); + + logger.error(message, exception); + } else { + if(level <= LEVEL_ERROR) { + System.out.println(message); + if (exception != null) { + exception.printStackTrace(System.out); + } + } + } + } +} diff --git a/console-proxy/src/com/cloud/consoleproxy/util/LoggerFactory.java b/console-proxy/src/com/cloud/consoleproxy/util/LoggerFactory.java new file mode 100644 index 00000000000..8e75c027835 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/util/LoggerFactory.java @@ -0,0 +1,23 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.consoleproxy.util; + +public interface LoggerFactory { + Logger getLogger(Class clazz); +} diff --git a/console-proxy/src/com/cloud/consoleproxy/util/Region.java b/console-proxy/src/com/cloud/consoleproxy/util/Region.java new file mode 100644 index 00000000000..6dd83a97859 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/util/Region.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.consoleproxy.util; + +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.List; + +public class Region { + private Rectangle bound; + private List rectList; + + public Region() { + bound = new Rectangle(0, 0, 0, 0); + rectList = new ArrayList(); + } + + public Region(Rectangle rect) { + bound = new Rectangle(rect.x, rect.y, rect.width, rect.height); + rectList = new ArrayList(); + rectList.add(rect); + } + + public Rectangle getBound() { + return bound; + } + + public void clearBound() { + assert(rectList.size() == 0); + bound.x = bound.y = bound.width = bound.height = 0; + } + + public List getRectangles() { + return rectList; + } + + public boolean add(Rectangle rect) { + if(bound.isEmpty()) { + assert(rectList.size() == 0); + bound.x = rect.x; + bound.y = rect.y; + bound.width = rect.width; + bound.height = rect.height; + + rectList.add(rect); + return true; + } + + Rectangle rcInflated = new Rectangle(rect.x - 1, rect.y- 1, rect.width + 2, rect.height + 2); + if(!bound.intersects(rcInflated)) + return false; + + for(Rectangle r : rectList) { + if(r.intersects(rcInflated)) { + if(!r.contains(rect)) { + enlargeBound(rect); + rectList.add(rect); + return true; + } + } + } + return false; + } + + private void enlargeBound(Rectangle rect) { + int boundLeft = Math.min(bound.x, rect.x); + int boundTop = Math.min(bound.y, rect.y); + int boundRight = Math.max(bound.x + bound.width, rect.x + rect.width); + int boundBottom = Math.max(bound.y + bound.height, rect.y + rect.height); + + bound.x = boundLeft; + bound.y = boundTop; + bound.width = boundRight - boundLeft; + bound.height = boundBottom - boundTop; + } +} diff --git a/console-proxy/src/com/cloud/consoleproxy/util/RegionClassifier.java b/console-proxy/src/com/cloud/consoleproxy/util/RegionClassifier.java new file mode 100644 index 00000000000..c1cabf9d03e --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/util/RegionClassifier.java @@ -0,0 +1,61 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.consoleproxy.util; + +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.List; + +public class RegionClassifier { + private List regionList; + + public RegionClassifier() { + regionList = new ArrayList(); + } + + public void add(Rectangle rect) { + // quickly identify that if we need a new region + boolean newRegion = true; + Rectangle rcInflated = new Rectangle(rect.x - 1, rect.y - 1, rect.width + 2, rect.height + 2); + for(Region region : regionList) { + if(region.getBound().intersects(rcInflated)) { + newRegion = false; + break; + } + } + + if(newRegion) { + regionList.add(new Region(rect)); + } else { + for(Region region : regionList) { + if(region.add(rect)) + return; + } + regionList.add(new Region(rect)); + } + } + + public List getRegionList() { + return regionList; + } + + public void clear() { + regionList.clear(); + } +} diff --git a/console-proxy/src/com/cloud/consoleproxy/util/TileInfo.java b/console-proxy/src/com/cloud/consoleproxy/util/TileInfo.java new file mode 100644 index 00000000000..90cff3a1662 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/util/TileInfo.java @@ -0,0 +1,57 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.consoleproxy.util; + +import java.awt.Rectangle; + +public class TileInfo { + private int row; + private int col; + private Rectangle tileRect; + + public TileInfo(int row, int col, Rectangle tileRect) { + this.row = row; + this.col = col; + this.tileRect = tileRect; + } + + public int getRow() { + return row; + } + + public void setRow(int row) { + this.row = row; + } + + public int getCol() { + return col; + } + + public void setCol(int col) { + this.col = col; + } + + public Rectangle getTileRect() { + return tileRect; + } + + public void setTileRect(Rectangle tileRect) { + this.tileRect = tileRect; + } +} diff --git a/console-proxy/src/com/cloud/consoleproxy/util/TileTracker.java b/console-proxy/src/com/cloud/consoleproxy/util/TileTracker.java new file mode 100644 index 00000000000..7ef79a2825e --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/util/TileTracker.java @@ -0,0 +1,271 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package com.cloud.consoleproxy.util; + +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.List; + +public class TileTracker { + + // 2 dimension tile status snapshot, a true value means the corresponding tile has been invalidated + private boolean[][] snapshot; + + private int tileWidth = 0; + private int tileHeight = 0; + private int trackWidth = 0; + private int trackHeight = 0; + + public TileTracker() { + } + + public int getTileWidth() { + return tileWidth; + } + + public void setTileWidth(int tileWidth) { + this.tileWidth = tileWidth; + } + + public int getTileHeight() { + return tileHeight; + } + + public void setTileHeight(int tileHeight) { + this.tileHeight = tileHeight; + } + + public int getTrackWidth() { + return trackWidth; + } + + public void setTrackWidth(int trackWidth) { + this.trackWidth = trackWidth; + } + + public int getTrackHeight() { + return trackHeight; + } + + public void setTrackHeight(int trackHeight) { + this.trackHeight = trackHeight; + } + + public void initTracking(int tileWidth, int tileHeight, int trackWidth, int trackHeight) { + assert(tileWidth > 0); + assert(tileHeight > 0); + assert(trackWidth > 0); + assert(trackHeight > 0); + assert(tileWidth <= trackWidth); + assert(tileHeight <= trackHeight); + + this.tileWidth = tileWidth; + this.tileHeight = tileHeight; + this.trackWidth = trackWidth; + this.trackHeight = trackHeight; + + int cols = getTileCols(); + int rows = getTileRows(); + snapshot = new boolean[rows][cols]; + for(int i = 0; i < rows; i++) + for(int j = 0; j < cols; j++) + snapshot[i][j] = false; + } + + public synchronized void resize(int trackWidth, int trackHeight) { + assert(tileWidth > 0); + assert(tileHeight > 0); + assert(trackWidth > 0); + assert(trackHeight > 0); + + this.trackWidth = trackWidth; + this.trackHeight = trackHeight; + + int cols = getTileCols(); + int rows = getTileRows(); + snapshot = new boolean[rows][cols]; + for(int i = 0; i < rows; i++) + for(int j = 0; j < cols; j++) + snapshot[i][j] = true; + } + + public void invalidate(Rectangle rect) { + setTileFlag(rect, true); + } + + public void validate(Rectangle rect) { + setTileFlag(rect, false); + } + + public List scan(boolean init) { + List l = new ArrayList(); + + synchronized(this) { + for(int i = 0; i < getTileRows(); i++) { + for(int j = 0; j < getTileCols(); j++) { + if(init || snapshot[i][j]) { + Rectangle rect = new Rectangle(); + rect.y = i*tileHeight; + rect.x = j*tileWidth; + rect.width = Math.min(trackWidth - rect.x, tileWidth); + rect.height = Math.min(trackHeight - rect.y, tileHeight); + + l.add(new TileInfo(i, j, rect)); + snapshot[i][j] = false; + } + } + } + + return l; + } + } + + public boolean hasFullCoverage() { + synchronized(this) { + for(int i = 0; i < getTileRows(); i++) { + for(int j = 0; j < getTileCols(); j++) { + if(!snapshot[i][j]) + return false; + } + } + } + return true; + } + + + + public void initCoverageTest() { + synchronized(this) { + for(int i = 0; i < getTileRows(); i++) { + for(int j = 0; j < getTileCols(); j++) { + snapshot[i][j] = false; + } + } + } + } + + // listener will be called while holding the object lock, use it + // with care to avoid deadlock condition being formed + public synchronized void scan(int nStartRow, int nStartCol, ITileScanListener listener) { + assert(listener != null); + + int cols = getTileCols(); + int rows = getTileRows(); + + nStartRow = nStartRow % rows; + nStartCol = nStartCol % cols; + + int nPos = nStartRow*cols + nStartCol; + int nUnits = rows*cols; + int nStartPos = nPos; + int nRow; + int nCol; + do { + nRow = nPos / cols; + nCol = nPos % cols; + + if(snapshot[nRow][nCol]) { + int nEndCol = nCol; + for(; nEndCol < cols && snapshot[nRow][nEndCol]; nEndCol++) { + snapshot[nRow][nEndCol] = false; + } + + Rectangle rect = new Rectangle(); + rect.y = nRow*tileHeight; + rect.height = tileHeight; + rect.x = nCol*tileWidth; + rect.width = (nEndCol - nCol)*tileWidth; + + if(!listener.onTileChange(rect, nRow, nEndCol)) + break; + } + + nPos = (nPos + 1) % nUnits; + } while(nPos != nStartPos); + } + + public void capture(ITileScanListener listener) { + assert(listener != null); + + int cols = getTileCols(); + int rows = getTileRows(); + + RegionClassifier classifier = new RegionClassifier(); + int left, top, right, bottom; + + synchronized(this) { + for(int i = 0; i < rows; i++) { + top = i*tileHeight; + bottom = Math.min(top + tileHeight, trackHeight); + for(int j = 0; j < cols; j++) { + left = j*tileWidth; + right = Math.min(left + tileWidth, trackWidth); + + if(snapshot[i][j]) { + snapshot[i][j] = false; + classifier.add(new Rectangle(left, top, right - left, bottom - top)); + } + } + } + } + listener.onRegionChange(classifier.getRegionList()); + } + + private synchronized void setTileFlag(Rectangle rect, boolean flag) { + int nStartTileRow; + int nStartTileCol; + int nEndTileRow; + int nEndTileCol; + + int cols = getTileCols(); + int rows = getTileRows(); + + if(rect != null) { + nStartTileRow = Math.min(getTileYPos(rect.y), rows - 1); + nStartTileCol = Math.min(getTileXPos(rect.x), cols - 1); + nEndTileRow = Math.min(getTileYPos(rect.y + rect.height - 1), rows -1); + nEndTileCol = Math.min(getTileXPos(rect.x + rect.width - 1), cols -1); + } else { + nStartTileRow = 0; + nStartTileCol = 0; + nEndTileRow = rows - 1; + nEndTileCol = cols - 1; + } + + for(int i = nStartTileRow; i <= nEndTileRow; i++) + for(int j = nStartTileCol; j <= nEndTileCol; j++) + snapshot[i][j] = flag; + } + + private int getTileRows() { + return (trackHeight + tileHeight - 1) / tileHeight; + } + + private int getTileCols() { + return (trackWidth + tileWidth - 1) / tileWidth; + } + + private int getTileXPos(int x) { + return x / tileWidth; + } + + public int getTileYPos(int y) { + return y / tileHeight; + } +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java b/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java new file mode 100644 index 00000000000..cd9a8378ba4 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/BufferedImageCanvas.java @@ -0,0 +1,69 @@ +package com.cloud.consoleproxy.vnc; + +import java.awt.Canvas; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +/** + * A BuffereImageCanvas component represents frame buffer image on the + * screen. It also notifies its subscribers when screen is repainted. + */ +public class BufferedImageCanvas extends Canvas { + private static final long serialVersionUID = 1L; + + // Offline screen buffer + private BufferedImage offlineImage; + + // Cached Graphics2D object for offline screen buffer + private Graphics2D graphics; + + private PaintNotificationListener listener; + + public BufferedImageCanvas(PaintNotificationListener listener, int width, int height) { + super(); + this.listener = listener; + + setBackground(Color.black); + + setFocusable(true); + + // Don't intercept TAB key + setFocusTraversalKeysEnabled(false); + + setCanvasSize(width, height); + } + + public void setCanvasSize(int width, int height) { + this.offlineImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + graphics = offlineImage.createGraphics(); + + setSize(offlineImage.getWidth(), offlineImage.getHeight()); + } + + @Override + public void update(Graphics g) { + // Call paint() directly, without clearing screen first + paint(g); + } + + @Override + public void paint(Graphics g) { + // Only part of image, requested with repaint(Rectangle), will be + // painted on screen. + g.drawImage(offlineImage, 0, 0, this); + + // Notify server that update is painted on screen + listener.imagePaintedOnScreen(); + } + + public BufferedImage getOfflineImage() { + return offlineImage; + } + + public Graphics2D getOfflineGraphics() { + return graphics; + } + +} \ No newline at end of file diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferUpdateListener.java b/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferUpdateListener.java new file mode 100644 index 00000000000..90d86425a4b --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/FrameBufferUpdateListener.java @@ -0,0 +1,10 @@ +package com.cloud.consoleproxy.vnc; + +public interface FrameBufferUpdateListener { + + /** + * Notify listener, that frame buffer update packet is received, so client is + * permitted (but not obligated) to ask server to send another update. + */ + void frameBufferPacketReceived(); +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/PaintNotificationListener.java b/console-proxy/src/com/cloud/consoleproxy/vnc/PaintNotificationListener.java new file mode 100644 index 00000000000..23db2540141 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/PaintNotificationListener.java @@ -0,0 +1,11 @@ +package com.cloud.consoleproxy.vnc; + +public interface PaintNotificationListener { + + /** + * Notify subscriber that screen is updated, so client can send another frame + * buffer update request to server. + */ + void imagePaintedOnScreen(); + +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/RfbConstants.java b/console-proxy/src/com/cloud/consoleproxy/vnc/RfbConstants.java new file mode 100644 index 00000000000..5e9ec7b7c30 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/RfbConstants.java @@ -0,0 +1,65 @@ +package com.cloud.consoleproxy.vnc; + +import java.nio.charset.Charset; + +public interface RfbConstants { + + public static final String RFB_PROTOCOL_VERSION_MAJOR = "RFB 003."; +// public static final String VNC_PROTOCOL_VERSION_MINOR = "003"; + public static final String VNC_PROTOCOL_VERSION_MINOR = "003"; + public static final String RFB_PROTOCOL_VERSION = RFB_PROTOCOL_VERSION_MAJOR + VNC_PROTOCOL_VERSION_MINOR; + + /** + * Server message types. + */ + final static int SERVER_FRAMEBUFFER_UPDATE = 0, SERVER_SET_COLOURMAP_ENTRIES = 1, SERVER_BELL = 2, SERVER_CUT_TEXT = 3; + + /** + * Client message types. + */ + public static final int CLIENT_SET_PIXEL_FORMAT = 0, CLIENT_FIX_COLOURMAP_ENTRIES = 1, CLIENT_SET_ENCODINGS = 2, CLIENT_FRAMEBUFFER_UPDATE_REQUEST = 3, + CLIENT_KEYBOARD_EVENT = 4, CLIENT_POINTER_EVENT = 5, CLIENT_CUT_TEXT = 6; + + /** + * Server authorization type + */ + public final static int CONNECTION_FAILED = 0, NO_AUTH = 1, VNC_AUTH = 2; + + /** + * Server authorization reply. + */ + public final static int VNC_AUTH_OK = 0, VNC_AUTH_FAILED = 1, VNC_AUTH_TOO_MANY = 2; + + /** + * Encodings. + */ + public final static int ENCODING_RAW = 0, ENCODING_COPY_RECT = 1, ENCODING_RRE = 2, ENCODING_CO_RRE = 4, ENCODING_HEXTILE = 5, ENCODING_ZRLE = 16; + + /** + * Pseudo-encodings. + */ + public final static int ENCODING_CURSOR = -239 /*0xFFFFFF11*/, ENCODING_DESKTOP_SIZE = -223 /*0xFFFFFF21*/; + + /** + * Encodings, which we support. + */ + public final static int[] SUPPORTED_ENCODINGS_ARRAY = { ENCODING_RAW, ENCODING_COPY_RECT, ENCODING_DESKTOP_SIZE }; + + /** + * Frame buffer update request type: update of whole screen or partial update. + */ + public static final int FRAMEBUFFER_FULL_UPDATE_REQUEST = 0, FRAMEBUFFER_INCREMENTAL_UPDATE_REQUEST = 1; + + public static final int KEY_UP = 0, KEY_DOWN = 1; + + public static final int LITTLE_ENDIAN = 0, BIG_ENDIAN = 1; + + public static final int EXCLUSIVE_ACCESS = 0, SHARED_ACCESS = 1; + + public static final int PALETTE = 0, TRUE_COLOR = 1; + + /** + * Default charset to use when communicating with server. + */ + public static final Charset CHARSET = Charset.availableCharsets().get("US-ASCII"); +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/SimpleLogger.java b/console-proxy/src/com/cloud/consoleproxy/vnc/SimpleLogger.java new file mode 100644 index 00000000000..2a6ae4d351e --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/SimpleLogger.java @@ -0,0 +1,40 @@ +package com.cloud.consoleproxy.vnc; + +public class SimpleLogger { + + public static void log(String message) { + System.out.println(getPrefix(1) + " LOG: " + message); + } + + public static void log(int skipFrames, String message) { + System.out.println(getPrefix(1+skipFrames) + " LOG: " + message); + } + + public static void debug(String message) { + System.out.println(getPrefix(1) + " DEBUG: " + message); + } + + public static void info(String message) { + System.out.println(getPrefix(1) + " INFO: " + message); + } + + public static void warn(String message) { + System.err.println(getPrefix(1) + " WARN: " + message); + } + + public static void error(String message) { + System.err.println(getPrefix(1) + " ERROR: " + message); + } + + private static String getPrefix(int skipFrames) { + StackTraceElement frame; + try { + throw new RuntimeException(); + } catch (Exception e) { + frame = e.getStackTrace()[1+skipFrames]; + } + + return "(" + frame.getFileName() + ":" + frame.getLineNumber() + ") " + frame.getMethodName() + "()"; + } + +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java b/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java new file mode 100644 index 00000000000..25035bda0ec --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/VncClient.java @@ -0,0 +1,350 @@ +package com.cloud.consoleproxy.vnc; + +import java.awt.Frame; +import java.awt.ScrollPane; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.spec.KeySpec; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; + +public class VncClient { + + private Socket socket; + private DataInputStream is; + private DataOutputStream os; + + private VncScreenDescription screen = new VncScreenDescription(); + + private VncClientPacketSender sender; + private VncServerPacketReceiver receiver; + + public static void main(String args[]) { + if (args.length < 3) { + printHelpMessage(); + System.exit(1); + } + + String host = args[0]; + String port = args[1]; + String password = args[2]; + + try { + new VncClient(host, Integer.parseInt(port), password); + } catch (NumberFormatException e) { + SimpleLogger.error("Incorrect VNC server port number: " + port + "."); + System.exit(1); + } catch (UnknownHostException e) { + SimpleLogger.error("Incorrect VNC server host name: " + host + "."); + System.exit(1); + } catch (IOException e) { + SimpleLogger.error("Cannot communicate with VNC server: " + e.getMessage()); + System.exit(1); + } catch (Throwable e) { + SimpleLogger.error("An error happened: " + e.getMessage()); + System.exit(1); + } + System.exit(0); + } + + private static void printHelpMessage() { + /* LOG */SimpleLogger.info("Usage: HOST PORT PASSWORD."); + } + + public VncClient(String host, int port, String password) throws UnknownHostException, IOException { + connectTo(host, port, password); + } + + void shutdown() { + sender.closeConnection(); + receiver.closeConnection(); + + try { + is.close(); + } catch (Throwable e) { + } + + try { + os.close(); + } catch (Throwable e) { + } + + try { + socket.close(); + } catch (Throwable e) { + } + + } + + public void connectTo(String host, int port, String password) throws UnknownHostException, IOException { + // If port number is too small, then interpret it as display number. + if (port < 100) + port += 5900; + + // Connect to server + SimpleLogger.info("Connecting to VNC server " + host + ":" + port + "..."); + this.socket = new Socket(host, port); + is = new DataInputStream(socket.getInputStream()); + os = new DataOutputStream(socket.getOutputStream()); + + // Initialize connection + handshake(); + authenticate(password); + initialize(); + + // Run client-to-server packet sender + sender = new VncClientPacketSender(os, screen, this); + + // Create buffered image canvas + BufferedImageCanvas canvas = new BufferedImageCanvas(sender, screen.getFramebufferWidth(), screen.getFramebufferHeight()); + + // Subscribe packet sender to various events + canvas.addMouseListener(sender); + canvas.addMouseMotionListener(sender); + canvas.addKeyListener(sender); + + Frame frame = createVncClientMainWindow(canvas, screen.getDesktopName()); + + new Thread(sender).start(); + + // Run server-to-client packet receiver + receiver = new VncServerPacketReceiver(is, canvas, screen, this, sender); + try { + receiver.run(); + } finally { + frame.setVisible(false); + frame.dispose(); + this.shutdown(); + } + + } + + private Frame createVncClientMainWindow(BufferedImageCanvas canvas, String title) { + // Create AWT windows + final Frame frame = new Frame(title + " - VNCle"); + + // Use scrolling pane to support screens, which are larger than ours + ScrollPane scroller = new ScrollPane(ScrollPane.SCROLLBARS_AS_NEEDED); + scroller.add(canvas); + scroller.setSize(screen.getFramebufferWidth(), screen.getFramebufferHeight()); + + frame.add(scroller); + frame.pack(); + frame.setVisible(true); + + frame.addWindowListener(new WindowAdapter() { + public void windowClosing(WindowEvent evt) { + frame.setVisible(false); + shutdown(); + } + }); + + return frame; + } + + /** + * Handshake with VNC server. + */ + private void handshake() throws IOException { + + // Read protocol version + byte[] buf = new byte[12]; + is.readFully(buf); + String rfbProtocol = new String(buf); + + // Server should use RFB protocol 3.x + if (!rfbProtocol.contains(RfbConstants.RFB_PROTOCOL_VERSION_MAJOR)) + throw new RuntimeException("Cannot handshake with VNC server. Unsupported protocol version: \"" + rfbProtocol + "\"."); + + // Send response: we support RFB 3.3 only + String ourProtocolString = RfbConstants.RFB_PROTOCOL_VERSION + "\n"; + os.write(ourProtocolString.getBytes()); + os.flush(); + } + + /** + * VNC authentication. + */ + private void authenticate(String password) throws IOException { + // Read security type + int authType = is.readInt(); + + switch (authType) { + case RfbConstants.CONNECTION_FAILED: { + // Server forbids to connect. Read reason and throw exception + + int length = is.readInt(); + byte[] buf = new byte[length]; + is.readFully(buf); + String reason = new String(buf, RfbConstants.CHARSET); + + throw new RuntimeException("Authentication to VNC server is failed. Reason: " + reason); + } + + case RfbConstants.NO_AUTH: { + // Client can connect without authorization. Nothing to do. + break; + } + + case RfbConstants.VNC_AUTH: { + doVncAuth(password); + break; + } + + default: + throw new RuntimeException("Unsupported VNC protocol authorization scheme, scheme code: " + authType + "."); + } + } + + /** + * Encode client password and send it to server. + */ + private void doVncAuth(String password) throws IOException { + + // Read challenge + byte[] challenge = new byte[16]; + is.readFully(challenge); + + // Encode challenge with password + byte[] response; + try { + response = encodePassword(challenge, password); + } catch (Exception e) { + throw new RuntimeException("Cannot encrypt client password to send to server: " + e.getMessage()); + } + + // Send encoded challenge + os.write(response); + os.flush(); + + // Read security result + int authResult = is.readInt(); + + switch (authResult) { + case RfbConstants.VNC_AUTH_OK: { + // Nothing to do + break; + } + + case RfbConstants.VNC_AUTH_TOO_MANY: + throw new RuntimeException("Connection to VNC server failed: too many wrong attempts."); + + case RfbConstants.VNC_AUTH_FAILED: + throw new RuntimeException("Connection to VNC server failed: wrong password."); + + default: + throw new RuntimeException("Connection to VNC server failed, reason code: " + authResult); + } + } + + /** + * Encode password using DES encryption with given challenge. + * + * @param challenge + * a random set of bytes. + * @param password + * a password + * @return DES hash of password and challenge + */ + public byte[] encodePassword(byte[] challenge, String password) throws Exception { + // VNC password consist of up to eight ASCII characters. + byte[] key = { 0, 0, 0, 0, 0, 0, 0, 0 }; // Padding + byte[] passwordAsciiBytes = password.getBytes(RfbConstants.CHARSET); + System.arraycopy(passwordAsciiBytes, 0, key, 0, Math.min(password.length(), 8)); + + // Flip bytes (reverse bits) in key + for (int i = 0; i < key.length; i++) { + key[i] = flipByte(key[i]); + } + + KeySpec desKeySpec = new DESKeySpec(key); + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("DES"); + SecretKey secretKey = secretKeyFactory.generateSecret(desKeySpec); + Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + byte[] response = cipher.doFinal(challenge); + return response; + } + + /** + * Reverse bits in byte, so least significant bit will be most significant + * bit. E.g. 01001100 will become 00110010. + * + * See also: http://www.vidarholen.net/contents/junk/vnc.html , + * http://bytecrafter .blogspot.com/2010/09/des-encryption-as-used-in-vnc.html + * + * @param b + * a byte + * @return byte in reverse order + */ + private static byte flipByte(byte b) { + int b1_8 = (b & 0x1) << 7; + int b2_7 = (b & 0x2) << 5; + int b3_6 = (b & 0x4) << 3; + int b4_5 = (b & 0x8) << 1; + int b5_4 = (b & 0x10) >>> 1; + int b6_3 = (b & 0x20) >>> 3; + int b7_2 = (b & 0x40) >>> 5; + int b8_1 = (b & 0x80) >>> 7; + byte c = (byte) (b1_8 | b2_7 | b3_6 | b4_5 | b5_4 | b6_3 | b7_2 | b8_1); + return c; + } + + private void initialize() throws IOException { + // Send client initialization message + { + // Send shared flag + os.writeByte(RfbConstants.EXCLUSIVE_ACCESS); + os.flush(); + } + + // Read server initialization message + { + // Read frame buffer size + int framebufferWidth = is.readUnsignedShort(); + int framebufferHeight = is.readUnsignedShort(); + screen.setFramebufferSize(framebufferWidth, framebufferHeight); + } + + // Read pixel format + { + int bitsPerPixel = is.readUnsignedByte(); + int depth = is.readUnsignedByte(); + + int bigEndianFlag = is.readUnsignedByte(); + int trueColorFlag = is.readUnsignedByte(); + + int redMax = is.readUnsignedShort(); + int greenMax = is.readUnsignedShort(); + int blueMax = is.readUnsignedShort(); + + int redShift = is.readUnsignedByte(); + int greenShift = is.readUnsignedByte(); + int blueShift = is.readUnsignedByte(); + + // Skip padding + is.skipBytes(3); + + screen.setPixelFormat(bitsPerPixel, depth, bigEndianFlag, trueColorFlag, redMax, greenMax, blueMax, redShift, greenShift, blueShift); + } + + // Read desktop name + { + int length = is.readInt(); + byte buf[] = new byte[length]; + is.readFully(buf); + String desktopName = new String(buf, RfbConstants.CHARSET); + screen.setDesktopName(desktopName); + } + } + +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/VncClientPacketSender.java b/console-proxy/src/com/cloud/consoleproxy/vnc/VncClientPacketSender.java new file mode 100644 index 00000000000..c6569deb0d1 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/VncClientPacketSender.java @@ -0,0 +1,237 @@ +package com.cloud.consoleproxy.vnc; + +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.io.DataOutputStream; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; + +import com.cloud.consoleproxy.vnc.packet.client.ClientPacket; +import com.cloud.consoleproxy.vnc.packet.client.FramebufferUpdateRequestPacket; +import com.cloud.consoleproxy.vnc.packet.client.KeyboardEventPacket; +import com.cloud.consoleproxy.vnc.packet.client.MouseEventPacket; +import com.cloud.consoleproxy.vnc.packet.client.SetEncodingsPacket; +import com.cloud.consoleproxy.vnc.packet.client.SetPixelFormatPacket; + +public class VncClientPacketSender implements Runnable, PaintNotificationListener, KeyListener, MouseListener, MouseMotionListener, FrameBufferUpdateListener { + + // Queue for outgoing packets + private final BlockingQueue queue = new ArrayBlockingQueue(30); + + private final DataOutputStream os; + private final VncScreenDescription screen; + private final VncClient vncConnection; + + private boolean connectionAlive = true; + + // Don't send update request again until we receive next frame buffer update + private boolean updateRequestSent = false; + + public VncClientPacketSender(DataOutputStream os, VncScreenDescription screen, VncClient vncConnection) { + this.os = os; + this.screen = screen; + this.vncConnection = vncConnection; + + sendSetPixelFormat(); + sendSetEncodings(); + requestFullScreenUpdate(); + } + + @Override + public void run() { + try { + while (connectionAlive) { + ClientPacket packet = queue.poll(1, TimeUnit.SECONDS); + if (packet != null) { + packet.write(os); + os.flush(); + } + } + } catch (Throwable e) { + if (connectionAlive) { + closeConnection(); + vncConnection.shutdown(); + } + } + } + + private void sendSetEncodings() { + queue.add(new SetEncodingsPacket(RfbConstants.SUPPORTED_ENCODINGS_ARRAY)); + } + + private void sendSetPixelFormat() { + if (!screen.isRGB888_32_LE()) { + queue.add(new SetPixelFormatPacket(screen, 32, 24, RfbConstants.LITTLE_ENDIAN, RfbConstants.TRUE_COLOR, 255, 255, 255, 16, 8, 0)); + } + } + + public void closeConnection() { + connectionAlive = false; + } + + public void requestFullScreenUpdate() { + queue.add(new FramebufferUpdateRequestPacket(RfbConstants.FRAMEBUFFER_FULL_UPDATE_REQUEST, 0, 0, screen.getFramebufferWidth(), screen + .getFramebufferHeight())); + updateRequestSent = true; + } + + @Override + public void imagePaintedOnScreen() { + if (!updateRequestSent) { + queue.add(new FramebufferUpdateRequestPacket(RfbConstants.FRAMEBUFFER_INCREMENTAL_UPDATE_REQUEST, 0, 0, screen.getFramebufferWidth(), screen + .getFramebufferHeight())); + updateRequestSent = true; + } + } + + @Override + public void frameBufferPacketReceived() { + updateRequestSent = false; + } + + @Override + public void mouseDragged(MouseEvent e) { + queue.add(new MouseEventPacket(mapAwtModifiersToVncButtonMask(e.getModifiersEx()), e.getX(), e.getY())); + } + + @Override + public void mouseMoved(MouseEvent e) { + queue.add(new MouseEventPacket(mapAwtModifiersToVncButtonMask(e.getModifiersEx()), e.getX(), e.getY())); + } + + @Override + public void mouseClicked(MouseEvent e) { + // Nothing to do + } + + @Override + public void mousePressed(MouseEvent e) { + queue.add(new MouseEventPacket(mapAwtModifiersToVncButtonMask(e.getModifiersEx()), e.getX(), e.getY())); + } + + @Override + public void mouseReleased(MouseEvent e) { + queue.add(new MouseEventPacket(mapAwtModifiersToVncButtonMask(e.getModifiersEx()), e.getX(), e.getY())); + } + + @Override + public void mouseEntered(MouseEvent e) { + // Nothing to do + } + + @Override + public void mouseExited(MouseEvent e) { + // Nothing to do + } + + /** + * Current state of buttons 1 to 8 are represented by bits 0 to 7 of + * button-mask respectively, 0 meaning up, 1 meaning down (pressed). On a + * conventional mouse, buttons 1, 2 and 3 correspond to the left, middle and + * right buttons on the mouse. On a wheel mouse, each step of the wheel + * upwards is represented by a press and release of button 4, and each step + * downwards is represented by a press and release of button 5. + * + * @param modifiers + * extended modifiers from AWT mouse event + * @return VNC mouse button mask + */ + public static int mapAwtModifiersToVncButtonMask(int modifiers) { + int mask = (((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) ? 0x1 : 0) | (((modifiers & MouseEvent.BUTTON2_DOWN_MASK) != 0) ? 0x2 : 0) + | (((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0) ? 0x4 : 0); + return mask; + } + + @Override + public void keyTyped(KeyEvent e) { + // Do nothing + } + + @Override + public void keyPressed(KeyEvent e) { + ClientPacket request = new KeyboardEventPacket(RfbConstants.KEY_DOWN, mapAwtKeyToVncKey(e.getKeyCode())); + queue.add(request); + } + + @Override + public void keyReleased(KeyEvent e) { + ClientPacket request = new KeyboardEventPacket(RfbConstants.KEY_UP, mapAwtKeyToVncKey(e.getKeyCode())); + queue.add(request); + } + + private int mapAwtKeyToVncKey(int key) { + switch (key) { + case KeyEvent.VK_BACK_SPACE: + return 0xff08; + case KeyEvent.VK_TAB: + return 0xff09; + case KeyEvent.VK_ENTER: + return 0xff0d; + case KeyEvent.VK_ESCAPE: + return 0xff1b; + case KeyEvent.VK_INSERT: + return 0xff63; + case KeyEvent.VK_DELETE: + return 0xffff; + case KeyEvent.VK_HOME: + return 0xff50; + case KeyEvent.VK_END: + return 0xff57; + case KeyEvent.VK_PAGE_UP: + return 0xff55; + case KeyEvent.VK_PAGE_DOWN: + return 0xff56; + case KeyEvent.VK_LEFT: + return 0xff51; + case KeyEvent.VK_UP: + return 0xff52; + case KeyEvent.VK_RIGHT: + return 0xff53; + case KeyEvent.VK_DOWN: + return 0xff54; + case KeyEvent.VK_F1: + return 0xffbe; + case KeyEvent.VK_F2: + return 0xffbf; + case KeyEvent.VK_F3: + return 0xffc0; + case KeyEvent.VK_F4: + return 0xffc1; + case KeyEvent.VK_F5: + return 0xffc2; + case KeyEvent.VK_F6: + return 0xffc3; + case KeyEvent.VK_F7: + return 0xffc4; + case KeyEvent.VK_F8: + return 0xffc5; + case KeyEvent.VK_F9: + return 0xffc6; + case KeyEvent.VK_F10: + return 0xffc7; + case KeyEvent.VK_F11: + return 0xffc8; + case KeyEvent.VK_F12: + return 0xffc9; + case KeyEvent.VK_SHIFT: + return 0xffe1; + case KeyEvent.VK_CONTROL: + return 0xffe3; + case KeyEvent.VK_META: + return 0xffe7; + case KeyEvent.VK_ALT: + return 0xffe9; + case KeyEvent.VK_ALT_GRAPH: + return 0xffea; + case KeyEvent.VK_BACK_QUOTE: + return 0x0060; + } + + return key; + } + +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/VncScreenDescription.java b/console-proxy/src/com/cloud/consoleproxy/vnc/VncScreenDescription.java new file mode 100644 index 00000000000..afd53cf3f4a --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/VncScreenDescription.java @@ -0,0 +1,74 @@ +package com.cloud.consoleproxy.vnc; + +/** + * VncScreenDescription - contains information about remote VNC screen. + */ +public class VncScreenDescription { + + // Frame buffer size + private int framebufferWidth = -1; + private int framebufferHeight = -1; + + // Desktop name + private String desktopName; + + // Bytes per pixel + private int bytesPerPixel; + + // Indicates that screen uses format which we want to use: + // RGB 24bit packed into 32bit little-endian int. + private boolean rgb888_32_le = false; + + public VncScreenDescription() { + } + + /** + * Store information about server pixel format. + */ + public void setPixelFormat(int bitsPerPixel, int depth, int bigEndianFlag, int trueColorFlag, int redMax, int greenMax, int blueMax, int redShift, + int greenShift, int blueShift) { + + bytesPerPixel = (bitsPerPixel + 7) / 8; + + rgb888_32_le = (depth == 24 && bitsPerPixel == 32 && redShift == 16 && greenShift == 8 && blueShift == 0 && redMax == 255 && greenMax == 255 + && blueMax == 255 && bigEndianFlag == RfbConstants.LITTLE_ENDIAN && trueColorFlag == RfbConstants.TRUE_COLOR); + } + + /** + * Store information about server screen size. + */ + public void setFramebufferSize(int framebufferWidth, int framebufferHeight) { + this.framebufferWidth = framebufferWidth; + this.framebufferHeight = framebufferHeight; + } + + /** + * Store server desktop name. + */ + public void setDesktopName(String desktopName) { + this.desktopName = desktopName; + } + + // Getters for variables, as usual + + public String getDesktopName() { + return desktopName; + } + + public int getBytesPerPixel() { + return bytesPerPixel; + } + + public int getFramebufferHeight() { + return framebufferHeight; + } + + public int getFramebufferWidth() { + return framebufferWidth; + } + + public boolean isRGB888_32_LE() { + return rgb888_32_le; + } + +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java b/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java new file mode 100644 index 00000000000..01d9cf887e6 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/VncServerPacketReceiver.java @@ -0,0 +1,94 @@ +package com.cloud.consoleproxy.vnc; + +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; +import java.io.DataInputStream; +import java.io.IOException; + +import com.cloud.consoleproxy.vnc.packet.server.FramebufferUpdatePacket; +import com.cloud.consoleproxy.vnc.packet.server.ServerCutText; + +public class VncServerPacketReceiver implements Runnable { + + private final VncScreenDescription screen; + private BufferedImageCanvas canvas; + private DataInputStream is; + + private boolean connectionAlive = true; + private VncClient vncConnection; + private final FrameBufferUpdateListener fburListener; + + public VncServerPacketReceiver(DataInputStream is, BufferedImageCanvas canvas, VncScreenDescription screen, VncClient vncConnection, + FrameBufferUpdateListener fburListener) { + this.screen = screen; + this.canvas = canvas; + this.is = is; + this.vncConnection = vncConnection; + this.fburListener = fburListener; + } + + @Override + public void run() { + try { + while (connectionAlive) { + + // Read server message type + int messageType = is.readUnsignedByte(); + + // Invoke packet handler by packet type. + switch (messageType) { + + case RfbConstants.SERVER_FRAMEBUFFER_UPDATE: { + // Notify sender that frame buffer update is received, + // so it can send another frame buffer update request + fburListener.frameBufferPacketReceived(); + // Handle frame buffer update + new FramebufferUpdatePacket(canvas, screen, is); + break; + } + + case RfbConstants.SERVER_BELL: { + serverBell(); + break; + } + + case RfbConstants.SERVER_CUT_TEXT: { + serverCutText(is); + break; + } + + default: + throw new RuntimeException("Unknown server packet type: " + messageType + "."); + } + + } + } catch (Throwable e) { + if (connectionAlive) { + closeConnection(); + vncConnection.shutdown(); + } + } + } + + public void closeConnection() { + connectionAlive = false; + } + + /** + * Handle server bell packet. + */ + private void serverBell() { + Toolkit.getDefaultToolkit().beep(); + } + + /** + * Handle packet with server clip-board. + */ + private void serverCutText(DataInputStream is) throws IOException { + ServerCutText clipboardContent = new ServerCutText(is); + StringSelection contents = new StringSelection(clipboardContent.getContent()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(contents, null); + + SimpleLogger.info("Server clipboard buffer: "+clipboardContent.getContent()); + } +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/ClientPacket.java b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/ClientPacket.java new file mode 100644 index 00000000000..46b98f7ed15 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/ClientPacket.java @@ -0,0 +1,10 @@ +package com.cloud.consoleproxy.vnc.packet.client; + +import java.io.DataOutputStream; +import java.io.IOException; + +public interface ClientPacket { + + void write(DataOutputStream os) throws IOException; + +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/FramebufferUpdateRequestPacket.java b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/FramebufferUpdateRequestPacket.java new file mode 100644 index 00000000000..c6e9be20d47 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/FramebufferUpdateRequestPacket.java @@ -0,0 +1,38 @@ +package com.cloud.consoleproxy.vnc.packet.client; + +import java.io.DataOutputStream; +import java.io.IOException; + +import com.cloud.consoleproxy.vnc.RfbConstants; + +/** + * FramebufferUpdateRequestPacket + * + * @author Volodymyr M. Lisivka + */ +public class FramebufferUpdateRequestPacket implements ClientPacket { + + private final int incremental; + private final int x, y, width, height; + + public FramebufferUpdateRequestPacket(int incremental, int x, int y, int width, int height) { + this.incremental = incremental; + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + + @Override + public void write(DataOutputStream os) throws IOException { + os.writeByte(RfbConstants.CLIENT_FRAMEBUFFER_UPDATE_REQUEST); + + os.writeByte(incremental); + os.writeShort(x); + os.writeShort(y); + os.writeShort(width); + os.writeShort(height); + } + +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/KeyboardEventPacket.java b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/KeyboardEventPacket.java new file mode 100644 index 00000000000..9a2d44f10c6 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/KeyboardEventPacket.java @@ -0,0 +1,26 @@ +package com.cloud.consoleproxy.vnc.packet.client; + +import java.io.DataOutputStream; +import java.io.IOException; + +import com.cloud.consoleproxy.vnc.RfbConstants; + +public class KeyboardEventPacket implements ClientPacket { + + private final int downFlag, key; + + public KeyboardEventPacket(int downFlag, int key) { + this.downFlag = downFlag; + this.key = key; + } + + @Override + public void write(DataOutputStream os) throws IOException { + os.writeByte(RfbConstants.CLIENT_KEYBOARD_EVENT); + + os.writeByte(downFlag); + os.writeShort(0); // padding + os.writeInt(key); + } + +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/MouseEventPacket.java b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/MouseEventPacket.java new file mode 100644 index 00000000000..6514936df07 --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/MouseEventPacket.java @@ -0,0 +1,27 @@ +package com.cloud.consoleproxy.vnc.packet.client; + +import java.io.DataOutputStream; +import java.io.IOException; + +import com.cloud.consoleproxy.vnc.RfbConstants; + +public class MouseEventPacket implements ClientPacket { + + private final int buttonMask, x, y; + + public MouseEventPacket(int buttonMask, int x, int y) { + this.buttonMask = buttonMask; + this.x = x; + this.y = y; + } + + @Override + public void write(DataOutputStream os) throws IOException { + os.writeByte(RfbConstants.CLIENT_POINTER_EVENT); + + os.writeByte(buttonMask); + os.writeShort(x); + os.writeShort(y); + } + +} diff --git a/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/SetEncodingsPacket.java b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/SetEncodingsPacket.java new file mode 100644 index 00000000000..56df9043a0a --- /dev/null +++ b/console-proxy/src/com/cloud/consoleproxy/vnc/packet/client/SetEncodingsPacket.java @@ -0,0 +1,32 @@ +package com.cloud.consoleproxy.vnc.packet.client; + +import java.io.DataOutputStream; +import java.io.IOException; + +import com.cloud.consoleproxy.vnc.RfbConstants; + +public class SetEncodingsPacket implements ClientPacket { + + private final int[] encodings; + + public SetEncodingsPacket(int[] encodings) + { + this.encodings = encodings; + } + + @Override + public void write(DataOutputStream os) throws IOException + { + os.writeByte(RfbConstants.CLIENT_SET_ENCODINGS); + + os.writeByte(0);//padding + + os.writeShort(encodings.length); + + for(int i=0;i getIsolatedNetworksOwnedByAccountInZone(long zoneId, Account owner) { - // TODO Auto-generated method stub - return null; - } - - @Override - public IpAddress associateIP(long ipId) throws ResourceAllocationException, InsufficientAddressCapacityException, ConcurrentOperationException, ResourceUnavailableException { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean disassociateIpAddress(long ipAddressId) { - // TODO Auto-generated method stub - return false; - } - - @Override - public Network createNetwork(CreateNetworkCmd cmd) throws InsufficientCapacityException, ConcurrentOperationException { - // TODO Auto-generated method stub - return null; - } - - @Override - public List searchForNetworks(ListNetworksCmd cmd) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean deleteNetwork(long networkId) { - // TODO Auto-generated method stub - return false; - } - - @Override - public int getActiveNicsInNetwork(long networkId) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public Network getNetwork(long networkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public IpAddress getIp(long id) { - // TODO Auto-generated method stub - return null; - } - - @Override - public NetworkProfile convertNetworkToNetworkProfile(long networkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Map> getNetworkCapabilities(long networkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean isNetworkAvailableInDomain(long networkId, long domainId) { - // TODO Auto-generated method stub - return false; - } - - @Override - public Long getDedicatedNetworkDomain(long networkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Integer getNetworkRate(long networkId, Long vmId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Network getSystemNetworkByZoneAndTrafficType(long zoneId, TrafficType trafficType) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean configure(String name, Map params) throws ConfigurationException { - return true; - } - - @Override - public boolean start() { - return true; - } - - @Override - public boolean stop() { - // TODO Auto-generated method stub - return false; - } - - @Override - public String getName() { - // TODO Auto-generated method stub - return null; - } - - - - @Override - public PublicIp assignSourceNatIpAddress(Account owner, Network network, long callerId) throws ConcurrentOperationException, InsufficientAddressCapacityException { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean releasePublicIpAddress(long id, long userId, Account caller) { - // TODO Auto-generated method stub - return false; - } - - @Override - public List listPublicIpAddressesInVirtualNetwork(long accountId, long dcId, Boolean sourceNat, Long associatedNetworkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List setupNetwork(Account owner, NetworkOfferingVO offering, DeploymentPlan plan, String name, String displayText, boolean isDefault) - throws ConcurrentOperationException { - // TODO Auto-generated method stub - return null; - } - - @Override - public List setupNetwork(Account owner, NetworkOfferingVO offering, Network predefined, DeploymentPlan plan, String name, String displayText, boolean errorIfAlreadySetup, Long domainId, - ACLType aclType, Boolean subdomainAccess) throws ConcurrentOperationException { - // TODO Auto-generated method stub - return null; - } - - @Override - public List getSystemAccountNetworkOfferings(String... offeringNames) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void allocate(VirtualMachineProfile vm, List> networks) throws InsufficientCapacityException, ConcurrentOperationException { - // TODO Auto-generated method stub - - } - - @Override - public void prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) throws InsufficientCapacityException, ConcurrentOperationException, - ResourceUnavailableException { - // TODO Auto-generated method stub - - } - - @Override - public void release(VirtualMachineProfile vmProfile, boolean forced) { - // TODO Auto-generated method stub - - } - - @Override - public void cleanupNics(VirtualMachineProfile vm) { - // TODO Auto-generated method stub - - } - - @Override - public void expungeNics(VirtualMachineProfile vm) { - // TODO Auto-generated method stub - - } - - @Override - public List getNics(long vmId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List getNicProfiles(VirtualMachine vm) { - // TODO Auto-generated method stub - return null; - } - - @Override - public String getNextAvailableMacAddressInNetwork(long networkConfigurationId) throws InsufficientAddressCapacityException { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean applyRules(List rules, boolean continueOnError) throws ResourceUnavailableException { - // TODO Auto-generated method stub - return false; - } - - @Override - public PublicIpAddress getPublicIpAddress(long ipAddressId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List listPodVlans(long podId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Pair implementNetwork(long networkId, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, - InsufficientCapacityException { - // TODO Auto-generated method stub - return null; - } - - @Override - public List listNetworksUsedByVm(long vmId, boolean isSystem) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void prepareNicForMigration(VirtualMachineProfile vm, DeployDestination dest) { - // TODO Auto-generated method stub - - } - - @Override - public boolean destroyNetwork(long networkId, ReservationContext context) { - // TODO Auto-generated method stub - return false; - } - - @Override - public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, String networkDomain, Account owner, boolean isSecurityGroupEnabled, - Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess) throws ConcurrentOperationException, InsufficientCapacityException { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean associateIpAddressListToAccount(long userId, long accountId, long zoneId, Long vlanId, Network networkToAssociateWith) throws InsufficientCapacityException, - ConcurrentOperationException, ResourceUnavailableException { - // TODO Auto-generated method stub - return false; - } - - @Override - public Nic getNicInNetwork(long vmId, long networkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List getNicsForTraffic(long vmId, TrafficType type) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Network getDefaultNetworkForVm(long vmId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Nic getDefaultNic(long vmId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean applyIpAssociations(Network network, boolean continueOnError) throws ResourceUnavailableException { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean areServicesSupportedByNetworkOffering(long networkOfferingId, Service... services) { - // TODO Auto-generated method stub - return false; - } - - @Override - public NetworkVO getNetworkWithSecurityGroupEnabled(Long zoneId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean startNetwork(long networkId, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - // TODO Auto-generated method stub - return false; - } - - @Override - public String getIpOfNetworkElementInVirtualNetwork(long accountId, long dataCenterId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public IPAddressVO markIpAsUnavailable(long addrId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public String acquireGuestIpAddress(Network network, String requestedIp) { - // TODO Auto-generated method stub - return null; - } - - @Override - public String getGlobalGuestDomainSuffix() { - // TODO Auto-generated method stub - return null; - } - - @Override - public String getStartIpAddress(long networkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean applyStaticNats(List staticNats, boolean continueOnError) throws ResourceUnavailableException { - // TODO Auto-generated method stub - return false; - } - - @Override - public String getIpInNetwork(long vmId, long networkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public String getIpInNetworkIncludingRemoved(long vmId, long networkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Map> getNetworkOfferingServiceProvidersMap(long networkOfferingId) { - return null; - } - - @Override - public List getRemoteAccessVpnElements() { - return null; - } - - @Override - public boolean isProviderSupportServiceInNetwork(long networkId, Service service, Provider provider) { - // TODO Auto-generated method stub - return false; - } - - @Override - public PhysicalNetwork createPhysicalNetwork(Long zoneId, String vnetRange, String networkSpeed, List isolationMethods, String broadcastDomainRange, Long domainId, List tags, String name) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List searchPhysicalNetworks(Long id, Long zoneId, String keyword, Long startIndex, Long pageSize, String name) { - // TODO Auto-generated method stub - return null; - } - - @Override - public PhysicalNetwork updatePhysicalNetwork(Long id, String networkSpeed, List tags, String newVnetRangeString, String state) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean deletePhysicalNetwork(Long id) { - // TODO Auto-generated method stub - return false; - } - - @Override - public List listNetworkServices(String providerName) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List listSupportedNetworkServiceProviders(String serviceName) { - // TODO Auto-generated method stub - return null; - } - - @Override - public PhysicalNetworkServiceProvider addProviderToPhysicalNetwork(Long physicalNetworkId, String providerName, Long destinationPhysicalNetworkId, List enabledServices) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List listNetworkServiceProviders(Long physicalNetworkId, String name, String state, Long startIndex, Long pageSize) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean deleteNetworkServiceProvider(Long id) { - // TODO Auto-generated method stub - return false; - } - - @Override - public PhysicalNetwork getPhysicalNetwork(Long physicalNetworkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public PhysicalNetwork getCreatedPhysicalNetwork(Long physicalNetworkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public PhysicalNetworkServiceProvider getPhysicalNetworkServiceProvider(Long providerId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public PhysicalNetworkServiceProvider getCreatedPhysicalNetworkServiceProvider(Long providerId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public long findPhysicalNetworkId(long zoneId, String tag, TrafficType trafficType) { - // TODO Auto-generated method stub - return 0; - } - - @Override - public PhysicalNetworkTrafficType getPhysicalNetworkTrafficType(Long id) { - // TODO Auto-generated method stub - return null; - } - - @Override - public PhysicalNetworkTrafficType updatePhysicalNetworkTrafficType(Long id, String xenLabel, String kvmLabel, String vmwareLabel) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean deletePhysicalNetworkTrafficType(Long id) { - // TODO Auto-generated method stub - return false; - } - - @Override - public List listTrafficTypes(Long physicalNetworkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public PhysicalNetwork getDefaultPhysicalNetworkByZoneAndTrafficType(long zoneId, TrafficType trafficType) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Network getExclusiveGuestNetwork(long zoneId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Long getPodIdForVlan(long vlanDbId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean networkIsConfiguredForExternalNetworking(long zoneId, long networkId) { - // TODO Auto-generated method stub - return false; - } - - @Override - public Map getNetworkServiceCapabilities(long networkId, Service service) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List listNetworksForAccount(long accountId, long zoneId, GuestType type) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List listNetworkOfferingsForUpgrade(long networkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public PhysicalNetwork translateZoneIdToPhysicalNetwork(long zoneId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean isSecurityGroupSupportedInNetwork(Network network) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean isProviderEnabledInPhysicalNetwork(long physicalNetowrkId, String providerName) { - // TODO Auto-generated method stub - return false; - } - - @Override - public List getElementServices(Provider provider) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean canElementEnableIndividualServices(Provider provider) { - // TODO Auto-generated method stub - return false; - } - - @Override - public List getPasswordResetElements() { - // TODO Auto-generated method stub - return null; - } - - @Override - public PhysicalNetworkServiceProvider updateNetworkServiceProvider(Long id, String state, List enabledServices) { - // TODO Auto-generated method stub - return null; - } - - @Override - public PhysicalNetworkTrafficType addTrafficTypeToPhysicalNetwork(Long physicalNetworkId, String trafficType, String xenLabel, String kvmLabel, String vmwareLabel, String simulatorLabel, String vlan) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean areServicesSupportedInNetwork(long networkId, Service... services) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean isNetworkSystem(Network network) { - // TODO Auto-generated method stub - return false; - } - - @Override - public PhysicalNetworkServiceProvider addDefaultVirtualRouterToPhysicalNetwork(long physicalNetworkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Map getNetworkOfferingServiceCapabilities(NetworkOffering offering, Service service) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean reallocate(VirtualMachineProfile vm, DataCenterDeployment dest) throws InsufficientCapacityException, ConcurrentOperationException { - // TODO Auto-generated method stub - return false; - } - - @Override - public Long getPhysicalNetworkId(Network network) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean getAllowSubdomainAccessGlobal() { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean isProviderForNetwork(Provider provider, long networkId) { - // TODO Auto-generated method stub - return false; - } - - @Override - public boolean restartNetwork(RestartNetworkCmd cmd, boolean cleanup) - throws ConcurrentOperationException, ResourceUnavailableException, - InsufficientCapacityException { - // TODO Auto-generated method stub - return false; - } - - @Override - public String getNetworkTag(HypervisorType hType, Network network) { - // TODO Auto-generated method stub - return null; - } - - @Override - public void canProviderSupportServices( - Map> providersMap) { - // TODO Auto-generated method stub - - } - - @Override - public boolean isProviderForNetworkOffering(Provider provider, long networkOfferingId) { - // TODO Auto-generated method stub - return false; - } - - @Override - public PhysicalNetworkServiceProvider addDefaultSecurityGroupProviderToPhysicalNetwork(long physicalNetworkId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List getPhysicalNetworkInfo(long dcId, HypervisorType hypervisorType) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean canAddDefaultSecurityGroup() { - // TODO Auto-generated method stub - return false; - } - - @Override - public List> listTrafficTypeImplementor(ListTrafficTypeImplementorsCmd cmd) { - // TODO Auto-generated method stub - return null; - } - - @Override - public List listNetworkOfferingServices(long networkOfferingId) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean areServicesEnabledInZone(long zoneId, NetworkOffering offering, List services) { - // TODO Auto-generated method stub - return false; - } - - @Override - public Map> getIpToServices(List publicIps, boolean rulesRevoked, boolean includingFirewall) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Map> getProviderToIpList(Network network, Map> ipToServices) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean checkIpForService(IPAddressVO ip, Service service) { - // TODO Auto-generated method stub - return false; - } - - @Override - public void checkVirtualNetworkCidrOverlap(Long zoneId, String cidr) { - // TODO Auto-generated method stub - - } - - @Override - public IpAddress allocateIP(long networkId, Account ipOwner, - boolean isSystem) throws ResourceAllocationException, - InsufficientAddressCapacityException, ConcurrentOperationException { - // TODO Auto-generated method stub - return null; - } - - @Override - public PublicIp assignPublicIpAddress(long dcId, Long podId, Account owner, - VlanType type, Long networkId, String requestedIp, boolean isSystem) - throws InsufficientAddressCapacityException { - // TODO Auto-generated method stub - return null; - } - - @Override - public void checkCapabilityForProvider(Set providers, - Service service, Capability cap, String capValue) { - // TODO Auto-generated method stub - - } - - @Override - public Provider getDefaultUniqueProviderForService(String serviceName) { - // TODO Auto-generated method stub - return null; - } - - @Override - public IpAddress assignSystemIp(long networkId, Account owner, - boolean forElasticLb, boolean forElasticIp) - throws InsufficientAddressCapacityException { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean handleSystemIpRelease(IpAddress ip) { - // TODO Auto-generated method stub - return false; - } - - @Override - public void checkNetworkPermissions(Account owner, Network network) { - // TODO Auto-generated method stub - - } - - @Override - public void allocateDirectIp(NicProfile nic, DataCenter dc, - VirtualMachineProfile vm, - Network network, String requestedIp) - throws InsufficientVirtualNetworkCapcityException, - InsufficientAddressCapacityException { - // TODO Auto-generated method stub - - } - - @Override - public boolean validateRule(FirewallRule rule) { - // TODO Auto-generated method stub - return false; - } - - @Override - public String getDefaultManagementTrafficLabel(long zoneId, HypervisorType hypervisorType) { - // TODO Auto-generated method stub - return null; - } - - @Override - public String getDefaultStorageTrafficLabel(long zoneId, HypervisorType hypervisorType) { - // TODO Auto-generated method stub - return null; - } - - @Override - public Network updateGuestNetwork(long networkId, String name, String displayText, Account callerAccount, User callerUser, String domainSuffix, Long networkOfferingId, Boolean changeCidr) { - // TODO Auto-generated method stub - return null; - } - - @Override - public boolean shutdownNetwork(long networkId, ReservationContext context, boolean cleanupElements) { - // TODO Auto-generated method stub - return false; - } - -} +package com.cloud.network; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ejb.Local; +import javax.naming.ConfigurationException; + +import com.cloud.acl.ControlledEntity.ACLType; +import com.cloud.api.commands.CreateNetworkCmd; +import com.cloud.api.commands.ListNetworksCmd; +import com.cloud.api.commands.ListTrafficTypeImplementorsCmd; +import com.cloud.api.commands.RestartNetworkCmd; +import com.cloud.dc.DataCenter; +import com.cloud.dc.Vlan; +import com.cloud.dc.Vlan.VlanType; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientVirtualNetworkCapcityException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.addr.PublicIp; +import com.cloud.network.element.RemoteAccessVPNServiceProvider; +import com.cloud.network.element.UserDataServiceProvider; +import com.cloud.network.guru.NetworkGuru; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.StaticNat; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.user.Account; +import com.cloud.user.User; +import com.cloud.utils.Pair; +import com.cloud.utils.component.Manager; +import com.cloud.vm.Nic; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +@Local(value = { NetworkManager.class, NetworkService.class }) +public class MockNetworkManagerImpl implements NetworkManager, Manager, NetworkService { + + @Override + public List getIsolatedNetworksOwnedByAccountInZone(long zoneId, Account owner) { + // TODO Auto-generated method stub + return null; + } + + @Override + public IpAddress associateIP(long ipId) throws ResourceAllocationException, InsufficientAddressCapacityException, ConcurrentOperationException, ResourceUnavailableException { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean disassociateIpAddress(long ipAddressId) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Network createNetwork(CreateNetworkCmd cmd) throws InsufficientCapacityException, ConcurrentOperationException { + // TODO Auto-generated method stub + return null; + } + + @Override + public List searchForNetworks(ListNetworksCmd cmd) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean deleteNetwork(long networkId) { + // TODO Auto-generated method stub + return false; + } + + @Override + public int getActiveNicsInNetwork(long networkId) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Network getNetwork(long networkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public IpAddress getIp(long id) { + // TODO Auto-generated method stub + return null; + } + + @Override + public NetworkProfile convertNetworkToNetworkProfile(long networkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map> getNetworkCapabilities(long networkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isNetworkAvailableInDomain(long networkId, long domainId) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Long getDedicatedNetworkDomain(long networkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Integer getNetworkRate(long networkId, Long vmId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Network getSystemNetworkByZoneAndTrafficType(long zoneId, TrafficType trafficType) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + return true; + } + + @Override + public boolean start() { + return true; + } + + @Override + public boolean stop() { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getName() { + // TODO Auto-generated method stub + return null; + } + + + + @Override + public PublicIp assignSourceNatIpAddress(Account owner, Network network, long callerId) throws ConcurrentOperationException, InsufficientAddressCapacityException { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean releasePublicIpAddress(long id, long userId, Account caller) { + // TODO Auto-generated method stub + return false; + } + + @Override + public List listPublicIpAddressesInVirtualNetwork(long accountId, long dcId, Boolean sourceNat, Long associatedNetworkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List setupNetwork(Account owner, NetworkOfferingVO offering, DeploymentPlan plan, String name, String displayText, boolean isDefault) + throws ConcurrentOperationException { + // TODO Auto-generated method stub + return null; + } + + @Override + public List setupNetwork(Account owner, NetworkOfferingVO offering, Network predefined, DeploymentPlan plan, String name, String displayText, boolean errorIfAlreadySetup, Long domainId, + ACLType aclType, Boolean subdomainAccess) throws ConcurrentOperationException { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getSystemAccountNetworkOfferings(String... offeringNames) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void allocate(VirtualMachineProfile vm, List> networks) throws InsufficientCapacityException, ConcurrentOperationException { + // TODO Auto-generated method stub + + } + + @Override + public void prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationContext context) throws InsufficientCapacityException, ConcurrentOperationException, + ResourceUnavailableException { + // TODO Auto-generated method stub + + } + + @Override + public void release(VirtualMachineProfile vmProfile, boolean forced) { + // TODO Auto-generated method stub + + } + + @Override + public void cleanupNics(VirtualMachineProfile vm) { + // TODO Auto-generated method stub + + } + + @Override + public void expungeNics(VirtualMachineProfile vm) { + // TODO Auto-generated method stub + + } + + @Override + public List getNics(long vmId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getNicProfiles(VirtualMachine vm) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getNextAvailableMacAddressInNetwork(long networkConfigurationId) throws InsufficientAddressCapacityException { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean applyRules(List rules, boolean continueOnError) throws ResourceUnavailableException { + // TODO Auto-generated method stub + return false; + } + + @Override + public PublicIpAddress getPublicIpAddress(long ipAddressId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List listPodVlans(long podId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Pair implementNetwork(long networkId, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, + InsufficientCapacityException { + // TODO Auto-generated method stub + return null; + } + + @Override + public List listNetworksUsedByVm(long vmId, boolean isSystem) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void prepareNicForMigration(VirtualMachineProfile vm, DeployDestination dest) { + // TODO Auto-generated method stub + + } + + @Override + return false; + } + + @Override + public boolean destroyNetwork(long networkId, ReservationContext context) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, String networkDomain, Account owner, boolean isSecurityGroupEnabled, + Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess) throws ConcurrentOperationException, InsufficientCapacityException { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean associateIpAddressListToAccount(long userId, long accountId, long zoneId, Long vlanId, Network networkToAssociateWith) throws InsufficientCapacityException, + ConcurrentOperationException, ResourceUnavailableException { + // TODO Auto-generated method stub + return false; + } + + @Override + public Nic getNicInNetwork(long vmId, long networkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getNicsForTraffic(long vmId, TrafficType type) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Network getDefaultNetworkForVm(long vmId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Nic getDefaultNic(long vmId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean applyIpAssociations(Network network, boolean continueOnError) throws ResourceUnavailableException { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean areServicesSupportedByNetworkOffering(long networkOfferingId, Service... services) { + // TODO Auto-generated method stub + return false; + } + + @Override + public NetworkVO getNetworkWithSecurityGroupEnabled(Long zoneId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean startNetwork(long networkId, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getIpOfNetworkElementInVirtualNetwork(long accountId, long dataCenterId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public IPAddressVO markIpAsUnavailable(long addrId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String acquireGuestIpAddress(Network network, String requestedIp) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getGlobalGuestDomainSuffix() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getStartIpAddress(long networkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean applyStaticNats(List staticNats, boolean continueOnError) throws ResourceUnavailableException { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getIpInNetwork(long vmId, long networkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getIpInNetworkIncludingRemoved(long vmId, long networkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Network updateGuestNetwork(long networkId, String name, + String displayText, Account callerAccount, User callerUser, + String domainSuffix, Long networkOfferingId, Boolean changeCidr) { + // TODO Auto-generated method stub + return null; + } + public Map> getNetworkOfferingServiceProvidersMap(long networkOfferingId) { + return null; + } + + @Override + public List getRemoteAccessVpnElements() { + return null; + } + + @Override + public boolean isProviderSupportServiceInNetwork(long networkId, Service service, Provider provider) { + // TODO Auto-generated method stub + return false; + } + + @Override + public PhysicalNetwork createPhysicalNetwork(Long zoneId, String vnetRange, String networkSpeed, List isolationMethods, String broadcastDomainRange, Long domainId, List tags, String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List searchPhysicalNetworks(Long id, Long zoneId, String keyword, Long startIndex, Long pageSize, String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public PhysicalNetwork updatePhysicalNetwork(Long id, String networkSpeed, List tags, String newVnetRangeString, String state) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean deletePhysicalNetwork(Long id) { + // TODO Auto-generated method stub + return false; + } + + @Override + public List listNetworkServices(String providerName) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List listSupportedNetworkServiceProviders(String serviceName) { + // TODO Auto-generated method stub + return null; + } + + @Override + public PhysicalNetworkServiceProvider addProviderToPhysicalNetwork(Long physicalNetworkId, String providerName, Long destinationPhysicalNetworkId, List enabledServices) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List listNetworkServiceProviders(Long physicalNetworkId, String name, String state, Long startIndex, Long pageSize) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean deleteNetworkServiceProvider(Long id) { + // TODO Auto-generated method stub + return false; + } + + @Override + public PhysicalNetwork getPhysicalNetwork(Long physicalNetworkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public PhysicalNetwork getCreatedPhysicalNetwork(Long physicalNetworkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public PhysicalNetworkServiceProvider getPhysicalNetworkServiceProvider(Long providerId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public PhysicalNetworkServiceProvider getCreatedPhysicalNetworkServiceProvider(Long providerId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public long findPhysicalNetworkId(long zoneId, String tag, TrafficType trafficType) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public PhysicalNetworkTrafficType getPhysicalNetworkTrafficType(Long id) { + // TODO Auto-generated method stub + return null; + } + + @Override + public PhysicalNetworkTrafficType updatePhysicalNetworkTrafficType(Long id, String xenLabel, String kvmLabel, String vmwareLabel) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean deletePhysicalNetworkTrafficType(Long id) { + // TODO Auto-generated method stub + return false; + } + + @Override + public List listTrafficTypes(Long physicalNetworkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public PhysicalNetwork getDefaultPhysicalNetworkByZoneAndTrafficType(long zoneId, TrafficType trafficType) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Network getExclusiveGuestNetwork(long zoneId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Long getPodIdForVlan(long vlanDbId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean networkIsConfiguredForExternalNetworking(long zoneId, long networkId) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Map getNetworkServiceCapabilities(long networkId, Service service) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List listNetworksForAccount(long accountId, long zoneId, GuestType type) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List listNetworkOfferingsForUpgrade(long networkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public PhysicalNetwork translateZoneIdToPhysicalNetwork(long zoneId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isSecurityGroupSupportedInNetwork(Network network) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isProviderEnabledInPhysicalNetwork(long physicalNetowrkId, String providerName) { + // TODO Auto-generated method stub + return false; + } + + @Override + public List getElementServices(Provider provider) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean canElementEnableIndividualServices(Provider provider) { + // TODO Auto-generated method stub + return false; + } + + @Override + public List getPasswordResetElements() { + // TODO Auto-generated method stub + return null; + } + + @Override + public PhysicalNetworkServiceProvider updateNetworkServiceProvider(Long id, String state, List enabledServices) { + // TODO Auto-generated method stub + return null; + } + + @Override + public PhysicalNetworkTrafficType addTrafficTypeToPhysicalNetwork(Long physicalNetworkId, String trafficType, String xenLabel, String kvmLabel, String vmwareLabel, String simulatorLabel, String vlan) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean areServicesSupportedInNetwork(long networkId, Service... services) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isNetworkSystem(Network network) { + // TODO Auto-generated method stub + return false; + } + + @Override + public PhysicalNetworkServiceProvider addDefaultVirtualRouterToPhysicalNetwork(long physicalNetworkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map getNetworkOfferingServiceCapabilities(NetworkOffering offering, Service service) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean reallocate(VirtualMachineProfile vm, DataCenterDeployment dest) throws InsufficientCapacityException, ConcurrentOperationException { + // TODO Auto-generated method stub + return false; + } + + @Override + public Long getPhysicalNetworkId(Network network) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean getAllowSubdomainAccessGlobal() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isProviderForNetwork(Provider provider, long networkId) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean restartNetwork(RestartNetworkCmd cmd, boolean cleanup) + throws ConcurrentOperationException, ResourceUnavailableException, + InsufficientCapacityException { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getNetworkTag(HypervisorType hType, Network network) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void canProviderSupportServices( + Map> providersMap) { + // TODO Auto-generated method stub + + } + + @Override + public boolean isProviderForNetworkOffering(Provider provider, long networkOfferingId) { + // TODO Auto-generated method stub + return false; + } + + @Override + public PhysicalNetworkServiceProvider addDefaultSecurityGroupProviderToPhysicalNetwork(long physicalNetworkId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getPhysicalNetworkInfo(long dcId, HypervisorType hypervisorType) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean canAddDefaultSecurityGroup() { + // TODO Auto-generated method stub + return false; + } + + @Override + public List> listTrafficTypeImplementor(ListTrafficTypeImplementorsCmd cmd) { + // TODO Auto-generated method stub + return null; + } + + @Override + public List listNetworkOfferingServices(long networkOfferingId) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean areServicesEnabledInZone(long zoneId, NetworkOffering offering, List services) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Map> getIpToServices(List publicIps, boolean rulesRevoked, boolean includingFirewall) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Map> getProviderToIpList(Network network, Map> ipToServices) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean checkIpForService(IPAddressVO ip, Service service) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void checkVirtualNetworkCidrOverlap(Long zoneId, String cidr) { + // TODO Auto-generated method stub + + } + + @Override + public IpAddress allocateIP(long networkId, Account ipOwner, + boolean isSystem) throws ResourceAllocationException, + InsufficientAddressCapacityException, ConcurrentOperationException { + // TODO Auto-generated method stub + return null; + } + + @Override + public PublicIp assignPublicIpAddress(long dcId, Long podId, Account owner, + VlanType type, Long networkId, String requestedIp, boolean isSystem) + throws InsufficientAddressCapacityException { + // TODO Auto-generated method stub + return null; + } + + @Override + public void checkCapabilityForProvider(Set providers, + Service service, Capability cap, String capValue) { + // TODO Auto-generated method stub + + } + + @Override + public Provider getDefaultUniqueProviderForService(String serviceName) { + // TODO Auto-generated method stub + return null; + } + + @Override + public IpAddress assignSystemIp(long networkId, Account owner, + boolean forElasticLb, boolean forElasticIp) + throws InsufficientAddressCapacityException { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean handleSystemIpRelease(IpAddress ip) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void checkNetworkPermissions(Account owner, Network network) { + // TODO Auto-generated method stub + + } + + @Override + public void allocateDirectIp(NicProfile nic, DataCenter dc, + VirtualMachineProfile vm, + Network network, String requestedIp) + throws InsufficientVirtualNetworkCapcityException, + InsufficientAddressCapacityException { + // TODO Auto-generated method stub + + } + + @Override + public boolean validateRule(FirewallRule rule) { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getDefaultManagementTrafficLabel(long zoneId, HypervisorType hypervisorType) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getDefaultStorageTrafficLabel(long zoneId, HypervisorType hypervisorType) { + // TODO Auto-generated method stub + return null; + } + @Override + public Network updateGuestNetwork(long networkId, String name, String displayText, Account callerAccount, User callerUser, String domainSuffix, Long networkOfferingId, Boolean changeCidr) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean shutdownNetwork(long networkId, ReservationContext context, boolean cleanupElements) { + // TODO Auto-generated method stub + return false; + } + +}