/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * Copyright 2014 ForgeRock AS */ package org.opends.server.replication.protocol; import java.util.Collection; import java.util.zip.DataFormatException; import org.forgerock.opendj.ldap.ByteSequenceReader; import org.forgerock.opendj.ldap.ByteString; import org.opends.server.replication.common.CSN; /** * Byte array scanner class helps decode data from byte arrays received via * messages over the replication protocol. Built on top of * {@link ByteSequenceReader}, it isolates the latter against legacy type * conversions from the replication protocol. * * @see ByteArrayBuilder ByteArrayBuilder class that encodes messages read with * current class. */ public class ByteArrayScanner { private final ByteSequenceReader bytes; /** * Builds a ByteArrayScanner object that will read from the supplied byte * array. * * @param bytes * the byte array input that will be read from */ public ByteArrayScanner(byte[] bytes) { this.bytes = ByteString.wrap(bytes).asReader(); } /** * Reads the next boolean. * * @return the next boolean * @throws DataFormatException * if no more data can be read from the input */ public boolean nextBoolean() throws DataFormatException { return nextByte() != 0; } /** * Reads the next byte. * * @return the next byte * @throws DataFormatException * if no more data can be read from the input */ public byte nextByte() throws DataFormatException { try { return bytes.get(); } catch (IndexOutOfBoundsException e) { throw new DataFormatException(e.getMessage()); } } /** * Reads the next short. * * @return the next short * @throws DataFormatException * if no more data can be read from the input */ public short nextShort() throws DataFormatException { try { return bytes.getShort(); } catch (IndexOutOfBoundsException e) { throw new DataFormatException(e.getMessage()); } } /** * Reads the next int. * * @return the next int * @throws DataFormatException * if no more data can be read from the input */ public int nextInt() throws DataFormatException { try { return bytes.getInt(); } catch (IndexOutOfBoundsException e) { throw new DataFormatException(e.getMessage()); } } /** * Reads the next long. * * @return the next long * @throws DataFormatException * if no more data can be read from the input */ public long nextLong() throws DataFormatException { try { return bytes.getLong(); } catch (IndexOutOfBoundsException e) { throw new DataFormatException(e.getMessage()); } } /** * Reads the next int that was encoded as a UTF8 string. * * @return the next int that was encoded as a UTF8 string. * @throws DataFormatException * if no more data can be read from the input */ public int nextIntUTF8() throws DataFormatException { return Integer.valueOf(nextString()); } /** * Reads the next long that was encoded as a UTF8 string. * * @return the next long that was encoded as a UTF8 string. * @throws DataFormatException * if no more data can be read from the input */ public long nextLongUTF8() throws DataFormatException { return Long.valueOf(nextString()); } /** * Reads the next UTF8-encoded string. * * @return the next UTF8-encoded string. * @throws DataFormatException * if no more data can be read from the input */ public String nextString() throws DataFormatException { try { final String s = bytes.getString(findZeroSeparator()); bytes.skip(1); // skip the zero separator return s; } catch (IndexOutOfBoundsException e) { throw new DataFormatException(e.getMessage()); } } private int findZeroSeparator() throws DataFormatException { int offset = 0; final int remaining = bytes.remaining(); while (bytes.peek(offset) != 0 && offset < remaining) { offset++; } if (offset == remaining) { throw new DataFormatException("No more data to read from"); } return offset; } /** * Reads the next UTF8-encoded strings in the provided collection. * * @param output * the collection where to add the next UTF8-encoded strings * @param * the collection's concrete type * @return the provided collection where the next UTF8-encoded strings have * been added. * @throws DataFormatException * if no more data can be read from the input */ public > TCol nextStrings(TCol output) throws DataFormatException { final int colSize = nextInt(); for (int i = 0; i < colSize; i++) { output.add(nextString()); } return output; } /** * Reads the next CSN. * * @return the next CSN. * @throws DataFormatException * if CSN was incorrectly encoded or no more data can be read from * the input */ public CSN nextCSN() throws DataFormatException { try { return CSN.valueOf(bytes.getByteSequence(CSN.BYTE_ENCODING_LENGTH)); } catch (IndexOutOfBoundsException e) { throw new DataFormatException(e.getMessage()); } } /** * Reads the next CSN that was encoded as a UTF8 string. * * @return the next CSN that was encoded as a UTF8 string. * @throws DataFormatException * if legacy CSN was incorrectly encoded or no more data can be read * from the input */ public CSN nextCSNUTF8() throws DataFormatException { try { return CSN.valueOf(nextString()); } catch (IndexOutOfBoundsException e) { throw new DataFormatException(e.getMessage()); } } /** * Returns whether the scanner has more bytes to consume. * * @return true if the scanner has more bytes to consume, false otherwise. */ public boolean isEmpty() { return bytes.remaining() == 0; } }