package gnu.javax.net.ssl.provider; import java.io.PrintWriter; import java.io.StringWriter; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; /** * A list of extensions, that may appear in either the {@link ClientHello} or * {@link ServerHello}. The form of the extensions list is: * * <tt> Extension extensions_list<1..2^16-1></tt> * * @author csm */ public class ExtensionList implements Builder, Iterable<Extension> { private final ByteBuffer buffer; private int modCount; public ExtensionList (ByteBuffer buffer) { this.buffer = buffer.duplicate().order(ByteOrder.BIG_ENDIAN); modCount = 0; } public ExtensionList(List<Extension> extensions) { int length = 2; for (Extension extension : extensions) length += extension.length(); buffer = ByteBuffer.allocate(length); buffer.putShort((short) (length - 2)); for (Extension extension : extensions) buffer.put(extension.buffer()); buffer.rewind(); } public ByteBuffer buffer() { return (ByteBuffer) buffer.duplicate().limit(length()); } public Extension get (final int index) { int length = length (); int i; int n = 0; for (i = 2; i < length && n < index; ) { int l = buffer.getShort (i+2) & 0xFFFF; i += l + 4; n++; } if (n < index) throw new IndexOutOfBoundsException ("no elemenet at " + index); int el = buffer.getShort (i+2) & 0xFFFF; ByteBuffer b = (ByteBuffer) buffer.duplicate().position(i).limit(i+el+4); return new Extension(b.slice()); } /** * Returns the number of extensions this list contains. * * @return The number of extensions. */ public int size () { int length = length (); if (length == 0) return 0; int n = 0; for (int i = 2; i < length; ) { int len = buffer.getShort (i+2) & 0xFFFF; i += len + 4; n++; } return n; } /** * Returns the length of this extension list, in bytes. * * @return The length of this extension list, in bytes. */ public int length () { return (buffer.getShort (0) & 0xFFFF) + 2; } /** * Sets the extension at index <i>i</i> to <i>e</i>. Note that setting an * element at an index <b>may</b> invalidate any other elements that come * after element at index <i>i</i>. In other words, no attempt is made to * move existing elements in this list, and since extensions are variable * length, you can <em>not</em> guarantee that extensions later in the list * will still be valid. * * <p>Thus, elements of this list <b>must</b> be set in order of increasing * index. * * @param index The index to set the extension at. * @param e The extension. * @throws java.nio.BufferOverflowException If setting the extension overflows * the buffer. * @throws IllegalArgumentException If it isn't possible to find the given index * in the current list (say, if no element index - 1 is set), or if setting * the extension will overflow the current list length (given by {@link * #length()}). */ public void set (final int index, Extension e) { int length = length(); int n = 0; int i; for (i = 2; i < length && n < index; ) { int len = buffer.getShort(i+2) & 0xFFFF; i += len + 4; n++; } if (n < index) throw new IllegalArgumentException("nothing set at index " + (index-1) + " or insufficient space"); if (i + e.length() + 2 > length) throw new IllegalArgumentException("adding this element will exceed the " + "list length"); buffer.putShort(i, (short) e.type().getValue()); buffer.putShort(i+2, (short) e.length()); ((ByteBuffer) buffer.duplicate().position(i+4)).put (e.valueBuffer()); modCount++; } /** * Reserve space for an extension at index <i>i</i> in the list. In other * words, this does the job of {@link #set(int, Extension)}, but does not * copy the extension value to the underlying buffer. * * @param index The index of the extension to reserve space for. * @param t The type of the extension. * @param eLength The number of bytes to reserve for this extension. The total * number of bytes used by this method is this length, plus four. */ public void set (final int index, Extension.Type t, final int eLength) { int length = length (); int n = 0; int i; for (i = 2; i < length && n < index; ) { int len = buffer.getShort (i+2) & 0xFFFF; i += len + 4; n++; } if (n < index) throw new IllegalArgumentException ("nothing set at index " + (index-1) + " or insufficient space"); if (i + eLength + 2 > length) throw new IllegalArgumentException ("adding this element will exceed the " + "list length"); buffer.putShort(i, (short) t.getValue()); buffer.putShort(i+2, (short) eLength); modCount++; } /** * Set the total length of this list, in bytes. * * @param newLength The new list length. */ public void setLength (final int newLength) { if (newLength < 0 || newLength > 65535) throw new IllegalArgumentException ("invalid length"); buffer.putShort (0, (short) newLength); modCount++; } public Iterator<Extension> iterator() { return new ExtensionsIterator(); } public String toString() { return toString (null); } public String toString(final String prefix) { StringWriter str = new StringWriter(); PrintWriter out = new PrintWriter(str); if (prefix != null) out.print(prefix); out.println("ExtensionList {"); if (prefix != null) out.print(prefix); out.print(" length = "); out.print(length()); out.println(";"); String subprefix = " "; if (prefix != null) subprefix = prefix + subprefix; for (Extension e : this) out.println(e.toString(subprefix)); if (prefix != null) out.print(prefix); out.print("};"); return str.toString(); } /** * List iterator interface to an extensions list. * * @author csm@gnu.org */ public final class ExtensionsIterator implements ListIterator<Extension> { private final int modCount; private int index; private final int size; public ExtensionsIterator () { this.modCount = ExtensionList.this.modCount; index = 0; size = size (); } public boolean hasNext() { return index < size; } public boolean hasPrevious() { return index > 0; } public Extension next() throws NoSuchElementException { if (modCount != ExtensionList.this.modCount) throw new ConcurrentModificationException (); if (!hasNext ()) throw new NoSuchElementException (); return get (index++); } public Extension previous() throws NoSuchElementException { if (modCount != ExtensionList.this.modCount) throw new ConcurrentModificationException (); if (!hasPrevious ()) throw new NoSuchElementException (); return get (--index); } public int nextIndex() { if (hasNext ()) return index + 1; return index; } public int previousIndex() { if (hasPrevious ()) return index - 1; return -1; } public void add(Extension e) { throw new UnsupportedOperationException ("cannot add items to this iterator"); } public void remove() { throw new UnsupportedOperationException ("cannot remove items from this iterator"); } public void set(Extension e) { ExtensionList.this.set (index, e); } } }