[Feature sync] fix handling arbitrary bytes in TXT value
RFC 6763 defines that TXT value can accept both utf-8 string and binary data. Current implementation will always cast the TXT value to a utf-8 string and will cause data lose when there are non-utf-8 chars in the TXT value. This commit fixes this by having the browser passing the TXT values back as byte[]. Also fixed the TXT key&value parsing issues per RFC 6763 section 6.5: accept cases of no '=' and reject empty key. Bug: 254155029 Test: atest FrameworksNetTests CtsNetTestCases Change-Id: I4b755e60ad6e59db19faa41556dd214993d73896
This commit is contained in:
@@ -22,16 +22,19 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.net.module.util.HexDump;
|
||||
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.Inet4Address;
|
||||
@@ -309,6 +312,14 @@ public class MdnsRecordTests {
|
||||
assertEquals("b=1234567890", strings.get(1));
|
||||
assertEquals("xyz=!@#$", strings.get(2));
|
||||
|
||||
List<TextEntry> entries = record.getEntries();
|
||||
assertNotNull(entries);
|
||||
assertEquals(3, entries.size());
|
||||
|
||||
assertEquals(new TextEntry("a", "hello there"), entries.get(0));
|
||||
assertEquals(new TextEntry("b", "1234567890"), entries.get(1));
|
||||
assertEquals(new TextEntry("xyz", "!@#$"), entries.get(2));
|
||||
|
||||
// Encode
|
||||
MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE);
|
||||
record.write(writer, record.getReceiptTime());
|
||||
@@ -321,4 +332,48 @@ public class MdnsRecordTests {
|
||||
|
||||
assertEquals(dataInText, dataOutText);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textRecord_recordDoesNotHaveDataOfGivenLength_throwsEOFException()
|
||||
throws Exception {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray(
|
||||
"0474657374000010"
|
||||
+ "000100001194000D"
|
||||
+ "0D613D68656C6C6F" //The TXT entry starts with length of 13, but only 12
|
||||
+ "2074686572"); // characters are following it.
|
||||
DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
String[] name = reader.readLabels();
|
||||
MdnsRecord.labelsToString(name);
|
||||
reader.readUInt16();
|
||||
|
||||
assertThrows(EOFException.class, () -> new MdnsTextRecord(name, reader));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textRecord_entriesIncludeNonUtf8Bytes_returnsTheSameUtf8Bytes() throws Exception {
|
||||
final byte[] dataIn = HexDump.hexStringToByteArray(
|
||||
"0474657374000010"
|
||||
+ "0001000011940024"
|
||||
+ "0D613D68656C6C6F"
|
||||
+ "2074686572650C62"
|
||||
+ "3D31323334353637"
|
||||
+ "3839300878797A3D"
|
||||
+ "FFEFDFCF");
|
||||
DatagramPacket packet = new DatagramPacket(dataIn, dataIn.length);
|
||||
MdnsPacketReader reader = new MdnsPacketReader(packet);
|
||||
String[] name = reader.readLabels();
|
||||
MdnsRecord.labelsToString(name);
|
||||
reader.readUInt16();
|
||||
|
||||
MdnsTextRecord record = new MdnsTextRecord(name, reader);
|
||||
|
||||
List<TextEntry> entries = record.getEntries();
|
||||
assertNotNull(entries);
|
||||
assertEquals(3, entries.size());
|
||||
assertEquals(new TextEntry("a", "hello there"), entries.get(0));
|
||||
assertEquals(new TextEntry("b", "1234567890"), entries.get(1));
|
||||
assertEquals(new TextEntry("xyz", HexDump.hexStringToByteArray("FFEFDFCF")),
|
||||
entries.get(2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.connectivity.mdns;
|
||||
|
||||
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.os.Parcel;
|
||||
|
||||
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner.class)
|
||||
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
|
||||
public class MdnsServiceInfoTest {
|
||||
@Test
|
||||
public void constructor_createWithOnlyTextStrings_correctAttributes() {
|
||||
MdnsServiceInfo info =
|
||||
new MdnsServiceInfo(
|
||||
"my-mdns-service",
|
||||
new String[] {"_googlecast", "_tcp"},
|
||||
List.of(),
|
||||
new String[] {"my-host", "local"},
|
||||
12345,
|
||||
"192.168.1.1",
|
||||
"2001::1",
|
||||
List.of("vn=Google Inc.", "mn=Google Nest Hub Max"),
|
||||
/* textEntries= */ null);
|
||||
|
||||
assertTrue(info.getAttributeByKey("vn").equals("Google Inc."));
|
||||
assertTrue(info.getAttributeByKey("mn").equals("Google Nest Hub Max"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructor_createWithOnlyTextEntries_correctAttributes() {
|
||||
MdnsServiceInfo info =
|
||||
new MdnsServiceInfo(
|
||||
"my-mdns-service",
|
||||
new String[] {"_googlecast", "_tcp"},
|
||||
List.of(),
|
||||
new String[] {"my-host", "local"},
|
||||
12345,
|
||||
"192.168.1.1",
|
||||
"2001::1",
|
||||
/* textStrings= */ null,
|
||||
List.of(MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
|
||||
MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max")));
|
||||
|
||||
assertTrue(info.getAttributeByKey("vn").equals("Google Inc."));
|
||||
assertTrue(info.getAttributeByKey("mn").equals("Google Nest Hub Max"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructor_createWithBothTextStringsAndTextEntries_acceptsOnlyTextEntries() {
|
||||
MdnsServiceInfo info =
|
||||
new MdnsServiceInfo(
|
||||
"my-mdns-service",
|
||||
new String[] {"_googlecast", "_tcp"},
|
||||
List.of(),
|
||||
new String[] {"my-host", "local"},
|
||||
12345,
|
||||
"192.168.1.1",
|
||||
"2001::1",
|
||||
List.of("vn=Alphabet Inc.", "mn=Google Nest Hub Max", "id=12345"),
|
||||
List.of(
|
||||
MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
|
||||
MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max")));
|
||||
|
||||
assertEquals(Map.of("vn", "Google Inc.", "mn", "Google Nest Hub Max"),
|
||||
info.getAttributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructor_createWithDuplicateKeys_acceptsTheFirstOne() {
|
||||
MdnsServiceInfo info =
|
||||
new MdnsServiceInfo(
|
||||
"my-mdns-service",
|
||||
new String[] {"_googlecast", "_tcp"},
|
||||
List.of(),
|
||||
new String[] {"my-host", "local"},
|
||||
12345,
|
||||
"192.168.1.1",
|
||||
"2001::1",
|
||||
List.of("vn=Alphabet Inc.", "mn=Google Nest Hub Max", "id=12345"),
|
||||
List.of(MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
|
||||
MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max"),
|
||||
MdnsServiceInfo.TextEntry.fromString("mn=Google WiFi Router")));
|
||||
|
||||
assertEquals(Map.of("vn", "Google Inc.", "mn", "Google Nest Hub Max"),
|
||||
info.getAttributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parcelable_canBeParceledAndUnparceled() {
|
||||
Parcel parcel = Parcel.obtain();
|
||||
MdnsServiceInfo beforeParcel =
|
||||
new MdnsServiceInfo(
|
||||
"my-mdns-service",
|
||||
new String[] {"_googlecast", "_tcp"},
|
||||
List.of(),
|
||||
new String[] {"my-host", "local"},
|
||||
12345,
|
||||
"192.168.1.1",
|
||||
"2001::1",
|
||||
List.of("vn=Alphabet Inc.", "mn=Google Nest Hub Max", "id=12345"),
|
||||
List.of(
|
||||
MdnsServiceInfo.TextEntry.fromString("vn=Google Inc."),
|
||||
MdnsServiceInfo.TextEntry.fromString("mn=Google Nest Hub Max")));
|
||||
|
||||
beforeParcel.writeToParcel(parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
MdnsServiceInfo afterParcel = MdnsServiceInfo.CREATOR.createFromParcel(parcel);
|
||||
|
||||
assertEquals(beforeParcel.getServiceInstanceName(), afterParcel.getServiceInstanceName());
|
||||
assertArrayEquals(beforeParcel.getServiceType(), afterParcel.getServiceType());
|
||||
assertEquals(beforeParcel.getSubtypes(), afterParcel.getSubtypes());
|
||||
assertArrayEquals(beforeParcel.getHostName(), afterParcel.getHostName());
|
||||
assertEquals(beforeParcel.getPort(), afterParcel.getPort());
|
||||
assertEquals(beforeParcel.getIpv4Address(), afterParcel.getIpv4Address());
|
||||
assertEquals(beforeParcel.getIpv6Address(), afterParcel.getIpv6Address());
|
||||
assertEquals(beforeParcel.getAttributes(), afterParcel.getAttributes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_parcelable_canBeParceledAndUnparceled() {
|
||||
Parcel parcel = Parcel.obtain();
|
||||
TextEntry beforeParcel = new TextEntry("AA", new byte[] {(byte) 0xFF, (byte) 0xFC});
|
||||
|
||||
beforeParcel.writeToParcel(parcel, 0);
|
||||
parcel.setDataPosition(0);
|
||||
TextEntry afterParcel = TextEntry.CREATOR.createFromParcel(parcel);
|
||||
|
||||
assertEquals(beforeParcel, afterParcel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_fromString_keyValueAreExpected() {
|
||||
TextEntry entry = TextEntry.fromString("AA=xxyyzz");
|
||||
|
||||
assertEquals("AA", entry.getKey());
|
||||
assertArrayEquals(new byte[] {'x', 'x', 'y', 'y', 'z', 'z'}, entry.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_fromStringToString_textUnchanged() {
|
||||
TextEntry entry = TextEntry.fromString("AA=xxyyzz");
|
||||
|
||||
assertEquals("AA=xxyyzz", entry.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_fromStringWithoutAssignPunc_valueisEmpty() {
|
||||
TextEntry entry = TextEntry.fromString("AA");
|
||||
|
||||
assertEquals("AA", entry.getKey());
|
||||
assertArrayEquals(new byte[] {}, entry.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_fromStringAssignPuncAtBeginning_returnsNull() {
|
||||
TextEntry entry = TextEntry.fromString("=AA");
|
||||
|
||||
assertNull(entry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_fromBytes_keyAndValueAreExpected() {
|
||||
TextEntry entry = TextEntry.fromBytes(
|
||||
new byte[] {'A', 'A', '=', 'x', 'x', 'y', 'y', 'z', 'z'});
|
||||
|
||||
assertEquals("AA", entry.getKey());
|
||||
assertArrayEquals(new byte[] {'x', 'x', 'y', 'y', 'z', 'z'}, entry.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_fromBytesToBytes_textUnchanged() {
|
||||
TextEntry entry = TextEntry.fromBytes(
|
||||
new byte[] {'A', 'A', '=', 'x', 'x', 'y', 'y', 'z', 'z'});
|
||||
|
||||
assertArrayEquals(new byte[] {'A', 'A', '=', 'x', 'x', 'y', 'y', 'z', 'z'},
|
||||
entry.toBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_fromBytesWithoutAssignPunc_valueisEmpty() {
|
||||
TextEntry entry = TextEntry.fromBytes(new byte[] {'A', 'A'});
|
||||
|
||||
assertEquals("AA", entry.getKey());
|
||||
assertArrayEquals(new byte[] {}, entry.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_fromBytesAssignPuncAtBeginning_returnsNull() {
|
||||
TextEntry entry = TextEntry.fromBytes(new byte[] {'=', 'A', 'A'});
|
||||
|
||||
assertNull(entry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_fromNonUtf8Bytes_keyValueAreExpected() {
|
||||
TextEntry entry = TextEntry.fromBytes(
|
||||
new byte[] {'A', 'A', '=', (byte) 0xFF, (byte) 0xFE, (byte) 0xFD});
|
||||
|
||||
assertEquals("AA", entry.getKey());
|
||||
assertArrayEquals(new byte[] {(byte) 0xFF, (byte) 0xFE, (byte) 0xFD}, entry.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textEntry_equals() {
|
||||
assertEquals(new TextEntry("AA", "xxyyzz"), new TextEntry("AA", "xxyyzz"));
|
||||
assertEquals(new TextEntry("BB", "xxyyzz"), new TextEntry("BB", "xxyyzz"));
|
||||
assertEquals(new TextEntry("AA", "XXYYZZ"), new TextEntry("AA", "XXYYZZ"));
|
||||
}
|
||||
}
|
||||
@@ -32,8 +32,11 @@ import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import com.android.server.connectivity.mdns.MdnsServiceInfo.TextEntry;
|
||||
import com.android.server.connectivity.mdns.MdnsServiceTypeClient.QueryTaskConfig;
|
||||
import com.android.testutils.DevSdkIgnoreRule;
|
||||
import com.android.testutils.DevSdkIgnoreRunner;
|
||||
@@ -53,7 +56,6 @@ import java.net.DatagramPacket;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -495,7 +497,7 @@ public class MdnsServiceTypeClientTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reportExistingServiceToNewlyRegisteredListeners() throws UnknownHostException {
|
||||
public void reportExistingServiceToNewlyRegisteredListeners() throws Exception {
|
||||
// Process the initial response.
|
||||
MdnsResponse initialResponse =
|
||||
createResponse(
|
||||
@@ -732,7 +734,7 @@ public class MdnsServiceTypeClientTests {
|
||||
int port,
|
||||
@NonNull List<String> subtypes,
|
||||
@NonNull Map<String, String> textAttributes)
|
||||
throws UnknownHostException {
|
||||
throws Exception {
|
||||
String[] hostName = new String[]{"hostname"};
|
||||
MdnsServiceRecord serviceRecord = mock(MdnsServiceRecord.class);
|
||||
when(serviceRecord.getServiceHost()).thenReturn(hostName);
|
||||
@@ -753,10 +755,13 @@ public class MdnsServiceTypeClientTests {
|
||||
|
||||
MdnsTextRecord textRecord = mock(MdnsTextRecord.class);
|
||||
List<String> textStrings = new ArrayList<>();
|
||||
List<TextEntry> textEntries = new ArrayList<>();
|
||||
for (Map.Entry<String, String> kv : textAttributes.entrySet()) {
|
||||
textStrings.add(kv.getKey() + "=" + kv.getValue());
|
||||
textEntries.add(new TextEntry(kv.getKey(), kv.getValue().getBytes(UTF_8)));
|
||||
}
|
||||
when(textRecord.getStrings()).thenReturn(textStrings);
|
||||
when(textRecord.getEntries()).thenReturn(textEntries);
|
||||
|
||||
response.setServiceRecord(serviceRecord);
|
||||
response.setTextRecord(textRecord);
|
||||
|
||||
Reference in New Issue
Block a user