blob: 609b0a1898bc8255bb75baf37e3de4e16c94b1a4 [file] [log] [blame]
/*
* Copyright (C) 2020-2021 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "config.h"
#import "NetworkRTCTCPSocketCocoa.h"
#if USE(LIBWEBRTC) && PLATFORM(COCOA)
#include "DataReference.h"
#include "LibWebRTCNetworkMessages.h"
#include "Logging.h"
#include "NWSPI.h"
#include "NetworkRTCUtilitiesCocoa.h"
#include <WebCore/STUNMessageParsing.h>
#include <dispatch/dispatch.h>
#include <wtf/BlockPtr.h>
namespace WebKit {
using namespace WebCore;
static dispatch_queue_t tcpSocketQueue()
{
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("WebRTC TCP socket queue", DISPATCH_QUEUE_CONCURRENT);
});
return queue;
}
std::unique_ptr<NetworkRTCProvider::Socket> NetworkRTCTCPSocketCocoa::createClientTCPSocket(LibWebRTCSocketIdentifier identifier, NetworkRTCProvider& rtcProvider, const rtc::SocketAddress& remoteAddress, int tcpOptions, const String& attributedBundleIdentifier, bool isFirstParty, bool isRelayDisabled, const WebCore::RegistrableDomain& domain, Ref<IPC::Connection>&& connection)
{
// FIXME: We should support ssltcp candidates, maybe support OPT_TLS_INSECURE as well.
return makeUnique<NetworkRTCTCPSocketCocoa>(identifier, rtcProvider, remoteAddress, tcpOptions, attributedBundleIdentifier, isFirstParty, isRelayDisabled, domain, WTFMove(connection));
}
static inline void processIncomingData(RetainPtr<nw_connection_t>&& nwConnection, Function<Vector<uint8_t>(Vector<uint8_t>&&)>&& processData, Vector<uint8_t>&& buffer = { })
{
auto nwConnectionReference = nwConnection.get();
nw_connection_receive(nwConnectionReference, 1, std::numeric_limits<uint32_t>::max(), makeBlockPtr([nwConnection = WTFMove(nwConnection), processData = WTFMove(processData), buffer = WTFMove(buffer)](dispatch_data_t content, nw_content_context_t context, bool isComplete, nw_error_t error) mutable {
if (content) {
dispatch_data_apply(content, makeBlockPtr([&](dispatch_data_t, size_t, const void* data, size_t size) {
// FIXME: Introduce uncheckedAppend version.
buffer.append(static_cast<const uint8_t*>(data), size);
return true;
}).get());
buffer = processData(WTFMove(buffer));
}
if (isComplete && context && nw_content_context_get_is_final(context))
return;
if (error) {
RELEASE_LOG_ERROR(WebRTC, "NetworkRTCTCPSocketCocoa processIncomingData failed with error %d", nw_error_get_error_code(error));
return;
}
processIncomingData(WTFMove(nwConnection), WTFMove(processData), WTFMove(buffer));
}).get());
}
NetworkRTCTCPSocketCocoa::NetworkRTCTCPSocketCocoa(LibWebRTCSocketIdentifier identifier, NetworkRTCProvider& rtcProvider, const rtc::SocketAddress& remoteAddress, int options, const String& attributedBundleIdentifier, bool isFirstParty, bool isRelayDisabled, const WebCore::RegistrableDomain& domain, Ref<IPC::Connection>&& connection)
: m_identifier(identifier)
, m_rtcProvider(rtcProvider)
, m_connection(WTFMove(connection))
, m_isSTUN(options & rtc::PacketSocketFactory::OPT_STUN)
{
auto hostName = remoteAddress.hostname();
if (hostName.empty())
hostName = remoteAddress.ipaddr().ToString();
auto host = adoptNS(nw_endpoint_create_host(hostName.c_str(), String::number(remoteAddress.port()).utf8().data()));
// FIXME: Handle TLS certificate validation like for other network code paths, using sec_protocol_options_set_verify_block
bool isTLS = options & rtc::PacketSocketFactory::OPT_TLS;
auto tcpTLS = adoptNS(nw_parameters_create_secure_tcp(isTLS ? NW_PARAMETERS_DEFAULT_CONFIGURATION : NW_PARAMETERS_DISABLE_PROTOCOL, ^(nw_protocol_options_t tcp_options) {
nw_tcp_options_set_no_delay(tcp_options, true);
}));
setNWParametersApplicationIdentifiers(tcpTLS.get(), rtcProvider.applicationBundleIdentifier(), rtcProvider.sourceApplicationAuditToken(), attributedBundleIdentifier);
setNWParametersTrackerOptions(tcpTLS.get(), isRelayDisabled, isFirstParty, isKnownTracker(domain));
m_nwConnection = adoptNS(nw_connection_create(host.get(), tcpTLS.get()));
nw_connection_set_queue(m_nwConnection.get(), tcpSocketQueue());
nw_connection_set_state_changed_handler(m_nwConnection.get(), makeBlockPtr([identifier = m_identifier, rtcProvider = Ref { rtcProvider }, connection = m_connection.copyRef()](nw_connection_state_t state, _Nullable nw_error_t error) {
ASSERT_UNUSED(error, !error);
switch (state) {
case nw_connection_state_invalid:
case nw_connection_state_waiting:
case nw_connection_state_preparing:
return;
case nw_connection_state_ready:
connection->send(Messages::LibWebRTCNetwork::SignalConnect(identifier), 0);
return;
case nw_connection_state_failed:
rtcProvider->callOnRTCNetworkThread([rtcProvider, identifier] {
rtcProvider->closeSocket(identifier);
});
connection->send(Messages::LibWebRTCNetwork::SignalClose(identifier, -1), 0);
return;
case nw_connection_state_cancelled:
return;
}
}).get());
processIncomingData(m_nwConnection.get(), [identifier = m_identifier, connection = m_connection.copyRef(), ip = remoteAddress.ipaddr(), port = remoteAddress.port(), isSTUN = m_isSTUN](auto&& buffer) mutable {
return WebRTC::extractMessages(WTFMove(buffer), isSTUN ? WebRTC::MessageType::STUN : WebRTC::MessageType::Data, [&](auto* message, auto size) {
IPC::DataReference data(message, size);
connection->send(Messages::LibWebRTCNetwork::SignalReadPacket { identifier, data, RTCNetwork::IPAddress(ip), port, rtc::TimeMillis() * 1000 }, 0);
});
});
nw_connection_start(m_nwConnection.get());
}
void NetworkRTCTCPSocketCocoa::close()
{
if (m_nwConnection)
nw_connection_cancel(m_nwConnection.get());
m_rtcProvider.takeSocket(m_identifier);
}
void NetworkRTCTCPSocketCocoa::setOption(int, int)
{
// FIXME: Validate this is not needed.
}
static RetainPtr<dispatch_data_t> dataFromVector(Vector<uint8_t>&& v)
{
auto bufferSize = v.size();
auto rawPointer = v.releaseBuffer().leakPtr();
return adoptNS(dispatch_data_create(rawPointer, bufferSize, dispatch_get_main_queue(), ^{
fastFree(rawPointer);
}));
}
Vector<uint8_t> NetworkRTCTCPSocketCocoa::createMessageBuffer(const uint8_t* data, size_t size)
{
if (size >= std::numeric_limits<uint16_t>::max())
return { };
if (m_isSTUN) {
auto messageLengths = WebRTC::getSTUNOrTURNMessageLengths(data, size);
if (!messageLengths)
return { };
ASSERT(messageLengths->messageLength == size);
ASSERT(messageLengths->messageLengthWithPadding >= size);
if (messageLengths->messageLengthWithPadding < size)
return { };
Vector<uint8_t> buffer;
buffer.reserveInitialCapacity(messageLengths->messageLengthWithPadding);
buffer.append(data, size);
for (size_t cptr = 0 ; cptr < messageLengths->messageLengthWithPadding - size; ++cptr)
buffer.uncheckedAppend(0);
return buffer;
}
// Prepend length.
Vector<uint8_t> buffer;
buffer.reserveInitialCapacity(size + 2);
buffer.uncheckedAppend((size >> 8) & 0xFF);
buffer.uncheckedAppend(size & 0xFF);
buffer.append(data, size);
return buffer;
}
void NetworkRTCTCPSocketCocoa::sendTo(const uint8_t* data, size_t size, const rtc::SocketAddress&, const rtc::PacketOptions& options)
{
auto buffer = createMessageBuffer(data, size);
if (buffer.isEmpty())
return;
nw_connection_send(m_nwConnection.get(), dataFromVector(WTFMove(buffer)).get(), NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, true, makeBlockPtr([identifier = m_identifier, connection = m_connection.copyRef(), options](_Nullable nw_error_t) {
connection->send(Messages::LibWebRTCNetwork::SignalSentPacket { identifier, options.packet_id, rtc::TimeMillis() }, 0);
}).get());
}
} // namespace WebKit
#endif // USE(LIBWEBRTC) && PLATFORM(COCOA)