diff --git a/gfx/layers/LayerScope.cpp b/gfx/layers/LayerScope.cpp index 05415ea568b..d5c6b46529c 100644 --- a/gfx/layers/LayerScope.cpp +++ b/gfx/layers/LayerScope.cpp @@ -10,6 +10,7 @@ #include "Effects.h" #include "mozilla/TimeStamp.h" #include "mozilla/Preferences.h" +#include "mozilla/Endian.h" #include "TexturePoolOGL.h" #include "mozilla/layers/TextureHostOGL.h" @@ -29,11 +30,16 @@ #include #include "mozilla/Compression.h" #include "mozilla/LinkedList.h" +#include "mozilla/Base64.h" +#include "mozilla/SHA1.h" +#include "mozilla/StaticPtr.h" #include "nsThreadUtils.h" #include "nsISocketTransport.h" #include "nsIServerSocket.h" +#include "nsReadLine.h" #include "nsNetCID.h" #include "nsIOutputStream.h" +#include "nsIAsyncInputStream.h" #include "nsIEventTarget.h" #include "nsProxyRelease.h" @@ -52,13 +58,267 @@ using namespace mozilla::gl; using namespace mozilla; class DebugDataSender; +class DebugGLData; -static bool gDebugConnected = false; -static nsCOMPtr gDebugServerSocket; -static nsCOMPtr gDebugSenderThread; -static nsCOMPtr gDebugSenderTransport; -static nsCOMPtr gDebugStream; -static nsCOMPtr gCurrentSender; +/* This class handle websocket protocol which included + * handshake and data frame's header + */ +class LayerScopeWebSocketHandler : public nsIInputStreamCallback { +public: + NS_DECL_THREADSAFE_ISUPPORTS + + enum SocketStateType { + NoHandshake, + HandshakeSuccess, + HandshakeFailed + }; + + LayerScopeWebSocketHandler() + : mState(NoHandshake) + { } + + virtual ~LayerScopeWebSocketHandler() + { + if (mTransport) { + mTransport->Close(NS_OK); + } + } + + void OpenStream(nsISocketTransport* aTransport) { + MOZ_ASSERT(aTransport); + + mTransport = aTransport; + mTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING, + 0, + 0, + getter_AddRefs(mOutputStream)); + + nsCOMPtr debugInputStream; + mTransport->OpenInputStream(0, + 0, + 0, + getter_AddRefs(debugInputStream)); + mInputStream = do_QueryInterface(debugInputStream); + mInputStream->AsyncWait(this, 0, 0, NS_GetCurrentThread()); + } + + bool WriteToStream(void *ptr, uint32_t size) { + if (mState == NoHandshake) { + // Not yet handshake, just return true in case of + // LayerScope remove this handle + return true; + } else if (mState == HandshakeFailed) { + return false; + } + + // Generate WebSocket header + uint8_t wsHeader[10]; + int wsHeaderSize = 0; + const uint8_t opcode = 0x2; + wsHeader[0] = 0x80 | (opcode & 0x0f); // FIN + opcode; + if (size <= 125) { + wsHeaderSize = 2; + wsHeader[1] = size; + } else if (size < 65536) { + wsHeaderSize = 4; + wsHeader[1] = 0x7E; + NetworkEndian::writeUint16(wsHeader + 2, size); + } else { + wsHeaderSize = 10; + wsHeader[1] = 0x7F; + NetworkEndian::writeUint64(wsHeader + 2, size); + } + + // Send WebSocket header + nsresult rv; + uint32_t cnt; + rv = mOutputStream->Write(reinterpret_cast(wsHeader), + wsHeaderSize, &cnt); + if (NS_FAILED(rv)) + return false; + + uint32_t written = 0; + while (written < size) { + uint32_t cnt; + rv = mOutputStream->Write(reinterpret_cast(ptr) + written, + size - written, &cnt); + if (NS_FAILED(rv)) + return false; + + written += cnt; + } + + return true; + } + + // nsIInputStreamCallback + NS_IMETHODIMP OnInputStreamReady(nsIAsyncInputStream *stream) MOZ_OVERRIDE + { + nsTArray protocolString; + ReadInputStreamData(protocolString); + + if (WebSocketHandshake(protocolString)) { + mState = HandshakeSuccess; + } else { + mState = HandshakeFailed; + } + return NS_OK; + } +private: + void ReadInputStreamData(nsTArray& aProtocolString) + { + nsLineBuffer lineBuffer; + nsCString line; + bool more = true; + do { + NS_ReadLine(mInputStream.get(), &lineBuffer, line, &more); + + if (line.Length() > 0) { + aProtocolString.AppendElement(line); + } + } while (more && line.Length() > 0); + } + + bool WebSocketHandshake(nsTArray& aProtocolString) + { + nsresult rv; + bool isWebSocket = false; + nsCString version; + nsCString wsKey; + nsCString protocol; + + // Validate WebSocket client request. + if (aProtocolString.Length() == 0) + return false; + + // Check that the HTTP method is GET + const char* HTTP_METHOD = "GET "; + if (strncmp(aProtocolString[0].get(), HTTP_METHOD, strlen(HTTP_METHOD)) != 0) { + return false; + } + + for (uint32_t i = 1; i < aProtocolString.Length(); ++i) { + const char* line = aProtocolString[i].get(); + const char* prop_pos = strchr(line, ':'); + if (prop_pos != nullptr) { + nsCString key(line, prop_pos - line); + nsCString value(prop_pos + 2); + if (key.EqualsIgnoreCase("upgrade") && + value.EqualsIgnoreCase("websocket")) { + isWebSocket = true; + } else if (key.EqualsIgnoreCase("sec-websocket-version")) { + version = value; + } else if (key.EqualsIgnoreCase("sec-websocket-key")) { + wsKey = value; + } else if (key.EqualsIgnoreCase("sec-websocket-protocol")) { + protocol = value; + } + } + } + + if (!isWebSocket) { + return false; + } + + if (!(version.Equals("7") || version.Equals("8") || version.Equals("13"))) { + return false; + } + + if (!(protocol.EqualsIgnoreCase("binary"))) { + return false; + } + + // Client request is valid. Start to generate and send server response. + nsAutoCString guid("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + nsAutoCString res; + SHA1Sum sha1; + nsCString combined(wsKey + guid); + sha1.update(combined.get(), combined.Length()); + uint8_t digest[SHA1Sum::HashSize]; // SHA1 digests are 20 bytes long. + sha1.finish(digest); + nsCString newString(reinterpret_cast(digest), SHA1Sum::HashSize); + Base64Encode(newString, res); + + nsCString response("HTTP/1.1 101 Switching Protocols\r\n"); + response.Append("Upgrade: websocket\r\n"); + response.Append("Connection: Upgrade\r\n"); + response.Append(nsCString("Sec-WebSocket-Accept: ") + res + nsCString("\r\n")); + response.Append("Sec-WebSocket-Protocol: binary\r\n\r\n"); + uint32_t written = 0; + uint32_t size = response.Length(); + while (written < size) { + uint32_t cnt; + rv = mOutputStream->Write(const_cast(response.get()) + written, + size - written, &cnt); + if (NS_FAILED(rv)) + return false; + + written += cnt; + } + mOutputStream->Flush(); + + return true; + } + + nsCOMPtr mOutputStream; + nsCOMPtr mInputStream; + nsCOMPtr mTransport; + SocketStateType mState; +}; + +NS_IMPL_ISUPPORTS1(LayerScopeWebSocketHandler, nsIInputStreamCallback); + +class LayerScopeWebSocketManager { +public: + LayerScopeWebSocketManager(); + ~LayerScopeWebSocketManager(); + + void AddConnection(nsISocketTransport *aTransport) + { + MOZ_ASSERT(aTransport); + nsRefPtr temp = new LayerScopeWebSocketHandler(); + temp->OpenStream(aTransport); + mHandlers.AppendElement(temp.get()); + } + + void RemoveConnection(uint32_t aIndex) + { + MOZ_ASSERT(aIndex < mHandlers.Length()); + mHandlers.RemoveElementAt(aIndex); + } + + void RemoveAllConnections() + { + mHandlers.Clear(); + } + + bool WriteAll(void *ptr, uint32_t size) + { + for (int32_t i = mHandlers.Length() - 1; i >= 0; --i) { + if (!mHandlers[i]->WriteToStream(ptr, size)) { + // Send failed, remove this handler + RemoveConnection(i); + } + } + + return true; + } + + bool IsConnected() + { + return (mHandlers.Length() != 0) ? true : false; + } + + void AppendDebugData(DebugGLData *aDebugData); + void DispatchDebugData(); +private: + nsTArray > mHandlers; + nsCOMPtr mDebugSenderThread; + nsCOMPtr mCurrentSender; + nsCOMPtr mServerSocket; +}; + +static StaticAutoPtr gLayerScopeWebSocketManager; class DebugGLData : public LinkedListElement { public: @@ -111,19 +371,9 @@ public: } static bool WriteToStream(void *ptr, uint32_t size) { - uint32_t written = 0; - nsresult rv; - while (written < size) { - uint32_t cnt; - rv = gDebugStream->Write(reinterpret_cast(ptr) + written, - size - written, &cnt); - if (NS_FAILED(rv)) - return false; - - written += cnt; - } - - return true; + if (!gLayerScopeWebSocketManager) + return true; + return gLayerScopeWebSocketManager->WriteAll(ptr, size); } protected: @@ -284,23 +534,15 @@ protected: static bool CheckSender() { - if (!gDebugConnected) + if (!gLayerScopeWebSocketManager) return false; - // At some point we may want to support sending - // data in between frames. -#if 1 - if (!gCurrentSender) + if (!gLayerScopeWebSocketManager->IsConnected()) return false; -#else - if (!gCurrentSender) - gCurrentSender = new DebugDataSender(); -#endif return true; } - class DebugListener : public nsIServerSocketListener { public: @@ -315,10 +557,11 @@ public: NS_IMETHODIMP OnSocketAccepted(nsIServerSocket *aServ, nsISocketTransport *aTransport) { + if (!gLayerScopeWebSocketManager) + return NS_OK; + printf_stderr("*** LayerScope: Accepted connection\n"); - gDebugConnected = true; - gDebugSenderTransport = aTransport; - aTransport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(gDebugStream)); + gLayerScopeWebSocketManager->AddConnection(aTransport); return NS_OK; } @@ -368,13 +611,6 @@ public: DebugGLData *d; nsresult rv = NS_OK; - // If we got closed while trying to write some stuff earlier, just - // throw away everything. - if (!gDebugStream) { - Cleanup(); - return NS_OK; - } - while ((d = mList->popFirst()) != nullptr) { std::auto_ptr cleaner(d); if (!d->Write()) { @@ -386,10 +622,7 @@ public: Cleanup(); if (NS_FAILED(rv)) { - gDebugSenderTransport->Close(rv); - gDebugConnected = false; - gDebugStream = nullptr; - gDebugServerSocket = nullptr; + LayerScope::DestroyServerSocket(); } return NS_OK; @@ -408,30 +641,26 @@ LayerScope::CreateServerSocket() return; } - if (!gDebugSenderThread) { - NS_NewThread(getter_AddRefs(gDebugSenderThread)); - } - - if (!gDebugServerSocket) { - gDebugServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID); - int port = Preferences::GetInt("gfx.layerscope.port", 23456); - gDebugServerSocket->Init(port, false, -1); - gDebugServerSocket->AsyncListen(new DebugListener); + if (!gLayerScopeWebSocketManager) { + gLayerScopeWebSocketManager = new LayerScopeWebSocketManager(); } } void LayerScope::DestroyServerSocket() { - gDebugConnected = false; - gDebugStream = nullptr; - gDebugServerSocket = nullptr; + if (gLayerScopeWebSocketManager) { + gLayerScopeWebSocketManager->RemoveAllConnections(); + } } void LayerScope::BeginFrame(GLContext* aGLContext, int64_t aFrameStamp) { - if (!gDebugConnected) + if (!gLayerScopeWebSocketManager) + return; + + if (!gLayerScopeWebSocketManager->IsConnected()) return; #if 0 @@ -442,8 +671,7 @@ LayerScope::BeginFrame(GLContext* aGLContext, int64_t aFrameStamp) } #endif - gCurrentSender = new DebugDataSender(); - gCurrentSender->Append(new DebugGLData(DebugGLData::FrameStart, aGLContext, aFrameStamp)); + gLayerScopeWebSocketManager->AppendDebugData(new DebugGLData(DebugGLData::FrameStart, aGLContext, aFrameStamp)); } void @@ -452,9 +680,8 @@ LayerScope::EndFrame(GLContext* aGLContext) if (!CheckSender()) return; - gCurrentSender->Append(new DebugGLData(DebugGLData::FrameEnd, aGLContext)); - gDebugSenderThread->Dispatch(gCurrentSender, NS_DISPATCH_NORMAL); - gCurrentSender = nullptr; + gLayerScopeWebSocketManager->AppendDebugData(new DebugGLData(DebugGLData::FrameEnd, aGLContext)); + gLayerScopeWebSocketManager->DispatchDebugData(); } static void @@ -463,7 +690,7 @@ SendColor(void* aLayerRef, const gfxRGBA& aColor, int aWidth, int aHeight) if (!CheckSender()) return; - gCurrentSender->Append( + gLayerScopeWebSocketManager->AppendDebugData( new DebugGLColorData(aLayerRef, aColor, aWidth, aHeight)); } @@ -500,7 +727,7 @@ SendTextureSource(GLContext* aGLContext, gfxIntSize(size.width, size.height), shaderProgram, aFlipY); - gCurrentSender->Append( + gLayerScopeWebSocketManager->AppendDebugData( new DebugGLTextureData(aGLContext, aLayerRef, textureTarget, textureId, img)); } @@ -585,5 +812,34 @@ LayerScope::SendEffectChain(GLContext* aGLContext, // TODO: } +LayerScopeWebSocketManager::LayerScopeWebSocketManager() +{ + NS_NewThread(getter_AddRefs(mDebugSenderThread)); + + mServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID); + int port = Preferences::GetInt("gfx.layerscope.port", 23456); + mServerSocket->Init(port, false, -1); + mServerSocket->AsyncListen(new DebugListener); +} + +LayerScopeWebSocketManager::~LayerScopeWebSocketManager() +{ +} + +void LayerScopeWebSocketManager::AppendDebugData(DebugGLData *aDebugData) +{ + if (!mCurrentSender) { + mCurrentSender = new DebugDataSender(); + } + + mCurrentSender->Append(aDebugData); +} + +void LayerScopeWebSocketManager::DispatchDebugData() +{ + mDebugSenderThread->Dispatch(mCurrentSender, NS_DISPATCH_NORMAL); + mCurrentSender = nullptr; +} + } /* layers */ } /* mozilla */