#include "config.h"
#if ENABLE(WEB_RTC)
#include "SDPProcessor.h"
#include "CommonVM.h"
#include "Document.h"
#include "Frame.h"
#include "SDPProcessorScriptResource.h"
#include "ScriptController.h"
#include "ScriptSourceCode.h"
#include "inspector/InspectorValues.h"
#include <bindings/ScriptObject.h>
#include <wtf/NeverDestroyed.h>
using namespace Inspector;
namespace WebCore {
#define STRING_FUNCTION(name) \
static const String& name##String() \
{ \
static NeverDestroyed<const String> name { ASCIILiteral(#name) }; \
return name; \
}
STRING_FUNCTION(address)
STRING_FUNCTION(apt)
STRING_FUNCTION(candidates)
STRING_FUNCTION(ccmfir)
STRING_FUNCTION(channels)
STRING_FUNCTION(clockRate)
STRING_FUNCTION(cname)
STRING_FUNCTION(componentId)
STRING_FUNCTION(dtls)
STRING_FUNCTION(encodingName)
STRING_FUNCTION(fingerprint)
STRING_FUNCTION(fingerprintHashFunction)
STRING_FUNCTION(foundation)
STRING_FUNCTION(ice)
STRING_FUNCTION(mediaDescriptions)
STRING_FUNCTION(mediaStreamId)
STRING_FUNCTION(mediaStreamTrackId)
STRING_FUNCTION(mid)
STRING_FUNCTION(mode)
STRING_FUNCTION(mux)
STRING_FUNCTION(nack)
STRING_FUNCTION(nackpli)
STRING_FUNCTION(originator)
STRING_FUNCTION(packetizationMode)
STRING_FUNCTION(parameters)
STRING_FUNCTION(password)
STRING_FUNCTION(payloads)
STRING_FUNCTION(port)
STRING_FUNCTION(priority)
STRING_FUNCTION(relatedAddress)
STRING_FUNCTION(relatedPort)
STRING_FUNCTION(rtcp)
STRING_FUNCTION(rtcpAddress)
STRING_FUNCTION(rtcpPort)
STRING_FUNCTION(rtxTime)
STRING_FUNCTION(sessionId)
STRING_FUNCTION(sessionVersion)
STRING_FUNCTION(setup)
STRING_FUNCTION(ssrcs)
STRING_FUNCTION(tcpType)
STRING_FUNCTION(transport)
STRING_FUNCTION(type)
STRING_FUNCTION(ufrag)
SDPProcessor::SDPProcessor(ScriptExecutionContext* context)
: ContextDestructionObserver(context)
{
}
static RefPtr<InspectorObject> createCandidateObject(const IceCandidate& candidate)
{
RefPtr<InspectorObject> candidateObject = InspectorObject::create();
candidateObject->setString(typeString(), candidate.type);
candidateObject->setString(foundationString(), candidate.foundation);
candidateObject->setInteger(componentIdString(), candidate.componentId);
candidateObject->setString(transportString(), candidate.transport);
candidateObject->setInteger(priorityString(), candidate.priority);
candidateObject->setString(addressString(), candidate.address);
candidateObject->setInteger(portString(), candidate.port);
if (!candidate.tcpType.isEmpty())
candidateObject->setString(tcpTypeString(), candidate.tcpType);
if (candidate.type.convertToASCIIUppercase() != "HOST") {
candidateObject->setString(relatedAddressString(), candidate.relatedAddress);
candidateObject->setInteger(relatedPortString(), candidate.relatedPort);
}
return candidateObject;
}
static IceCandidate createCandidate(const InspectorObject& candidateObject)
{
IceCandidate candidate;
String stringValue;
unsigned intValue;
if (candidateObject.getString(typeString(), stringValue))
candidate.type = stringValue;
if (candidateObject.getString(foundationString(), stringValue))
candidate.foundation = stringValue;
if (candidateObject.getInteger(componentIdString(), intValue))
candidate.componentId = intValue;
if (candidateObject.getString(transportString(), stringValue))
candidate.transport = stringValue;
if (candidateObject.getInteger(priorityString(), intValue))
candidate.priority = intValue;
if (candidateObject.getString(addressString(), stringValue))
candidate.address = stringValue;
if (candidateObject.getInteger(portString(), intValue))
candidate.port = intValue;
if (candidateObject.getString(tcpTypeString(), stringValue))
candidate.tcpType = stringValue;
if (candidateObject.getString(relatedAddressString(), stringValue))
candidate.relatedAddress = stringValue;
if (candidateObject.getInteger(relatedPortString(), intValue))
candidate.relatedPort = intValue;
return candidate;
}
static RefPtr<MediaEndpointSessionConfiguration> configurationFromJSON(const String& json)
{
RefPtr<InspectorValue> value;
if (!InspectorValue::parseJSON(json, value))
return nullptr;
RefPtr<InspectorObject> object;
if (!value->asObject(object))
return nullptr;
RefPtr<MediaEndpointSessionConfiguration> configuration = MediaEndpointSessionConfiguration::create();
String stringValue;
unsigned intValue;
unsigned long longValue;
bool boolValue;
RefPtr<InspectorObject> originatorObject = InspectorObject::create();
if (object->getObject(originatorString(), originatorObject)) {
if (originatorObject->getInteger(sessionIdString(), longValue))
configuration->setSessionId(longValue);
if (originatorObject->getInteger(sessionVersionString(), intValue))
configuration->setSessionVersion(intValue);
}
RefPtr<InspectorArray> mediaDescriptionsArray = InspectorArray::create();
object->getArray(mediaDescriptionsString(), mediaDescriptionsArray);
for (unsigned i = 0; i < mediaDescriptionsArray->length(); ++i) {
RefPtr<InspectorObject> mediaDescriptionObject = InspectorObject::create();
mediaDescriptionsArray->get(i)->asObject(mediaDescriptionObject);
PeerMediaDescription mediaDescription;
if (mediaDescriptionObject->getString(typeString(), stringValue))
mediaDescription.type = stringValue;
if (mediaDescriptionObject->getInteger(portString(), intValue))
mediaDescription.port = intValue;
if (mediaDescriptionObject->getString(addressString(), stringValue))
mediaDescription.address = stringValue;
if (mediaDescriptionObject->getString(modeString(), stringValue))
mediaDescription.mode = stringValue;
if (mediaDescriptionObject->getString(midString(), stringValue))
mediaDescription.mid = stringValue;
RefPtr<InspectorArray> payloadsArray = InspectorArray::create();
mediaDescriptionObject->getArray(payloadsString(), payloadsArray);
for (unsigned j = 0; j < payloadsArray->length(); ++j) {
RefPtr<InspectorObject> payloadsObject = InspectorObject::create();
payloadsArray->get(j)->asObject(payloadsObject);
MediaPayload payload;
if (payloadsObject->getInteger(typeString(), intValue))
payload.type = intValue;
if (payloadsObject->getString(encodingNameString(), stringValue))
payload.encodingName = stringValue;
if (payloadsObject->getInteger(clockRateString(), intValue))
payload.clockRate = intValue;
if (payloadsObject->getInteger(channelsString(), intValue))
payload.channels = intValue;
if (payloadsObject->getBoolean(ccmfirString(), boolValue))
payload.ccmfir = boolValue;
if (payloadsObject->getBoolean(nackpliString(), boolValue))
payload.nackpli = boolValue;
if (payloadsObject->getBoolean(nackString(), boolValue))
payload.nack = boolValue;
RefPtr<InspectorObject> parametersObject = InspectorObject::create();
if (payloadsObject->getObject(parametersString(), parametersObject)) {
if (parametersObject->getInteger(packetizationModeString(), intValue))
payload.addParameter("packetizationMode", intValue);
if (parametersObject->getInteger(aptString(), intValue))
payload.addParameter("apt", intValue);
if (parametersObject->getInteger(rtxTimeString(), intValue))
payload.addParameter("rtxTime", intValue);
}
mediaDescription.addPayload(WTFMove(payload));
}
RefPtr<InspectorObject> rtcpObject = InspectorObject::create();
if (mediaDescriptionObject->getObject(rtcpString(), rtcpObject)) {
if (rtcpObject->getBoolean(muxString(), boolValue))
mediaDescription.rtcpMux = boolValue;
if (rtcpObject->getString(rtcpAddressString(), stringValue))
mediaDescription.rtcpAddress = stringValue;
if (rtcpObject->getInteger(rtcpPortString(), intValue))
mediaDescription.rtcpPort = intValue;
}
if (mediaDescriptionObject->getString(mediaStreamIdString(), stringValue))
mediaDescription.mediaStreamId = stringValue;
if (mediaDescriptionObject->getString(mediaStreamTrackIdString(), stringValue))
mediaDescription.mediaStreamTrackId = stringValue;
RefPtr<InspectorObject> dtlsObject = InspectorObject::create();
if (mediaDescriptionObject->getObject(dtlsString(), dtlsObject)) {
if (dtlsObject->getString(setupString(), stringValue))
mediaDescription.dtlsSetup = stringValue;
if (dtlsObject->getString(fingerprintHashFunctionString(), stringValue))
mediaDescription.dtlsFingerprintHashFunction = stringValue;
if (dtlsObject->getString(fingerprintString(), stringValue))
mediaDescription.dtlsFingerprint = stringValue;
}
RefPtr<InspectorArray> ssrcsArray = InspectorArray::create();
mediaDescriptionObject->getArray(ssrcsString(), ssrcsArray);
for (unsigned j = 0; j < ssrcsArray->length(); ++j) {
ssrcsArray->get(j)->asInteger(intValue);
mediaDescription.addSsrc(intValue);
}
if (mediaDescriptionObject->getString(cnameString(), stringValue))
mediaDescription.cname = stringValue;
RefPtr<InspectorObject> iceObject = InspectorObject::create();
if (mediaDescriptionObject->getObject(iceString(), iceObject)) {
if (iceObject->getString(ufragString(), stringValue))
mediaDescription.iceUfrag = stringValue;
if (iceObject->getString(passwordString(), stringValue))
mediaDescription.icePassword = stringValue;
RefPtr<InspectorArray> candidatesArray = InspectorArray::create();
iceObject->getArray(candidatesString(), candidatesArray);
for (unsigned j = 0; j < candidatesArray->length(); ++j) {
RefPtr<InspectorObject> candidateObject = InspectorObject::create();
candidatesArray->get(j)->asObject(candidateObject);
mediaDescription.addIceCandidate(createCandidate(*candidateObject));
}
}
configuration->addMediaDescription(WTFMove(mediaDescription));
}
return configuration;
}
static std::optional<IceCandidate> iceCandidateFromJSON(const String& json)
{
RefPtr<InspectorValue> value;
if (!InspectorValue::parseJSON(json, value))
return std::nullopt;
RefPtr<InspectorObject> candidateObject;
if (!value->asObject(candidateObject))
return std::nullopt;
return createCandidate(*candidateObject);
}
static String configurationToJSON(const MediaEndpointSessionConfiguration& configuration)
{
RefPtr<InspectorObject> object = InspectorObject::create();
RefPtr<InspectorObject> originatorObject = InspectorObject::create();
originatorObject->setDouble(sessionIdString(), configuration.sessionId());
originatorObject->setInteger(sessionVersionString(), configuration.sessionVersion());
object->setObject(originatorString(), originatorObject);
RefPtr<InspectorArray> mediaDescriptionsArray = InspectorArray::create();
for (const auto& mediaDescription : configuration.mediaDescriptions()) {
RefPtr<InspectorObject> mediaDescriptionObject = InspectorObject::create();
mediaDescriptionObject->setString(typeString(), mediaDescription.type);
mediaDescriptionObject->setInteger(portString(), mediaDescription.port);
mediaDescriptionObject->setString(addressString(), mediaDescription.address);
mediaDescriptionObject->setString(modeString(), mediaDescription.mode);
mediaDescriptionObject->setString(midString(), mediaDescription.mid);
RefPtr<InspectorArray> payloadsArray = InspectorArray::create();
for (auto& payload : mediaDescription.payloads) {
RefPtr<InspectorObject> payloadObject = InspectorObject::create();
payloadObject->setInteger(typeString(), payload.type);
payloadObject->setString(encodingNameString(), payload.encodingName);
payloadObject->setInteger(clockRateString(), payload.clockRate);
payloadObject->setInteger(channelsString(), payload.channels);
payloadObject->setBoolean(ccmfirString(), payload.ccmfir);
payloadObject->setBoolean(nackpliString(), payload.nackpli);
payloadObject->setBoolean(nackString(), payload.nack);
if (!payload.parameters.isEmpty()) {
RefPtr<InspectorObject> parametersObject = InspectorObject::create();
for (auto& name : payload.parameters.keys())
parametersObject->setInteger(name, payload.parameters.get(name));
payloadObject->setObject(parametersString(), parametersObject);
}
payloadsArray->pushObject(payloadObject);
}
mediaDescriptionObject->setArray(payloadsString(), payloadsArray);
RefPtr<InspectorObject> rtcpObject = InspectorObject::create();
rtcpObject->setBoolean(muxString(), mediaDescription.rtcpMux);
rtcpObject->setString(addressString(), mediaDescription.rtcpAddress);
rtcpObject->setInteger(portString(), mediaDescription.rtcpPort);
mediaDescriptionObject->setObject(rtcpString(), rtcpObject);
mediaDescriptionObject->setString(mediaStreamIdString(), mediaDescription.mediaStreamId);
mediaDescriptionObject->setString(mediaStreamTrackIdString(), mediaDescription.mediaStreamTrackId);
RefPtr<InspectorObject> dtlsObject = InspectorObject::create();
dtlsObject->setString(setupString(), mediaDescription.dtlsSetup);
dtlsObject->setString(fingerprintHashFunctionString(), mediaDescription.dtlsFingerprintHashFunction);
dtlsObject->setString(fingerprintString(), mediaDescription.dtlsFingerprint);
mediaDescriptionObject->setObject(dtlsString(), dtlsObject);
RefPtr<InspectorArray> ssrcsArray = InspectorArray::create();
for (auto ssrc : mediaDescription.ssrcs)
ssrcsArray->pushDouble(ssrc);
mediaDescriptionObject->setArray(ssrcsString(), ssrcsArray);
mediaDescriptionObject->setString(cnameString(), mediaDescription.cname);
RefPtr<InspectorObject> iceObject = InspectorObject::create();
iceObject->setString(ufragString(), mediaDescription.iceUfrag);
iceObject->setString(passwordString(), mediaDescription.icePassword);
RefPtr<InspectorArray> candidatesArray = InspectorArray::create();
for (auto& candidate : mediaDescription.iceCandidates)
candidatesArray->pushObject(createCandidateObject(candidate));
iceObject->setArray(candidatesString(), candidatesArray);
mediaDescriptionObject->setObject(iceString(), iceObject);
mediaDescriptionsArray->pushObject(mediaDescriptionObject);
}
object->setArray(mediaDescriptionsString(), mediaDescriptionsArray);
return object->toJSONString();
}
static String iceCandidateToJSON(const IceCandidate& candidate)
{
return createCandidateObject(candidate)->toJSONString();
}
SDPProcessor::Result SDPProcessor::generate(const MediaEndpointSessionConfiguration& configuration, String& outSdpString) const
{
String sdpString;
if (!callScript("generate", configurationToJSON(configuration), sdpString))
return Result::InternalError;
outSdpString = sdpString;
return Result::Success;
}
SDPProcessor::Result SDPProcessor::parse(const String& sdp, RefPtr<MediaEndpointSessionConfiguration>& outConfiguration) const
{
String scriptOutput;
if (!callScript("parse", sdp, scriptOutput))
return Result::InternalError;
if (scriptOutput == "ParseError")
return Result::ParseError;
RefPtr<MediaEndpointSessionConfiguration> configuration = configurationFromJSON(scriptOutput);
if (!configuration)
return Result::InternalError;
outConfiguration = configuration;
return Result::Success;
}
SDPProcessor::Result SDPProcessor::generateCandidateLine(const IceCandidate& candidate, String& outCandidateLine) const
{
String candidateLine;
if (!callScript("generateCandidateLine", iceCandidateToJSON(candidate), candidateLine))
return Result::InternalError;
outCandidateLine = candidateLine;
return Result::Success;
}
SDPProcessor::ParsingResult SDPProcessor::parseCandidateLine(const String& candidateLine) const
{
String scriptOutput;
if (!callScript("parseCandidateLine", candidateLine, scriptOutput))
return { Result::InternalError };
if (scriptOutput == "ParseError")
return { Result::ParseError };
auto candidate = iceCandidateFromJSON(scriptOutput);
if (!candidate)
return { Result::InternalError };
return { WTFMove(candidate.value()) };
}
bool SDPProcessor::callScript(const String& functionName, const String& argument, String& outResult) const
{
if (!scriptExecutionContext())
return false;
Document* document = downcast<Document>(scriptExecutionContext());
if (!document->frame())
return false;
if (!m_isolatedWorld)
m_isolatedWorld = DOMWrapperWorld::create(commonVM());
ScriptController& scriptController = document->frame()->script();
JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(*m_isolatedWorld));
JSC::VM& vm = globalObject->vm();
JSC::JSLockHolder lock(vm);
auto scope = DECLARE_CATCH_SCOPE(vm);
JSC::ExecState* exec = globalObject->globalExec();
JSC::JSValue probeFunctionValue = globalObject->get(exec, JSC::Identifier::fromString(exec, "generate"));
if (!probeFunctionValue.isFunction()) {
URL scriptURL;
scriptController.evaluateInWorld(ScriptSourceCode(SDPProcessorScriptResource::scriptString(), scriptURL), *m_isolatedWorld);
if (UNLIKELY(scope.exception())) {
scope.clearException();
return false;
}
}
JSC::JSValue functionValue = globalObject->get(exec, JSC::Identifier::fromString(exec, functionName));
if (!functionValue.isFunction())
return false;
JSC::JSObject* function = functionValue.toObject(exec);
JSC::CallData callData;
JSC::CallType callType = function->methodTable()->getCallData(function, callData);
if (callType == JSC::CallType::None)
return false;
JSC::MarkedArgumentBuffer argList;
argList.append(JSC::jsString(exec, argument));
JSC::JSValue result = JSC::call(exec, function, callType, callData, globalObject, argList);
if (UNLIKELY(scope.exception())) {
LOG_ERROR("SDPProcessor script threw in function %s", functionName.ascii().data());
scope.clearException();
return false;
}
if (!result.isString())
return false;
outResult = asString(result)->value(exec);
return true;
}
}
#endif // ENABLE(WEB_RTC)