Bug 1272100: Websocket changes in preparation of FlyWeb landing. Make websocket code support acting as the server side of a websocket connection. r=michal

This commit is contained in:
Jonas Sicking 2016-06-01 02:35:33 -07:00
parent 1c8273e348
commit 2e9e463e19
10 changed files with 553 additions and 272 deletions

View File

@ -86,6 +86,7 @@ public:
explicit WebSocketImpl(WebSocket* aWebSocket)
: mWebSocket(aWebSocket)
, mIsServerSide(false)
, mOnCloseScheduled(false)
, mFailed(false)
, mDisconnectingOrDisconnected(false)
@ -118,6 +119,7 @@ public:
void Init(JSContext* aCx,
nsIPrincipal* aPrincipal,
bool aIsServerSide,
const nsAString& aURL,
nsTArray<nsString>& aProtocolArray,
const nsACString& aScriptFile,
@ -127,6 +129,8 @@ public:
bool* aConnectionFailed);
void AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
nsITransportProvider* aTransportProvider,
const nsACString& aNegotiatedExtensions,
ErrorResult& aRv);
nsresult ParseURL(const nsAString& aURL);
@ -169,6 +173,9 @@ public:
nsCOMPtr<nsIWebSocketChannel> mChannel;
bool mIsServerSide; // True if we're implementing the server side of a
// websocket connection
bool mSecure; // if true it is using SSL and the wss scheme,
// otherwise it is using the ws scheme with no SSL
@ -951,7 +958,8 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
ErrorResult& aRv)
{
Sequence<nsString> protocols;
return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr,
EmptyCString(), aRv);
}
already_AddRefed<WebSocket>
@ -966,7 +974,18 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
return nullptr;
}
return WebSocket::Constructor(aGlobal, aUrl, protocols, aRv);
return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr,
EmptyCString(), aRv);
}
already_AddRefed<WebSocket>
WebSocket::Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
const Sequence<nsString>& aProtocols,
ErrorResult& aRv)
{
return WebSocket::ConstructorCommon(aGlobal, aUrl, aProtocols, nullptr,
EmptyCString(), aRv);
}
namespace {
@ -1027,7 +1046,8 @@ protected:
class InitRunnable final : public WebSocketMainThreadRunnable
{
public:
InitRunnable(WebSocketImpl* aImpl, const nsAString& aURL,
InitRunnable(WebSocketImpl* aImpl, bool aIsServerSide,
const nsAString& aURL,
nsTArray<nsString>& aProtocolArray,
const nsACString& aScriptFile, uint32_t aScriptLine,
uint32_t aScriptColumn,
@ -1035,6 +1055,7 @@ public:
: WebSocketMainThreadRunnable(aImpl->mWorkerPrivate,
NS_LITERAL_CSTRING("WebSocket :: init"))
, mImpl(aImpl)
, mIsServerSide(aIsServerSide)
, mURL(aURL)
, mProtocolArray(aProtocolArray)
, mScriptFile(aScriptFile)
@ -1070,8 +1091,9 @@ protected:
return true;
}
mImpl->Init(jsapi.cx(), principal, mURL, mProtocolArray, mScriptFile,
mScriptLine, mScriptColumn, mRv, mConnectionFailed);
mImpl->Init(jsapi.cx(), principal, mIsServerSide, mURL, mProtocolArray,
mScriptFile, mScriptLine, mScriptColumn, mRv,
mConnectionFailed);
return true;
}
@ -1080,15 +1102,16 @@ protected:
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
mImpl->Init(nullptr, aTopLevelWorkerPrivate->GetPrincipal(), mURL,
mProtocolArray, mScriptFile, mScriptLine, mScriptColumn, mRv,
mConnectionFailed);
mImpl->Init(nullptr, aTopLevelWorkerPrivate->GetPrincipal(), mIsServerSide,
mURL, mProtocolArray, mScriptFile, mScriptLine, mScriptColumn,
mRv, mConnectionFailed);
return true;
}
// Raw pointer. This worker runs synchronously.
WebSocketImpl* mImpl;
bool mIsServerSide;
const nsAString& mURL;
nsTArray<nsString>& mProtocolArray;
nsCString mScriptFile;
@ -1140,7 +1163,7 @@ protected:
windowID = topInner->WindowID();
}
mImpl->AsyncOpen(principal, windowID, mRv);
mImpl->AsyncOpen(principal, windowID, nullptr, EmptyCString(), mRv);
return true;
}
@ -1149,7 +1172,8 @@ protected:
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, mRv);
mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPrincipal(), 0, nullptr,
EmptyCString(), mRv);
return true;
}
@ -1163,11 +1187,14 @@ private:
} // namespace
already_AddRefed<WebSocket>
WebSocket::Constructor(const GlobalObject& aGlobal,
const nsAString& aUrl,
const Sequence<nsString>& aProtocols,
ErrorResult& aRv)
WebSocket::ConstructorCommon(const GlobalObject& aGlobal,
const nsAString& aUrl,
const Sequence<nsString>& aProtocols,
nsITransportProvider* aTransportProvider,
const nsACString& aNegotiatedExtensions,
ErrorResult& aRv)
{
MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsPIDOMWindowInner> ownerWindow;
@ -1229,8 +1256,9 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
bool connectionFailed = true;
if (NS_IsMainThread()) {
webSocket->mImpl->Init(aGlobal.Context(), principal, aUrl, protocolArray,
EmptyCString(), 0, 0, aRv, &connectionFailed);
webSocket->mImpl->Init(aGlobal.Context(), principal, !!aTransportProvider,
aUrl, protocolArray, EmptyCString(),
0, 0, aRv, &connectionFailed);
} else {
// In workers we have to keep the worker alive using a feature in order to
// dispatch messages correctly.
@ -1247,9 +1275,9 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
}
RefPtr<InitRunnable> runnable =
new InitRunnable(webSocket->mImpl, aUrl, protocolArray,
nsDependentCString(file.get()), lineno, column, aRv,
&connectionFailed);
new InitRunnable(webSocket->mImpl, !!aTransportProvider, aUrl,
protocolArray, nsDependentCString(file.get()), lineno,
column, aRv, &connectionFailed);
runnable->Dispatch(aRv);
}
@ -1327,8 +1355,11 @@ WebSocket::Constructor(const GlobalObject& aGlobal,
windowID = topInner->WindowID();
}
webSocket->mImpl->AsyncOpen(principal, windowID, aRv);
webSocket->mImpl->AsyncOpen(principal, windowID, aTransportProvider,
aNegotiatedExtensions, aRv);
} else {
MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(),
"not yet implemented");
RefPtr<AsyncOpenRunnable> runnable =
new AsyncOpenRunnable(webSocket->mImpl, aRv);
runnable->Dispatch(aRv);
@ -1425,6 +1456,7 @@ WebSocket::DisconnectFromOwner()
void
WebSocketImpl::Init(JSContext* aCx,
nsIPrincipal* aPrincipal,
bool aIsServerSide,
const nsAString& aURL,
nsTArray<nsString>& aProtocolArray,
const nsACString& aScriptFile,
@ -1484,6 +1516,8 @@ WebSocketImpl::Init(JSContext* aCx,
}
}
mIsServerSide = aIsServerSide;
// If we don't have aCx, we are window-less, so we don't have a
// inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in
// DedicateWorkers created by JSM.
@ -1497,19 +1531,6 @@ WebSocketImpl::Init(JSContext* aCx,
return;
}
nsCOMPtr<nsIURI> uri;
{
nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
// We crash here because we are sure that mURI is a valid URI, so either we
// are OOM'ing or something else bad is happening.
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_CRASH();
}
}
// Check content policy.
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
nsCOMPtr<nsIDocument> originDoc = mWebSocket->GetDocumentIfCurrent();
if (!originDoc) {
nsresult rv = mWebSocket->CheckInnerWindowCorrectness();
@ -1518,25 +1539,40 @@ WebSocketImpl::Init(JSContext* aCx,
return;
}
}
mOriginDocument = do_GetWeakReference(originDoc);
aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
uri,
aPrincipal,
originDoc,
EmptyCString(),
nullptr,
&shouldLoad,
nsContentUtils::GetContentPolicy(),
nsContentUtils::GetSecurityManager());
if (NS_WARN_IF(aRv.Failed())) {
return;
}
if (NS_CP_REJECTED(shouldLoad)) {
// Disallowed by content policy.
aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
return;
if (!mIsServerSide) {
nsCOMPtr<nsIURI> uri;
{
nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
// We crash here because we are sure that mURI is a valid URI, so either we
// are OOM'ing or something else bad is happening.
if (NS_WARN_IF(NS_FAILED(rv))) {
MOZ_CRASH();
}
}
// Check content policy.
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
aRv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_WEBSOCKET,
uri,
aPrincipal,
originDoc,
EmptyCString(),
nullptr,
&shouldLoad,
nsContentUtils::GetContentPolicy(),
nsContentUtils::GetSecurityManager());
if (NS_WARN_IF(aRv.Failed())) {
return;
}
if (NS_CP_REJECTED(shouldLoad)) {
// Disallowed by content policy.
aRv.Throw(NS_ERROR_CONTENT_BLOCKED);
return;
}
}
// Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
@ -1544,7 +1580,8 @@ WebSocketImpl::Init(JSContext* aCx,
// to reflect that upgrade. Please note that we can not upgrade from ws:
// to wss: before performing content policy checks because CSP needs to
// send reports in case the scheme is about to be upgraded.
if (!mSecure && originDoc && originDoc->GetUpgradeInsecureRequests(false)) {
if (!mIsServerSide && !mSecure && originDoc &&
originDoc->GetUpgradeInsecureRequests(false)) {
// let's use the old specification before the upgrade for logging
NS_ConvertUTF8toUTF16 reportSpec(mURI);
@ -1567,7 +1604,7 @@ WebSocketImpl::Init(JSContext* aCx,
}
// Don't allow https:// to open ws://
if (!mSecure &&
if (!mIsServerSide && !mSecure &&
!Preferences::GetBool("network.websocket.allowInsecureFromHTTPS",
false)) {
// Confirmed we are opening plain ws:// and want to prevent this from a
@ -1704,9 +1741,12 @@ WebSocketImpl::Init(JSContext* aCx,
void
WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
nsITransportProvider* aTransportProvider,
const nsACString& aNegotiatedExtensions,
ErrorResult& aRv)
{
MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread");
MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty());
nsCString asciiOrigin;
aRv = nsContentUtils::GetASCIIOrigin(aPrincipal, asciiOrigin);
@ -1714,11 +1754,20 @@ WebSocketImpl::AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID,
return;
}
if (aTransportProvider) {
aRv = mChannel->SetServerParameters(aTransportProvider, aNegotiatedExtensions);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
}
ToLowerCase(asciiOrigin);
nsCOMPtr<nsIURI> uri;
aRv = NS_NewURI(getter_AddRefs(uri), mURI);
MOZ_ASSERT(!aRv.Failed());
if (!aTransportProvider) {
aRv = NS_NewURI(getter_AddRefs(uri), mURI);
MOZ_ASSERT(!aRv.Failed());
}
aRv = mChannel->AsyncOpen(uri, asciiOrigin, aInnerWindowID, this, nullptr);
if (NS_WARN_IF(aRv.Failed())) {
@ -1984,6 +2033,13 @@ WebSocketImpl::ParseURL(const nsAString& aURL)
AssertIsOnMainThread();
NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR);
if (mIsServerSide) {
mWebSocket->mURI = aURL;
CopyUTF16toUTF8(mWebSocket->mURI, mURI);
return NS_OK;
}
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL);
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR);

View File

@ -24,6 +24,7 @@
#define DEFAULT_WSS_SCHEME_PORT 443
class nsIInputStream;
class nsITransportProvider;
namespace mozilla {
namespace dom {
@ -82,6 +83,13 @@ public: // WebIDL interface:
const Sequence<nsString>& aProtocols,
ErrorResult& rv);
static already_AddRefed<WebSocket> ConstructorCommon(const GlobalObject& aGlobal,
const nsAString& aUrl,
const Sequence<nsString>& aProtocols,
nsITransportProvider* aTransportProvider,
const nsACString& aNegotiatedExtensions,
ErrorResult& rv);
// webIDL: readonly attribute DOMString url
void GetUrl(nsAString& aResult);

View File

@ -950,6 +950,7 @@ GK_ATOM(onwebkitAnimationEnd, "onwebkitAnimationEnd")
GK_ATOM(onwebkitAnimationIteration, "onwebkitAnimationIteration")
GK_ATOM(onwebkitAnimationStart, "onwebkitAnimationStart")
GK_ATOM(onwebkitTransitionEnd, "onwebkitTransitionEnd")
GK_ATOM(onwebsocket, "onwebsocket")
GK_ATOM(onwheel, "onwheel")
GK_ATOM(open, "open")
GK_ATOM(optgroup, "optgroup")

View File

@ -16,6 +16,7 @@
#include "LoadInfo.h"
#include "nsIDOMNode.h"
#include "mozilla/dom/ContentChild.h"
#include "nsITransportProvider.h"
using mozilla::dom::ContentChild;
@ -39,6 +40,7 @@ BaseWebSocketChannel::BaseWebSocketChannel()
, mClientSetPingTimeout(0)
, mEncrypted(0)
, mPingForced(0)
, mIsServerSide(false)
, mPingInterval(0)
, mPingResponseTimeout(10000)
{
@ -243,6 +245,17 @@ BaseWebSocketChannel::SetSerial(uint32_t aSerial)
return NS_OK;
}
NS_IMETHODIMP
BaseWebSocketChannel::SetServerParameters(nsITransportProvider* aProvider,
const nsACString& aNegotiatedExtensions)
{
MOZ_ASSERT(aProvider);
mServerTransportProvider = aProvider;
mNegotiatedExtensions = aNegotiatedExtensions;
mIsServerSide = true;
return NS_OK;
}
//-----------------------------------------------------------------------------
// BaseWebSocketChannel::nsIProtocolHandler
//-----------------------------------------------------------------------------

View File

@ -57,6 +57,8 @@ class BaseWebSocketChannel : public nsIWebSocketChannel,
uint32_t aContentPolicyType) override;
NS_IMETHOD GetSerial(uint32_t* aSerial) override;
NS_IMETHOD SetSerial(uint32_t aSerial) override;
NS_IMETHOD SetServerParameters(nsITransportProvider* aProvider,
const nsACString& aNegotiatedExtensions) override;
// Off main thread URI access.
virtual void GetEffectiveURL(nsAString& aEffectiveURL) const = 0;
@ -85,6 +87,7 @@ class BaseWebSocketChannel : public nsIWebSocketChannel,
nsCOMPtr<nsILoadGroup> mLoadGroup;
nsCOMPtr<nsILoadInfo> mLoadInfo;
nsCOMPtr<nsIEventTarget> mTargetThread;
nsCOMPtr<nsITransportProvider> mServerTransportProvider;
nsCString mProtocol;
nsCString mOrigin;
@ -97,6 +100,7 @@ class BaseWebSocketChannel : public nsIWebSocketChannel,
Atomic<bool> mEncrypted;
bool mPingForced;
bool mIsServerSide;
uint32_t mPingInterval; /* milliseconds */
uint32_t mPingResponseTimeout; /* milliseconds */

View File

@ -40,6 +40,8 @@
#include "nsThreadUtils.h"
#include "nsINetworkLinkService.h"
#include "nsIObserverService.h"
#include "nsITransportProvider.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsAutoPtr.h"
#include "nsNetCID.h"
@ -761,8 +763,8 @@ class PMCECompression
{
public:
PMCECompression(bool aNoContextTakeover,
int32_t aClientMaxWindowBits,
int32_t aServerMaxWindowBits)
int32_t aLocalMaxWindowBits,
int32_t aRemoteMaxWindowBits)
: mActive(false)
, mNoContextTakeover(aNoContextTakeover)
, mResetDeflater(false)
@ -775,8 +777,8 @@ public:
mDeflater.opaque = mInflater.opaque = Z_NULL;
if (deflateInit2(&mDeflater, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
-aClientMaxWindowBits, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
if (inflateInit2(&mInflater, -aServerMaxWindowBits) == Z_OK) {
-aLocalMaxWindowBits, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
if (inflateInit2(&mInflater, -aRemoteMaxWindowBits) == Z_OK) {
mActive = true;
} else {
deflateEnd(&mDeflater);
@ -1072,7 +1074,7 @@ public:
}
if (!aCompressor->UsingContextTakeover() && temp->Length() > mLength) {
// When "client_no_context_takeover" was negotiated, do not send deflated
// When "<local>_no_context_takeover" was negotiated, do not send deflated
// payload if it's larger that the original one. OTOH, it makes sense
// to send the larger deflated payload when the sliding window is not
// reset between messages because if we would skip some deflated block
@ -1574,15 +1576,32 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n",
opcode));
if (!maskBit && mIsServerSide) {
LOG(("WebSocketChannel::ProcessInput: unmasked frame received "
"from client\n"));
return NS_ERROR_ILLEGAL_VALUE;
}
if (maskBit) {
// This is unexpected - the server does not generally send masked
// frames to the client, but it is allowed
LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
if (!mIsServerSide) {
// The server should not be allowed to send masked frames to clients.
// But we've been allowing it for some time, so this should be
// deprecated with care.
LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
}
mask = NetworkEndian::readUint32(payload - 4);
ApplyMask(mask, payload, payloadLength);
}
if (mask) {
ApplyMask(mask, payload, payloadLength);
} else if (mIsServerSide) {
LOG(("WebSocketChannel::ProcessInput: masked frame with mask 0 received"
"from client\n"));
return NS_ERROR_ILLEGAL_VALUE;
}
// Control codes are required to have the fin bit set
if (!finBit && (opcode & kControlFrameMask)) {
LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode));
@ -2021,6 +2040,9 @@ WebSocketChannel::PrimeNewOutgoingMessage()
mCurrentOutSent = 0;
mHdrOut = mOutHeader;
uint8_t maskBit = mIsServerSide ? 0 : kMaskBit;
uint8_t maskSize = mIsServerSide ? 0 : 4;
uint8_t *payload = nullptr;
if (msgType == kMsgTypeFin) {
@ -2033,10 +2055,10 @@ WebSocketChannel::PrimeNewOutgoingMessage()
mClientClosed = 1;
mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_CLOSE;
mOutHeader[1] = kMaskBit;
mOutHeader[1] = maskBit;
// payload is offset 6 including 4 for the mask
payload = mOutHeader + 6;
// payload is offset 2 plus size of the mask
payload = mOutHeader + 2 + maskSize;
// The close reason code sits in the first 2 bytes of payload
// If the channel user provided a code and reason during Close()
@ -2045,7 +2067,7 @@ WebSocketChannel::PrimeNewOutgoingMessage()
if (mScriptCloseCode) {
NetworkEndian::writeUint16(payload, mScriptCloseCode);
mOutHeader[1] += 2;
mHdrOutToSend = 8;
mHdrOutToSend = 4 + maskSize;
if (!mScriptCloseReason.IsEmpty()) {
MOZ_ASSERT(mScriptCloseReason.Length() <= 123,
"Close Reason Too Long");
@ -2059,12 +2081,12 @@ WebSocketChannel::PrimeNewOutgoingMessage()
// No close code/reason, so payload length = 0. We must still send mask
// even though it's not used. Keep payload offset so we write mask
// below.
mHdrOutToSend = 6;
mHdrOutToSend = 2 + maskSize;
}
} else {
NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose));
mOutHeader[1] += 2;
mHdrOutToSend = 8;
mHdrOutToSend = 4 + maskSize;
}
if (mServerClosed) {
@ -2131,38 +2153,40 @@ WebSocketChannel::PrimeNewOutgoingMessage()
}
if (mCurrentOut->Length() < 126) {
mOutHeader[1] = mCurrentOut->Length() | kMaskBit;
mHdrOutToSend = 6;
mOutHeader[1] = mCurrentOut->Length() | maskBit;
mHdrOutToSend = 2 + maskSize;
} else if (mCurrentOut->Length() <= 0xffff) {
mOutHeader[1] = 126 | kMaskBit;
mOutHeader[1] = 126 | maskBit;
NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t),
mCurrentOut->Length());
mHdrOutToSend = 8;
mHdrOutToSend = 4 + maskSize;
} else {
mOutHeader[1] = 127 | kMaskBit;
mOutHeader[1] = 127 | maskBit;
NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length());
mHdrOutToSend = 14;
mHdrOutToSend = 10 + maskSize;
}
payload = mOutHeader + mHdrOutToSend;
}
MOZ_ASSERT(payload, "payload offset not found");
// Perform the sending mask. Never use a zero mask
uint32_t mask;
do {
uint8_t *buffer;
nsresult rv = mRandomGenerator->GenerateRandomBytes(4, &buffer);
if (NS_FAILED(rv)) {
LOG(("WebSocketChannel::PrimeNewOutgoingMessage(): "
"GenerateRandomBytes failure %x\n", rv));
StopSession(rv);
return;
}
mask = * reinterpret_cast<uint32_t *>(buffer);
free(buffer);
} while (!mask);
NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
uint32_t mask = 0;
if (!mIsServerSide) {
// Perform the sending mask. Never use a zero mask
do {
uint8_t *buffer;
nsresult rv = mRandomGenerator->GenerateRandomBytes(4, &buffer);
if (NS_FAILED(rv)) {
LOG(("WebSocketChannel::PrimeNewOutgoingMessage(): "
"GenerateRandomBytes failure %x\n", rv));
StopSession(rv);
return;
}
mask = * reinterpret_cast<uint32_t *>(buffer);
free(buffer);
} while (!mask);
NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
}
LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask));
@ -2189,16 +2213,17 @@ WebSocketChannel::PrimeNewOutgoingMessage()
mService->FrameSent(mSerial, mInnerWindowID, frame.forget());
}
while (payload < (mOutHeader + mHdrOutToSend)) {
*payload ^= mask >> 24;
mask = RotateLeft(mask, 8);
payload++;
if (mask) {
while (payload < (mOutHeader + mHdrOutToSend)) {
*payload ^= mask >> 24;
mask = RotateLeft(mask, 8);
payload++;
}
// Mask the real message payloads
ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length());
}
// Mask the real message payloads
ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length());
int32_t len = mCurrentOut->Length();
// for small frames, copy it all together for a contiguous write
@ -2313,10 +2338,10 @@ WebSocketChannel::StopSession(nsresult reason)
if (!mOpenedHttpChannel) {
// The HTTP channel information will never be used in this case
mChannel = nullptr;
mHttpChannel = nullptr;
mLoadGroup = nullptr;
mCallbacks = nullptr;
NS_ReleaseOnMainThread(mChannel.forget());
NS_ReleaseOnMainThread(mHttpChannel.forget());
NS_ReleaseOnMainThread(mLoadGroup.forget());
NS_ReleaseOnMainThread(mCallbacks.forget());
}
if (mCloseTimer) {
@ -2477,6 +2502,128 @@ WebSocketChannel::DecrementSessionCount()
}
}
namespace {
enum ExtensionParseMode { eParseServerSide, eParseClientSide };
}
static nsresult
ParseWebSocketExtension(const nsACString& aExtension,
ExtensionParseMode aMode,
bool& aClientNoContextTakeover,
bool& aServerNoContextTakeover,
int32_t& aClientMaxWindowBits,
int32_t& aServerMaxWindowBits)
{
nsCCharSeparatedTokenizer tokens(aExtension, ';');
if (!tokens.hasMoreTokens() ||
!tokens.nextToken().Equals(NS_LITERAL_CSTRING("permessage-deflate"))) {
LOG(("WebSocketChannel::ParseWebSocketExtension: "
"HTTP Sec-WebSocket-Extensions negotiated unknown value %s\n",
PromiseFlatCString(aExtension).get()));
return NS_ERROR_ILLEGAL_VALUE;
}
aClientNoContextTakeover = aServerNoContextTakeover = false;
aClientMaxWindowBits = aServerMaxWindowBits = -1;
while (tokens.hasMoreTokens()) {
auto token = tokens.nextToken();
int32_t nameEnd, valueStart;
int32_t delimPos = token.FindChar('=');
if (delimPos == kNotFound) {
nameEnd = token.Length();
valueStart = token.Length();
} else {
nameEnd = delimPos;
valueStart = delimPos + 1;
}
auto paramName = Substring(token, 0, nameEnd);
auto paramValue = Substring(token, valueStart);
if (paramName.EqualsLiteral("client_no_context_takeover")) {
if (!paramValue.IsEmpty()) {
LOG(("WebSocketChannel::ParseWebSocketExtension: parameter "
"client_no_context_takeover must not have value, found %s\n",
PromiseFlatCString(paramValue).get()));
return NS_ERROR_ILLEGAL_VALUE;
}
if (aClientNoContextTakeover) {
LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
"parameters client_no_context_takeover\n"));
return NS_ERROR_ILLEGAL_VALUE;
}
aClientNoContextTakeover = true;
} else if (paramName.EqualsLiteral("server_no_context_takeover")) {
if (!paramValue.IsEmpty()) {
LOG(("WebSocketChannel::ParseWebSocketExtension: parameter "
"server_no_context_takeover must not have value, found %s\n",
PromiseFlatCString(paramValue).get()));
return NS_ERROR_ILLEGAL_VALUE;
}
if (aServerNoContextTakeover) {
LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
"parameters server_no_context_takeover\n"));
return NS_ERROR_ILLEGAL_VALUE;
}
aServerNoContextTakeover = true;
} else if (paramName.EqualsLiteral("client_max_window_bits")) {
if (aClientMaxWindowBits != -1) {
LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
"parameters client_max_window_bits\n"));
return NS_ERROR_ILLEGAL_VALUE;
}
if (aMode == eParseServerSide && paramValue.IsEmpty()) {
// Use -2 to indicate that "client_max_window_bits" has been parsed,
// but had no value.
aClientMaxWindowBits = -2;
}
else {
nsresult errcode;
aClientMaxWindowBits =
PromiseFlatCString(paramValue).ToInteger(&errcode);
if (NS_FAILED(errcode) || aClientMaxWindowBits < 8 ||
aClientMaxWindowBits > 15) {
LOG(("WebSocketChannel::ParseWebSocketExtension: found invalid "
"parameter client_max_window_bits %s\n",
PromiseFlatCString(paramValue).get()));
return NS_ERROR_ILLEGAL_VALUE;
}
}
} else if (paramName.EqualsLiteral("server_max_window_bits")) {
if (aServerMaxWindowBits != -1) {
LOG(("WebSocketChannel::ParseWebSocketExtension: found multiple "
"parameters server_max_window_bits\n"));
return NS_ERROR_ILLEGAL_VALUE;
}
nsresult errcode;
aServerMaxWindowBits =
PromiseFlatCString(paramValue).ToInteger(&errcode);
if (NS_FAILED(errcode) || aServerMaxWindowBits < 8 ||
aServerMaxWindowBits > 15) {
LOG(("WebSocketChannel::ParseWebSocketExtension: found invalid "
"parameter server_max_window_bits %s\n",
PromiseFlatCString(paramValue).get()));
return NS_ERROR_ILLEGAL_VALUE;
}
} else {
LOG(("WebSocketChannel::ParseWebSocketExtension: found unknown "
"parameter %s\n", PromiseFlatCString(paramName).get()));
return NS_ERROR_ILLEGAL_VALUE;
}
}
if (aClientMaxWindowBits == -2) {
aClientMaxWindowBits = -1;
}
return NS_OK;
}
nsresult
WebSocketChannel::HandleExtensions()
{
@ -2489,168 +2636,137 @@ WebSocketChannel::HandleExtensions()
rv = mHttpChannel->GetResponseHeader(
NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions);
if (NS_SUCCEEDED(rv)) {
LOG(("WebSocketChannel::HandleExtensions: received "
"Sec-WebSocket-Extensions header: %s\n", extensions.get()));
extensions.CompressWhitespace();
if (extensions.IsEmpty()) {
return NS_OK;
}
extensions.CompressWhitespace();
LOG(("WebSocketChannel::HandleExtensions: received "
"Sec-WebSocket-Extensions header: %s\n", extensions.get()));
if (!extensions.IsEmpty()) {
if (StringBeginsWith(extensions,
NS_LITERAL_CSTRING("permessage-deflate"))) {
if (!mAllowPMCE) {
LOG(("WebSocketChannel::HandleExtensions: "
"Recvd permessage-deflate which wasn't offered\n"));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
bool clientNoContextTakeover;
bool serverNoContextTakeover;
int32_t clientMaxWindowBits;
int32_t serverMaxWindowBits;
bool skipExtensionName = false;
bool clientNoContextTakeover = false;
bool serverNoContextTakeover = false;
int32_t clientMaxWindowBits = -1;
int32_t serverMaxWindowBits = -1;
rv = ParseWebSocketExtension(extensions,
eParseClientSide,
clientNoContextTakeover,
serverNoContextTakeover,
clientMaxWindowBits,
serverMaxWindowBits);
if (NS_FAILED(rv)) {
AbortSession(rv);
return rv;
}
while (!extensions.IsEmpty()) {
nsAutoCString paramName;
nsAutoCString paramValue;
if (!mAllowPMCE) {
LOG(("WebSocketChannel::HandleExtensions: "
"Recvd permessage-deflate which wasn't offered\n"));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
int32_t delimPos = extensions.FindChar(';');
if (delimPos != kNotFound) {
paramName = Substring(extensions, 0, delimPos);
extensions = Substring(extensions, delimPos + 1);
} else {
paramName = extensions;
extensions.Truncate();
}
paramName.CompressWhitespace();
extensions.CompressWhitespace();
if (clientMaxWindowBits == -1) {
clientMaxWindowBits = 15;
}
if (serverMaxWindowBits == -1) {
serverMaxWindowBits = 15;
}
delimPos = paramName.FindChar('=');
if (delimPos != kNotFound) {
paramValue = Substring(paramName, delimPos + 1);
paramName.Truncate(delimPos);
}
mPMCECompressor = new PMCECompression(clientNoContextTakeover,
clientMaxWindowBits,
serverMaxWindowBits);
if (mPMCECompressor->Active()) {
LOG(("WebSocketChannel::HandleExtensions: PMCE negotiated, %susing "
"context takeover, clientMaxWindowBits=%d, "
"serverMaxWindowBits=%d\n",
clientNoContextTakeover ? "NOT " : "", clientMaxWindowBits,
serverMaxWindowBits));
if (!skipExtensionName) {
skipExtensionName = true;
if (!paramName.EqualsLiteral("permessage-deflate") ||
!paramValue.IsEmpty()) {
LOG(("WebSocketChannel::HandleExtensions: HTTP "
"Sec-WebSocket-Extensions negotiated unknown extension\n"));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
} else if (paramName.EqualsLiteral("client_no_context_takeover")) {
if (!paramValue.IsEmpty()) {
LOG(("WebSocketChannel::HandleExtensions: parameter "
"client_no_context_takeover must not have value, found %s\n",
paramValue.get()));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
if (clientNoContextTakeover) {
LOG(("WebSocketChannel::HandleExtensions: found multiple "
"parameters client_no_context_takeover\n"));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
clientNoContextTakeover = true;
} else if (paramName.EqualsLiteral("server_no_context_takeover")) {
if (!paramValue.IsEmpty()) {
LOG(("WebSocketChannel::HandleExtensions: parameter "
"server_no_context_takeover must not have value, found %s\n",
paramValue.get()));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
if (serverNoContextTakeover) {
LOG(("WebSocketChannel::HandleExtensions: found multiple "
"parameters server_no_context_takeover\n"));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
serverNoContextTakeover = true;
} else if (paramName.EqualsLiteral("client_max_window_bits")) {
if (clientMaxWindowBits != -1) {
LOG(("WebSocketChannel::HandleExtensions: found multiple "
"parameters client_max_window_bits\n"));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
nsresult errcode;
clientMaxWindowBits = paramValue.ToInteger(&errcode);
if (NS_FAILED(errcode) || clientMaxWindowBits < 8 ||
clientMaxWindowBits > 15) {
LOG(("WebSocketChannel::HandleExtensions: found invalid "
"parameter client_max_window_bits %s\n", paramValue.get()));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
} else if (paramName.EqualsLiteral("server_max_window_bits")) {
if (serverMaxWindowBits != -1) {
LOG(("WebSocketChannel::HandleExtensions: found multiple "
"parameters server_max_window_bits\n"));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
nsresult errcode;
serverMaxWindowBits = paramValue.ToInteger(&errcode);
if (NS_FAILED(errcode) || serverMaxWindowBits < 8 ||
serverMaxWindowBits > 15) {
LOG(("WebSocketChannel::HandleExtensions: found invalid "
"parameter server_max_window_bits %s\n", paramValue.get()));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
} else {
LOG(("WebSocketChannel::HandleExtensions: found unknown "
"parameter %s\n", paramName.get()));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
}
if (clientMaxWindowBits == -1) {
clientMaxWindowBits = 15;
}
if (serverMaxWindowBits == -1) {
serverMaxWindowBits = 15;
}
mPMCECompressor = new PMCECompression(clientNoContextTakeover,
clientMaxWindowBits,
serverMaxWindowBits);
if (mPMCECompressor->Active()) {
LOG(("WebSocketChannel::HandleExtensions: PMCE negotiated, %susing "
"context takeover, clientMaxWindowBits=%d, "
"serverMaxWindowBits=%d\n",
clientNoContextTakeover ? "NOT " : "", clientMaxWindowBits,
serverMaxWindowBits));
mNegotiatedExtensions = "permessage-deflate";
} else {
LOG(("WebSocketChannel::HandleExtensions: Cannot init PMCE "
"compression object\n"));
mPMCECompressor = nullptr;
AbortSession(NS_ERROR_UNEXPECTED);
return NS_ERROR_UNEXPECTED;
}
} else {
LOG(("WebSocketChannel::HandleExtensions: "
"HTTP Sec-WebSocket-Extensions negotiated unknown value %s\n",
extensions.get()));
AbortSession(NS_ERROR_ILLEGAL_VALUE);
return NS_ERROR_ILLEGAL_VALUE;
}
}
mNegotiatedExtensions = "permessage-deflate";
} else {
LOG(("WebSocketChannel::HandleExtensions: Cannot init PMCE "
"compression object\n"));
mPMCECompressor = nullptr;
AbortSession(NS_ERROR_UNEXPECTED);
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
void
ProcessServerWebSocketExtensions(const nsACString& aExtensions,
nsACString& aNegotiatedExtensions)
{
aNegotiatedExtensions.Truncate();
nsCOMPtr<nsIPrefBranch> prefService =
do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefService) {
bool boolpref;
nsresult rv = prefService->
GetBoolPref("network.websocket.extensions.permessage-deflate", &boolpref);
if (NS_SUCCEEDED(rv) && !boolpref) {
return;
}
}
nsCCharSeparatedTokenizer extList(aExtensions, ',');
while (extList.hasMoreTokens()) {
bool clientNoContextTakeover;
bool serverNoContextTakeover;
int32_t clientMaxWindowBits;
int32_t serverMaxWindowBits;
nsresult rv = ParseWebSocketExtension(extList.nextToken(),
eParseServerSide,
clientNoContextTakeover,
serverNoContextTakeover,
clientMaxWindowBits,
serverMaxWindowBits);
if (NS_FAILED(rv)) {
// Ignore extensions that we can't parse
continue;
}
aNegotiatedExtensions.AssignLiteral("permessage-deflate");
if (clientNoContextTakeover) {
aNegotiatedExtensions.AppendLiteral(";client_no_context_takeover");
}
if (serverNoContextTakeover) {
aNegotiatedExtensions.AppendLiteral(";server_no_context_takeover");
}
if (clientMaxWindowBits != -1) {
aNegotiatedExtensions.AppendLiteral(";client_max_window_bits=");
aNegotiatedExtensions.AppendInt(clientMaxWindowBits);
}
if (serverMaxWindowBits != -1) {
aNegotiatedExtensions.AppendLiteral(";server_max_window_bits=");
aNegotiatedExtensions.AppendInt(serverMaxWindowBits);
}
return;
}
}
nsresult
CalculateWebSocketHashedSecret(const nsACString& aKey, nsACString& aHash)
{
nsresult rv;
nsCString key =
aKey + NS_LITERAL_CSTRING("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
nsCOMPtr<nsICryptoHash> hasher =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Init(nsICryptoHash::SHA1);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Update((const uint8_t *)key.BeginWriting(), key.Length());
NS_ENSURE_SUCCESS(rv, rv);
return hasher->Finish(true, aHash);
}
nsresult
WebSocketChannel::SetupRequest()
{
@ -2716,16 +2832,7 @@ WebSocketChannel::SetupRequest()
// prepare the value we expect to see in
// the sec-websocket-accept response header
secKeyString.AppendLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
nsCOMPtr<nsICryptoHash> hasher =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Init(nsICryptoHash::SHA1);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Update((const uint8_t *) secKeyString.BeginWriting(),
secKeyString.Length());
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Finish(true, mHashedSecret);
rv = CalculateWebSocketHashedSecret(secKeyString, mHashedSecret);
NS_ENSURE_SUCCESS(rv, rv);
LOG(("WebSocketChannel::SetupRequest: expected server key %s\n",
mHashedSecret.get()));
@ -3182,7 +3289,7 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI,
return NS_ERROR_UNEXPECTED;
}
if (!aURI || !aListener) {
if ((!aURI && !mIsServerSide) || !aListener) {
LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
return NS_ERROR_UNEXPECTED;
}
@ -3203,13 +3310,6 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI,
return rv;
}
mRandomGenerator =
do_GetService("@mozilla.org/security/random-generator;1", &rv);
if (NS_FAILED(rv)) {
NS_WARNING("unable to continue without random number generator");
return rv;
}
nsCOMPtr<nsIPrefBranch> prefService;
prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
@ -3273,11 +3373,29 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI,
return NS_ERROR_SOCKET_CREATE_FAILED;
}
mInnerWindowID = aInnerWindowID;
mOriginalURI = aURI;
mURI = mOriginalURI;
mURI->GetHostPort(mHost);
mOrigin = aOrigin;
mInnerWindowID = aInnerWindowID;
if (mIsServerSide) {
//IncrementSessionCount();
mWasOpened = 1;
mListenerMT = new ListenerAndContextContainer(aListener, aContext);
mServerTransportProvider->SetListener(this);
mServerTransportProvider = nullptr;
return NS_OK;
}
mURI->GetHostPort(mHost);
mRandomGenerator =
do_GetService("@mozilla.org/security/random-generator;1", &rv);
if (NS_FAILED(rv)) {
NS_WARNING("unable to continue without random number generator");
return rv;
}
nsCOMPtr<nsIURI> localURI;
nsCOMPtr<nsIChannel> localChannel;
@ -3540,6 +3658,51 @@ WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
return StartWebsocketData();
}
if (mIsServerSide) {
if (!mNegotiatedExtensions.IsEmpty()) {
bool clientNoContextTakeover;
bool serverNoContextTakeover;
int32_t clientMaxWindowBits;
int32_t serverMaxWindowBits;
rv = ParseWebSocketExtension(mNegotiatedExtensions,
eParseServerSide,
clientNoContextTakeover,
serverNoContextTakeover,
clientMaxWindowBits,
serverMaxWindowBits);
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "illegal value provided by server");
if (clientMaxWindowBits == -1) {
clientMaxWindowBits = 15;
}
if (serverMaxWindowBits == -1) {
serverMaxWindowBits = 15;
}
mPMCECompressor = new PMCECompression(serverNoContextTakeover,
serverMaxWindowBits,
clientMaxWindowBits);
if (mPMCECompressor->Active()) {
LOG(("WebSocketChannel::OnTransportAvailable: PMCE negotiated, %susing "
"context takeover, serverMaxWindowBits=%d, "
"clientMaxWindowBits=%d\n",
serverNoContextTakeover ? "NOT " : "", serverMaxWindowBits,
clientMaxWindowBits));
mNegotiatedExtensions = "permessage-deflate";
} else {
LOG(("WebSocketChannel::OnTransportAvailable: Cannot init PMCE "
"compression object\n"));
mPMCECompressor = nullptr;
AbortSession(NS_ERROR_UNEXPECTED);
return NS_ERROR_UNEXPECTED;
}
}
return StartWebsocketData();
}
return NS_OK;
}

View File

@ -52,6 +52,12 @@ class CallOnServerClose;
class CallAcknowledge;
class WebSocketEventService;
extern nsresult
CalculateWebSocketHashedSecret(const nsACString& aKey, nsACString& aHash);
extern void
ProcessServerWebSocketExtensions(const nsACString& aExtensions,
nsACString& aNegotiatedExtensions);
// Used to enforce "1 connecting websocket per host" rule, and reconnect delays
enum wsConnectingState {
NOT_CONNECTING = 0, // Not yet (or no longer) trying to open connection

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPIDL_SOURCES += [
'nsITransportProvider.idl',
'nsIWebSocketChannel.idl',
'nsIWebSocketEventService.idl',
'nsIWebSocketListener.idl',

View File

@ -0,0 +1,19 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 ts=4 et 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/. */
interface nsIHttpUpgradeListener;
#include "nsISupports.idl"
/**
* An interface which can be used to asynchronously request a nsITransport
* together with the input and output streams that go together with it.
*/
[scriptable, uuid(6fcec704-cfd2-46ef-a394-a64d5cb1475c)]
interface nsITransportProvider : nsISupports
{
void setListener(in nsIHttpUpgradeListener listener);
};

View File

@ -12,6 +12,7 @@ interface nsIInputStream;
interface nsILoadInfo;
interface nsIDOMNode;
interface nsIPrincipal;
interface nsITransportProvider;
#include "nsISupports.idl"
@ -229,6 +230,15 @@ interface nsIWebSocketChannel : nsISupports
*/
attribute unsigned long serial;
/**
* Set a nsITransportProvider and negotated extensions to be used by this
* channel. Calling this function also means that this channel will
* implement the server-side part of a websocket connection rather than the
* client-side part.
*/
void setServerParameters(in nsITransportProvider aProvider,
in ACString aNegotiatedExtensions);
%{C++
inline uint32_t Serial()
{