/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/**
* @author Alexei Y. Zakharov
*/
package org.apache.harmony.jndi.provider.dns;
import java.util.StringTokenizer;
import org.apache.harmony.jndi.internal.nls.Messages;
/**
* Contains some useful routines that are used in other classes.
*/
public class ProviderMgr {
static final int LOG_NONE = 0;
static final int LOG_ERROR = 1;
static final int LOG_WARNING = 2;
static final int LOG_DEBUG = 3;
static final boolean CHECK_NAMES = false;
/**
* Parses the given domain name and converts it into
* <code>length label length label ... length label</code> sequence of
* bytes.
*
* @param name
* a domain name, a dot-separated list of labels
* @param buffer
* target buffer in which the result will be written
* @param startIdx
* the index to start at while writing to the buffer array
* @return updated index of the buffer array
*/
public static int writeName(String name, byte[] buffer, int startIdx)
throws DomainProtocolException {
StringTokenizer st;
int idx = startIdx;
if (name != null) {
// initial check
if (buffer == null) {
// jndi.32=buffer is null
throw new NullPointerException(Messages.getString("jndi.32")); //$NON-NLS-1$
}
if (startIdx > buffer.length || startIdx < 0) {
throw new ArrayIndexOutOfBoundsException();
}
// parsing the name
// if (CHECK_NAMES && !checkName(name)) {
// throw new DomainProtocolException(
// "The syntax of the domain name " +
// name + " does not conform to RFC 1035");
// }
st = new StringTokenizer(name, "."); //$NON-NLS-1$
while (st.hasMoreTokens()) {
String token = st.nextToken();
byte[] tokenBytes;
int tokenBytesLen;
if (token == null || token.length() == 0) {
break;
}
tokenBytes = token.getBytes();
tokenBytesLen = tokenBytes.length;
if (tokenBytesLen > ProviderConstants.LABEL_MAX_CHARS) {
// jndi.64=The domain label is too long: {0}
throw new DomainProtocolException(Messages.getString(
"jndi.64", token)); //$NON-NLS-1$
}
if (idx + tokenBytesLen + 1 > buffer.length) {
throw new ArrayIndexOutOfBoundsException();
}
buffer[idx++] = (byte) tokenBytesLen;
for (int i = 0; i < tokenBytesLen; i++) {
buffer[idx++] = tokenBytes[i];
}
if (idx - startIdx + 1 > ProviderConstants.NAME_MAX_CHARS) {
// jndi.5A=The domain name is more than {0} octets long: {1}
throw new DomainProtocolException(Messages.getString(
"jndi.5A", ProviderConstants.NAME_MAX_CHARS, name)); //$NON-NLS-1$
}
}
// every domain name should end with an zero octet
buffer[idx++] = (byte) 0;
}
return idx;
}
/**
* Parses the domain name from the sequence of bytes.
*
* @param mesBytes
* byte representation of the message
* @param startIdx
* the position to start the parsing at
* @param result
* the string buffer to store parsed strings into
* @return updated index of <code>mesBytes</code> array
* @throws DomainProtocolException
* if something went wrong
*/
public static int parseName(byte[] mesBytes, int startIdx,
StringBuffer result) throws DomainProtocolException {
int idx = startIdx;
boolean firstTime = true;
if (mesBytes == null) {
// jndi.5B=Input byte array is null
throw new NullPointerException(Messages.getString("jndi.5B")); //$NON-NLS-1$
}
if (result == null) {
// jndi.5C=The result string buffer is null
throw new NullPointerException(Messages.getString("jndi.5C")); //$NON-NLS-1$
}
while (true) {
int n = parse8Int(mesBytes, idx++);
if (n == 0) {
// end of the domain name reached
break;
}
if ((n & 0xc0) == 0xc0) {
// compressed label
int namePtr = parse16Int(mesBytes, --idx) & 0x3fff;
idx += 2;
if (!firstTime) {
result.append('.');
}
parseName(mesBytes, namePtr, result);
break;
}
// plain label
if (n > ProviderConstants.LABEL_MAX_CHARS) {
// jndi.59=Domain label is too long.
throw new DomainProtocolException(Messages.getString("jndi.59")); //$NON-NLS-1$
}
if (idx + n > mesBytes.length) {
// jndi.5D=Truncated data while parsing the domain name
throw new DomainProtocolException(Messages.getString("jndi.5D")); //$NON-NLS-1$
}
// append parsed label
if (firstTime) {
firstTime = false;
} else {
result.append('.');
}
result.append(new String(mesBytes, idx, n));
idx += n;
}
return idx;
}
/**
* Compares two labels and returns the matching count (number of the
* matching labels down from the root).
*
* @param name1
* first name
* @param name2
* second name
* @return number of equal labels from the root to leaves
*/
public static int getMatchingCount(String name1, String name2) {
StringTokenizer st1, st2;
int k = 0;
if (name1 == null || name2 == null) {
return 0;
}
st1 = new StringTokenizer(name1, "."); //$NON-NLS-1$
st2 = new StringTokenizer(name2, "."); //$NON-NLS-1$
while (st1.hasMoreTokens() && st2.hasMoreTokens()) {
if (st1.nextToken().equalsIgnoreCase(st2.nextToken())) {
k++;
} else {
break;
}
}
return k;
}
/**
* Returns the name of parent DNS zone for given zone.
*
* @param name
* the current DNS zone name
* @return the name of the parent
*/
public static String getParentName(String name) {
int n;
if (name == null) {
return null;
}
if (name.trim().equals(".") || name.trim().length() == 0) { //$NON-NLS-1$
return "."; //$NON-NLS-1$
}
n = name.indexOf('.');
if (n != -1 && name.length() > n + 1) {
return name.substring(n + 1, name.length());
}
return "."; //$NON-NLS-1$
}
/**
* Writes a 16-bit integer value into the buffer, high byte first
*
* @param value
* the value to write, first 16 bits will be taken
* @param buffer
* the buffer to write into
* @param startIdx
* a starting index
* @return updated index
*/
public static int write16Int(int value, byte[] buffer, int startIdx) {
int idx = startIdx;
buffer[idx++] = (byte) ((value & 0xff00) >> 8);
buffer[idx++] = (byte) (value & 0xff);
return idx;
}
/**
* Writes a 32-bit integer value into the buffer, highest byte first
*
* @param value
* the value to write, first 32 bits will be taken
* @param buffer
* the buffer to write into
* @param startIdx
* a starting index
* @return updated index
*/
public static int write32Int(long value, byte[] buffer, int startIdx) {
int idx = startIdx;
buffer[idx++] = (byte) ((value & 0xff000000l) >> 24);
buffer[idx++] = (byte) ((value & 0xff0000) >> 16);
buffer[idx++] = (byte) ((value & 0xff00) >> 8);
buffer[idx++] = (byte) (value & 0xff);
return idx;
}
/**
* Parses 8 bit integer. Buffer index should be updated manually after call
* to this method.
*
* @param buffer
* sequence of bytes
* @param startIdx
* the index to start at
* @return parsed integer value
*/
public static int parse8Int(byte[] buffer, int idx) {
return (buffer[idx]) & 0xff;
}
/**
* Parses 16 bit integer. Buffer index should be updated manually after call
* to this method.
*
* @param buffer
* sequence of bytes
* @param startIdx
* the index to start at
* @return parsed integer value
*/
public static int parse16Int(byte[] buffer, int idx) {
int a = ((buffer[idx]) & 0xff) << 8;
int b = (buffer[idx + 1]) & 0xff;
return (a | b);
}
/**
* Parses 32 bit integer. Buffer index should be updated manually after call
* to this method.
*
* @param buffer
* sequence of bytes
* @param startIdx
* the index to start at
* @return parsed integer value
*/
public static long parse32Int(byte[] buffer, int idx) {
long a = (((long) buffer[idx]) & 0xff) << 24;
long b = (((long) buffer[idx + 1]) & 0xff) << 16;
long c = (((long) buffer[idx + 2]) & 0xff) << 8;
long d = ((long) buffer[idx + 3]) & 0xff;
return (a | b | c | d);
}
/**
* Writes character string preceded with length octet.
*
* @param value
* string value to write
* @param buffer
* buffer to write to
* @param startIdx
* index in buffer to start from
* @return updated index
* @throws NullPointerException
* if some argument is null
* @throws DomainProtocolException
* if string is too long
*/
public static int writeCharString(String value, byte[] buffer, int startIdx)
throws DomainProtocolException {
byte[] bytes;
int idx = startIdx;
if (value == null || buffer == null) {
// jndi.5E=value or buffer is null
throw new NullPointerException(Messages.getString("jndi.5E")); //$NON-NLS-1$
}
if (value.length() > 255) {
// jndi.5F=Character string is too long
throw new DomainProtocolException(Messages.getString("jndi.5F")); //$NON-NLS-1$
}
bytes = value.getBytes();
buffer[idx++] = (byte) bytes.length;
for (byte element : bytes) {
buffer[idx++] = element;
}
return idx;
}
/**
* Parses the string of characters preceded with length octet.
*
* @param mesBytes
* message bytes
* @param startIdx
* the index to start parsing from
* @param result
* string buffer to write the result too
* @return updated index
*/
public static int parseCharString(byte[] mesBytes, int startIdx,
StringBuffer result) {
int len;
if (mesBytes == null || result == null) {
// jndi.60=mesBytes or result is null
throw new NullPointerException(Messages.getString("jndi.60")); //$NON-NLS-1$
}
len = mesBytes[startIdx];
result.append(new String(mesBytes, startIdx + 1, len));
return startIdx + 1 + len;
}
/**
* Sets or drops specific bit(s) of the given number.
*
* @param value
* target integer value
* @param mask
* specifies bit(s) position(s)
* @param bit
* set if <code>true</code>, drop if <code>false</code>
* @return updated <code>value</code>
*/
public static int setBit(int value, int mask, boolean bit) {
if (bit) {
value |= mask;
} else {
value &= ~mask;
}
return value;
}
/**
* Checks if any of specified bits is set.
*
* @param value
* the number to look at
* @param mask
* a bit mask
* @return <code>true</code> of <code>false</code>
*/
public static boolean checkBit(int value, int mask) {
if ((value & mask) == 0) {
return false;
}
return true;
}
/**
* Compares two DNS names.
* <ol>
* <li>Case insensitive</li>
* <li>Appends "." to the end of the name if necessary before comparison</li>
* </ol>
*
* @param name1
* name1
* @param name2
* name2
* @return <code>true</code> if names are equal; <code>false</code>
* otherwise
*/
public static boolean namesAreEqual(String name1, String name2) {
if (!name1.endsWith(".")) { //$NON-NLS-1$
name1 += "."; //$NON-NLS-1$
}
if (!name2.endsWith(".")) { //$NON-NLS-1$
name2 += "."; //$NON-NLS-1$
}
return name1.equalsIgnoreCase(name2);
}
/**
* Removes _Service._Proto fields from SRV-style qName if any. Adds final
* dot to the end of name
*
* @param name
* name to process
* @return converted name
*/
/*
* public static String removeSRVExtra(String name) { StringTokenizer st;
* int k = 0; StringBuffer res = new StringBuffer();
*
* if (name == null) { return name; } st = new StringTokenizer(name, ".",
* false); while (st.hasMoreTokens()) { String token = st.nextToken();
*
* if ((k != 0 && k != 1) || !token.startsWith("_")) { res.append(token); }
* k++; } return res.toString(); }
*/
/**
* Converts all letters to lower case and adds "." to the end of zone name
* if necessary.
*
* @param zone
* zone name
* @return expanded zone name
*/
public static String normalizeName(String zone) {
if (zone == null) {
return zone;
}
return zone.endsWith(".") ? zone.toLowerCase() : //$NON-NLS-1$
zone.toLowerCase() + "."; //$NON-NLS-1$
}
/**
* Creates the text representation of IPv4 address.
*
* @param ip
* the first four bytes should contain an IPv4 address
* @return string in <code>n.n.n.n</code> format
* @throws IllegalArgumentException
* if given array has the length less than four
*/
public static String getIpStr(byte[] ip) {
StringBuilder sb = new StringBuilder();
if (ip == null || ip.length < 4) {
// jndi.61=Given array is null or has the length less than four
throw new IllegalArgumentException(Messages.getString("jndi.61")); //$NON-NLS-1$
}
for (int i = 0; i < 4; i++) {
if (i > 0) {
sb.append("."); //$NON-NLS-1$
}
sb.append("" + ((ip[i]) & 0xff)); //$NON-NLS-1$
}
return sb.toString();
}
/**
* Parses the text representation of IPv4 address.
*
* @param ipStr
* string in <code>n.n.n.n</code> format.
* @return four bytes with parsed IP address
* @throws NullPointerException
* if <code>ipStr</code> is null
* @throws IllegalArgumentException
* if given string is not in appropriate format
*/
public static byte[] parseIpStr(String ipStr) {
StringTokenizer st;
byte[] b = new byte[4];
// jndi.62=Given string is not in appropriate format
final String errMsg1 = Messages.getString("jndi.62"); //$NON-NLS-1$
if (ipStr != null) {
int k = 0;
st = new StringTokenizer(ipStr, "."); //$NON-NLS-1$
while (st.hasMoreTokens()) {
String token = st.nextToken();
int n;
try {
n = Integer.parseInt(token);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(errMsg1);
}
b[k++] = (byte) n;
}
if (k != 4) {
throw new IllegalArgumentException(errMsg1);
}
} else {
// jndi.63=Given string representation is null
throw new NullPointerException(Messages.getString("jndi.63")); //$NON-NLS-1$
}
return b;
}
/**
* @param str
* string name of the DNS record class
* @return integer number for the class; <code>-1</code> if not found
*/
public static int getRecordClassNumber(String str) {
for (int i = 0; i < ProviderConstants.rrClassNames.length; i++) {
if (ProviderConstants.rrClassNames[i] != null) {
if (str.equalsIgnoreCase(ProviderConstants.rrClassNames[i])) {
return i;
}
}
}
return -1;
}
/**
* @param str
* string name of the DNS record type
* @return integer number for the type; <code>-1</code> if not found
*/
public static int getRecordTypeNumber(String str) {
for (int i = 0; i < ProviderConstants.rrTypeNames.length; i++) {
if (ProviderConstants.rrTypeNames[i] != null) {
if (str.equalsIgnoreCase(ProviderConstants.rrTypeNames[i])) {
return i;
}
}
}
return -1;
}
}