blob: 050ecd9fac6119f157c5c8a5dc5645074594ae9a [file] [log] [blame] [edit]
/*
* Copyright 2024 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "p2p/dtls/dtls_utils.h"
#include <cstdint>
#include <optional>
#include <vector>
#include "api/array_view.h"
#include "rtc_base/byte_buffer.h"
#include "rtc_base/checks.h"
namespace {
// https://datatracker.ietf.org/doc/html/rfc5246#appendix-A.1
const uint8_t kDtlsHandshakeRecord = 22;
const uint8_t kDtlsChangeCipherSpecRecord = 20;
} // namespace
namespace cricket {
bool IsDtlsPacket(rtc::ArrayView<const uint8_t> payload) {
const uint8_t* u = payload.data();
return (payload.size() >= kDtlsRecordHeaderLen && (u[0] > 19 && u[0] < 64));
}
bool IsDtlsClientHelloPacket(rtc::ArrayView<const uint8_t> payload) {
if (!IsDtlsPacket(payload)) {
return false;
}
const uint8_t* u = payload.data();
return payload.size() > 17 && u[0] == kDtlsHandshakeRecord && u[13] == 1;
}
bool IsDtlsHandshakePacket(rtc::ArrayView<const uint8_t> payload) {
if (!IsDtlsPacket(payload)) {
return false;
}
// change cipher spec is not a handshake packet. This used
// to work because it was aggregated with the session ticket
// which is no more. It is followed by the encrypted handshake
// message which starts with a handshake record (22) again.
return payload.size() > 17 && (payload[0] == kDtlsHandshakeRecord ||
payload[0] == kDtlsChangeCipherSpecRecord);
}
// Returns a (unsorted) list of (msg_seq) received as part of the handshake.
std::optional<std::vector<uint16_t>> GetDtlsHandshakeAcks(
rtc::ArrayView<const uint8_t> dtls_packet) {
std::vector<uint16_t> acks;
rtc::ByteBufferReader record_buf(dtls_packet);
// https://datatracker.ietf.org/doc/html/rfc6347#section-4.1
while (record_buf.Length() >= kDtlsRecordHeaderLen) {
uint8_t content_type;
uint64_t epoch_and_seq;
uint16_t len;
// Read content_type(1), skip version(2), read epoch+seq(2+6),
// read len(2)
if (!(record_buf.ReadUInt8(&content_type) && record_buf.Consume(2) &&
record_buf.ReadUInt64(&epoch_and_seq) &&
record_buf.ReadUInt16(&len) && record_buf.Length() >= len)) {
return std::nullopt;
}
if (content_type != kDtlsHandshakeRecord) {
record_buf.Consume(len);
continue;
}
// Epoch 1+ is encrypted so we can not parse it.
if (epoch_and_seq >> 6 != 0) {
record_buf.Consume(len);
continue;
}
// https://www.rfc-editor.org/rfc/rfc6347.html#section-4.2.2
rtc::ByteBufferReader handshake_buf(record_buf.DataView().subview(0, len));
while (handshake_buf.Length() > 0) {
uint16_t msg_seq;
uint32_t fragment_len;
uint32_t fragment_offset;
// Skip msg_type(1) and length(3), read msg_seq(2), skip
// fragment_offset(3), read fragment_length(3) and consume it.
if (!(handshake_buf.Consume(1 + 3) &&
handshake_buf.ReadUInt16(&msg_seq) &&
handshake_buf.ReadUInt24(&fragment_offset) &&
handshake_buf.ReadUInt24(&fragment_len) &&
handshake_buf.Consume(fragment_len))) {
return std::nullopt;
}
acks.push_back(msg_seq);
// Advance outer buffer.
record_buf.Consume(12 + fragment_len);
}
RTC_DCHECK(handshake_buf.Length() == 0);
}
// Should have consumed everything.
if (record_buf.Length() != 0) {
return std::nullopt;
}
return acks;
}
} // namespace cricket