Bug 1677086 - Parse SvcParamKeyAlpn as defined in spec r=necko-reviewers,valentin

Differential Revision: https://phabricator.services.mozilla.com/D97000
This commit is contained in:
Kershaw Chang 2020-11-17 11:07:03 +00:00
Родитель 4e5d703a9d
Коммит 2e58759c35
14 изменённых файлов: 112 добавлений и 71 удалений

Просмотреть файл

@ -376,9 +376,15 @@ nsresult LookupHelper::ConstructHTTPSRRAnswer(LookupArgument* aArgument) {
nextRecord->mAlpn.Construct();
nextRecord->mAlpn.Value().mType = type;
nsCOMPtr<nsISVCParamAlpn> alpnParam = do_QueryInterface(value);
nsCString alpnStr;
Unused << alpnParam->GetAlpn(alpnStr);
CopyASCIItoUTF16(alpnStr, nextRecord->mAlpn.Value().mAlpn);
nsTArray<nsCString> alpn;
Unused << alpnParam->GetAlpn(alpn);
nsAutoCString alpnStr;
for (const auto& str : alpn) {
alpnStr.Append(str);
alpnStr.Append(',');
}
CopyASCIItoUTF16(Span(alpnStr.BeginReading(), alpnStr.Length() - 1),
nextRecord->mAlpn.Value().mAlpn);
break;
}
case SvcParamKeyNoDefaultAlpn: {

Просмотреть файл

@ -67,12 +67,12 @@ SvcParam::GetType(uint16_t* aType) {
}
NS_IMETHODIMP
SvcParam::GetAlpn(nsACString& aAlpn) {
SvcParam::GetAlpn(nsTArray<nsCString>& aAlpn) {
if (!mValue.is<SvcParamAlpn>()) {
MOZ_ASSERT(false, "Unexpected type for variant");
return NS_ERROR_NOT_AVAILABLE;
}
aAlpn = mValue.as<SvcParamAlpn>().mValue;
aAlpn.AppendElements(mValue.as<SvcParamAlpn>().mValue);
return NS_OK;
}
@ -172,13 +172,13 @@ bool SVCB::NoDefaultAlpn() const {
Maybe<Tuple<nsCString, bool>> SVCB::GetAlpn(bool aNoHttp2,
bool aNoHttp3) const {
Maybe<Tuple<nsCString, bool>> alpn;
nsAutoCString alpnValue;
for (const auto& value : mSvcFieldValue) {
if (value.mValue.is<SvcParamAlpn>()) {
alpn.emplace();
alpnValue = value.mValue.as<SvcParamAlpn>().mValue;
if (!alpnValue.IsEmpty()) {
alpn = Some(SelectAlpnFromAlpnList(alpnValue, aNoHttp2, aNoHttp3));
nsTArray<nsCString> alpnList;
alpnList.AppendElements(value.mValue.as<SvcParamAlpn>().mValue);
if (!alpnList.IsEmpty()) {
alpn.emplace();
alpn = Some(SelectAlpnFromAlpnList(alpnList, aNoHttp2, aNoHttp3));
}
return alpn;
}

Просмотреть файл

@ -29,7 +29,7 @@ struct SvcParamAlpn {
bool operator==(const SvcParamAlpn& aOther) const {
return mValue == aOther.mValue;
}
nsCString mValue;
CopyableTArray<nsCString> mValue;
};
struct SvcParamNoDefaultAlpn {

Просмотреть файл

@ -1399,8 +1399,20 @@ nsresult TRR::ParseSvcParam(unsigned int svcbIndex, uint16_t key,
break;
}
case SvcParamKeyAlpn: {
field.mValue = AsVariant(SvcParamAlpn{
.mValue = nsCString((const char*)(&mResponse[svcbIndex]), length)});
field.mValue = AsVariant(SvcParamAlpn());
auto& alpnArray = field.mValue.as<SvcParamAlpn>().mValue;
while (length > 0) {
uint8_t alpnIdLength = mResponse[svcbIndex++];
length -= 1;
if (alpnIdLength > length) {
return NS_ERROR_UNEXPECTED;
}
alpnArray.AppendElement(
nsCString((const char*)&mResponse[svcbIndex], alpnIdLength));
length -= alpnIdLength;
svcbIndex += alpnIdLength;
}
break;
}
case SvcParamKeyNoDefaultAlpn: {

Просмотреть файл

@ -60,7 +60,7 @@ interface nsISVCParam : nsISupports {
[scriptable, uuid(0dc58309-4d67-4fc4-a4e3-38dbde9d9f4c)]
interface nsISVCParamAlpn : nsISupports {
readonly attribute ACString alpn;
readonly attribute Array<ACString> alpn;
};
[scriptable, uuid(b3ed89c3-2ae6-4c92-8176-b76bc2437fcb)]

Просмотреть файл

@ -1003,16 +1003,12 @@ nsresult HttpProxyResponseToErrorCode(uint32_t aStatusCode) {
return rv;
}
Tuple<nsCString, bool> SelectAlpnFromAlpnList(const nsACString& aAlpnList,
bool aNoHttp2, bool aNoHttp3) {
Tuple<nsCString, bool> SelectAlpnFromAlpnList(
const nsTArray<nsCString>& aAlpnList, bool aNoHttp2, bool aNoHttp3) {
nsCString h3Value;
nsCString h2Value;
nsCString h1Value;
// aAlpnList is a list of alpn-id and use comma as a delimiter.
nsCCharSeparatedTokenizer tokenizer(aAlpnList, ',');
nsAutoCString npnStr;
while (tokenizer.hasMoreTokens()) {
const nsACString& npnToken(tokenizer.nextToken());
for (const auto& npnToken : aAlpnList) {
bool isHttp3 = gHttpHandler->IsHttp3VersionSupported(npnToken);
if (isHttp3 && h3Value.IsEmpty()) {
h3Value.Assign(npnToken);

Просмотреть файл

@ -379,8 +379,8 @@ nsresult HttpProxyResponseToErrorCode(uint32_t aStatusCode);
// Tuple<alpn-id, isHttp3>. The first element is the alpn-id and the second one
// is a boolean to indicate if this alpn-id is for http3. If no supported
// alpn-id is found, the first element would be a n empty string.
Tuple<nsCString, bool> SelectAlpnFromAlpnList(const nsACString& aAlpnList,
bool aNoHttp2, bool aNoHttp3);
Tuple<nsCString, bool> SelectAlpnFromAlpnList(
const nsTArray<nsCString>& aAlpnList, bool aNoHttp2, bool aNoHttp3);
} // namespace net
} // namespace mozilla

Просмотреть файл

@ -15,38 +15,44 @@ TEST(TestSupportAlpn, testSvcParamKeyAlpn)
do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
// h2 and h3 are enabled, so first appearance h3 alpn-id is returned.
Tuple<nsCString, bool> result =
SelectAlpnFromAlpnList("h3-28,h3-27,h2,http/1.1"_ns, false, false);
nsTArray<nsCString> alpn = {"h3-28"_ns, "h3-27"_ns, "h2"_ns, "http/1.1"_ns};
Tuple<nsCString, bool> result = SelectAlpnFromAlpnList(alpn, false, false);
ASSERT_EQ(Get<0>(result), "h3-28"_ns);
ASSERT_EQ(Get<1>(result), true);
// We don't support h3-26, so we should choose h2.
result = SelectAlpnFromAlpnList("h3-26,h2,http/1.1"_ns, false, false);
alpn = {"h3-26"_ns, "h2"_ns, "http/1.1"_ns};
result = SelectAlpnFromAlpnList(alpn, false, false);
ASSERT_EQ(Get<0>(result), "h2"_ns);
ASSERT_EQ(Get<1>(result), false);
// h3 is disabled, so we will use h2.
result = SelectAlpnFromAlpnList("h3-28,h3-27,h2,http/1.1"_ns, false, true);
alpn = {"h3-28"_ns, "h3-27"_ns, "h2"_ns, "http/1.1"_ns};
result = SelectAlpnFromAlpnList(alpn, false, true);
ASSERT_EQ(Get<0>(result), "h2"_ns);
ASSERT_EQ(Get<1>(result), false);
// h3 is disabled and h2 is not found, so we will select http/1.1.
result = SelectAlpnFromAlpnList("h3-28,h3-27,http/1.1"_ns, false, true);
alpn = {"h3-28"_ns, "h3-27"_ns, "http/1.1"_ns};
result = SelectAlpnFromAlpnList(alpn, false, true);
ASSERT_EQ(Get<0>(result), "http/1.1"_ns);
ASSERT_EQ(Get<1>(result), false);
// h3 and h2 are disabled, so we will select http/1.1.
result = SelectAlpnFromAlpnList("h3-28,h3-27,h2,http/1.1"_ns, true, true);
alpn = {"h3-28"_ns, "h3-27"_ns, "h2"_ns, "http/1.1"_ns};
result = SelectAlpnFromAlpnList(alpn, true, true);
ASSERT_EQ(Get<0>(result), "http/1.1"_ns);
ASSERT_EQ(Get<1>(result), false);
// h3 and h2 are disabled and http1.1 is not found, we return an empty string.
result = SelectAlpnFromAlpnList("h3-28,h3-27,h2"_ns, true, true);
alpn = {"h3-28"_ns, "h3-27"_ns, "h2"_ns};
result = SelectAlpnFromAlpnList(alpn, true, true);
ASSERT_EQ(Get<0>(result), ""_ns);
ASSERT_EQ(Get<1>(result), false);
// No supported alpn.
result = SelectAlpnFromAlpnList("ftp,h2c"_ns, true, true);
alpn = {"ftp"_ns, "h2c"_ns};
result = SelectAlpnFromAlpnList(alpn, true, true);
ASSERT_EQ(Get<0>(result), ""_ns);
ASSERT_EQ(Get<1>(result), false);
}

Просмотреть файл

@ -127,7 +127,7 @@ add_task(async function testStoreIPHint() {
priority: 1,
name: "test.IPHint.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "port", value: 8888 },
{ key: "ipv4hint", value: ["1.2.3.4", "5.6.7.8"] },
{ key: "ipv6hint", value: ["::1", "fe80::794f:6d2c:3d5e:7836"] },
@ -156,9 +156,9 @@ add_task(async function testStoreIPHint() {
Assert.equal(answer[0].priority, 1);
Assert.equal(answer[0].name, "test.IPHint.com");
Assert.equal(answer[0].values.length, 4);
Assert.equal(
Assert.deepEqual(
answer[0].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
"h2,h3",
["h2", "h3"],
"got correct answer"
);
Assert.equal(

Просмотреть файл

@ -121,7 +121,7 @@ add_task(async function testPriorityAndECHConfig() {
data: {
priority: 1,
name: "test.p1.com",
values: [{ key: "alpn", value: "h2,h3" }],
values: [{ key: "alpn", value: ["h2", "h3"] }],
},
},
{

Просмотреть файл

@ -159,7 +159,7 @@ add_task(async function testFallbackToTheLastRecord() {
priority: 1,
name: "test.fallback1.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "123..." },
],
},
@ -173,7 +173,7 @@ add_task(async function testFallbackToTheLastRecord() {
priority: 4,
name: "foo.example.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "port", value: h2Port },
{ key: "echconfig", value: "456..." },
],
@ -188,7 +188,7 @@ add_task(async function testFallbackToTheLastRecord() {
priority: 3,
name: "test.fallback3.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "456..." },
],
},
@ -202,7 +202,7 @@ add_task(async function testFallbackToTheLastRecord() {
priority: 2,
name: "test.fallback2.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "456..." },
],
},
@ -262,7 +262,7 @@ add_task(async function testFallbackToTheOrigin() {
priority: 1,
name: "test.foo1.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "123..." },
],
},
@ -276,7 +276,7 @@ add_task(async function testFallbackToTheOrigin() {
priority: 3,
name: "test.foo3.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "456..." },
],
},
@ -290,7 +290,7 @@ add_task(async function testFallbackToTheOrigin() {
priority: 2,
name: "test.foo2.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "456..." },
],
},
@ -361,7 +361,7 @@ add_task(async function testAllRecordsFailed() {
priority: 1,
name: "test.bar1.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "123..." },
],
},
@ -375,7 +375,7 @@ add_task(async function testAllRecordsFailed() {
priority: 3,
name: "test.bar3.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "456..." },
],
},
@ -389,7 +389,7 @@ add_task(async function testAllRecordsFailed() {
priority: 2,
name: "test.bar2.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "456..." },
],
},
@ -447,7 +447,7 @@ add_task(async function testFallbackToTheOrigin2() {
data: {
priority: 1,
name: "test.example1.com",
values: [{ key: "alpn", value: "h2,h3" }],
values: [{ key: "alpn", value: ["h2", "h3"] }],
},
},
{
@ -458,7 +458,7 @@ add_task(async function testFallbackToTheOrigin2() {
data: {
priority: 3,
name: "test.example3.com",
values: [{ key: "alpn", value: "h2,h3" }],
values: [{ key: "alpn", value: ["h2", "h3"] }],
},
},
]);
@ -539,7 +539,7 @@ add_task(async function testFallbackToTheOrigin3() {
priority: 1,
name: "vulnerable1.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "456..." },
],
},
@ -553,7 +553,7 @@ add_task(async function testFallbackToTheOrigin3() {
priority: 2,
name: "vulnerable2.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "456..." },
],
},
@ -566,7 +566,7 @@ add_task(async function testFallbackToTheOrigin3() {
data: {
priority: 3,
name: "vulnerable3.com",
values: [{ key: "alpn", value: "h2,h3" }],
values: [{ key: "alpn", value: ["h2", "h3"] }],
},
},
]);
@ -624,7 +624,7 @@ add_task(async function testResetExclusionList() {
priority: 1,
name: "test.reset1.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "port", value: h2Port },
{ key: "echconfig", value: "456..." },
],
@ -639,7 +639,7 @@ add_task(async function testResetExclusionList() {
priority: 2,
name: "test.reset2.com",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "echconfig", value: "456..." },
],
},

Просмотреть файл

@ -134,9 +134,9 @@ add_task(async function testHTTPSSVC() {
Assert.equal(answer[0].priority, 1);
Assert.equal(answer[0].name, "h3pool");
Assert.equal(answer[0].values.length, 6);
Assert.equal(
Assert.deepEqual(
answer[0].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
"h2,h3",
["h2", "h3"],
"got correct answer"
);
Assert.ok(
@ -168,9 +168,9 @@ add_task(async function testHTTPSSVC() {
Assert.equal(answer[1].priority, 2);
Assert.equal(answer[1].name, "test.httpssvc.com");
Assert.equal(answer[1].values.length, 4);
Assert.equal(
Assert.deepEqual(
answer[1].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn,
"h2",
["h2"],
"got correct answer"
);
Assert.equal(
@ -310,7 +310,7 @@ add_task(async function test_aliasform() {
priority: 1,
name: "h3pool",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "no-default-alpn" },
{ key: "port", value: 8888 },
{ key: "ipv4hint", value: "1.2.3.4" },
@ -396,7 +396,7 @@ add_task(async function test_aliasform() {
priority: 1,
name: "h3pool",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "no-default-alpn" },
{ key: "port", value: 8888 },
{ key: "ipv4hint", value: "1.2.3.4" },
@ -452,7 +452,7 @@ add_task(async function test_aliasform() {
{ key: "ipv4hint", value: "1.2.3.4" },
{ key: "port", value: 8888 },
{ key: "no-default-alpn" },
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
],
},
},
@ -487,8 +487,8 @@ add_task(async function test_aliasform() {
priority: 1,
name: "h3pool",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: "h2,h3,h4" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "alpn", value: ["h2", "h3", "h4"] },
],
},
},
@ -524,7 +524,7 @@ add_task(async function test_aliasform() {
name: "h3pool",
values: [
{ key: "mandatory", value: ["key100"] },
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "key100" },
],
},
@ -568,7 +568,7 @@ add_task(async function test_aliasform() {
"ipv6hint",
],
},
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "no-default-alpn" },
{ key: "port", value: 8888 },
{ key: "ipv4hint", value: "1.2.3.4" },
@ -634,7 +634,7 @@ add_task(async function test_aliasform() {
data: {
priority: 1,
name: ".",
values: [{ key: "alpn", value: "h2,h3" }],
values: [{ key: "alpn", value: ["h2", "h3"] }],
},
},
]);

Просмотреть файл

@ -1347,13 +1347,27 @@ svcparam.encode = function(param, buf, offset) {
svcparam.encode.bytes += 2;
}
} else if (key == 1) { // alpn
let len = param.value.length
buf.writeUInt16BE(len || 0, offset);
let val = param.value;
if (!Array.isArray(val)) val = [val];
// The alpn param is prefixed by its length as a single byte, so the
// initialValue to reduce function is the length of the array.
let total = val.reduce(function(result, id) {
return result += id.length;
}, val.length);
buf.writeUInt16BE(total, offset);
offset += 2;
svcparam.encode.bytes += 2;
buf.write(param.value, offset)
offset += len;
svcparam.encode.bytes += len;
for (let id of val) {
buf.writeUInt8(id.length, offset);
offset += 1;
svcparam.encode.bytes += 1;
buf.write(id, offset);
offset += id.length;
svcparam.encode.bytes += id.length;
}
} else if (key == 2) { // no-default-alpn
buf.writeUInt16BE(0, offset);
offset += 2;
@ -1433,7 +1447,14 @@ svcparam.encodingLength = function (param) {
switch (param.key) {
case 'mandatory' : return 4 + 2*(Array.isArray(param.value) ? param.value.length : 1)
case 'alpn' : return 4 + param.value.length
case 'alpn' : {
let val = param.value;
if (!Array.isArray(val)) val = [val];
let total = val.reduce(function(result, id) {
return result += id.length;
}, val.length);
return 4 + total;
}
case 'no-default-alpn' : return 4
case 'port' : return 4 + 2
case 'ipv4hint' : return 4 + 4 * (Array.isArray(param.value) ? param.value.length : 1)

Просмотреть файл

@ -849,7 +849,7 @@ function handleRequest(req, res) {
priority: 1,
name: "h3pool",
values: [
{ key: "alpn", value: "h2,h3" },
{ key: "alpn", value: ["h2", "h3"] },
{ key: "no-default-alpn" },
{ key: "port", value: 8888 },
{ key: "ipv4hint", value: "1.2.3.4" },