| /* -*- 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.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 mSeperator = '='; |
| |
| 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] == mSeperator)) { |
| 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] = mSeperator; |
| 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] == mSeperator) 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] == mSeperator) { |
| 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 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]; |
| } |
| }; |
| } |