Parse and validate txt records.
Bug: 27696905 Change-Id: I9affcf02a51c92a2be1c2bfc5efbd09065e100bc
This commit is contained in:
@@ -16,8 +16,11 @@
|
||||
|
||||
package android.net.nsd;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.Parcelable;
|
||||
import android.os.Parcel;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.ArrayMap;
|
||||
|
||||
@@ -95,8 +98,99 @@ public final class NsdServiceInfo implements Parcelable {
|
||||
mPort = p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack txt information from a base-64 encoded byte array.
|
||||
*
|
||||
* @param rawRecords The raw base64 encoded records string read from netd.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setTxtRecords(@NonNull String rawRecords) {
|
||||
byte[] txtRecordsRawBytes = Base64.decode(rawRecords, Base64.DEFAULT);
|
||||
|
||||
// There can be multiple TXT records after each other. Each record has to following format:
|
||||
//
|
||||
// byte type required meaning
|
||||
// ------------------- ------------------- -------- ----------------------------------
|
||||
// 0 unsigned 8 bit yes size of record excluding this byte
|
||||
// 1 - n ASCII but not '=' yes key
|
||||
// n + 1 '=' optional separator of key and value
|
||||
// n + 2 - record size uninterpreted bytes optional value
|
||||
//
|
||||
// Example legal records:
|
||||
// [11, 'm', 'y', 'k', 'e', 'y', '=', 0x0, 0x4, 0x65, 0x7, 0xff]
|
||||
// [17, 'm', 'y', 'K', 'e', 'y', 'W', 'i', 't', 'h', 'N', 'o', 'V', 'a', 'l', 'u', 'e', '=']
|
||||
// [12, 'm', 'y', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'K', 'e', 'y']
|
||||
//
|
||||
// Example corrupted records
|
||||
// [3, =, 1, 2] <- key is empty
|
||||
// [3, 0, =, 2] <- key contains non-ASCII character. We handle this by replacing the
|
||||
// invalid characters instead of skipping the record.
|
||||
// [30, 'a', =, 2] <- length exceeds total left over bytes in the TXT records array, we
|
||||
// handle this by reducing the length of the record as needed.
|
||||
int pos = 0;
|
||||
while (pos < txtRecordsRawBytes.length) {
|
||||
// recordLen is an unsigned 8 bit value
|
||||
int recordLen = txtRecordsRawBytes[pos] & 0xff;
|
||||
pos += 1;
|
||||
|
||||
try {
|
||||
if (recordLen == 0) {
|
||||
throw new IllegalArgumentException("Zero sized txt record");
|
||||
} else if (pos + recordLen > txtRecordsRawBytes.length) {
|
||||
Log.w(TAG, "Corrupt record length (pos = " + pos + "): " + recordLen);
|
||||
recordLen = txtRecordsRawBytes.length - pos;
|
||||
}
|
||||
|
||||
// Decode key-value records
|
||||
String key = null;
|
||||
byte[] value = null;
|
||||
int valueLen = 0;
|
||||
for (int i = pos; i < pos + recordLen; i++) {
|
||||
if (key == null) {
|
||||
if (txtRecordsRawBytes[i] == '=') {
|
||||
key = new String(txtRecordsRawBytes, pos, i - pos,
|
||||
StandardCharsets.US_ASCII);
|
||||
}
|
||||
} else {
|
||||
if (value == null) {
|
||||
value = new byte[recordLen - key.length() - 1];
|
||||
}
|
||||
value[valueLen] = txtRecordsRawBytes[i];
|
||||
valueLen++;
|
||||
}
|
||||
}
|
||||
|
||||
// If '=' was not found we have a boolean record
|
||||
if (key == null) {
|
||||
key = new String(txtRecordsRawBytes, pos, recordLen, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
// Empty keys are not allowed (RFC6763 6.4)
|
||||
throw new IllegalArgumentException("Invalid txt record (key is empty)");
|
||||
}
|
||||
|
||||
if (getAttributes().containsKey(key)) {
|
||||
// When we have a duplicate record, the later ones are ignored (RFC6763 6.4)
|
||||
throw new IllegalArgumentException("Invalid txt record (duplicate key \"" + key + "\")");
|
||||
}
|
||||
|
||||
setAttribute(key, value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "While parsing txt records (pos = " + pos + "): " + e.getMessage());
|
||||
}
|
||||
|
||||
pos += recordLen;
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void setAttribute(String key, byte[] value) {
|
||||
if (TextUtils.isEmpty(key)) {
|
||||
throw new IllegalArgumentException("Key cannot be empty");
|
||||
}
|
||||
|
||||
// Key must be printable US-ASCII, excluding =.
|
||||
for (int i = 0; i < key.length(); ++i) {
|
||||
char character = key.charAt(i);
|
||||
@@ -177,10 +271,10 @@ public final class NsdServiceInfo implements Parcelable {
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public byte[] getTxtRecord() {
|
||||
public @NonNull byte[] getTxtRecord() {
|
||||
int txtRecordSize = getTxtRecordSize();
|
||||
if (txtRecordSize == 0) {
|
||||
return null;
|
||||
return new byte[]{};
|
||||
}
|
||||
|
||||
byte[] txtRecord = new byte[txtRecordSize];
|
||||
|
||||
@@ -30,16 +30,14 @@ import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Base64;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import com.android.internal.util.AsyncChannel;
|
||||
@@ -492,6 +490,7 @@ public class NsdService extends INsdManager.Stub {
|
||||
clientInfo.mResolvedService.setServiceName(name);
|
||||
clientInfo.mResolvedService.setServiceType(type);
|
||||
clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
|
||||
clientInfo.mResolvedService.setTxtRecords(cooked[6]);
|
||||
|
||||
stopResolveService(id);
|
||||
removeRequestMap(clientId, id, clientInfo);
|
||||
@@ -708,20 +707,9 @@ public class NsdService extends INsdManager.Stub {
|
||||
if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
|
||||
try {
|
||||
Command cmd = new Command("mdnssd", "register", regId, service.getServiceName(),
|
||||
service.getServiceType(), service.getPort());
|
||||
|
||||
// Add TXT records as additional arguments.
|
||||
Map<String, byte[]> txtRecords = service.getAttributes();
|
||||
for (String key : txtRecords.keySet()) {
|
||||
try {
|
||||
// TODO: Send encoded TXT record as bytes once NDC/netd supports binary data.
|
||||
byte[] recordValue = txtRecords.get(key);
|
||||
cmd.appendArg(String.format(Locale.US, "%s=%s", key,
|
||||
recordValue != null ? new String(recordValue, "UTF_8") : ""));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Slog.e(TAG, "Failed to encode txtRecord " + e);
|
||||
}
|
||||
}
|
||||
service.getServiceType(), service.getPort(),
|
||||
Base64.encodeToString(service.getTxtRecord(), Base64.DEFAULT)
|
||||
.replace("\n", ""));
|
||||
|
||||
mNativeConnector.execute(cmd);
|
||||
} catch(NativeDaemonConnectorException e) {
|
||||
|
||||
Reference in New Issue
Block a user