| /******************************************************************************* |
| * Copyright (c) 2011 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| |
| |
| #include "stdafx.h" |
| #include "CrossfireServer.h" |
| |
| /* initialize statics */ |
| const wchar_t* CrossfireServer::CONTEXTID_PREAMBLE = L"xfIE::"; |
| const wchar_t* CrossfireServer::HANDSHAKE = L"CrossfireHandshake\r\n"; |
| const wchar_t* CrossfireServer::HEADER_CONTENTLENGTH = L"Content-Length:"; |
| const wchar_t* CrossfireServer::LINEBREAK = L"\r\n"; |
| const size_t CrossfireServer::LINEBREAK_LENGTH = 2; |
| |
| const wchar_t* CrossfireServer::COMMAND_CHANGEBREAKPOINT = L"changebreakpoint"; |
| const wchar_t* CrossfireServer::COMMAND_CLEARBREAKPOINT = L"clearbreakpoint"; |
| const wchar_t* CrossfireServer::COMMAND_GETBREAKPOINT = L"getbreakpoint"; |
| const wchar_t* CrossfireServer::COMMAND_GETBREAKPOINTS = L"getbreakpoints"; |
| const wchar_t* CrossfireServer::COMMAND_SETBREAKPOINT = L"setbreakpoint"; |
| |
| /* command: listContexts */ |
| const wchar_t* CrossfireServer::COMMAND_LISTCONTEXTS = L"listcontexts"; |
| const wchar_t* CrossfireServer::KEY_CONTEXTS = L"contexts"; |
| const wchar_t* CrossfireServer::KEY_CURRENT = L"current"; |
| |
| /* command: version */ |
| const wchar_t* CrossfireServer::COMMAND_VERSION = L"version"; |
| const wchar_t* CrossfireServer::KEY_VERSION = L"version"; |
| const wchar_t* CrossfireServer::VERSION_STRING = L"0.3"; |
| |
| /* event: closed */ |
| const wchar_t* CrossfireServer::EVENT_CLOSED = L"closed"; |
| |
| /* event: onContextChanged */ |
| const wchar_t* CrossfireServer::EVENT_CONTEXTCHANGED = L"onContextChanged"; |
| const wchar_t* CrossfireServer::KEY_NEWHREF = L"new-href"; |
| |
| /* event: onContextCreated */ |
| const wchar_t* CrossfireServer::EVENT_CONTEXTCREATED = L"onContextCreated"; |
| |
| /* event: onContextDestroyed */ |
| const wchar_t* CrossfireServer::EVENT_CONTEXTDESTROYED = L"onContextDestroyed"; |
| |
| /* event: onContextLoaded */ |
| const wchar_t* CrossfireServer::EVENT_CONTEXTLOADED = L"onContextLoaded"; |
| |
| /* shared */ |
| const wchar_t* CrossfireServer::KEY_HREF = L"href"; |
| |
| |
| CrossfireServer::CrossfireServer() { |
| m_bpManager = new CrossfireBPManager(); |
| m_connection = NULL; |
| m_contexts = new std::map<DWORD, CrossfireContext*>; |
| m_currentContext = NULL; |
| m_handshakeReceived = false; |
| m_inProgressPacket = new std::wstring; |
| m_lastRequestSeq = -1; |
| m_pendingEvents = new std::vector<CrossfireEvent*>; |
| m_port = -1; |
| m_processingRequest = false; |
| m_processor = new CrossfireProcessor(); |
| m_windowHandle = 0; |
| } |
| |
| CrossfireServer::~CrossfireServer() { |
| delete m_bpManager; |
| |
| std::map<DWORD,CrossfireContext*>::iterator iterator = m_contexts->begin(); |
| while (iterator != m_contexts->end()) { |
| delete iterator->second; |
| iterator++; |
| } |
| delete m_contexts; |
| |
| std::vector<CrossfireEvent*>::iterator iterator2 = m_pendingEvents->begin(); |
| while (iterator2 != m_pendingEvents->end()) { |
| if (m_connection) { |
| sendEvent(*iterator2); |
| } |
| delete *iterator2; |
| iterator2++; |
| } |
| delete m_pendingEvents; |
| |
| delete m_inProgressPacket; |
| delete m_processor; |
| if (m_connection) { |
| delete m_connection; |
| } |
| |
| if (m_windowHandle) { |
| CComPtr<ICrossfireServerClass> serverClass = NULL; |
| HRESULT hr = CoGetClassObject(CLSID_CrossfireServer, CLSCTX_ALL, 0, IID_ICrossfireServerClass, (LPVOID*)&serverClass); |
| if (FAILED(hr)) { |
| Logger::error("~CrossfireServer: CoGetClassObject() failed", hr); |
| return; |
| } |
| hr = serverClass->RemoveServer(m_windowHandle); |
| if (FAILED(hr)) { |
| Logger::error("~CrossfireServer: RemoveServer() failed", hr); |
| return; |
| } |
| } |
| } |
| |
| HRESULT STDMETHODCALLTYPE CrossfireServer::contextCreated(DWORD threadId, OLECHAR* href) { |
| if (m_port == -1) { |
| return S_FALSE; |
| } |
| |
| HRESULT hr = registerContext(threadId, href); |
| if (SUCCEEDED(hr)) { |
| std::map<DWORD, CrossfireContext*>::iterator iterator = m_contexts->find(threadId); |
| CrossfireContext* context = iterator->second; |
| eventContextCreated(context); |
| } |
| return hr; |
| } |
| |
| HRESULT STDMETHODCALLTYPE CrossfireServer::contextDestroyed(DWORD threadId) { |
| if (m_port == -1) { |
| return S_FALSE; |
| } |
| |
| std::map<DWORD, CrossfireContext*>::iterator iterator = m_contexts->find(threadId); |
| if (iterator == m_contexts->end()) { |
| Logger::error("CrossfireServer.contextDestroyed(): unknown context", threadId); |
| return S_FALSE; |
| } |
| CrossfireContext* context = iterator->second; |
| eventContextDestroyed(context); |
| m_contexts->erase(iterator); |
| delete context; |
| return S_OK; |
| } |
| |
| HRESULT STDMETHODCALLTYPE CrossfireServer::contextLoaded(DWORD threadId) { |
| if (m_port == -1) { |
| return S_FALSE; |
| } |
| |
| std::map<DWORD, CrossfireContext*>::iterator iterator = m_contexts->find(threadId); |
| if (iterator == m_contexts->end()) { |
| Logger::error("CrossfireServer.contextLoaded(): unknown context", threadId); |
| return S_FALSE; |
| } |
| CrossfireContext* context = iterator->second; |
| eventContextLoaded(context); |
| return S_OK; |
| } |
| |
| void CrossfireServer::disconnected() { |
| reset(); |
| } |
| |
| CrossfireBPManager* CrossfireServer::getBreakpointManager() { |
| return m_bpManager; |
| } |
| |
| CrossfireContext* CrossfireServer::getRequestContext(CrossfireRequest* request) { |
| std::wstring* contextId = request->getContextId(); |
| if (!contextId) { |
| return NULL; |
| } |
| |
| wchar_t* searchString = (wchar_t*)contextId->c_str(); |
| std::map<DWORD,CrossfireContext*>::iterator iterator = m_contexts->begin(); |
| while (iterator != m_contexts->end()) { |
| if (wcscmp(iterator->second->getName(), searchString) == 0) { |
| return iterator->second; |
| } |
| iterator++; |
| } |
| return NULL; |
| } |
| |
| HRESULT STDMETHODCALLTYPE CrossfireServer::isActive(boolean* value) { |
| *value = m_port != -1; |
| return S_OK; |
| } |
| |
| bool CrossfireServer::performRequest(CrossfireRequest* request) { |
| wchar_t* command = request->getName(); |
| Value* arguments = request->getArguments(); |
| Value* responseBody = NULL; |
| bool success = false; |
| if (wcscmp(command, COMMAND_VERSION) == 0) { |
| success = commandVersion(arguments, &responseBody); |
| } else if (wcscmp(command, COMMAND_CHANGEBREAKPOINT) == 0) { |
| CrossfireContext* context = getRequestContext(request); |
| IBreakpointTarget* target = context ? (IBreakpointTarget*)context : (IBreakpointTarget*)m_bpManager; |
| success = m_bpManager->commandChangeBreakpoint(arguments, target, &responseBody); |
| } else if (wcscmp(command, COMMAND_CLEARBREAKPOINT) == 0) { |
| CrossfireContext* context = getRequestContext(request); |
| IBreakpointTarget* target = context ? (IBreakpointTarget*)context : (IBreakpointTarget*)m_bpManager; |
| success = m_bpManager->commandClearBreakpoint(arguments, target, &responseBody); |
| } else if (wcscmp(command, COMMAND_LISTCONTEXTS) == 0) { |
| success = commandListContexts(arguments, &responseBody); |
| } else if (wcscmp(command, COMMAND_GETBREAKPOINT) == 0) { |
| CrossfireContext* context = getRequestContext(request); |
| IBreakpointTarget* target = context ? (IBreakpointTarget*)context : (IBreakpointTarget*)m_bpManager; |
| success = m_bpManager->commandGetBreakpoint(arguments, target, &responseBody); |
| } else if (wcscmp(command, COMMAND_GETBREAKPOINTS) == 0) { |
| CrossfireContext* context = getRequestContext(request); |
| IBreakpointTarget* target = context ? (IBreakpointTarget*)context : (IBreakpointTarget*)m_bpManager; |
| success = m_bpManager->commandGetBreakpoints(arguments, target, &responseBody); |
| } else if (wcscmp(command, COMMAND_SETBREAKPOINT) == 0) { |
| CrossfireContext* context = getRequestContext(request); |
| IBreakpointTarget* target = context ? (IBreakpointTarget*)context : (IBreakpointTarget*)m_bpManager; |
| success = m_bpManager->commandSetBreakpoint(arguments, target, &responseBody); |
| } else { |
| return false; /* command not handled */ |
| } |
| |
| CrossfireResponse response; |
| response.setName(command); |
| response.setRequestSeq(request->getSeq()); |
| response.setRunning(true); |
| response.setSuccess(success); |
| if (success) { |
| response.setBody(responseBody); |
| } else { |
| Value emptyBody; |
| emptyBody.setType(TYPE_OBJECT); |
| response.setBody(&emptyBody); |
| } |
| if (responseBody) { |
| delete responseBody; |
| } |
| sendResponse(&response); |
| return true; |
| } |
| |
| void CrossfireServer::received(wchar_t* msg) { |
| if (!m_handshakeReceived) { |
| if (wcscmp(msg, HANDSHAKE) == 0) { |
| m_handshakeReceived = true; |
| m_connection->send(HANDSHAKE); |
| } else { |
| Logger::error("Crossfire content received before handshake, not processing it"); |
| } |
| return; |
| } |
| |
| m_inProgressPacket->append(std::wstring(msg)); |
| std::wstring packet; |
| do { |
| packet.clear(); |
| if (m_inProgressPacket->find(HEADER_CONTENTLENGTH) != 0) { |
| Logger::error("request packet does not start with 'Content-Length:', not processing it"); |
| Logger::log(m_inProgressPacket); |
| m_inProgressPacket->clear(); |
| break; |
| } |
| |
| size_t endIndex = m_inProgressPacket->find(wchar_t('\r')); |
| if (endIndex == std::wstring::npos) { |
| Logger::error("request packet does not contain '\r', not processing it"); |
| Logger::log(m_inProgressPacket); |
| m_inProgressPacket->clear(); |
| break; |
| } |
| |
| size_t headerLength = wcslen(HEADER_CONTENTLENGTH); |
| std::wstring lengthString = m_inProgressPacket->substr(headerLength, endIndex - headerLength); |
| int lengthValue = _wtoi(lengthString.c_str()); |
| if (!lengthValue) { |
| Logger::error("request packet does not have a valid 'Content-Length' value, not processing it"); |
| Logger::log(m_inProgressPacket); |
| m_inProgressPacket->clear(); |
| break; |
| } |
| |
| if (m_inProgressPacket->find(L"\r\n", endIndex) != endIndex) { |
| Logger::error("request packet does not follow initial '\\r' with '\\n', not processing it"); |
| Logger::log(m_inProgressPacket); |
| m_inProgressPacket->clear(); |
| break; |
| } |
| |
| // TODO for now just skip over "tool:" lines, though these should really be validated |
| |
| size_t toolEnd = m_inProgressPacket->find(L"\r\n\r\n", endIndex); |
| if (toolEnd == std::wstring::npos) { |
| Logger::error("request packet does not contain '\\r\\n\\r\\n' to delimit its header, not processing it"); |
| Logger::log(m_inProgressPacket); |
| m_inProgressPacket->clear(); |
| break; |
| } |
| size_t toolsLength = toolEnd - endIndex; |
| size_t targetLength = wcslen(HEADER_CONTENTLENGTH) + lengthString.length() + 3 * LINEBREAK_LENGTH + toolsLength + lengthValue; |
| |
| if (targetLength <= m_inProgressPacket->length()) { |
| packet.assign(m_inProgressPacket->substr(0, targetLength)); |
| m_inProgressPacket->erase(0, targetLength); |
| } |
| |
| if (packet.length()) { |
| CrossfireRequest* request = NULL; |
| if (!m_processor->parseRequestPacket(&packet, &request)) { |
| Logger::error("invalid request packet received, not processing it"); |
| Logger::log(&packet); |
| } else { |
| unsigned int seq = request->getSeq(); |
| if (seq <= m_lastRequestSeq) { |
| // TODO handle out-of-order packets |
| Logger::log("packet received out of sequence, still processing it"); |
| } |
| m_lastRequestSeq = seq; |
| m_processingRequest = true; |
| if (!performRequest(request)) { |
| /* |
| * the request's command was not handled by the server, |
| * so try to delegate to the specified context, if any |
| */ |
| CrossfireContext* context = getRequestContext(request); |
| if (!context) { |
| Logger::error("request command was unknown to the server and a valid context id was not provided, not processing it"); |
| Logger::log(&packet); |
| } else { |
| if (!context->performRequest(request)) { |
| Logger::error("request command was unknown to the server and to the specified context, not processing it"); |
| Logger::log(&packet); |
| } |
| } |
| } |
| m_processingRequest = false; |
| delete request; |
| |
| /* |
| * Debugger events may have been received in response to the request that was |
| * just processed. These events can be sent now that processing of the request |
| * is complete. |
| */ |
| if (m_pendingEvents->size() > 0) { |
| // GWG HACK! helps JSDT with stepping |
| Logger::error("start sleep"); |
| Sleep(500); |
| Logger::error("end sleep"); |
| |
| std::vector<CrossfireEvent*>::iterator iterator = m_pendingEvents->begin(); |
| while (iterator != m_pendingEvents->end()) { |
| sendEvent(*iterator); |
| delete *iterator; |
| iterator++; |
| } |
| m_pendingEvents->clear(); |
| } |
| } |
| } |
| } while (packet.length() > 0 && m_inProgressPacket->length() > 0); |
| } |
| |
| HRESULT STDMETHODCALLTYPE CrossfireServer::registerContext(DWORD threadId, OLECHAR* href) { |
| if (m_port == -1) { |
| return S_FALSE; |
| } |
| |
| static int s_contextCounter = 0; |
| |
| CrossfireContext* context = NULL; |
| std::map<DWORD, CrossfireContext*>::iterator iterator = m_contexts->find(threadId); |
| if (iterator == m_contexts->end()) { |
| context = new CrossfireContext(threadId, this); |
| m_contexts->insert(std::pair<DWORD,CrossfireContext*>(threadId, context)); |
| } else { |
| Logger::error("CrossfireServer.registerContext(): a context already exists for thread", threadId); |
| return S_FALSE; |
| } |
| |
| std::wstringstream stream; |
| stream << CONTEXTID_PREAMBLE; |
| stream << threadId; |
| stream << "-"; |
| stream << s_contextCounter++; |
| context->setName((wchar_t*)stream.str().c_str()); |
| context->setHref(href); |
| return S_OK; |
| } |
| |
| void CrossfireServer::reset() { |
| m_connection->close(); |
| delete m_connection; |
| m_connection = NULL; |
| |
| std::map<DWORD,CrossfireContext*>::iterator iterator = m_contexts->begin(); |
| while (iterator != m_contexts->end()) { |
| delete iterator->second; |
| iterator++; |
| } |
| m_contexts->clear(); |
| m_currentContext = NULL; |
| m_handshakeReceived = false; |
| m_inProgressPacket->clear(); |
| m_lastRequestSeq = -1; |
| m_port = -1; |
| } |
| |
| void CrossfireServer::sendEvent(CrossfireEvent* eventObj) { |
| /* |
| * If a request is being processed then events to be sent to |
| * the client should be queued and sent after the response to |
| * the request has been sent first. |
| */ |
| if (m_processingRequest) { |
| CrossfireEvent* copy = NULL; |
| eventObj->clone((CrossfirePacket**)©); |
| m_pendingEvents->push_back(copy); |
| return; |
| } |
| |
| std::wstring* string = NULL; |
| if (!m_processor->createEventPacket(eventObj, &string)) { |
| Logger::error("CrossfireServer.sendEvent(): Invalid event packet, not sending it"); |
| return; |
| } |
| |
| m_connection->send(string->c_str()); |
| delete string; |
| } |
| |
| void CrossfireServer::sendResponse(CrossfireResponse* response) { |
| std::wstring* string = NULL; |
| if (!m_processor->createResponsePacket(response, &string)) { |
| Logger::error("CrossfireServer.sendResponse(): Invalid response packet, not sending it"); |
| return; |
| } |
| m_connection->send(string->c_str()); |
| delete string; |
| } |
| |
| HRESULT STDMETHODCALLTYPE CrossfireServer::setCurrentContext(DWORD threadId) { |
| if (m_port == -1) { |
| return S_FALSE; |
| } |
| |
| std::map<DWORD, CrossfireContext*>::iterator iterator = m_contexts->find(threadId); |
| if (iterator == m_contexts->end()) { |
| Logger::error("CrossfireServer.setCurrentContext(): unknown context", threadId); |
| return S_FALSE; |
| } |
| CrossfireContext* context = iterator->second; |
| CrossfireContext* oldContext = m_currentContext; |
| m_currentContext = context; |
| eventContextChanged(context, oldContext); |
| return S_OK; |
| } |
| |
| void CrossfireServer::setWindowHandle(unsigned long value) { |
| m_windowHandle = value; |
| } |
| |
| HRESULT STDMETHODCALLTYPE CrossfireServer::start(unsigned int port) { |
| if (m_port != -1) { |
| return S_FALSE; |
| } |
| |
| m_connection = new WindowsSocketConnection(this); |
| if (!m_connection->init(port)) { |
| delete m_connection; |
| return S_FALSE; |
| } |
| m_port = port; |
| m_connection->acceptConnection(); |
| return S_OK; |
| } |
| |
| HRESULT STDMETHODCALLTYPE CrossfireServer::stop() { |
| if (m_port == -1) { |
| return S_FALSE; |
| } |
| |
| eventClosed(); |
| reset(); |
| return S_OK; |
| } |
| |
| /* commands */ |
| |
| bool CrossfireServer::commandListContexts(Value* arguments, Value** _responseBody) { |
| Value contexts; |
| contexts.setType(TYPE_ARRAY); |
| std::map<DWORD,CrossfireContext*>::iterator iterator = m_contexts->begin(); |
| while (iterator != m_contexts->end()) { |
| CrossfireContext* context = iterator->second; |
| Value value_context; |
| value_context.addObjectValue(/*KEY_CONTEXTID*/L"context_id", &Value(context->getName())); |
| value_context.addObjectValue(KEY_HREF, &Value(context->getHref())); |
| value_context.addObjectValue(KEY_CURRENT, &Value((bool)(context == m_currentContext))); |
| contexts.addArrayValue(&value_context); |
| iterator++; |
| } |
| |
| Value* result = new Value(); |
| result->addObjectValue(KEY_CONTEXTS, &contexts); |
| *_responseBody = result; |
| return true; |
| } |
| |
| bool CrossfireServer::commandVersion(Value* arguments, Value** _responseBody) { |
| Value* result = new Value(); |
| result->addObjectValue(KEY_VERSION, &Value(VERSION_STRING)); |
| *_responseBody = result; |
| return true; |
| } |
| |
| /* events */ |
| |
| void CrossfireServer::eventClosed() { |
| CrossfireEvent eventObj; |
| eventObj.setName(EVENT_CLOSED); |
| sendEvent(&eventObj); |
| } |
| |
| void CrossfireServer::eventContextChanged(CrossfireContext* newContext, CrossfireContext* oldContext) { |
| CrossfireEvent eventObj; |
| eventObj.setName(EVENT_CONTEXTCHANGED); |
| eventObj.setContextId(&std::wstring(newContext->getName())); |
| Value data; |
| data.addObjectValue(KEY_NEWHREF, &Value(&std::wstring(newContext->getHref()))); |
| if (oldContext) { |
| data.addObjectValue(KEY_HREF, &Value(&std::wstring(oldContext->getHref()))); |
| } |
| eventObj.setData(&data); |
| sendEvent(&eventObj); |
| } |
| |
| void CrossfireServer::eventContextCreated(CrossfireContext* context) { |
| CrossfireEvent eventObj; |
| eventObj.setName(EVENT_CONTEXTCREATED); |
| eventObj.setContextId(&std::wstring(context->getName())); |
| Value data; |
| data.addObjectValue(KEY_HREF, &Value(&std::wstring(context->getHref()))); |
| eventObj.setData(&data); |
| sendEvent(&eventObj); |
| } |
| |
| void CrossfireServer::eventContextDestroyed(CrossfireContext* context) { |
| CrossfireEvent eventObj; |
| eventObj.setName(EVENT_CONTEXTDESTROYED); |
| eventObj.setContextId(&std::wstring(context->getName())); |
| sendEvent(&eventObj); |
| } |
| |
| void CrossfireServer::eventContextLoaded(CrossfireContext* context) { |
| CrossfireEvent eventObj; |
| eventObj.setName(EVENT_CONTEXTLOADED); |
| eventObj.setContextId(&std::wstring(context->getName())); |
| Value data; |
| data.addObjectValue(KEY_HREF, &Value(&std::wstring(context->getHref()))); |
| eventObj.setData(&data); |
| sendEvent(&eventObj); |
| } |