Bug 1315782 - Support sending/receiving empty strings/bytes over DataChannels. r=jesup

Differential Revision: https://phabricator.services.mozilla.com/D166008
This commit is contained in:
Jan-Ivar Bruaroey 2023-01-11 20:02:38 +00:00
parent 28ce608a7f
commit 280721318e
4 changed files with 85 additions and 23 deletions

View File

@ -1680,7 +1680,8 @@ void DataChannelConnection::HandleDataMessage(const void* data, size_t length,
const char* info = "";
if (ppid == DATA_CHANNEL_PPID_DOMSTRING_PARTIAL ||
ppid == DATA_CHANNEL_PPID_DOMSTRING) {
ppid == DATA_CHANNEL_PPID_DOMSTRING ||
ppid == DATA_CHANNEL_PPID_DOMSTRING_EMPTY) {
is_binary = false;
}
if (is_binary != channel->mIsRecvBinary && !channel->mRecvBuffer.IsEmpty()) {
@ -1736,6 +1737,8 @@ void DataChannelConnection::HandleDataMessage(const void* data, size_t length,
data_length, WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL));
}
bool is_empty = false;
switch (ppid) {
case DATA_CHANNEL_PPID_DOMSTRING:
DC_DEBUG(
@ -1750,6 +1753,18 @@ void DataChannelConnection::HandleDataMessage(const void* data, size_t length,
// WebSockets checks IsUTF8() here; we can try to deliver it
break;
case DATA_CHANNEL_PPID_DOMSTRING_EMPTY:
DC_DEBUG(
("DataChannel: Received empty string message of length %u on channel "
"%u",
data_length, channel->mStream));
type = DataChannelOnMessageAvailable::ON_DATA_STRING;
if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED) {
info = " (string fragmented)";
}
is_empty = true;
break;
case DATA_CHANNEL_PPID_BINARY:
DC_DEBUG(
("DataChannel: Received binary message of length %u on channel id %u",
@ -1762,6 +1777,18 @@ void DataChannelConnection::HandleDataMessage(const void* data, size_t length,
// else send using recvData normally
break;
case DATA_CHANNEL_PPID_BINARY_EMPTY:
DC_DEBUG(
("DataChannel: Received empty binary message of length %u on channel "
"id %u",
data_length, channel->mStream));
type = DataChannelOnMessageAvailable::ON_DATA_BINARY;
if (bufferFlags & DATA_CHANNEL_BUFFER_MESSAGE_FLAGS_BUFFERED) {
info = " (binary fragmented)";
}
is_empty = true;
break;
default:
NS_ERROR("Unknown data PPID");
DC_ERROR(("Unknown data PPID %" PRIu32, ppid));
@ -1784,7 +1811,8 @@ void DataChannelConnection::HandleDataMessage(const void* data, size_t length,
type, this, channel, channel->mRecvBuffer));
channel->mRecvBuffer.Truncate(0);
} else {
nsAutoCString recvData(buffer, data_length); // copies (<64) or allocates
nsAutoCString recvData(is_empty ? "" : buffer,
is_empty ? 0 : data_length); // allocates >64
channel->SendOrQueue(
new DataChannelOnMessageAvailable(type, this, channel, recvData));
}
@ -1875,8 +1903,10 @@ void DataChannelConnection::HandleMessage(const void* buffer, size_t length,
break;
case DATA_CHANNEL_PPID_DOMSTRING_PARTIAL:
case DATA_CHANNEL_PPID_DOMSTRING:
case DATA_CHANNEL_PPID_DOMSTRING_EMPTY:
case DATA_CHANNEL_PPID_BINARY_PARTIAL:
case DATA_CHANNEL_PPID_BINARY:
case DATA_CHANNEL_PPID_BINARY_EMPTY:
HandleDataMessage(buffer, length, ppid, stream, flags);
break;
default:
@ -2763,7 +2793,8 @@ int DataChannelConnection::SendDataMsgInternalOrBuffer(DataChannel& channel,
int error =
SendMsgInternalOrBuffer(channel.mBufferedData, msg, buffered, &written);
mDeferSend = false;
if (written) {
if (written && ppid != DATA_CHANNEL_PPID_DOMSTRING_EMPTY &&
ppid != DATA_CHANNEL_PPID_BINARY_EMPTY) {
channel.DecrementBufferedAmount(written);
}
@ -2971,16 +3002,23 @@ int DataChannelConnection::SendDataMsgCommon(uint16_t stream,
if (NS_WARN_IF(!channelPtr)) {
return EINVAL; // TODO: Find a better error code
}
bool is_empty = len == 0;
const uint8_t byte = 0;
if (is_empty) {
data = &byte;
len = 1;
}
auto& channel = *channelPtr;
int err = 0;
MutexAutoLock lock(mLock);
if (isBinary) {
err = SendDataMsg(channel, data, len, DATA_CHANNEL_PPID_BINARY_PARTIAL,
DATA_CHANNEL_PPID_BINARY);
err = SendDataMsg(
channel, data, len, DATA_CHANNEL_PPID_BINARY_PARTIAL,
is_empty ? DATA_CHANNEL_PPID_BINARY_EMPTY : DATA_CHANNEL_PPID_BINARY);
} else {
err = SendDataMsg(channel, data, len, DATA_CHANNEL_PPID_DOMSTRING_PARTIAL,
DATA_CHANNEL_PPID_DOMSTRING);
is_empty ? DATA_CHANNEL_PPID_DOMSTRING_EMPTY
: DATA_CHANNEL_PPID_DOMSTRING);
}
if (!err) {
channel.WithTrafficCounters([&len](DataChannel::TrafficCounters& counters) {

View File

@ -33,6 +33,8 @@
#define DATA_CHANNEL_PPID_BINARY 53
#define DATA_CHANNEL_PPID_DOMSTRING_PARTIAL 54
#define DATA_CHANNEL_PPID_DOMSTRING 51
#define DATA_CHANNEL_PPID_DOMSTRING_EMPTY 56
#define DATA_CHANNEL_PPID_BINARY_EMPTY 57
#define DATA_CHANNEL_MAX_BINARY_FRAGMENT 0x4000

View File

@ -1,24 +1,8 @@
[RTCDataChannel-send.html]
[Datachannel should be able to send an empty string and receive an empty string]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1315782
expected: FAIL
[Datachannel should be able to send an empty ArrayBuffer message and receive as ArrayBuffer]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1315782
expected: FAIL
[Datachannel send() up to max size should succeed, above max size should fail]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1278299
expected: FAIL
[Negotiated datachannel should be able to send an empty string and receive an empty string]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1315782
expected: FAIL
[Negotiated datachannel should be able to send an empty ArrayBuffer message and receive as ArrayBuffer]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1315782
expected: FAIL
[Negotiated datachannel send() up to max size should succeed, above max size should fail]
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1278299
expected: FAIL

View File

@ -55,6 +55,9 @@ const helloString = 'hello';
const helloBuffer = Uint8Array.of(0x68, 0x65, 0x6c, 0x6c, 0x6f);
const helloBlob = new Blob([helloBuffer]);
const emptyBuffer = Uint8Array.of();
const emptyBlob = new Blob([emptyBuffer]);
// Unicode string with multiple code units
const unicodeString = '世界你好';
// UTF-8 encoded buffer representation of the string
@ -96,6 +99,17 @@ for (const options of [{}, {negotiated: true, id: 0}]) {
}, `${mode} bufferedAmount should increase to byte length of encoded` +
`unicode string sent`);
promise_test(async (t) => {
const [dc1, dc2] = await createDataChannelPair(t, options);
dc1.send("");
assert_equals(dc1.bufferedAmount, 0,
'Expect bufferedAmount to stay at zero after sending empty string');
await awaitMessage(dc2);
assert_equals(dc1.bufferedAmount, 0, 'Expect sender bufferedAmount unchanged');
}, `${mode} bufferedAmount should stay at zero for empty string sent`);
/*
6.2. send()
3. Execute the sub step that corresponds to the type of the methods argument:
@ -116,6 +130,18 @@ for (const options of [{}, {negotiated: true, id: 0}]) {
'Expect sender bufferedAmount to be reduced after message is sent');
}, `${mode} bufferedAmount should increase to byte length of buffer sent`);
promise_test(async (t) => {
const [dc1, dc2] = await createDataChannelPair(t, options);
dc1.send(emptyBuffer.buffer);
assert_equals(dc1.bufferedAmount, 0,
'Expect bufferedAmount to stay at zero after sending empty buffer');
await awaitMessage(dc2);
assert_equals(dc1.bufferedAmount, 0,
'Expect sender bufferedAmount unchanged');
}, `${mode} bufferedAmount should stay at zero for empty buffer sent`);
/*
6.2. send()
3. Execute the sub step that corresponds to the type of the methods argument:
@ -135,6 +161,18 @@ for (const options of [{}, {negotiated: true, id: 0}]) {
'Expect sender bufferedAmount to be reduced after message is sent');
}, `${mode} bufferedAmount should increase to size of blob sent`);
promise_test(async (t) => {
const [dc1, dc2] = await createDataChannelPair(t, options);
dc1.send(emptyBlob);
assert_equals(dc1.bufferedAmount, 0,
'Expect bufferedAmount to stay at zero after sending empty blob');
await awaitMessage(dc2);
assert_equals(dc1.bufferedAmount, 0,
'Expect sender bufferedAmount unchanged');
}, `${mode} bufferedAmount should stay at zero for empty blob sent`);
// Test sending 3 messages: helloBuffer, unicodeString, helloBlob
promise_test(async (t) => {
const resolver = new Resolver();