/* * @(#) DNSName.java - Class for representing DNS resource name. * (c) 1999-2001 Ivan Maidanski http://ivmai.chat.ru * Freeware class library sources. All rights reserved. ** * Language: Java [pure] * Tested with: JDK v1.1.6 * Last modified: 2001-01-29 11:15:00 GMT+03:00 */ /* * This software is the proprietary information of the author. ** * Permission to use, copy, and distribute this software and its * documentation for non-commercial purposes and without fee is * hereby granted provided that this copyright notice appears in all * copies. ** * This software should not be modified in any way; any found bug * should be reported to the author. ** * The author disclaims all warranties with regard to this software, * including all implied warranties of merchantability and fitness. * In no event shall the author be liable for any special, indirect * or consequential damages or any damages whatsoever resulting from * loss of use, data or profits, whether in an action of contract, * negligence or other tortuous action, arising out of or in * connection with the use or performance of this software. */ package ivmai.dns; import java.io.InvalidObjectException; import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import ivmai.util.Immutable; import ivmai.util.JavaConsts; import ivmai.util.ParserException; import ivmai.util.ReallyCloneable; import ivmai.util.Sortable; import ivmai.util.Verifiable; /** * Class for representing DNS resource name (as defined in RFC1035). ** * @version 3.0 * @author Ivan Maidanski */ public final class DNSName implements Immutable, ReallyCloneable, Serializable, Sortable, Verifiable { /** * The class version unique identifier for serialization * interoperability. ** * @since 2.3 */ private static final long serialVersionUID = 902307735131026602L; public static final char SEPARATOR = '.'; public static final char WILDCARD = '*'; public static final char ESCAPE = '\\'; public static final char THIS_ZONE = '@'; public static final char EMAIL = '@'; public static final char QUOTE = '"'; public static final int MAX_LABEL_LEN = 0x3F; public static final int COMPRESSED_NAME_TAG = 0xC0; /** * NOTE: These are possible error codes for DNSName(name, domain) * and parse(name, escapeSeparator, domainBytes, domainOffset). ** * @since 3.0 */ public static final int ERROR_BAD_CHAR = 1; public static final int ERROR_BAD_ESCAPING = 2; public static final int ERROR_EMPTY_LABEL = 3; public static final int ERROR_LONG_NAME = 4; public static final int ERROR_UNSUPPORTED = 5; public static final DNSName ROOT = new DNSName(new String(), null); public static final DNSName IN_ADDR_ARPA = new DNSName("in-addr.arpa", null); public static final DNSName IP6_INT = new DNSName("ip6.int", null); /** * NOTE: bytes != null. bytes length must be == lengthOf(bytes, 0). * bytes may be not canonical, bytes cannot be compressed. bytes * content is described in RFC1035. ** * @serial */ protected final byte[] bytes; /** * NOTE: name must be != null, name may be == "", name may be * THIS_ZONE. domain may be == null. * ParserException(name, index, error) is thrown only if name cannot * be parsed. ** * @since 2.0 */ public DNSName(String name, DNSName domain) throws NullPointerException, ParserException { this.bytes = parse(name.length() != 1 || name.charAt(0) != THIS_ZONE ? name : new String(), true, domain != null ? domain.bytes : null, 0); } /** * NOTE: name must be != null. Constructed DNS name is canonized. ** * @since 2.5 */ public DNSName(DNSName name) throws NullPointerException { byte[] bytes, nameBytes = name.bytes; this.bytes = canonize(bytes = (byte[])nameBytes.clone(), 0) ? bytes : nameBytes; } /** * NOTE: bytes must be != null. ArrayIndexOutOfBoundsException is * thrown only if 0 > offset or offset >= bytes length. * Decompression is supported. If bytes content is not valid then * IllegalArgumentException is thrown. bytes array is not changed * anyway. ** * @since 2.1 */ public DNSName(byte[] bytes, int offset) throws NullPointerException, ArrayIndexOutOfBoundsException, IllegalArgumentException { int len; byte[] bytesCopy; if ((len = lengthOf(bytes, offset)) > 0) { System.arraycopy(bytes, offset, bytesCopy = new byte[len], 0, len); if (lengthOf(this.bytes = bytesCopy, 0) == len) return; } else if ((len = -len) > 0) { System.arraycopy(bytes, offset, bytesCopy = new byte[len], 0, len); if ((bytes = decompress(bytesCopy, 0, bytes)) != null && lengthOf(this.bytes = bytes, 0) == bytes.length) return; } throw new IllegalArgumentException("Bad resource name"); } /** * NOTE: bytes must be != null. Method for putting resource name to * byte array. No compression. Enough capacity must be provided (at * least getBytesLen() bytes). Result is new offset (just after this * name). ** * @since 2.5 */ public int putTo(byte[] bytes, int offset) throws NullPointerException, ArrayIndexOutOfBoundsException { byte[] bytesCopy; int len = (bytesCopy = this.bytes).length; System.arraycopy(bytesCopy, 0, bytes, offset, len); return offset + len; } /** * NOTE: Result != null, result length > 0, result is copy. */ public final byte[] getBytes() { return (byte[])this.bytes.clone(); } /** * NOTE: Result > 0. ** * @since 2.1 */ public final int getBytesLen() { return this.bytes.length; } /** * NOTE: Letters case in result is not adjusted to canonical. */ public String getAbsolute() { byte[] bytes = this.bytes; return toString(bytes, 0, countLabels(bytes, 0), true, true); } /** * NOTE: All separator characters in labels (if exist) are escaped. */ public String[] getLabels() { byte[] bytes = this.bytes; int level = countLabels(bytes, 0); String[] labels = new String[level]; for (int offset = 0; level > 0; offset += (bytes[offset] & JavaConsts.BYTE_MASK) + 1) labels[--level] = toString(bytes, offset, 1, true, false); return labels; } public String getLabelAt(int level) throws ArrayIndexOutOfBoundsException { byte[] bytes = this.bytes; int backLevel; if (level >= 0 && (backLevel = countLabels(bytes, 0) - level - 1) >= 0) return toString(bytes, labelOffset(bytes, 0, backLevel), 1, true, false); throw new ArrayIndexOutOfBoundsException(level); } public int getLevel() { return countLabels(this.bytes, 0); } /** * NOTE: domain may be == null. Result != null, result != "". * THIS_ZONE may be returned. */ public String getRelative(DNSName domain) { if (this != domain) { byte[] bytes = this.bytes, domainBytes; int level = countLabels(bytes, 0); boolean absolute = true; if (domain != null) { int domainLevel = countLabels(domainBytes = domain.bytes, 0); if (level >= domainLevel && bytes.length >= domainBytes.length && compareNames(bytes, labelOffset(bytes, 0, level - domainLevel), domainBytes, 0) == 0) { level -= domainLevel; absolute = false; } } if (absolute || level > 0) return toString(bytes, 0, level, true, absolute); } char[] chars = new char[1]; chars[0] = THIS_ZONE; return new String(chars); } /** * NOTE: domain may be == null. Letters case is ignored. */ public boolean isInDomain(DNSName domain, boolean strict) { strict = !strict; if (this != domain) { if (domain == null) return false; byte[] bytes = this.bytes, domainBytes; int level; int domainLevel = countLabels(domainBytes = domain.bytes, 0); strict = (level = countLabels(bytes, 0)) >= domainLevel && (strict || level != domainLevel) && bytes.length >= domainBytes.length && compareNames(bytes, labelOffset(bytes, 0, level - domainLevel), domainBytes, 0) == 0; } return strict; } /** * NOTE: Negative level is treated as zero. Result != null. */ public DNSName getDomain(int level) throws ArrayIndexOutOfBoundsException { byte[] bytes = this.bytes; int backLevel; if (level <= 0) level = 0; if ((backLevel = countLabels(bytes, 0) - level) >= 0) return new DNSName(bytes, labelOffset(bytes, 0, backLevel)); throw new ArrayIndexOutOfBoundsException(level); } /** * NOTE: Result != null. */ public DNSName getDomain() { byte[] bytes = this.bytes; int offset; if ((offset = bytes[0] & JavaConsts.BYTE_MASK) > 0) offset++; return new DNSName(bytes, offset); } public String getLastLabel() { byte[] bytes = this.bytes; return toString(bytes, 0, bytes[0] != 0 ? 1 : 0, true, false); } public String getRelativeAt(int level) throws ArrayIndexOutOfBoundsException { byte[] bytes = this.bytes; if (level >= 0) return toString(bytes, 0, countLabels(bytes, 0) - level, true, false); throw new ArrayIndexOutOfBoundsException(level); } public boolean isCaseSensitive() { return false; } public String joinLabels(String[] labels, int level, int count, boolean absolute) throws NullPointerException, ArrayIndexOutOfBoundsException { int len = 0, index; if (count > 0) for (index = level + count; index > level; len += labels[--index].length() + 1); if (len <= 0) len = 1; StringBuffer sBuf = new StringBuffer(len); if (count > 0) { if (labels[level].length() <= 0) { if (count == 1) sBuf.append(SEPARATOR); absolute = true; } level += count; do { String label = labels[--level]; char ch; len = label.length(); if (label.indexOf(SEPARATOR, 0) < 0) { sBuf.append(label); while (--len >= 0 && label.charAt(len) == ESCAPE); if (((label.length() - len) & 1) == 0) sBuf.append(ESCAPE); } else for (index = 0; index < len; sBuf.append(ch)) if ((ch = label.charAt(index++)) == ESCAPE || ch == SEPARATOR) { if (ch == ESCAPE && index < len) ch = label.charAt(index++); sBuf.append(ESCAPE); } if (--count <= 0) break; sBuf.append(SEPARATOR); } while (true); } len = labels.length; if (absolute) sBuf.append(SEPARATOR); return new String(sBuf); } public final boolean reversed() { return true; } public final char separator() { return SEPARATOR; } public DNSName root() { return ROOT; } /** * NOTE: bytes must be != null. ArrayIndexOutOfBoundsException is * thrown only if 0 > offset or offset >= bytes length. If result * > 0 then bytes content is valid (and not compressed), else if 0 * > result then bytes content is compressed with result length * (compressed), else name is not valid. */ public static final int lengthOf(byte[] bytes, int offset) throws NullPointerException, ArrayIndexOutOfBoundsException { int len, bytesLen = bytes.length, beginning = offset; while ((len = bytes[offset++] & JavaConsts.BYTE_MASK) > 0) if (len > MAX_LABEL_LEN) if (len >= COMPRESSED_NAME_TAG && offset < bytesLen) return ~(offset - beginning); else return 0; else if ((offset += len) >= bytesLen) return 0; return offset - beginning; } /** * NOTE: bytes and msgBytes must be != null. If bytes content is * valid and not compressed then result == bytes. Else if * compressed-label tag is found then name is decompressed to new * bytes array, preserving (copying) content from bytes array before * and after specified name. After processing all compressed-label * tags (in specified name) new bytes array is returned (if * decompression fails then result == null). * ArrayIndexOutOfBoundsException is thrown only if 0 > offset or * offset >= bytes length. bytes and msgBytes arrays are not changed * anyway. */ public static final byte[] decompress(byte[] bytes, int offset, byte[] msgBytes) throws NullPointerException, ArrayIndexOutOfBoundsException { int len, bytesLen = bytes.length, msgLength; int lastAddr = msgLength = msgBytes.length; while ((len = bytes[offset] & JavaConsts.BYTE_MASK) > 0) if (len > MAX_LABEL_LEN) if (len >= COMPRESSED_NAME_TAG) { if (bytesLen - 1 <= offset) return null; int address = len = bytes[offset + 1] & JavaConsts.BYTE_MASK | ((len - COMPRESSED_NAME_TAG) << JavaConsts.BYTE_SIZE), count; do { if (len >= msgLength) return null; if ((count = msgBytes[len++] & JavaConsts.BYTE_MASK) <= 0) break; if (count > MAX_LABEL_LEN) if (++len > msgLength) return null; else break; len += count; } while (true); if (len > lastAddr) return null; len -= lastAddr = address; if ((bytesLen -= 2) + len <= 0) return null; byte[] newBytes; System.arraycopy(bytes, offset + 2, newBytes = new byte[bytesLen + len], offset + len, bytesLen - offset); System.arraycopy(bytes, 0, newBytes, 0, offset); System.arraycopy(msgBytes, address, bytes = newBytes, offset, len); bytesLen += len; } else return null; else if ((offset += len + 1) >= bytesLen) return null; return bytes; } /** * NOTE: msgBytes must be valid (at offset and at baseNameOffset) * and not compressed. If possible then name at offset is compressed * (using only content at baseNameOffset). msgBytes array is * altered. Result is new offset (after processed name). ** * @since 2.5 */ public static final int compressAt(byte[] msgBytes, int offset, int baseNameOffset) throws NullPointerException, ArrayIndexOutOfBoundsException { int len = countLabels(msgBytes, offset) - countLabels(msgBytes, baseNameOffset); int destOffset = 0, srcOffset = 0, beginning = offset; if (len > 0) do { offset += (msgBytes[offset] & JavaConsts.BYTE_MASK) + 1; } while (--len > 0); else while (++len <= 0) baseNameOffset += (msgBytes[baseNameOffset] & JavaConsts.BYTE_MASK) + 1; for (int baseLen; (len = msgBytes[offset++] & JavaConsts.BYTE_MASK) > 0; offset += len, baseNameOffset += baseLen) if ((baseLen = msgBytes[baseNameOffset++] & JavaConsts.BYTE_MASK) == len) { do { if (msgBytes[offset] != msgBytes[baseNameOffset]) break; offset++; baseNameOffset++; } while (--len > 0); if (len > 0) destOffset = 0; else if (destOffset <= 0) { destOffset = offset - baseLen; srcOffset = baseNameOffset - baseLen - 1; } baseLen = len; } else destOffset = 0; if (destOffset > 0 && baseNameOffset < beginning && srcOffset >> JavaConsts.BYTE_SIZE <= JavaConsts.BYTE_MASK - COMPRESSED_NAME_TAG) { msgBytes[(offset = destOffset) - 1] = (byte)((srcOffset >> JavaConsts.BYTE_SIZE) + COMPRESSED_NAME_TAG); msgBytes[offset++] = (byte)srcOffset; } return offset; } /** * NOTE: bytes must be valid (and not compressed). Upper-case * letters are converted to lower-case. Result is true if and only * if bytes array is altered. ** * @since 2.5 */ public static final boolean canonize(byte[] bytes, int offset) throws NullPointerException, ArrayIndexOutOfBoundsException { int value, len; boolean changed = false; while ((len = bytes[offset++] & JavaConsts.BYTE_MASK) > 0) do { if ((char)((value = bytes[offset] & JavaConsts.BYTE_MASK) - 'A') <= 'Z' - 'A') { bytes[offset] = (byte)(value + ('a' - 'A')); changed = true; } offset++; } while (--len > 0); return changed; } /** * NOTE: bytes must be valid (and not compressed). Result >= 0. */ public static final int countLabels(byte[] bytes, int offset) throws NullPointerException, ArrayIndexOutOfBoundsException { int count = 0; for (int len; (len = bytes[offset] & JavaConsts.BYTE_MASK) > 0; offset += len + 1, count++); return count; } /** * NOTE: bytes must be valid (and not compressed). Negative * backLevel is treated as zero. Result >= offset. */ public static final int labelOffset(byte[] bytes, int offset, int backLevel) throws NullPointerException, ArrayIndexOutOfBoundsException { while (backLevel-- > 0) offset += (bytes[offset] & JavaConsts.BYTE_MASK) + 1; return offset; } /** * NOTE: bytes must be valid (and not compressed). Upper-case * letters are treated as lower-case ones. */ public static final int hashCode(byte[] bytes, int offset) throws NullPointerException, ArrayIndexOutOfBoundsException { int code = 0, hash, index = 0; while ((hash = bytes[offset++] & JavaConsts.BYTE_MASK) > 0) { int len = hash, value; do { if ((char)((value = bytes[offset++] & JavaConsts.BYTE_MASK) - 'A') <= 'Z' - 'A') value += 'a' - 'A'; hash = ((hash << 5) - hash) ^ value; } while (--len > 0); code ^= ++index * hash; } return code; } /** * NOTE: bytesA and bytesB must be valid (and not compressed). Names * are compared in label-by-label manner, starting at level 0. * Upper-case letters are treated as lower-case (as defined in * RFC2065). */ public static final int compareNames(byte[] bytesA, int offsetA, byte[] bytesB, int offsetB) throws NullPointerException, ArrayIndexOutOfBoundsException { int levelA = 0, levelB = 0; if (offsetA != offsetB || bytesA != bytesB) { levelA = countLabels(bytesA, offsetA); levelB = countLabels(bytesB, offsetB); while ((--levelA | --levelB) >= 0) { int indexA, indexB, valueA, valueB; int lenA = bytesA[indexA = labelOffset(bytesA, offsetA, levelA)] & JavaConsts.BYTE_MASK; int lenB = bytesB[indexB = labelOffset(bytesB, offsetB, levelB)] & JavaConsts.BYTE_MASK; while ((--lenA | --lenB) >= 0) if (bytesA[++indexA] != bytesB[++indexB]) { if ((char)((valueA = bytesA[indexA] & JavaConsts.BYTE_MASK) - 'A') <= 'Z' - 'A') valueA += 'a' - 'A'; if ((char)((valueB = bytesB[indexB] & JavaConsts.BYTE_MASK) - 'A') <= 'Z' - 'A') valueB += 'a' - 'A'; if (valueA != valueB) return valueA - valueB; } if ((lenA -= lenB) != 0) return lenA; } } return levelA - levelB; } /** * NOTE: bytes must be valid (and not compressed). If * !escapeSeparator then separator characters in labels (if any) are * not escaped. Result != null, result is escaped, result is * 'in-line'. Negative labelsCount is treated as zero. */ public static final String toString(byte[] bytes, int offset, int labelsCount, boolean escapeSeparator, boolean absolute) throws NullPointerException, ArrayIndexOutOfBoundsException { int len = labelOffset(bytes, offset, labelsCount); StringBuffer sBuf = new StringBuffer(len - offset + 1); if (labelsCount > 0) do { char ch; for (len = bytes[offset++] & JavaConsts.BYTE_MASK; len-- > 0; sBuf.append(ch)) if ((ch = (char)(bytes[offset++] & JavaConsts.BYTE_MASK)) != SEPARATOR && (char)(ch - 'a') > 'z' - 'a' && (char)(ch - 'A') > 'Z' - 'A' && (char)(ch - '0') > '9' - '0' && ch != WILDCARD && ch != '-' && ch != '_' || ch == SEPARATOR && escapeSeparator) { sBuf.append(ESCAPE); if (ch == '[' || (char)(ch - (' ' + 1)) >= (JavaConsts.BYTE_MASK >>> 1) - (' ' + 1)) { sBuf.append((char)(ch / (('9' - '0' + 1) * ('9' - '0' + 1)) + '0')).append((char)(ch / ('9' - '0' + 1) % ('9' - '0' + 1) + '0')); ch = (char)(ch % ('9' - '0' + 1) + '0'); } } if (--labelsCount <= 0) break; sBuf.append(SEPARATOR); } while (true); if (absolute) sBuf.append(SEPARATOR); return new String(sBuf); } /** * NOTE: name must be != null, domainBytes must be != null. If name * is not absolute then name is relative to the specified domain. * Escaping in name is allowed. name must not contain non-printable * characters. Result != null. ParserException(name, index, error) * is thrown only if name cannot be parsed. */ public static final byte[] parse(String name, boolean escapeSeparator, byte[] domainBytes, int domainOffset) throws NullPointerException, ParserException, ArrayIndexOutOfBoundsException { int offset = 0, count = 0, len = name.length(); int index = -1, error = 0; byte[] bytes = new byte[len + 1]; char ch, ch1; while (++index < len) if ((char)((ch = name.charAt(index)) - (' ' + 1)) >= (JavaConsts.BYTE_MASK >>> 1) - (' ' + 1)) { error = ERROR_BAD_CHAR; break; } else if (!escapeSeparator || ch != SEPARATOR) { if (ch == THIS_ZONE) { error = ERROR_BAD_CHAR; break; } else if (ch == ESCAPE && index + 1 < len) if ((ch = name.charAt(++index)) == '[') { error = ERROR_UNSUPPORTED; break; } else if ((char)(ch - '0') <= '9' - '0') { ch -= '0'; if (index + 1 < len && (ch1 = (char)(name.charAt(index + 1) - '0')) <= '9' - '0') { ch = (char)(ch * ('9' - '0' + 1) + ch1); if (++index + 1 < len && (ch1 = (char)(name.charAt(index + 1) - '0')) <= '9' - '0') { ch = (char)(ch * ('9' - '0' + 1) + ch1); index++; } } } if (ch > JavaConsts.BYTE_MASK) { error = ERROR_BAD_ESCAPING; break; } if (++count > MAX_LABEL_LEN) { error = ERROR_LONG_NAME; break; } bytes[++offset] = (byte)ch; } else if (count > 0) { bytes[(offset++) - count] = (byte)count; count = 0; } else if (len > 1) { error = ERROR_EMPTY_LABEL; break; } if (error > 0) throw new ParserException(name, index, error); if (len > 0) bytes[(offset++) - count] = (byte)count; if ((len == 0 || count > 0) && (domainBytes == null || (count = lengthOf(domainBytes, domainOffset)) <= 0)) count = 1; if (bytes.length != offset + count) { byte[] newBytes; System.arraycopy(bytes, 0, newBytes = new byte[offset + count > 0 ? offset + count : -1 >>> 1], 0, offset); bytes = newBytes; } if (count > 1) System.arraycopy(domainBytes, domainOffset, bytes, offset, count); return bytes; } /** * NOTE: Result is e-mail string represenation of DNS name. Result * != null, result is 'in-line'. Result == "" only if getLevel() * == 0. */ public String getAsEmail() { byte[] bytes = this.bytes; int level; StringBuffer sBuf = new StringBuffer(bytes.length - 1); if ((level = countLabels(bytes, 0)) > 0) sBuf.append(toString(bytes, 0, 1, false, false)).append(EMAIL). append(toString(bytes, (bytes[0] & JavaConsts.BYTE_MASK) + 1, level - 1, true, level == 1)); return new String(sBuf); } public boolean isWildcard() { byte[] bytes = this.bytes; return bytes[0] == 1 && (bytes[1] & JavaConsts.BYTE_MASK) == WILDCARD; } /** * NOTE: wildcard may be == null. Letters case is ignored. ** * @since 2.0 */ public boolean equalsWildcard(DNSName wildcard) { if (this != wildcard) { if (wildcard == null) return false; byte[] bytes = this.bytes, wildcardBytes; if ((wildcardBytes = wildcard.bytes) != bytes) { int offset = 0, wildcardOffset = 0; if (wildcardBytes[0] == 1 && (wildcardBytes[1] & JavaConsts.BYTE_MASK) == WILDCARD) if ((offset = countLabels(bytes, 0) - countLabels(wildcardBytes, wildcardOffset = 2)) > 0) offset = labelOffset(bytes, 0, offset); else return false; return compareNames(bytes, offset, wildcardBytes, wildcardOffset) == 0; } } return true; } /** * NOTE: name may be == null. This comparison is case-sensitive. ** * @since 2.5 */ public boolean equalsExact(DNSName name) { if (this != name) { if (name == null) return false; int offset; byte[] bytes = this.bytes, nameBytes; if ((nameBytes = name.bytes) != bytes) if ((offset = bytes.length) != nameBytes.length) return false; else while (offset-- > 0) if (bytes[offset] != nameBytes[offset]) return false; } return true; } public Object clone() { Object obj; try { if ((obj = super.clone()) instanceof DNSName && obj != this) return obj; } catch (CloneNotSupportedException e) {} throw new InternalError("CloneNotSupportedException"); } public int hashCode() { return hashCode(this.bytes, 0); } /** * NOTE: Letters case is ignored (according to RFC2065). */ public boolean equals(Object obj) { byte[] bytes = this.bytes, nameBytes; return obj == this || obj instanceof DNSName && (nameBytes = ((DNSName)obj).bytes).length == bytes.length && compareNames(bytes, 0, nameBytes, 0) == 0; } /** * NOTE: Method for canonical ordering (according to RFC2065). ** * @since 2.5 */ public boolean greaterThan(Object obj) { return obj != this && obj instanceof DNSName && compareNames(this.bytes, 0, ((DNSName)obj).bytes, 0) > 0; } /** * NOTE: name must be != null. */ public int compareTo(DNSName name) throws NullPointerException { return compareNames(this.bytes, 0, name.bytes, 0); } /** * NOTE: Result is 'in-line'. */ public String toString() { return getAbsolute(); } /** * NOTE: Check object for its integrity. For debug purpose only. ** * @since 2.5 */ public void integrityCheck() { byte[] bytes; if ((bytes = this.bytes) == null) throw new InternalError("bytes: null"); if (bytes.length <= 0 || lengthOf(bytes, 0) != bytes.length) throw new InternalError("Bad resource name"); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); byte[] bytes; if ((bytes = this.bytes) == null) throw new InvalidObjectException("bytes: null"); if (bytes.length <= 0 || lengthOf(bytes, 0) != bytes.length) throw new InvalidObjectException("Bad resource name"); } }