257 lines
7.5 KiB
C++
257 lines
7.5 KiB
C++
/* -*- 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/. */
|
|
|
|
#include "CapsuleParser.h"
|
|
|
|
#include "CapsuleDecoder.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
|
|
namespace mozilla::net {
|
|
|
|
bool CapsuleParserListener::OnCapsule(Capsule&& aParsed) {
|
|
mParsedCapsules.AppendElement(std::move(aParsed));
|
|
return true;
|
|
}
|
|
|
|
void CapsuleParserListener::OnCapsuleParseFailure(nsresult aError) {
|
|
mError = Some(aError);
|
|
}
|
|
|
|
CapsuleParser::CapsuleParser(Listener* aListener) : mListener(aListener) {}
|
|
|
|
bool CapsuleParser::ProcessCapsuleData(const uint8_t* aData, uint32_t aCount) {
|
|
// Prevent reentrant calls: if we're already processing, just return.
|
|
if (mProcessing) {
|
|
return false;
|
|
}
|
|
mProcessing = true;
|
|
|
|
auto resetGuard = MakeScopeExit([&]() { mProcessing = false; });
|
|
|
|
Span<const uint8_t> input;
|
|
if (!mBuffer.IsEmpty()) {
|
|
mBuffer.AppendElements(aData, aCount);
|
|
input = Span<const uint8_t>(mBuffer.Elements(), mBuffer.Length());
|
|
} else {
|
|
input = Span<const uint8_t>(aData, aCount);
|
|
}
|
|
size_t pos = 0;
|
|
size_t length = input.Length();
|
|
|
|
while (true) {
|
|
Span<const uint8_t> toProcess = input.Subspan(pos, length - pos);
|
|
auto result = ParseCapsuleData(toProcess);
|
|
if (result.isErr()) {
|
|
mBuffer.Clear();
|
|
return false;
|
|
}
|
|
|
|
size_t processed = result.unwrap();
|
|
if (processed == 0) {
|
|
if (mBuffer.IsEmpty()) {
|
|
// Store the remaining data in mBuffer.
|
|
mBuffer.AppendElements(toProcess.Elements(), toProcess.Length());
|
|
} else {
|
|
// Simply remove the already processed data.
|
|
mBuffer.RemoveElementsAt(0, pos);
|
|
}
|
|
break;
|
|
}
|
|
|
|
pos += processed;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Result<size_t, nsresult> CapsuleParser::ParseCapsuleData(
|
|
Span<const uint8_t> aData) {
|
|
if (aData.IsEmpty()) {
|
|
return 0;
|
|
}
|
|
|
|
CapsuleDecoder decoder(aData.Elements(), aData.Length());
|
|
auto type = decoder.DecodeVarint();
|
|
|
|
if (!type) {
|
|
return 0;
|
|
}
|
|
|
|
CapsuleType capsuleType = static_cast<CapsuleType>(*type);
|
|
auto payloadLength = decoder.DecodeVarint();
|
|
if (!payloadLength) {
|
|
return 0;
|
|
}
|
|
|
|
auto payload = decoder.Decode(*payloadLength);
|
|
if (!payload) {
|
|
return 0;
|
|
}
|
|
|
|
CapsuleDecoder payloadParser(payload->Elements(), payload->Length());
|
|
auto result =
|
|
ParseCapsulePayload(payloadParser, capsuleType, payload->Length());
|
|
if (result.isErr()) {
|
|
nsresult error = result.unwrapErr();
|
|
mListener->OnCapsuleParseFailure(error);
|
|
return Err(error);
|
|
}
|
|
|
|
Capsule capsule = result.unwrap();
|
|
if (!mListener->OnCapsule(std::move(capsule))) {
|
|
return Err(NS_ERROR_FAILURE);
|
|
}
|
|
|
|
return decoder.CurrentPos();
|
|
}
|
|
|
|
Result<Capsule, nsresult> CapsuleParser::ParseCapsulePayload(
|
|
CapsuleDecoder& aDecoder, CapsuleType aType, size_t aPayloadLength) {
|
|
switch (aType) {
|
|
case CapsuleType::CLOSE_WEBTRANSPORT_SESSION: {
|
|
if (aPayloadLength < 4) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
Maybe<uint32_t> status = aDecoder.DecodeUint<uint32_t>();
|
|
if (!status) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
// https://www.ietf.org/archive/id/draft-ietf-webtrans-http2-10.html#section-6.12
|
|
// The reason MUST not exceed 1024 bytes.
|
|
if (aDecoder.BytesRemaining() > 1024) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
auto reason = aDecoder.Decode(aPayloadLength - 4);
|
|
if (!reason) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::CloseWebTransportSession(
|
|
*status, nsCString(reinterpret_cast<const char*>(reason->Elements()),
|
|
reason->Length()));
|
|
}
|
|
case CapsuleType::DRAIN_WEBTRANSPORT_SESSION:
|
|
break;
|
|
case CapsuleType::PADDING:
|
|
break;
|
|
case CapsuleType::WT_RESET_STREAM: {
|
|
auto id = aDecoder.DecodeVarint();
|
|
if (!id) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
auto error = aDecoder.DecodeVarint();
|
|
if (!error) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
auto size = aDecoder.DecodeVarint();
|
|
if (!size) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::WebTransportResetStream(*error, *size, *id);
|
|
}
|
|
case CapsuleType::WT_STOP_SENDING: {
|
|
auto id = aDecoder.DecodeVarint();
|
|
if (!id) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
auto error = aDecoder.DecodeVarint();
|
|
if (!error) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::WebTransportStopSending(*error, *id);
|
|
}
|
|
case CapsuleType::WT_STREAM: {
|
|
auto id = aDecoder.DecodeVarint();
|
|
if (!id) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
nsTArray<uint8_t> data(aDecoder.GetRemaining());
|
|
return Capsule::WebTransportStreamData(*id, false, std::move(data));
|
|
}
|
|
case CapsuleType::WT_STREAM_FIN: {
|
|
auto id = aDecoder.DecodeVarint();
|
|
if (!id) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
nsTArray<uint8_t> data(aDecoder.GetRemaining());
|
|
return Capsule::WebTransportStreamData(*id, true, std::move(data));
|
|
}
|
|
case CapsuleType::WT_MAX_DATA: {
|
|
auto value = aDecoder.DecodeVarint();
|
|
if (!value) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::WebTransportMaxData(*value);
|
|
}
|
|
case CapsuleType::WT_MAX_STREAM_DATA: {
|
|
auto id = aDecoder.DecodeVarint();
|
|
if (!id) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
auto limit = aDecoder.DecodeVarint();
|
|
if (!limit) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::WebTransportMaxStreamData(*limit, *id);
|
|
}
|
|
case CapsuleType::WT_MAX_STREAMS_BIDI: {
|
|
auto value = aDecoder.DecodeVarint();
|
|
if (!value) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::WebTransportMaxStreams(*value, true);
|
|
}
|
|
case CapsuleType::WT_MAX_STREAMS_UNIDI: {
|
|
auto value = aDecoder.DecodeVarint();
|
|
if (!value) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::WebTransportMaxStreams(*value, false);
|
|
}
|
|
case CapsuleType::WT_DATA_BLOCKED: {
|
|
auto limit = aDecoder.DecodeVarint();
|
|
if (!limit) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::WebTransportDataBlocked(*limit);
|
|
}
|
|
case CapsuleType::WT_STREAM_DATA_BLOCKED: {
|
|
auto id = aDecoder.DecodeVarint();
|
|
if (!id) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
auto limit = aDecoder.DecodeVarint();
|
|
if (!limit) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::WebTransportStreamDataBlocked(*limit, *id);
|
|
}
|
|
case CapsuleType::WT_STREAMS_BLOCKED_BIDI: {
|
|
auto value = aDecoder.DecodeVarint();
|
|
if (!value) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::WebTransportStreamsBlocked(*value, true);
|
|
}
|
|
case CapsuleType::WT_STREAMS_BLOCKED_UNIDI: {
|
|
auto value = aDecoder.DecodeVarint();
|
|
if (!value) {
|
|
return Err(NS_ERROR_UNEXPECTED);
|
|
}
|
|
return Capsule::WebTransportStreamsBlocked(*value, false);
|
|
}
|
|
case CapsuleType::DATAGRAM: {
|
|
nsTArray<uint8_t> payload(aDecoder.GetRemaining());
|
|
return Capsule::WebTransportDatagram(std::move(payload));
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return Capsule::Unknown(static_cast<uint64_t>(aType),
|
|
nsTArray<uint8_t>(aDecoder.GetRemaining()));
|
|
}
|
|
|
|
} // namespace mozilla::net
|