blob: 81768f7ed164cbff8218242f51b74a819e3ee3d4 [file] [log] [blame]
/* -*- Mode: Java; tab-width: 4 -*-
*
* Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
*
* 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.
To do:
- implement remove()
- fix set() to replace existing values
*/
package android.net.util.nsd;
import android.os.Parcelable;
import android.os.Parcel;
import java.util.Arrays;
/**
* This class handles TXT record data for DNS based service discovery as specified at
* http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
*
* DNS-SD specifies that a TXT record corresponding to an SRV record consist of
* a packed array of bytes, each preceded by a length byte. Each string
* is an attribute-value pair.
*
* The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
* as need be to implement its various methods.
* @hide
*
*/
public class DnsSdTxtRecord implements Parcelable {
private static final byte mSeparator = '=';
private byte[] mData;
/** Constructs a new, empty TXT record. */
public DnsSdTxtRecord() {
mData = new byte[0];
}
/** Constructs a new TXT record from a byte array in the standard format. */
public DnsSdTxtRecord(byte[] data) {
mData = (byte[]) data.clone();
}
/** Copy constructor */
public DnsSdTxtRecord(DnsSdTxtRecord src) {
if (src != null && src.mData != null) {
mData = (byte[]) src.mData.clone();
}
}
/**
* Set a key/value pair. Setting an existing key will replace its value.
* @param key Must be ascii with no '='
* @param value matching value to key
*/
public void set(String key, String value) {
byte[] keyBytes;
byte[] valBytes;
int valLen;
if (value != null) {
valBytes = value.getBytes();
valLen = valBytes.length;
} else {
valBytes = null;
valLen = 0;
}
try {
keyBytes = key.getBytes("US-ASCII");
}
catch (java.io.UnsupportedEncodingException e) {
throw new IllegalArgumentException("key should be US-ASCII");
}
for (int i = 0; i < keyBytes.length; i++) {
if (keyBytes[i] == '=') {
throw new IllegalArgumentException("= is not a valid character in key");
}
}
if (keyBytes.length + valLen >= 255) {
throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
}
int currentLoc = remove(key);
if (currentLoc == -1)
currentLoc = keyCount();
insert(keyBytes, valBytes, currentLoc);
}
/**
* Get a value for a key
*
* @param key
* @return The value associated with the key
*/
public String get(String key) {
byte[] val = this.getValue(key);
return val != null ? new String(val) : null;
}
/** Remove a key/value pair. If found, returns the index or -1 if not found */
public int remove(String key) {
int avStart = 0;
for (int i=0; avStart < mData.length; i++) {
int avLen = mData[avStart];
if (key.length() <= avLen &&
(key.length() == avLen || mData[avStart + key.length() + 1] == mSeparator)) {
String s = new String(mData, avStart + 1, key.length());
if (0 == key.compareToIgnoreCase(s)) {
byte[] oldBytes = mData;
mData = new byte[oldBytes.length - avLen - 1];
System.arraycopy(oldBytes, 0, mData, 0, avStart);
System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart,
oldBytes.length - avStart - avLen - 1);
return i;
}
}
avStart += (0xFF & (avLen + 1));
}
return -1;
}
/** Return the count of keys */
public int keyCount() {
int count = 0, nextKey;
for (nextKey = 0; nextKey < mData.length; count++) {
nextKey += (0xFF & (mData[nextKey] + 1));
}
return count;
}
/** Return true if key is present, false if not. */
public boolean contains(String key) {
String s = null;
for (int i = 0; null != (s = this.getKey(i)); i++) {
if (0 == key.compareToIgnoreCase(s)) return true;
}
return false;
}
/* Gets the size in bytes */
public int size() {
return mData.length;
}
/* Gets the raw data in bytes */
public byte[] getRawData() {
return (byte[]) mData.clone();
}
private void insert(byte[] keyBytes, byte[] value, int index) {
byte[] oldBytes = mData;
int valLen = (value != null) ? value.length : 0;
int insertion = 0;
int newLen, avLen;
for (int i = 0; i < index && insertion < mData.length; i++) {
insertion += (0xFF & (mData[insertion] + 1));
}
avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
newLen = avLen + oldBytes.length + 1;
mData = new byte[newLen];
System.arraycopy(oldBytes, 0, mData, 0, insertion);
int secondHalfLen = oldBytes.length - insertion;
System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen);
mData[insertion] = (byte) avLen;
System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length);
if (value != null) {
mData[insertion + 1 + keyBytes.length] = mSeparator;
System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen);
}
}
/** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */
private String getKey(int index) {
int avStart = 0;
for (int i=0; i < index && avStart < mData.length; i++) {
avStart += mData[avStart] + 1;
}
if (avStart < mData.length) {
int avLen = mData[avStart];
int aLen = 0;
for (aLen=0; aLen < avLen; aLen++) {
if (mData[avStart + aLen + 1] == mSeparator) break;
}
return new String(mData, avStart + 1, aLen);
}
return null;
}
/**
* Look up a key in the TXT record by zero-based index and return its value.
* Returns null if index exceeds the total number of keys.
* Returns null if the key is present with no value.
*/
private byte[] getValue(int index) {
int avStart = 0;
byte[] value = null;
for (int i=0; i < index && avStart < mData.length; i++) {
avStart += mData[avStart] + 1;
}
if (avStart < mData.length) {
int avLen = mData[avStart];
int aLen = 0;
for (aLen=0; aLen < avLen; aLen++) {
if (mData[avStart + aLen + 1] == mSeparator) {
value = new byte[avLen - aLen - 1];
System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1);
break;
}
}
}
return value;
}
private String getValueAsString(int index) {
byte[] value = this.getValue(index);
return value != null ? new String(value) : null;
}
private byte[] getValue(String forKey) {
String s = null;
int i;
for (i = 0; null != (s = this.getKey(i)); i++) {
if (0 == forKey.compareToIgnoreCase(s)) {
return this.getValue(i);
}
}
return null;
}
/**
* Return a string representation.
* Example : {key1=value1},{key2=value2}..
*
* For a key say like "key3" with null value
* {key1=value1},{key2=value2}{key3}
*/
public String toString() {
String a, result = null;
for (int i = 0; null != (a = this.getKey(i)); i++) {
String av = "{" + a;
String val = this.getValueAsString(i);
if (val != null)
av += "=" + val + "}";
else
av += "}";
if (result == null)
result = av;
else
result = result + ", " + av;
}
return result != null ? result : "";
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof DnsSdTxtRecord)) {
return false;
}
DnsSdTxtRecord record = (DnsSdTxtRecord)o;
return Arrays.equals(record.mData, mData);
}
@Override
public int hashCode() {
return Arrays.hashCode(mData);
}
/** Implement the Parcelable interface */
public int describeContents() {
return 0;
}
/** Implement the Parcelable interface */
public void writeToParcel(Parcel dest, int flags) {
dest.writeByteArray(mData);
}
/** Implement the Parcelable interface */
public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR =
new Creator<DnsSdTxtRecord>() {
public DnsSdTxtRecord createFromParcel(Parcel in) {
DnsSdTxtRecord info = new DnsSdTxtRecord();
in.readByteArray(info.mData);
return info;
}
public DnsSdTxtRecord[] newArray(int size) {
return new DnsSdTxtRecord[size];
}
};
}