Bug 1203802 - Websocket Frame Listener API for devtool Network Inspector - part 2 - WebSocketFrameService, r=michal

This commit is contained in:
Andrea Marchesini 2015-10-26 15:30:11 +00:00
parent c997842c3d
commit a2f1eba556
12 changed files with 883 additions and 48 deletions

View File

@ -1143,6 +1143,10 @@ protected:
nsCOMPtr<nsIDOMWindow> topWindow;
aWindow->GetScriptableTop(getter_AddRefs(topWindow));
nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
if (pTopWindow) {
pTopWindow = pTopWindow->GetCurrentInnerWindow();
}
if (pTopWindow) {
windowID = pTopWindow->WindowID();
}
@ -1323,6 +1327,10 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
nsCOMPtr<nsIDOMWindow> topWindow;
ownerWindow->GetScriptableTop(getter_AddRefs(topWindow));
nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
if (pTopWindow) {
pTopWindow = pTopWindow->GetCurrentInnerWindow();
}
if (pTopWindow) {
windowID = pTopWindow->WindowID();
}

View File

@ -26,3 +26,4 @@ run-if = os == 'linux'
[test_bug1008126.html]
run-if = os == 'linux'
[test_sandboxed_blob_uri.html]
[test_websocket_frame.html]

View File

@ -0,0 +1,128 @@
<!DOCTYPE HTML>
<html>
<!--
-->
<head>
<title>Basic websocket frame interception test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
</head>
<body>
<script class="testbody" type="text/javascript">
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
var frameReceivedCounter = 0;
var frameSentCounter = 0;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var tests = [
{ payload: "Hello world!" },
{ payload: (function() { var buffer = ""; for (var i = 0; i < 120; ++i) buffer += i; return buffer; }()) },
{ payload: "end" },
]
var innerId =
window.top.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
ok(innerId, "We have a valid innerWindowID: " + innerId);
var service = Cc["@mozilla.org/websocketframe/service;1"]
.getService(Ci.nsIWebSocketFrameService);
ok(!!service, "We have the nsIWebSocketFrameService");
var listener = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebSocketFrameListener]),
frameReceived: function(aWebSocketSerialID, aFrame) {
ok(!!aFrame, "We have received a frame");
if (tests.length) {
is(aFrame.finBit, true, "Checking finBit");
is(aFrame.rsvBit1, true, "Checking rsvBit1");
is(aFrame.rsvBit2, false, "Checking rsvBit2");
is(aFrame.rsvBit3, false, "Checking rsvBit3");
is(aFrame.opCode, aFrame.OPCODE_TEXT, "Checking opCode");
is(aFrame.maskBit, false, "Checking maskBit");
is(aFrame.mask, 0, "Checking mask");
is(aFrame.payload, tests[0].payload, "Checking payload: " + aFrame.payload);
} else {
is(aFrame.finBit, true, "Checking finBit");
is(aFrame.rsvBit1, false, "Checking rsvBit1");
is(aFrame.rsvBit2, false, "Checking rsvBit2");
is(aFrame.rsvBit3, false, "Checking rsvBit3");
is(aFrame.opCode, aFrame.OPCODE_CLOSE, "Checking opCode");
is(aFrame.maskBit, false, "Checking maskBit");
is(aFrame.mask, 0, "Checking mask");
}
frameReceivedCounter++;
},
frameSent: function(aWebSocketSerialID, aFrame) {
ok(!!aFrame, "We have sent a frame");
if (tests.length) {
is(aFrame.finBit, true, "Checking finBit");
is(aFrame.rsvBit1, true, "Checking rsvBit1");
is(aFrame.rsvBit2, false, "Checking rsvBit2");
is(aFrame.rsvBit3, false, "Checking rsvBit3");
is(aFrame.opCode, aFrame.OPCODE_TEXT, "Checking opCode");
is(aFrame.maskBit, true, "Checking maskBit");
ok(!!aFrame.mask, "Checking mask: " + aFrame.mask);
is(aFrame.payload, tests[0].payload, "Checking payload: " + aFrame.payload);
} else {
is(aFrame.finBit, true, "Checking finBit");
is(aFrame.rsvBit1, false, "Checking rsvBit1");
is(aFrame.rsvBit2, false, "Checking rsvBit2");
is(aFrame.rsvBit3, false, "Checking rsvBit3");
is(aFrame.opCode, aFrame.OPCODE_CLOSE, "Checking opCode");
is(aFrame.maskBit, true, "Checking maskBit");
ok(!!aFrame.mask, "Checking mask: " + aFrame.mask);
}
frameSentCounter++;
}
};
service.addListener(innerId, listener);
ok(true, "Listener added");
function checkListener() {
service.removeListener(innerId, listener);
ok(frameReceivedCounter, "We received some frames!");
ok(frameSentCounter, "We sent some frames!");
SimpleTest.finish();
}
var ws = new WebSocket("ws://mochi.test:8888/tests/dom/base/test/file_websocket_basic", "frame");
ws.onopen = function(e) {
info("onopen");
ws.send(tests[0].payload);
}
ws.onclose = function(e) {
info("onclose");
ws.close();
checkListener();
}
ws.onmessage = function(e) {
info("onmessage");
is(e.data, tests[0].payload, "Wrong data");
tests.shift();
if (tests.length) {
ws.send(tests[0].payload);
}
}
SimpleTest.waitForExplicitFinish();
</script>
</body>
</html>

View File

@ -231,6 +231,7 @@ static void Shutdown();
#include "mozilla/dom/nsMixedContentBlocker.h"
#include "AudioChannelService.h"
#include "mozilla/net/WebSocketFrameService.h"
#include "mozilla/dom/DataStoreService.h"
@ -632,6 +633,9 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(Geolocation, Init)
#define NS_AUDIOCHANNEL_SERVICE_CID \
{ 0xf712e983, 0x048a, 0x443f, { 0x88, 0x02, 0xfc, 0xc3, 0xd9, 0x27, 0xce, 0xac }}
#define NS_WEBSOCKETFRAME_SERVICE_CID \
{ 0x5973dd8f, 0xed2c, 0x41ff, { 0x9e, 0x64, 0x25, 0x1f, 0xf5, 0x5a, 0x67, 0xb9 }}
#define NS_DATASTORE_SERVICE_CID \
{ 0x0d4285fe, 0xf1b3, 0x49fa, { 0xbc, 0x51, 0xa4, 0xa8, 0x3f, 0x0a, 0xaf, 0x85 }}
@ -639,6 +643,8 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationServ
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(AudioChannelService, AudioChannelService::GetOrCreate)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(WebSocketFrameService, WebSocketFrameService::GetOrCreate)
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DataStoreService, DataStoreService::GetOrCreate)
#ifdef MOZ_WEBSPEECH_TEST_BACKEND
@ -789,6 +795,7 @@ NS_DEFINE_NAMED_CID(NS_TEXTSERVICESDOCUMENT_CID);
NS_DEFINE_NAMED_CID(NS_GEOLOCATION_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_GEOLOCATION_CID);
NS_DEFINE_NAMED_CID(NS_AUDIOCHANNEL_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_WEBSOCKETFRAME_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_DATASTORE_SERVICE_CID);
NS_DEFINE_NAMED_CID(NS_FOCUSMANAGER_CID);
NS_DEFINE_NAMED_CID(NS_CONTENTSECURITYMANAGER_CID);
@ -1096,6 +1103,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
{ &kNS_GEOLOCATION_SERVICE_CID, false, nullptr, nsGeolocationServiceConstructor },
{ &kNS_GEOLOCATION_CID, false, nullptr, GeolocationConstructor },
{ &kNS_AUDIOCHANNEL_SERVICE_CID, false, nullptr, AudioChannelServiceConstructor },
{ &kNS_WEBSOCKETFRAME_SERVICE_CID, false, nullptr, WebSocketFrameServiceConstructor },
{ &kNS_DATASTORE_SERVICE_CID, false, nullptr, DataStoreServiceConstructor },
{ &kNS_FOCUSMANAGER_CID, false, nullptr, CreateFocusManager },
#ifdef MOZ_WEBSPEECH_TEST_BACKEND
@ -1263,6 +1271,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
{ "@mozilla.org/geolocation/service;1", &kNS_GEOLOCATION_SERVICE_CID },
{ "@mozilla.org/geolocation;1", &kNS_GEOLOCATION_CID },
{ "@mozilla.org/audiochannel/service;1", &kNS_AUDIOCHANNEL_SERVICE_CID },
{ "@mozilla.org/websocketframe/service;1", &kNS_WEBSOCKETFRAME_SERVICE_CID },
{ "@mozilla.org/datastore-service;1", &kNS_DATASTORE_SERVICE_CID },
{ "@mozilla.org/focus-manager;1", &kNS_FOCUSMANAGER_CID },
#ifdef MOZ_WEBSPEECH_TEST_BACKEND

View File

@ -4,6 +4,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WebSocketFrame.h"
#include "WebSocketLog.h"
#include "WebSocketChannel.h"
@ -11,6 +12,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/Endian.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/net/WebSocketFrameService.h"
#include "nsIURI.h"
#include "nsIChannel.h"
@ -963,7 +965,8 @@ public:
: mMsgType(type), mDeflated(false), mOrigLength(0)
{
MOZ_COUNT_CTOR(OutboundMessage);
mMsg.pString = str;
mMsg.pString.mValue = str;
mMsg.pString.mOrigValue = nullptr;
mLength = str ? str->Length() : 0;
}
@ -983,7 +986,9 @@ public:
case kMsgTypeBinaryString:
case kMsgTypePing:
case kMsgTypePong:
delete mMsg.pString;
delete mMsg.pString.mValue;
if (mMsg.pString.mOrigValue)
delete mMsg.pString.mOrigValue;
break;
case kMsgTypeStream:
// for now this only gets hit if msg deleted w/o being sent
@ -1004,13 +1009,21 @@ public:
uint8_t* BeginWriting() {
MOZ_ASSERT(mMsgType != kMsgTypeStream,
"Stream should have been converted to string by now");
return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginWriting() : nullptr);
return (uint8_t *)(mMsg.pString.mValue ? mMsg.pString.mValue->BeginWriting() : nullptr);
}
uint8_t* BeginReading() {
MOZ_ASSERT(mMsgType != kMsgTypeStream,
"Stream should have been converted to string by now");
return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginReading() : nullptr);
return (uint8_t *)(mMsg.pString.mValue ? mMsg.pString.mValue->BeginReading() : nullptr);
}
uint8_t* BeginOrigReading() {
MOZ_ASSERT(mMsgType != kMsgTypeStream,
"Stream should have been converted to string by now");
if (!mDeflated)
return BeginReading();
return (uint8_t *)(mMsg.pString.mOrigValue ? mMsg.pString.mOrigValue->BeginReading() : nullptr);
}
nsresult ConvertStreamToString()
@ -1031,7 +1044,8 @@ public:
mMsg.pStream->Close();
mMsg.pStream->Release();
mMsg.pString = temp.forget();
mMsg.pString.mValue = temp.forget();
mMsg.pString.mOrigValue = nullptr;
mMsgType = kMsgTypeBinaryString;
return NS_OK;
@ -1074,14 +1088,17 @@ public:
mOrigLength = mLength;
mDeflated = true;
mLength = temp->Length();
delete mMsg.pString;
mMsg.pString = temp.forget();
mMsg.pString.mOrigValue = mMsg.pString.mValue;
mMsg.pString.mValue = temp.forget();
return true;
}
private:
union {
nsCString *pString;
struct {
nsCString *mValue;
nsCString *mOrigValue;
} pString;
nsIInputStream *pStream;
} mMsg;
WsMsgType mMsgType;
@ -1149,7 +1166,7 @@ WebSocketChannel::WebSocketChannel() :
mStopOnClose(NS_OK),
mServerCloseCode(CLOSE_ABNORMAL),
mScriptCloseCode(0),
mFragmentOpcode(kContinuation),
mFragmentOpcode(nsIWebSocketFrame::OPCODE_CONTINUATION),
mFragmentAccumulator(0),
mBuffered(0),
mBufferSize(kIncomingBufferInitialSize),
@ -1178,6 +1195,8 @@ WebSocketChannel::WebSocketChannel() :
LOG(("Failed to initiate dashboard service."));
mSerial = sSerialSeed++;
mFrameService = WebSocketFrameService::GetOrCreate();
}
WebSocketChannel::~WebSocketChannel()
@ -1209,6 +1228,7 @@ WebSocketChannel::~WebSocketChannel()
NS_ReleaseOnMainThread(mLoadGroup);
NS_ReleaseOnMainThread(mLoadInfo);
NS_ReleaseOnMainThread(static_cast<nsIWebSocketFrameService*>(mFrameService.forget().take()));
}
NS_IMETHODIMP
@ -1501,11 +1521,15 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
uint32_t totalAvail = avail;
while (avail >= 2) {
int64_t payloadLength64 = mFramePtr[1] & 0x7F;
int64_t payloadLength64 = mFramePtr[1] & kPayloadLengthBitsMask;
uint8_t finBit = mFramePtr[0] & kFinalFragBit;
uint8_t rsvBits = mFramePtr[0] & kRsvBitsMask;
uint8_t rsvBit1 = mFramePtr[0] & kRsv1Bit;
uint8_t rsvBit2 = mFramePtr[0] & kRsv2Bit;
uint8_t rsvBit3 = mFramePtr[0] & kRsv3Bit;
uint8_t opcode = mFramePtr[0] & kOpcodeBitsMask;
uint8_t maskBit = mFramePtr[1] & kMaskBit;
uint8_t opcode = mFramePtr[0] & 0x0F;
uint32_t mask = 0;
uint32_t framingLength = 2;
if (maskBit)
@ -1560,7 +1584,7 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
// frames to the client, but it is allowed
LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
uint32_t mask = NetworkEndian::readUint32(payload - 4);
mask = NetworkEndian::readUint32(payload - 4);
ApplyMask(mask, payload, payloadLength);
}
@ -1584,22 +1608,23 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
}
}
if (!finBit || opcode == kContinuation) {
if (!finBit || opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
// This is part of a fragment response
// Only the first frame has a non zero op code: Make sure we don't see a
// first frame while some old fragments are open
if ((mFragmentAccumulator != 0) && (opcode != kContinuation)) {
if ((mFragmentAccumulator != 0) &&
(opcode != nsIWebSocketFrame::OPCODE_CONTINUATION)) {
LOG(("WebSocketChannel:: nested fragments\n"));
return NS_ERROR_ILLEGAL_VALUE;
}
LOG(("WebSocketChannel:: Accumulating Fragment %ld\n", payloadLength));
if (opcode == kContinuation) {
if (opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
// Make sure this continuation fragment isn't the first fragment
if (mFragmentOpcode == kContinuation) {
if (mFragmentOpcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
LOG(("WebSocketHeandler:: continuation code in first fragment\n"));
return NS_ERROR_ILLEGAL_VALUE;
}
@ -1624,9 +1649,9 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
mFragmentAccumulator = 0;
opcode = mFragmentOpcode;
// reset to detect if next message illegally starts with continuation
mFragmentOpcode = kContinuation;
mFragmentOpcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
} else {
opcode = kContinuation;
opcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
mFragmentAccumulator += payloadLength;
}
} else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) {
@ -1644,7 +1669,7 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
} else if (mStopped) {
LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n",
opcode));
} else if (opcode == kText) {
} else if (opcode == nsIWebSocketFrame::OPCODE_TEXT) {
bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated();
LOG(("WebSocketChannel:: %stext frame received\n",
isDeflated ? "deflated " : ""));
@ -1673,6 +1698,14 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
return NS_ERROR_CANNOT_CONVERT_DATA;
}
RefPtr<WebSocketFrame> frame =
mFrameService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
opcode, maskBit, mask, utf8Data);
if (frame) {
mFrameService->FrameReceived(mSerial, mInnerWindowID, frame);
}
mTargetThread->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1),
NS_DISPATCH_NORMAL);
if (mConnectionLogService && !mPrivateBrowsing) {
@ -1688,7 +1721,12 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
return NS_ERROR_ILLEGAL_VALUE;
}
if (opcode == kClose) {
RefPtr<WebSocketFrame> frame =
mFrameService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
opcode, maskBit, mask, payload,
payloadLength);
if (opcode == nsIWebSocketFrame::OPCODE_CLOSE) {
LOG(("WebSocketChannel:: close received\n"));
mServerClosed = 1;
@ -1720,6 +1758,14 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
mCloseTimer->Cancel();
mCloseTimer = nullptr;
}
if (frame) {
// We send the frame immediately becuase we want to have it dispatched
// before the CallOnServerClose.
mFrameService->FrameReceived(mSerial, mInnerWindowID, frame);
frame = nullptr;
}
if (mListenerMT) {
mTargetThread->Dispatch(new CallOnServerClose(this, mServerCloseCode,
mServerCloseReason),
@ -1728,12 +1774,12 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
if (mClientClosed)
ReleaseSession();
} else if (opcode == kPing) {
} else if (opcode == nsIWebSocketFrame::OPCODE_PING) {
LOG(("WebSocketChannel:: ping received\n"));
GeneratePong(payload, payloadLength);
} else if (opcode == kPong) {
// opcode kPong: the mere act of receiving the packet is all we need
// to do for the pong to trigger the activity timers
} else if (opcode == nsIWebSocketFrame::OPCODE_PONG) {
// opcode OPCODE_PONG: the mere act of receiving the packet is all we
// need to do for the pong to trigger the activity timers
LOG(("WebSocketChannel:: pong received\n"));
} else {
/* unknown control frame opcode */
@ -1754,7 +1800,11 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
mBuffered -= framingLength + payloadLength;
payloadLength = 0;
}
} else if (opcode == kBinary) {
if (frame) {
mFrameService->FrameReceived(mSerial, mInnerWindowID, frame);
}
} else if (opcode == nsIWebSocketFrame::OPCODE_BINARY) {
bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated();
LOG(("WebSocketChannel:: %sbinary frame received\n",
isDeflated ? "deflated " : ""));
@ -1777,6 +1827,13 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
}
}
RefPtr<WebSocketFrame> frame =
mFrameService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3,
opcode, maskBit, mask, binaryData);
if (frame) {
mFrameService->FrameReceived(mSerial, mInnerWindowID, frame);
}
mTargetThread->Dispatch(
new CallOnMessageAvailable(this, binaryData, binaryData.Length()),
NS_DISPATCH_NORMAL);
@ -1786,7 +1843,7 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
LOG(("Added new received msg for %s", mHost.get()));
}
}
} else if (opcode != kContinuation) {
} else if (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION) {
/* unknown opcode */
LOG(("WebSocketChannel:: unknown op code %d\n", opcode));
return NS_ERROR_ILLEGAL_VALUE;
@ -1837,7 +1894,7 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
return NS_OK;
}
void
/* static */ void
WebSocketChannel::ApplyMask(uint32_t mask, uint8_t *data, uint64_t len)
{
if (!data || len == 0)
@ -1980,7 +2037,7 @@ WebSocketChannel::PrimeNewOutgoingMessage()
}
mClientClosed = 1;
mOutHeader[0] = kFinalFragBit | kClose;
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_CLOSE;
mOutHeader[1] = kMaskBit;
// payload is offset 6 including 4 for the mask
@ -2034,13 +2091,13 @@ WebSocketChannel::PrimeNewOutgoingMessage()
} else {
switch (msgType) {
case kMsgTypePong:
mOutHeader[0] = kFinalFragBit | kPong;
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PONG;
break;
case kMsgTypePing:
mOutHeader[0] = kFinalFragBit | kPing;
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PING;
break;
case kMsgTypeString:
mOutHeader[0] = kFinalFragBit | kText;
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_TEXT;
break;
case kMsgTypeStream:
// HACK ALERT: read in entire stream into string.
@ -2057,7 +2114,7 @@ WebSocketChannel::PrimeNewOutgoingMessage()
// no break: fall down into binary string case
case kMsgTypeBinaryString:
mOutHeader[0] = kFinalFragBit | kBinary;
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_BINARY;
break;
case kMsgTypeFin:
MOZ_ASSERT(false, "unreachable"); // avoid compiler warning
@ -2119,6 +2176,23 @@ WebSocketChannel::PrimeNewOutgoingMessage()
// handful of bytes and might rotate the mask, so we can just do it locally.
// For real data frames we ship the bulk of the payload off to ApplyMask()
RefPtr<WebSocketFrame> frame =
mFrameService->CreateFrameIfNeeded(
mOutHeader[0] & WebSocketChannel::kFinalFragBit,
mOutHeader[0] & WebSocketChannel::kRsv1Bit,
mOutHeader[0] & WebSocketChannel::kRsv2Bit,
mOutHeader[0] & WebSocketChannel::kRsv3Bit,
mOutHeader[0] & WebSocketChannel::kOpcodeBitsMask,
mOutHeader[1] & WebSocketChannel::kMaskBit,
mask,
payload, mHdrOutToSend - (payload - mOutHeader),
mCurrentOut->BeginOrigReading(),
mCurrentOut->OrigLength());
if (frame) {
mFrameService->FrameSent(mSerial, mInnerWindowID, frame);
}
while (payload < (mOutHeader + mHdrOutToSend)) {
*payload ^= mask >> 24;
mask = RotateLeft(mask, 8);
@ -2141,7 +2215,7 @@ WebSocketChannel::PrimeNewOutgoingMessage()
// Transmitting begins - mHdrOutToSend bytes from mOutHeader and
// mCurrentOut->Length() bytes from mCurrentOut. The latter may be
// coaleseced into the former for small messages or as the result of the
// compression process,
// compression process.
}
void

View File

@ -39,7 +39,8 @@ class nsIRandomGenerator;
class nsISocketTransport;
class nsIURI;
namespace mozilla { namespace net {
namespace mozilla {
namespace net {
class OutboundMessage;
class OutboundEnqueuer;
@ -49,6 +50,7 @@ class CallOnMessageAvailable;
class CallOnStop;
class CallOnServerClose;
class CallAcknowledge;
class WebSocketFrameService;
// Used to enforce "1 connecting websocket per host" rule, and reconnect delays
enum wsConnectingState {
@ -70,6 +72,8 @@ class WebSocketChannel : public BaseWebSocketChannel,
public nsIInterfaceRequestor,
public nsIChannelEventSink
{
friend class WebSocketFrame;
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIHTTPUPGRADELISTENER
@ -105,23 +109,19 @@ public:
void GetEffectiveURL(nsAString& aEffectiveURL) const override;
bool IsEncrypted() const override;
enum {
// Non Control Frames
kContinuation = 0x0,
kText = 0x1,
kBinary = 0x2,
// Control Frames
kClose = 0x8,
kPing = 0x9,
kPong = 0xA
};
const static uint32_t kControlFrameMask = 0x8;
const static uint8_t kMaskBit = 0x80;
// First byte of the header
const static uint8_t kFinalFragBit = 0x80;
const static uint8_t kRsvBitsMask = 0x70;
const static uint8_t kRsv1Bit = 0x40;
const static uint8_t kRsv2Bit = 0x20;
const static uint8_t kRsv3Bit = 0x10;
const static uint8_t kOpcodeBitsMask = 0x0F;
// Second byte of the header
const static uint8_t kMaskBit = 0x80;
const static uint8_t kPayloadLengthBitsMask = 0x7F;
protected:
virtual ~WebSocketChannel();
@ -167,7 +167,8 @@ private:
void DecrementSessionCount();
void EnsureHdrOut(uint32_t size);
void ApplyMask(uint32_t mask, uint8_t *data, uint64_t len);
static void ApplyMask(uint32_t mask, uint8_t *data, uint64_t len);
bool IsPersistentFramePtr();
nsresult ProcessInput(uint8_t *buffer, uint32_t count);
@ -226,6 +227,8 @@ private:
const static int32_t kLingeringCloseTimeout = 1000;
const static int32_t kLingeringCloseThreshold = 50;
RefPtr<WebSocketFrameService> mFrameService;
int32_t mMaxConcurrentConnections;
uint64_t mInnerWindowID;

View File

@ -0,0 +1,73 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WebSocketFrame.h"
#include "WebSocketChannel.h"
extern PRThread *gSocketThread;
namespace mozilla {
namespace net {
NS_INTERFACE_MAP_BEGIN(WebSocketFrame)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketFrame)
NS_INTERFACE_MAP_ENTRY(nsIWebSocketFrame)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(WebSocketFrame)
NS_IMPL_RELEASE(WebSocketFrame)
WebSocketFrame::WebSocketFrame(bool aFinBit, bool aRsvBit1, bool aRsvBit2,
bool aRsvBit3, uint8_t aOpCode, bool aMaskBit,
uint32_t aMask, const nsCString& aPayload)
: mFinBit(aFinBit)
, mRsvBit1(aRsvBit1)
, mRsvBit2(aRsvBit2)
, mRsvBit3(aRsvBit3)
, mMaskBit(aMaskBit)
, mOpCode(aOpCode)
, mMask(aMask)
, mPayload(aPayload)
{
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
}
WebSocketFrame::~WebSocketFrame()
{}
#define WSF_GETTER( method, value , type ) \
NS_IMETHODIMP \
WebSocketFrame::method(type* aValue) \
{ \
MOZ_ASSERT(NS_IsMainThread()); \
if (!aValue) { \
return NS_ERROR_FAILURE; \
} \
*aValue = value; \
return NS_OK; \
}
WSF_GETTER(GetFinBit, mFinBit, bool);
WSF_GETTER(GetRsvBit1, mRsvBit1, bool);
WSF_GETTER(GetRsvBit2, mRsvBit2, bool);
WSF_GETTER(GetRsvBit3, mRsvBit3, bool);
WSF_GETTER(GetOpCode, mOpCode, uint16_t);
WSF_GETTER(GetMaskBit, mMaskBit, bool);
WSF_GETTER(GetMask, mMask, uint32_t);
#undef WSF_GETTER
NS_IMETHODIMP
WebSocketFrame::GetPayload(nsACString& aValue)
{
MOZ_ASSERT(NS_IsMainThread());
aValue = mPayload;
return NS_OK;
}
} // net namespace
} // mozilla namespace

View File

@ -0,0 +1,44 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_net_WebSocketFrame_h
#define mozilla_net_WebSocketFrame_h
#include "nsAutoPtr.h"
#include "nsIWebSocketFrameService.h"
namespace mozilla {
namespace net {
class WebSocketFrame final : public nsIWebSocketFrame
{
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIWEBSOCKETFRAME
WebSocketFrame(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
const nsCString& aPayload);
private:
~WebSocketFrame();
bool mFinBit : 1;
bool mRsvBit1 : 1;
bool mRsvBit2 : 1;
bool mRsvBit3 : 1;
bool mMaskBit : 1;
uint8_t mOpCode;
uint32_t mMask;
nsCString mPayload;
};
} // net namespace
} // mozilla namespace
#endif // mozilla_net_WebSocketFrame_h

View File

@ -0,0 +1,356 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WebSocketFrame.h"
#include "WebSocketFrameService.h"
#include "mozilla/StaticPtr.h"
#include "nsISupportsPrimitives.h"
#include "nsXULAppAPI.h"
extern PRThread *gSocketThread;
namespace mozilla {
namespace net {
namespace {
StaticRefPtr<WebSocketFrameService> gWebSocketFrameService;
} // anonymous namespace
class WebSocketFrameRunnable final : public nsRunnable
{
public:
WebSocketFrameRunnable(uint32_t aWebSocketSerialID,
uint64_t aInnerWindowID,
WebSocketFrame* aFrame,
bool aFrameSent)
: mWebSocketSerialID(aWebSocketSerialID)
, mInnerWindowID(aInnerWindowID)
, mFrame(aFrame)
, mFrameSent(aFrameSent)
{}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<WebSocketFrameService> service =
WebSocketFrameService::GetOrCreate();
MOZ_ASSERT(service);
WebSocketFrameService::WindowListeners* listeners =
service->GetListeners(mInnerWindowID);
if (!listeners) {
return NS_OK;
}
nsresult rv;
WebSocketFrameService::WindowListeners::ForwardIterator iter(*listeners);
while (iter.HasMore()) {
nsCOMPtr<nsIWebSocketFrameListener> listener = iter.GetNext();
if (mFrameSent) {
rv = listener->FrameSent(mWebSocketSerialID, mFrame);
} else {
rv = listener->FrameReceived(mWebSocketSerialID, mFrame);
}
NS_WARN_IF(NS_FAILED(rv));
}
return NS_OK;
}
protected:
~WebSocketFrameRunnable()
{}
uint32_t mWebSocketSerialID;
uint64_t mInnerWindowID;
RefPtr<WebSocketFrame> mFrame;
bool mFrameSent;
};
/* static */ already_AddRefed<WebSocketFrameService>
WebSocketFrameService::GetOrCreate()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(XRE_IsParentProcess());
if (!gWebSocketFrameService) {
gWebSocketFrameService = new WebSocketFrameService();
}
RefPtr<WebSocketFrameService> service = gWebSocketFrameService.get();
return service.forget();
}
NS_INTERFACE_MAP_BEGIN(WebSocketFrameService)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebSocketFrameService)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY(nsIWebSocketFrameService)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(WebSocketFrameService)
NS_IMPL_RELEASE(WebSocketFrameService)
WebSocketFrameService::WebSocketFrameService()
: mCountListeners(0)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->AddObserver(this, "xpcom-shutdown", false);
obs->AddObserver(this, "inner-window-destroyed", false);
}
}
WebSocketFrameService::~WebSocketFrameService()
{
MOZ_ASSERT(NS_IsMainThread());
}
void
WebSocketFrameService::FrameReceived(uint32_t aWebSocketSerialID,
uint64_t aInnerWindowID,
WebSocketFrame* aFrame)
{
MOZ_ASSERT(aFrame);
// This method can be called only from a the network thread.
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
// Let's continue only if we have some listeners.
if (!HasListeners()) {
return;
}
RefPtr<WebSocketFrameRunnable> runnable =
new WebSocketFrameRunnable(aWebSocketSerialID, aInnerWindowID,
aFrame, false /* frameSent */);
nsresult rv = NS_DispatchToMainThread(runnable);
NS_WARN_IF(NS_FAILED(rv));
}
void
WebSocketFrameService::FrameSent(uint32_t aWebSocketSerialID,
uint64_t aInnerWindowID,
WebSocketFrame* aFrame)
{
MOZ_ASSERT(aFrame);
// This method can be called only from a the network thread.
MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
// Let's continue only if we have some listeners.
if (!HasListeners()) {
return;
}
RefPtr<WebSocketFrameRunnable> runnable =
new WebSocketFrameRunnable(aWebSocketSerialID, aInnerWindowID,
aFrame, true /* frameSent */);
nsresult rv = NS_DispatchToMainThread(runnable);
NS_WARN_IF(NS_FAILED(rv));
}
NS_IMETHODIMP
WebSocketFrameService::AddListener(uint64_t aInnerWindowID,
nsIWebSocketFrameListener* aListener)
{
MOZ_ASSERT(NS_IsMainThread());
if (!aListener) {
return NS_ERROR_FAILURE;
}
++mCountListeners;
WindowListeners* listeners = mWindows.Get(aInnerWindowID);
if (!listeners) {
listeners = new WindowListeners();
mWindows.Put(aInnerWindowID, listeners);
}
listeners->AppendElement(aListener);
return NS_OK;
}
NS_IMETHODIMP
WebSocketFrameService::RemoveListener(uint64_t aInnerWindowID,
nsIWebSocketFrameListener* aListener)
{
MOZ_ASSERT(NS_IsMainThread());
if (!aListener) {
return NS_ERROR_FAILURE;
}
WindowListeners* listeners = mWindows.Get(aInnerWindowID);
if (!listeners) {
return NS_ERROR_FAILURE;
}
if (!listeners->RemoveElement(aListener)) {
return NS_ERROR_FAILURE;
}
// The last listener for this window.
if (listeners->IsEmpty()) {
mWindows.Remove(aInnerWindowID);
}
MOZ_ASSERT(mCountListeners);
--mCountListeners;
return NS_OK;
}
NS_IMETHODIMP
WebSocketFrameService::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(NS_IsMainThread());
if (!strcmp(aTopic, "xpcom-shutdown")) {
Shutdown();
return NS_OK;
}
if (!strcmp(aTopic, "inner-window-destroyed") && HasListeners()) {
nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
uint64_t innerID;
nsresult rv = wrapper->GetData(&innerID);
NS_ENSURE_SUCCESS(rv, rv);
WindowListeners* listeners = mWindows.Get(innerID);
if (!listeners) {
return NS_OK;
}
MOZ_ASSERT(mCountListeners >= listeners->Length());
mCountListeners -= listeners->Length();
mWindows.Remove(innerID);
}
// This should not happen.
return NS_ERROR_FAILURE;
}
void
WebSocketFrameService::Shutdown()
{
MOZ_ASSERT(NS_IsMainThread());
if (gWebSocketFrameService) {
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
if (obs) {
obs->RemoveObserver(gWebSocketFrameService, "xpcom-shutdown");
obs->RemoveObserver(gWebSocketFrameService, "inner-window-destroyed");
}
mWindows.Clear();
gWebSocketFrameService = nullptr;
}
}
bool
WebSocketFrameService::HasListeners() const
{
return !!mCountListeners;
}
WebSocketFrameService::WindowListeners*
WebSocketFrameService::GetListeners(uint64_t aInnerWindowID) const
{
return mWindows.Get(aInnerWindowID);
}
WebSocketFrame*
WebSocketFrameService::CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1,
bool aRsvBit2, bool aRsvBit3,
uint8_t aOpCode, bool aMaskBit,
uint32_t aMask,
const nsCString& aPayload)
{
if (!HasListeners()) {
return nullptr;
}
return new WebSocketFrame(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, aOpCode,
aMaskBit, aMask, aPayload);
}
WebSocketFrame*
WebSocketFrameService::CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1,
bool aRsvBit2, bool aRsvBit3,
uint8_t aOpCode, bool aMaskBit,
uint32_t aMask, uint8_t* aPayload,
uint32_t aPayloadLength)
{
if (!HasListeners()) {
return nullptr;
}
nsAutoCString payloadStr;
if (NS_WARN_IF(!(payloadStr.Assign((const char*) aPayload, aPayloadLength,
mozilla::fallible)))) {
return nullptr;
}
return new WebSocketFrame(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, aOpCode,
aMaskBit, aMask, payloadStr);
}
WebSocketFrame*
WebSocketFrameService::CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1,
bool aRsvBit2, bool aRsvBit3,
uint8_t aOpCode, bool aMaskBit,
uint32_t aMask,
uint8_t* aPayloadInHdr,
uint32_t aPayloadInHdrLength,
uint8_t* aPayload,
uint32_t aPayloadLength)
{
if (!HasListeners()) {
return nullptr;
}
uint32_t payloadLength = aPayloadLength + aPayloadInHdrLength;
nsAutoArrayPtr<uint8_t> payload(new uint8_t[payloadLength]);
if (NS_WARN_IF(!payload)) {
return nullptr;
}
if (aPayloadInHdrLength) {
memcpy(payload, aPayloadInHdr, aPayloadInHdrLength);
}
memcpy(payload + aPayloadInHdrLength, aPayload, aPayloadLength);
nsAutoCString payloadStr;
if (NS_WARN_IF(!(payloadStr.Assign((const char*) payload.get(), payloadLength,
mozilla::fallible)))) {
return nullptr;
}
return new WebSocketFrame(aFinBit, aRsvBit1, aRsvBit2, aRsvBit3, aOpCode,
aMaskBit, aMask, payloadStr);
}
} // net namespace
} // mozilla namespace

View File

@ -0,0 +1,81 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_net_WebSocketFrameService_h
#define mozilla_net_WebSocketFrameService_h
#include "mozilla/Atomics.h"
#include "nsIWebSocketFrameService.h"
#include "nsAutoPtr.h"
#include "nsCOMPtr.h"
#include "nsClassHashtable.h"
#include "nsHashKeys.h"
#include "nsIObserver.h"
#include "nsISupportsImpl.h"
#include "nsTObserverArray.h"
namespace mozilla {
namespace net {
class WebSocketFrame;
class WebSocketFrameService final : public nsIWebSocketFrameService
, public nsIObserver
{
friend class WebSocketFrameRunnable;
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
NS_DECL_NSIWEBSOCKETFRAMESERVICE
static already_AddRefed<WebSocketFrameService> GetOrCreate();
void FrameReceived(uint32_t aWebSocketSerialID,
uint64_t aInnerWindowID,
WebSocketFrame* aFrame);
void FrameSent(uint32_t aWebSocketSerialID,
uint64_t aInnerWindowID,
WebSocketFrame* aFrame);
WebSocketFrame*
CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
const nsCString& aPayload);
WebSocketFrame*
CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
uint8_t* aPayload, uint32_t aPayloadLength);
WebSocketFrame*
CreateFrameIfNeeded(bool aFinBit, bool aRsvBit1, bool aRsvBit2, bool aRsvBit3,
uint8_t aOpCode, bool aMaskBit, uint32_t aMask,
uint8_t* aPayloadInHdr, uint32_t aPayloadInHdrLength,
uint8_t* aPayload, uint32_t aPayloadLength);
private:
WebSocketFrameService();
~WebSocketFrameService();
bool HasListeners() const;
void Shutdown();
typedef nsTObserverArray<nsCOMPtr<nsIWebSocketFrameListener>> WindowListeners;
WindowListeners* GetListeners(uint64_t aInnerWindowID) const;
// Used only on the main-thread.
nsClassHashtable<nsUint64HashKey, WindowListeners> mWindows;
Atomic<uint64_t> mCountListeners;
};
} // net namespace
} // mozilla namespace
#endif // mozilla_net_WebSocketFrameService_h

View File

@ -6,6 +6,7 @@
XPIDL_SOURCES += [
'nsIWebSocketChannel.idl',
'nsIWebSocketFrameService.idl',
'nsIWebSocketListener.idl',
]
@ -16,6 +17,7 @@ EXPORTS.mozilla.net += [
'WebSocketChannel.h',
'WebSocketChannelChild.h',
'WebSocketChannelParent.h',
'WebSocketFrameService.h',
]
UNIFIED_SOURCES += [
@ -23,6 +25,8 @@ UNIFIED_SOURCES += [
'WebSocketChannel.cpp',
'WebSocketChannelChild.cpp',
'WebSocketChannelParent.cpp',
'WebSocketFrame.cpp',
'WebSocketFrameService.cpp',
]
IPDL_SOURCES += [

View File

@ -0,0 +1,54 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
[scriptable, builtinclass, uuid(e668b6bf-d29d-4861-b1bc-bab70da4da5c)]
interface nsIWebSocketFrame : nsISupports
{
readonly attribute boolean finBit;
readonly attribute boolean rsvBit1;
readonly attribute boolean rsvBit2;
readonly attribute boolean rsvBit3;
readonly attribute unsigned short opCode;
readonly attribute boolean maskBit;
readonly attribute unsigned long mask;
readonly attribute ACString payload;
// Non-Control opCode values:
const long OPCODE_CONTINUATION = 0x0;
const long OPCODE_TEXT = 0x1;
const long OPCODE_BINARY = 0x2;
// Control opCode values:
const long OPCODE_CLOSE = 0x8;
const long OPCODE_PING = 0x9;
const long OPCODE_PONG = 0xA;
};
[scriptable, uuid(f6a7ec44-23b2-4c77-bb94-f11a8df5a874)]
interface nsIWebSocketFrameListener : nsISupports
{
void frameReceived(in unsigned long aWebSocketSerialID,
in nsIWebSocketFrame aFrame);
void frameSent(in unsigned long aWebSocketSerialID,
in nsIWebSocketFrame aFrame);
};
[scriptable, builtinclass, uuid(b89d1b90-2cf3-4d8f-ac21-5aedfb25c760)]
interface nsIWebSocketFrameService : nsISupports
{
void addListener(in unsigned long long aInnerWindowID,
in nsIWebSocketFrameListener aListener);
void removeListener(in unsigned long long aInnerWindowID,
in nsIWebSocketFrameListener aListener);
};