/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 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/. */
// We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire. #define SEC_WEBSOCKET_VERSION "13"
/* * About SSL unsigned certificates * * wss will not work to a host using an unsigned certificate unless there * is already an exception (i.e. it cannot popup a dialog asking for * a security exception). This is similar to how an inlined img will * fail without a dialog if fails for the same reason. This should not * be a problem in practice as it is expected the websocket javascript * is served from the same host as the websocket server (or of course, * a valid cert could just be provided). *
*/
// some helper classes
//----------------------------------------------------------------------------- // FailDelayManager // // Stores entries (searchable by {host, port}) of connections that have recently // failed, so we can do delay of reconnects per RFC 6455 Section 7.2.3 //-----------------------------------------------------------------------------
// Initial reconnect delay is randomly chosen between 200-400 ms. // This is a gentler backoff than the 0-5 seconds the spec offhandedly suggests. const uint32_t kWSReconnectInitialBaseDelay = 200; const uint32_t kWSReconnectInitialRandomDelay = 200;
// Base lifetime (in ms) of a FailDelay: kept longer if more failures occur const uint32_t kWSReconnectBaseLifeTime = 60 * 1000; // Maximum reconnect delay (in ms) const uint32_t kWSReconnectMaxDelay = 60 * 1000;
// hold record of failed connections, and calculates needed delay for reconnects // to same host/path/port. class FailDelay { public:
FailDelay(nsCString address, nsCString path, int32_t port)
: mAddress(std::move(address)), mPath(std::move(path)), mPort(port) {
mLastFailure = TimeStamp::Now();
mNextDelay = kWSReconnectInitialBaseDelay +
(rand() % kWSReconnectInitialRandomDelay);
}
// Called to update settings when connection fails again. void FailedAgain() {
mLastFailure = TimeStamp::Now(); // We use a truncated exponential backoff as suggested by RFC 6455, // but multiply by 1.5 instead of 2 to be more gradual.
mNextDelay = static_cast<uint32_t>(
std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5));
LOG(
("WebSocket: FailedAgain: host=%s, path=%s, port=%d: incremented delay " "to " "%" PRIu32,
mAddress.get(), mPath.get(), mPort, mNextDelay));
}
// returns 0 if there is no need to delay (i.e. delay interval is over)
uint32_t RemainingDelay(TimeStamp rightNow) {
TimeDuration dur = rightNow - mLastFailure;
uint32_t sinceFail = (uint32_t)dur.ToMilliseconds(); if (sinceFail > mNextDelay) return 0;
nsCString mAddress; // IP address (or hostname if using proxy)
nsCString mPath;
int32_t mPort;
private:
TimeStamp mLastFailure; // Time of last failed attempt // mLastFailure + mNextDelay is the soonest we'll allow a reconnect
uint32_t mNextDelay; // milliseconds
};
class FailDelayManager { public:
FailDelayManager() {
MOZ_COUNT_CTOR(FailDelayManager);
// Element returned may not be valid after next main thread event: don't keep // pointer to it around
FailDelay* Lookup(nsCString& address, nsCString& path, int32_t port,
uint32_t* outIndex = nullptr) { if (mDelaysDisabled) return nullptr;
FailDelay* result = nullptr;
TimeStamp rightNow = TimeStamp::Now();
// We also remove expired entries during search: iterate from end to make // indexing simpler for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
FailDelay* fail = mEntries[i].get(); if (fail->mAddress.Equals(address) && fail->mPath.Equals(path) &&
fail->mPort == port) { if (outIndex) *outIndex = i;
result = fail; // break here: removing more entries would mess up *outIndex. // Any remaining expired entries will be deleted next time Lookup // finds nothing, which is the most common case anyway. break;
} if (fail->IsExpired(rightNow)) {
mEntries.RemoveElementAt(i);
}
} return result;
}
// returns true if channel connects immediately, or false if it's delayed void DelayOrBegin(WebSocketChannel* ws) { if (!mDelaysDisabled) {
uint32_t failIndex = 0;
FailDelay* fail = Lookup(ws->mAddress, ws->mPath, ws->mPort, &failIndex);
if (fail) {
TimeStamp rightNow = TimeStamp::Now();
uint32_t remainingDelay = fail->RemainingDelay(rightNow); if (remainingDelay) { // reconnecting within delay interval: delay by remaining time
nsresult rv;
MutexAutoLock lock(ws->mMutex);
rv = NS_NewTimerWithCallback(getter_AddRefs(ws->mReconnectDelayTimer),
ws, remainingDelay,
nsITimer::TYPE_ONE_SHOT); if (NS_SUCCEEDED(rv)) {
LOG(
("WebSocket: delaying websocket [this=%p] by %lu ms, changing" " state to CONNECTING_DELAYED",
ws, (unsignedlong)remainingDelay));
ws->mConnecting = CONNECTING_DELAYED; return;
} // if timer fails (which is very unlikely), drop down to BeginOpen // call
} elseif (fail->IsExpired(rightNow)) {
mEntries.RemoveElementAt(failIndex);
}
}
}
// Delays disabled, or no previous failure, or we're reconnecting after // scheduled delay interval has passed: connect.
ws->BeginOpen(true);
}
// Remove() also deletes all expired entries as it iterates: better for // battery life than using a periodic timer. void Remove(nsCString& address, nsCString& path, int32_t port) {
TimeStamp rightNow = TimeStamp::Now();
// iterate from end, to make deletion indexing easier for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
FailDelay* entry = mEntries[i].get(); if ((entry->mAddress.Equals(address) && entry->mPath.Equals(path) &&
entry->mPort == port) ||
entry->IsExpired(rightNow)) {
mEntries.RemoveElementAt(i);
}
}
}
//----------------------------------------------------------------------------- // nsWSAdmissionManager // // 1) Ensures that only one websocket at a time is CONNECTING to a given IP // address (or hostname, if using proxy), per RFC 6455 Section 4.1. // 2) Delays reconnects to IP/host after connection failure, per Section 7.2.3 //-----------------------------------------------------------------------------
class nsWSAdmissionManager { public: staticvoid Init() {
StaticMutexAutoLock lock(sLock); if (!sManager) {
sManager = new nsWSAdmissionManager();
}
}
// Determine if we will open connection immediately (returns true), or // delay/queue the connection (returns false) staticvoid ConditionallyConnect(WebSocketChannel* ws) {
LOG(("Websocket: ConditionallyConnect: [this=%p]", ws));
MOZ_ASSERT(NS_IsMainThread(), "not main thread");
MOZ_ASSERT(ws->mConnecting == NOT_CONNECTING, "opening state");
StaticMutexAutoLock lock(sLock); if (!sManager) { return;
}
// If there is already another WS channel connecting to this IP address, // defer BeginOpen and mark as waiting in queue. bool hostFound = (sManager->IndexOf(ws->mAddress, ws->mOriginSuffix) >= 0);
// Always add ourselves to queue, even if we'll connect immediately
UniquePtr<nsOpenConn> newdata( new nsOpenConn(ws->mAddress, ws->mOriginSuffix, existingFail, ws));
// If a connection has not previously failed then prioritize it over // connections that have if (existingFail) {
sManager->mQueue.AppendElement(std::move(newdata));
} else {
uint32_t insertionIndex = sManager->IndexOfFirstFailure();
MOZ_ASSERT(insertionIndex <= sManager->mQueue.Length(), "Insertion index outside bounds");
sManager->mQueue.InsertElementAt(insertionIndex, std::move(newdata));
}
if (hostFound) {
LOG(
("Websocket: some other channel is connecting, changing state to " "CONNECTING_QUEUED"));
ws->mConnecting = CONNECTING_QUEUED;
} else {
sManager->mFailures.DelayOrBegin(ws);
}
}
MOZ_ASSERT(NS_IsMainThread(), "not main thread");
MOZ_ASSERT(aChannel->mConnecting == CONNECTING_IN_PROGRESS, "Channel completed connect, but not connecting?");
StaticMutexAutoLock lock(sLock); if (!sManager) { return;
}
LOG(("Websocket: changing state to NOT_CONNECTING"));
aChannel->mConnecting = NOT_CONNECTING;
// Remove from queue
sManager->RemoveFromQueue(aChannel);
// Connection succeeded, so stop keeping track of any previous failures
sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPath,
aChannel->mPort);
// Check for queued connections to same host. // Note: still need to check for failures, since next websocket with same // host may have different port
sManager->ConnectNext(aChannel->mAddress, aChannel->mOriginSuffix);
}
// Called every time a websocket channel ends its session (including going // away w/o ever successfully creating a connection) staticvoid OnStopSession(WebSocketChannel* aChannel, nsresult aReason) {
LOG(("Websocket: OnStopSession: [this=%p, reason=0x%08" PRIx32 "]",
aChannel, static_cast<uint32_t>(aReason)));
StaticMutexAutoLock lock(sLock); if (!sManager) { return;
}
if (NS_FAILED(aReason)) { // Have we seen this failure before?
FailDelay* knownFailure = sManager->mFailures.Lookup(
aChannel->mAddress, aChannel->mPath, aChannel->mPort); if (knownFailure) { if (aReason == NS_ERROR_NOT_CONNECTED) { // Don't count close() before connection as a network error
LOG(
("Websocket close() before connection to %s, %s, %d completed" " [this=%p]",
aChannel->mAddress.get(), aChannel->mPath.get(),
(int)aChannel->mPort, aChannel));
} else { // repeated failure to connect: increase delay for next connection
knownFailure->FailedAgain();
}
} else { // new connection failure: record it.
LOG(("WebSocket: connection to %s, %s, %d failed: [this=%p]",
aChannel->mAddress.get(), aChannel->mPath.get(),
(int)aChannel->mPort, aChannel));
sManager->mFailures.Add(aChannel->mAddress, aChannel->mPath,
aChannel->mPort);
}
}
// Only way a connecting channel may get here w/o failing is if it // was closed with GOING_AWAY (1001) because of navigation, tab // close, etc. #ifdef DEBUG
{
MutexAutoLock lock(aChannel->mMutex);
MOZ_ASSERT(
NS_FAILED(aReason) || aChannel->mScriptCloseCode == CLOSE_GOING_AWAY, "websocket closed while connecting w/o failing?");
} #endif
Unused << aReason;
sManager->RemoveFromQueue(aChannel);
bool wasNotQueued = (aChannel->mConnecting != CONNECTING_QUEUED);
LOG(("Websocket: changing state to NOT_CONNECTING"));
aChannel->mConnecting = NOT_CONNECTING; if (wasNotQueued) {
sManager->ConnectNext(aChannel->mAddress, aChannel->mOriginSuffix);
}
}
void ConnectNext(nsCString& hostName, nsCString& originSuffix) {
MOZ_ASSERT(NS_IsMainThread(), "not main thread");
int32_t index = IndexOf(hostName, originSuffix); if (index >= 0) {
WebSocketChannel* chan = mQueue[index]->mChannel;
MOZ_ASSERT(chan->mConnecting == CONNECTING_QUEUED, "transaction not queued but in queue");
LOG(("WebSocket: ConnectNext: found channel [this=%p] in queue", chan));
mFailures.DelayOrBegin(chan);
}
}
void RemoveFromQueue(WebSocketChannel* aChannel) {
LOG(("Websocket: RemoveFromQueue: [this=%p]", aChannel));
int32_t index = IndexOf(aChannel);
MOZ_ASSERT(index >= 0, "connection to remove not in queue"); if (index >= 0) {
mQueue.RemoveElementAt(index);
}
}
int32_t IndexOf(nsCString& aAddress, nsCString& aOriginSuffix) { for (uint32_t i = 0; i < mQueue.Length(); i++) { bool isPartitioned = StaticPrefs::privacy_partition_network_state() ||
StaticPrefs::privacy_firstparty_isolate(); if (aAddress == (mQueue[i])->mAddress &&
(!isPartitioned || aOriginSuffix == (mQueue[i])->mOriginSuffix)) { return i;
}
} return -1;
}
int32_t IndexOf(WebSocketChannel* aChannel) { for (uint32_t i = 0; i < mQueue.Length(); i++) { if (aChannel == (mQueue[i])->mChannel) return i;
} return -1;
}
// Returns the index of the first entry that failed, or else the last entry if // none found
uint32_t IndexOfFirstFailure() { for (uint32_t i = 0; i < mQueue.Length(); i++) { if (mQueue[i]->mFailed) return i;
} return mQueue.Length();
}
// SessionCount might be decremented from the main or the socket // thread, so manage it with atomic counters
Atomic<int32_t> mSessionCount;
// Queue for websockets that have not completed connecting yet. // The first nsOpenConn with a given address will be either be // CONNECTING_IN_PROGRESS or CONNECTING_DELAYED. Later ones with the same // hostname must be CONNECTING_QUEUED. // // We could hash hostnames instead of using a single big vector here, but the // dataset is expected to be small.
nsTArray<UniquePtr<nsOpenConn>> mQueue;
~OutboundMessage() {
MOZ_COUNT_DTOR(OutboundMessage); switch (mMsgType) { case kMsgTypeString: case kMsgTypeBinaryString: case kMsgTypePing: case kMsgTypePong: break; case kMsgTypeStream: // for now this only gets hit if msg deleted w/o being sent if (mMsg.as<StreamWithLength>().mStream) {
mMsg.as<StreamWithLength>().mStream->Close();
} break; case kMsgTypeFin: break; // do-nothing: avoid compiler warning
}
}
uint8_t* BeginWriting() {
MOZ_ASSERT(mMsgType != kMsgTypeStream, "Stream should have been converted to string by now"); if (!mMsg.as<pString>().mValue.IsVoid()) { return (uint8_t*)mMsg.as<pString>().mValue.BeginWriting();
} return nullptr;
}
uint8_t* BeginReading() {
MOZ_ASSERT(mMsgType != kMsgTypeStream, "Stream should have been converted to string by now"); if (!mMsg.as<pString>().mValue.IsVoid()) { return (uint8_t*)mMsg.as<pString>().mValue.BeginReading();
} return nullptr;
}
uint8_t* BeginOrigReading() {
MOZ_ASSERT(mMsgType != kMsgTypeStream, "Stream should have been converted to string by now"); if (!mDeflated) return BeginReading(); if (!mMsg.as<pString>().mOrigValue.IsVoid()) { return (uint8_t*)mMsg.as<pString>().mOrigValue.BeginReading();
} return nullptr;
}
bool DeflatePayload(PMCECompression* aCompressor) {
MOZ_ASSERT(mMsgType != kMsgTypeStream, "Stream should have been converted to string by now");
MOZ_ASSERT(!mDeflated);
if (!aCompressor->UsingContextTakeover() &&
temp.Length() > ref.mValue.Length()) { // 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 // we would need to empty the sliding window which could affect the // compression of the subsequent messages.
LOG(
("WebSocketChannel::OutboundMessage: Not deflating message since the " "deflated payload is larger than the original one [deflated=%zd, " "original=%zd]",
temp.Length(), ref.mValue.Length())); returnfalse;
}
if (mWasOpened) {
MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called");
MOZ_ASSERT(mStopped, "WebSocket was opened but never stopped");
}
MOZ_ASSERT(!mCancelable, "DNS/Proxy Request still alive at destruction");
MOZ_ASSERT(!mConnecting, "Should not be connecting in destructor");
if (strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0) {
nsCString converted = NS_ConvertUTF16toUTF8(data); constchar* state = converted.get();
if (strcmp(state, NS_NETWORK_LINK_DATA_CHANGED) == 0) {
LOG(("WebSocket: received network CHANGED event"));
if (!mIOThread) { // there has not been an asyncopen yet on the object and then we need // no ping.
LOG(("WebSocket: early object, no ping needed"));
} else {
mIOThread->Dispatch(
NewRunnableMethod("net::WebSocketChannel::OnNetworkChanged", this,
&WebSocketChannel::OnNetworkChanged),
NS_DISPATCH_NORMAL);
}
}
}
return NS_OK;
}
nsresult WebSocketChannel::OnNetworkChanged() { if (!mDataStarted) {
LOG(("WebSocket: data not started yet, no ping needed")); return NS_OK;
}
MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
LOG(("WebSocketChannel::OnNetworkChanged() - on socket thread %p", this));
if (mPingOutstanding) { // If there's an outstanding ping that's expected to get a pong back // we let that do its thing.
LOG(("WebSocket: pong already pending")); return NS_OK;
}
if (mPingForced) { // avoid more than one
LOG(("WebSocket: forced ping timer already fired")); return NS_OK;
}
LOG(("nsWebSocketChannel:: Generating Ping as network changed\n"));
if (!mPingTimer) { // The ping timer is only conditionally running already. If it wasn't // already created do it here.
mPingTimer = NS_NewTimer(); if (!mPingTimer) {
LOG(("WebSocket: unable to create ping timer!"));
NS_WARNING("unable to create ping timer!"); return NS_ERROR_OUT_OF_MEMORY;
}
} // Trigger the ping timeout asap to fire off a new ping. Wait just // a little bit to better avoid multi-triggers.
mPingForced = true;
mPingTimer->InitWithCallback(this, 200, nsITimer::TYPE_ONE_SHOT);
// Important that we set CONNECTING_IN_PROGRESS before any call to // AbortSession here: ensures that any remaining queued connection(s) are // scheduled in OnStopSession
LOG(("Websocket: changing state to CONNECTING_IN_PROGRESS"));
mConnecting = CONNECTING_IN_PROGRESS;
if (aCalledFromAdmissionManager) { // When called from nsWSAdmissionManager post an event to avoid potential // re-entering of nsWSAdmissionManager and its lock.
NS_DispatchToMainThread(
NewRunnableMethod("net::WebSocketChannel::BeginOpenInternal", this,
&WebSocketChannel::BeginOpenInternal),
NS_DISPATCH_NORMAL);
} else {
BeginOpenInternal();
}
}
// Extends the internal buffer by count and returns the total // amount of data available for read // // Accumulated fragment size is passed in instead of using the member // variable beacuse when transitioning from the stack to the persistent // read buffer we want to explicitly include them in the buffer instead // of as already existing data. bool WebSocketChannel::UpdateReadBuffer(uint8_t* buffer, uint32_t count,
uint32_t accumulatedFragments,
uint32_t* available) {
LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n", this, buffer,
count));
if (available) *available = mBuffered - (mFramePtr - mBuffer);
returntrue;
}
nsresult WebSocketChannel::ProcessInput(uint8_t* buffer, uint32_t count) {
LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered));
MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread");
nsresult rv;
// The purpose of ping/pong is to actively probe the peer so that an // unreachable peer is not mistaken for a period of idleness. This // implementation accepts any application level read activity as a sign of // life, it does not necessarily have to be a pong.
ResetPingTimer();
uint32_t avail;
if (!mBuffered) { // Most of the time we can process right off the stack buffer without // having to accumulate anything
mFramePtr = buffer;
avail = count;
} else { if (!UpdateReadBuffer(buffer, count, mFragmentAccumulator, &avail)) { return NS_ERROR_FILE_TOO_BIG;
}
}
if (payloadLength64 < 126) { // Section 5.2 says that the minimal number of bytes MUST // be used to encode the length in all cases
LOG(("WebSocketChannel:: non-minimal-encoded payload length")); return NS_ERROR_ILLEGAL_VALUE;
}
} else { // 64 bit length
framingLength += 8; if (avail < framingLength) break;
if (mFramePtr[2] & 0x80) { // Section 4.2 says that the most significant bit MUST be // 0. (i.e. this is really a 63 bit value)
LOG(("WebSocketChannel:: high bit of 64 bit length set")); return NS_ERROR_ILLEGAL_VALUE;
}
// copy this in case it is unaligned
payloadLength64 = NetworkEndian::readInt64(mFramePtr + 2);
if (payloadLength64 <= 0xffff) { // Section 5.2 says that the minimal number of bytes MUST // be used to encode the length in all cases
LOG(("WebSocketChannel:: non-minimal-encoded payload length")); return NS_ERROR_ILLEGAL_VALUE;
}
}
if (!maskBit && mIsServerSide) {
LOG(
("WebSocketChannel::ProcessInput: unmasked frame received " "from client\n")); return NS_ERROR_ILLEGAL_VALUE;
}
if (maskBit) { 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."));
}
// Control codes are required to have the fin bit set if (!finBit && (opcode & kControlFrameMask)) {
LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode)); return NS_ERROR_ILLEGAL_VALUE;
}
if (rsvBits) { // PMCE sets RSV1 bit in the first fragment when the non-control frame // is deflated
MutexAutoLock lock(mCompressorMutex); if (mPMCECompressor && rsvBits == kRsv1Bit && mFragmentAccumulator == 0 &&
!(opcode & kControlFrameMask)) {
mPMCECompressor->SetMessageDeflated();
LOG(("WebSocketChannel::ProcessInput: received deflated frame\n"));
} else {
LOG(("WebSocketChannel::ProcessInput: unexpected reserved bits %x\n",
rsvBits)); return NS_ERROR_ILLEGAL_VALUE;
}
}
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 != nsIWebSocketFrame::OPCODE_CONTINUATION)) {
LOG(("WebSocketChannel:: nested fragments\n")); return NS_ERROR_ILLEGAL_VALUE;
}
LOG(("WebSocketChannel:: Accumulating Fragment %" PRIu32 "\n",
payloadLength));
if (opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) { // Make sure this continuation fragment isn't the first fragment if (mFragmentOpcode == nsIWebSocketFrame::OPCODE_CONTINUATION) {
LOG(("WebSocketHeandler:: continuation code in first fragment\n")); return NS_ERROR_ILLEGAL_VALUE;
}
// For frag > 1 move the data body back on top of the headers // so we have contiguous stream of data
MOZ_ASSERT(mFramePtr + framingLength == payload, "payload offset from frameptr wrong");
::memmove(mFramePtr, payload, avail);
payload = mFramePtr; if (mBuffered) mBuffered -= framingLength;
} else {
mFragmentOpcode = opcode;
}
if (finBit) {
LOG(("WebSocketChannel:: Finalizing Fragment\n"));
payload -= mFragmentAccumulator;
payloadLength += mFragmentAccumulator;
avail += mFragmentAccumulator;
mFragmentAccumulator = 0;
opcode = mFragmentOpcode; // reset to detect if next message illegally starts with continuation
mFragmentOpcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
} else {
opcode = nsIWebSocketFrame::OPCODE_CONTINUATION;
mFragmentAccumulator += payloadLength;
}
} elseif (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) { // This frame is not part of a fragment sequence but we // have an open fragment.. it must be a control code or else // we have a problem
LOG(("WebSocketChannel:: illegal fragment sequence\n")); return NS_ERROR_ILLEGAL_VALUE;
}
// Section 8.1 says to fail connection if invalid utf-8 in text message if (!IsUtf8(utf8Data)) {
LOG(("WebSocketChannel:: text frame invalid utf-8\n")); return NS_ERROR_CANNOT_CONVERT_DATA;
}
// section 8.1 says to replace received non utf-8 sequences // (which are non-conformant to send) with u+fffd, // but secteam feels that silently rewriting messages is // inappropriate - so we will fail the connection instead. if (!IsUtf8(mServerCloseReason)) {
LOG(("WebSocketChannel:: close frame invalid utf-8\n")); return NS_ERROR_CANNOT_CONVERT_DATA;
}
LOG(("WebSocketChannel:: close msg %s\n",
mServerCloseReason.get()));
}
}
if (mCloseTimer) {
mCloseTimer->Cancel();
mCloseTimer = nullptr;
}
if (frame) { // We send the frame immediately becuase we want to have it dispatched // before the CallOnServerClose.
mService->FrameReceived(mSerial, mInnerWindowID, frame.forget());
frame = nullptr;
}
if (mClientClosed) ReleaseSession();
} elseif (opcode == nsIWebSocketFrame::OPCODE_PING) {
LOG(("WebSocketChannel:: ping received\n"));
GeneratePong(payload, payloadLength);
} elseif (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 */
LOG(("WebSocketChannel:: unknown control op code %d\n", opcode)); return NS_ERROR_ILLEGAL_VALUE;
}
if (mFragmentAccumulator) { // Remove the control frame from the stream so we have a contiguous // data buffer of reassembled fragments
LOG(("WebSocketChannel:: Removing Control From Read buffer\n"));
MOZ_ASSERT(mFramePtr + framingLength == payload, "payload offset from frameptr wrong");
::memmove(mFramePtr, payload + payloadLength, avail - payloadLength);
payload = mFramePtr;
avail -= payloadLength; if (mBuffered) mBuffered -= framingLength + payloadLength;
payloadLength = 0;
}
// Adjust the stateful buffer. If we were operating off the stack and // now have a partial message then transition to the buffer, or if // we were working off the buffer but no longer have any active state // then transition to the stack if (!IsPersistentFramePtr()) {
mBuffered = 0;
if (mFragmentAccumulator) {
LOG(("WebSocketChannel:: Setup Buffer due to fragment"));
// UpdateReadBuffer will reset the frameptr to the beginning // of new saved state, so we need to skip past processed framgents
mFramePtr += mFragmentAccumulator;
} elseif (totalAvail) {
LOG(("WebSocketChannel:: Setup Buffer due to partial frame")); if (!UpdateReadBuffer(mFramePtr, totalAvail, 0, nullptr)) { return NS_ERROR_FILE_TOO_BIG;
}
}
} elseif (!mFragmentAccumulator && !totalAvail) { // If we were working off a saved buffer state and there is no partial // frame or fragment in process, then revert to stack behavior
LOG(("WebSocketChannel:: Internal buffering not needed anymore"));
mBuffered = 0;
// release memory if we've been processing a large message if (mBufferSize > kIncomingBufferStableSize) {
mBufferSize = kIncomingBufferStableSize;
free(mBuffer);
mBuffer = (uint8_t*)moz_xmalloc(mBufferSize);
}
} return NS_OK;
}
/* static */ void WebSocketChannel::ApplyMask(uint32_t mask, uint8_t* data, uint64_t len) { if (!data || len == 0) return;
// Optimally we want to apply the mask 32 bits at a time, // but the buffer might not be alligned. So we first deal with // 0 to 3 bytes of preamble individually
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.