blob: fc8566dad4906c1686f51a1eee56235ec9c23e6f [file] [log] [blame]
/*
* Copyright (c) 1999, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.jndi.ldap;
import java.io.UnsupportedEncodingException;
/**
* A BER encoder.
*
* @author Jagane Sundar
* @author Scott Seligman
* @author Vincent Ryan
*/
public final class BerEncoder extends Ber {
private int curSeqIndex;
private int seqOffset[];
private static final int INITIAL_SEQUENCES = 16;
private static final int DEFAULT_BUFSIZE = 1024;
// When buf is full, expand its size by the following factor.
private static final int BUF_GROWTH_FACTOR = 8;
/**
* Creates a BER buffer for encoding.
*/
public BerEncoder() {
this(DEFAULT_BUFSIZE);
}
/**
* Creates a BER buffer of a specified size for encoding.
* Specify the initial bufsize. Buffer will be expanded as needed.
* @param bufsize The number of bytes for the buffer.
*/
public BerEncoder(int bufsize) {
buf = new byte[bufsize];
this.bufsize = bufsize;
offset = 0;
seqOffset = new int[INITIAL_SEQUENCES];
curSeqIndex = 0;
}
/**
* Resets encoder to state when newly constructed. Zeros out
* internal data structures.
*/
public void reset() {
while (offset > 0) {
buf[--offset] = 0;
}
while (curSeqIndex > 0) {
seqOffset[--curSeqIndex] = 0;
}
}
// ------------------ Accessor methods ------------
/**
* Gets the number of encoded bytes in this BER buffer.
*/
public int getDataLen() {
return offset;
}
/**
* Gets the buffer that contains the BER encoding. Throws an
* exception if unmatched beginSeq() and endSeq() pairs were
* encountered. Not entire buffer contains encoded bytes.
* Use getDataLen() to determine number of encoded bytes.
* Use getBuffer(true) to get rid of excess bytes in array.
* @throws IllegalStateException If buffer contains unbalanced sequence.
*/
public byte[] getBuf() {
if (curSeqIndex != 0) {
throw new IllegalStateException("BER encode error: Unbalanced SEQUENCEs.");
}
return buf;
}
/**
* Gets the buffer that contains the BER encoding, trimming unused bytes.
*
* @throws IllegalStateException If buffer contains unbalanced sequence.
*/
public byte[] getTrimmedBuf() {
int len = getDataLen();
byte[] trimBuf = new byte[len];
System.arraycopy(getBuf(), 0, trimBuf, 0, len);
return trimBuf;
}
// -------------- encoding methods -------------
/**
* Begin encoding a sequence with a tag.
*/
public void beginSeq(int tag) {
// Double the size of the SEQUENCE array if it overflows
if (curSeqIndex >= seqOffset.length) {
int[] seqOffsetTmp = new int[seqOffset.length * 2];
for (int i = 0; i < seqOffset.length; i++) {
seqOffsetTmp[i] = seqOffset[i];
}
seqOffset = seqOffsetTmp;
}
encodeByte(tag);
seqOffset[curSeqIndex] = offset;
// Save space for sequence length.
// %%% Currently we save enough space for sequences up to 64k.
// For larger sequences we'll need to shift the data to the right
// in endSeq(). If we could instead pad the length field with
// zeros, it would be a big win.
ensureFreeBytes(3);
offset += 3;
curSeqIndex++;
}
/**
* Terminate a BER sequence.
*/
public void endSeq() throws EncodeException {
curSeqIndex--;
if (curSeqIndex < 0) {
throw new IllegalStateException("BER encode error: Unbalanced SEQUENCEs.");
}
int start = seqOffset[curSeqIndex] + 3; // index beyond length field
int len = offset - start;
if (len <= 0x7f) {
shiftSeqData(start, len, -2);
buf[seqOffset[curSeqIndex]] = (byte) len;
} else if (len <= 0xff) {
shiftSeqData(start, len, -1);
buf[seqOffset[curSeqIndex]] = (byte) 0x81;
buf[seqOffset[curSeqIndex] + 1] = (byte) len;
} else if (len <= 0xffff) {
buf[seqOffset[curSeqIndex]] = (byte) 0x82;
buf[seqOffset[curSeqIndex] + 1] = (byte) (len >> 8);
buf[seqOffset[curSeqIndex] + 2] = (byte) len;
} else if (len <= 0xffffff) {
shiftSeqData(start, len, 1);
buf[seqOffset[curSeqIndex]] = (byte) 0x83;
buf[seqOffset[curSeqIndex] + 1] = (byte) (len >> 16);
buf[seqOffset[curSeqIndex] + 2] = (byte) (len >> 8);
buf[seqOffset[curSeqIndex] + 3] = (byte) len;
} else {
throw new EncodeException("SEQUENCE too long");
}
}
/**
* Shifts contents of buf in the range [start,start+len) a specified amount.
* Positive shift value means shift to the right.
*/
private void shiftSeqData(int start, int len, int shift) {
if (shift > 0) {
ensureFreeBytes(shift);
}
System.arraycopy(buf, start, buf, start + shift, len);
offset += shift;
}
/**
* Encode a single byte.
*/
public void encodeByte(int b) {
ensureFreeBytes(1);
buf[offset++] = (byte) b;
}
/*
private void deleteByte() {
offset--;
}
*/
/*
* Encodes an int.
*<blockquote><pre>
* BER integer ::= 0x02 berlength byte {byte}*
*</pre></blockquote>
*/
public void encodeInt(int i) {
encodeInt(i, 0x02);
}
/**
* Encodes an int and a tag.
*<blockquote><pre>
* BER integer w tag ::= tag berlength byte {byte}*
*</pre></blockquote>
*/
public void encodeInt(int i, int tag) {
int mask = 0xff800000;
int intsize = 4;
while( (((i & mask) == 0) || ((i & mask) == mask)) && (intsize > 1) ) {
intsize--;
i <<= 8;
}
encodeInt(i, tag, intsize);
}
//
// encodes an int using numbytes for the actual encoding.
//
private void encodeInt(int i, int tag, int intsize) {
//
// integer ::= 0x02 asnlength byte {byte}*
//
if (intsize > 4) {
throw new IllegalArgumentException("BER encode error: INTEGER too long.");
}
ensureFreeBytes(2 + intsize);
buf[offset++] = (byte) tag;
buf[offset++] = (byte) intsize;
int mask = 0xff000000;
while (intsize-- > 0) {
buf[offset++] = (byte) ((i & mask) >> 24);
i <<= 8;
}
}
/**
* Encodes a boolean.
*<blockquote><pre>
* BER boolean ::= 0x01 0x01 {0xff|0x00}
*</pre></blockquote>
*/
public void encodeBoolean(boolean b) {
encodeBoolean(b, ASN_BOOLEAN);
}
/**
* Encodes a boolean and a tag
*<blockquote><pre>
* BER boolean w TAG ::= tag 0x01 {0xff|0x00}
*</pre></blockquote>
*/
public void encodeBoolean(boolean b, int tag) {
ensureFreeBytes(3);
buf[offset++] = (byte) tag;
buf[offset++] = 0x01;
buf[offset++] = b ? (byte) 0xff : (byte) 0x00;
}
/**
* Encodes a string.
*<blockquote><pre>
* BER string ::= 0x04 strlen byte1 byte2...
*</pre></blockquote>
* The string is converted into bytes using UTF-8 or ISO-Latin-1.
*/
public void encodeString(String str, boolean encodeUTF8)
throws EncodeException {
encodeString(str, ASN_OCTET_STR, encodeUTF8);
}
/**
* Encodes a string and a tag.
*<blockquote><pre>
* BER string w TAG ::= tag strlen byte1 byte2...
*</pre></blockquote>
*/
public void encodeString(String str, int tag, boolean encodeUTF8)
throws EncodeException {
encodeByte(tag);
int i = 0;
int count;
byte[] bytes = null;
if (str == null) {
count = 0;
} else if (encodeUTF8) {
try {
bytes = str.getBytes("UTF8");
count = bytes.length;
} catch (UnsupportedEncodingException e) {
throw new EncodeException("UTF8 not available on platform");
}
} else {
try {
bytes = str.getBytes("8859_1");
count = bytes.length;
} catch (UnsupportedEncodingException e) {
throw new EncodeException("8859_1 not available on platform");
}
}
encodeLength(count);
ensureFreeBytes(count);
while (i < count) {
buf[offset++] = bytes[i++];
}
}
/**
* Encodes a portion of an octet string and a tag.
*/
public void encodeOctetString(byte tb[], int tag, int tboffset, int length)
throws EncodeException {
encodeByte(tag);
encodeLength(length);
if (length > 0) {
ensureFreeBytes(length);
System.arraycopy(tb, tboffset, buf, offset, length);
offset += length;
}
}
/**
* Encodes an octet string and a tag.
*/
public void encodeOctetString(byte tb[], int tag) throws EncodeException {
encodeOctetString(tb, tag, 0, tb.length);
}
private void encodeLength(int len) throws EncodeException {
ensureFreeBytes(4); // worst case
if (len < 128) {
buf[offset++] = (byte) len;
} else if (len <= 0xff) {
buf[offset++] = (byte) 0x81;
buf[offset++] = (byte) len;
} else if (len <= 0xffff) {
buf[offset++] = (byte) 0x82;
buf[offset++] = (byte) (len >> 8);
buf[offset++] = (byte) (len & 0xff);
} else if (len <= 0xffffff) {
buf[offset++] = (byte) 0x83;
buf[offset++] = (byte) (len >> 16);
buf[offset++] = (byte) (len >> 8);
buf[offset++] = (byte) (len & 0xff);
} else {
throw new EncodeException("string too long");
}
}
/**
* Encodes an array of strings.
*/
public void encodeStringArray(String strs[], boolean encodeUTF8)
throws EncodeException {
if (strs == null)
return;
for (int i = 0; i < strs.length; i++) {
encodeString(strs[i], encodeUTF8);
}
}
/*
private void encodeNull() {
//
// NULL ::= 0x05 0x00
//
encodeByte(0x05);
encodeByte(0x00);
}
*/
/**
* Ensures that there are at least "len" unused bytes in "buf".
* When more space is needed "buf" is expanded by a factor of
* BUF_GROWTH_FACTOR, then "len" bytes are added if "buf" still
* isn't large enough.
*/
private void ensureFreeBytes(int len) {
if (bufsize - offset < len) {
int newsize = bufsize * BUF_GROWTH_FACTOR;
if (newsize - offset < len) {
newsize += len;
}
byte newbuf[] = new byte[newsize];
// Only copy bytes in the range [0, offset)
System.arraycopy(buf, 0, newbuf, 0, offset);
buf = newbuf;
bufsize = newsize;
}
}
}