001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2014-2016 ForgeRock AS. 015 */ 016package org.opends.server.replication.protocol; 017 018import java.util.Collection; 019import java.util.zip.DataFormatException; 020 021import org.forgerock.i18n.LocalizedIllegalArgumentException; 022import org.forgerock.opendj.io.ASN1; 023import org.forgerock.opendj.io.ASN1Reader; 024import org.forgerock.opendj.ldap.ByteSequenceReader; 025import org.forgerock.opendj.ldap.ByteString; 026import org.forgerock.opendj.ldap.DN; 027import org.opends.server.replication.common.CSN; 028import org.opends.server.replication.common.ServerState; 029 030/** 031 * Byte array scanner class helps decode data from byte arrays received via 032 * messages over the replication protocol. Built on top of 033 * {@link ByteSequenceReader}, it isolates the latter against legacy type 034 * conversions from the replication protocol. 035 * 036 * @see ByteArrayBuilder ByteArrayBuilder class that encodes messages read with 037 * current class. 038 */ 039public class ByteArrayScanner 040{ 041 042 private final ByteSequenceReader bytes; 043 private final byte[] byteArray; 044 045 /** 046 * Builds a ByteArrayScanner object that will read from the supplied byte 047 * array. 048 * 049 * @param bytes 050 * the byte array input that will be read from 051 */ 052 public ByteArrayScanner(byte[] bytes) 053 { 054 this.bytes = ByteString.wrap(bytes).asReader(); 055 this.byteArray = bytes; 056 } 057 058 /** 059 * Reads the next boolean. 060 * 061 * @return the next boolean 062 * @throws DataFormatException 063 * if no more data can be read from the input 064 */ 065 public boolean nextBoolean() throws DataFormatException 066 { 067 return nextByte() != 0; 068 } 069 070 /** 071 * Reads the next byte. 072 * 073 * @return the next byte 074 * @throws DataFormatException 075 * if no more data can be read from the input 076 */ 077 public byte nextByte() throws DataFormatException 078 { 079 try 080 { 081 return bytes.readByte(); 082 } 083 catch (IndexOutOfBoundsException e) 084 { 085 throw new DataFormatException(e.getMessage()); 086 } 087 } 088 089 /** 090 * Reads the next short. 091 * 092 * @return the next short 093 * @throws DataFormatException 094 * if no more data can be read from the input 095 */ 096 public short nextShort() throws DataFormatException 097 { 098 try 099 { 100 return bytes.readShort(); 101 } 102 catch (IndexOutOfBoundsException e) 103 { 104 throw new DataFormatException(e.getMessage()); 105 } 106 } 107 108 /** 109 * Reads the next int. 110 * 111 * @return the next int 112 * @throws DataFormatException 113 * if no more data can be read from the input 114 */ 115 public int nextInt() throws DataFormatException 116 { 117 try 118 { 119 return bytes.readInt(); 120 } 121 catch (IndexOutOfBoundsException e) 122 { 123 throw new DataFormatException(e.getMessage()); 124 } 125 } 126 127 /** 128 * Reads the next long. 129 * 130 * @return the next long 131 * @throws DataFormatException 132 * if no more data can be read from the input 133 */ 134 public long nextLong() throws DataFormatException 135 { 136 try 137 { 138 return bytes.readLong(); 139 } 140 catch (IndexOutOfBoundsException e) 141 { 142 throw new DataFormatException(e.getMessage()); 143 } 144 } 145 146 /** 147 * Reads the next int that was encoded as a UTF8 string. 148 * 149 * @return the next int that was encoded as a UTF8 string. 150 * @throws DataFormatException 151 * if no more data can be read from the input 152 */ 153 public int nextIntUTF8() throws DataFormatException 154 { 155 return Integer.valueOf(nextString()); 156 } 157 158 /** 159 * Reads the next long that was encoded as a UTF8 string. 160 * 161 * @return the next long that was encoded as a UTF8 string. 162 * @throws DataFormatException 163 * if no more data can be read from the input 164 */ 165 public long nextLongUTF8() throws DataFormatException 166 { 167 return Long.valueOf(nextString()); 168 } 169 170 /** 171 * Reads the next UTF8-encoded string. 172 * 173 * @return the next UTF8-encoded string or null if the string length is zero 174 * @throws DataFormatException 175 * if no more data can be read from the input 176 */ 177 public String nextString() throws DataFormatException 178 { 179 try 180 { 181 final int offset = findZeroSeparator(); 182 if (offset > 0) 183 { 184 final String s = bytes.readStringUtf8(offset); 185 skipZeroSeparator(); 186 return s; 187 } 188 skipZeroSeparator(); 189 return null; 190 } 191 catch (IndexOutOfBoundsException e) 192 { 193 throw new DataFormatException(e.getMessage()); 194 } 195 } 196 197 private int findZeroSeparator() throws DataFormatException 198 { 199 int offset = 0; 200 final int remaining = bytes.remaining(); 201 while (bytes.peek(offset) != 0 && offset < remaining) 202 { 203 offset++; 204 } 205 if (offset == remaining) 206 { 207 throw new DataFormatException("No more data to read from"); 208 } 209 return offset; 210 } 211 212 /** 213 * Reads the next UTF8-encoded strings in the provided collection. 214 * 215 * @param output 216 * the collection where to add the next UTF8-encoded strings 217 * @param <TCol> 218 * the collection's concrete type 219 * @return the provided collection where the next UTF8-encoded strings have 220 * been added. 221 * @throws DataFormatException 222 * if no more data can be read from the input 223 */ 224 public <TCol extends Collection<String>> TCol nextStrings(TCol output) 225 throws DataFormatException 226 { 227 // nextInt() would have been safer, but byte is compatible with legacy code. 228 final int colSize = nextByte(); 229 for (int i = 0; i < colSize; i++) 230 { 231 output.add(nextString()); 232 } 233 return output; 234 } 235 236 /** 237 * Reads the next CSN. 238 * 239 * @return the next CSN. 240 * @throws DataFormatException 241 * if CSN was incorrectly encoded or no more data can be read from 242 * the input 243 */ 244 public CSN nextCSN() throws DataFormatException 245 { 246 try 247 { 248 return CSN.valueOf(bytes.readByteSequence(CSN.BYTE_ENCODING_LENGTH)); 249 } 250 catch (IndexOutOfBoundsException e) 251 { 252 throw new DataFormatException(e.getMessage()); 253 } 254 } 255 256 /** 257 * Reads the next CSN that was encoded as a UTF8 string. 258 * 259 * @return the next CSN that was encoded as a UTF8 string. 260 * @throws DataFormatException 261 * if legacy CSN was incorrectly encoded or no more data can be read 262 * from the input 263 */ 264 public CSN nextCSNUTF8() throws DataFormatException 265 { 266 try 267 { 268 return CSN.valueOf(nextString()); 269 } 270 catch (IndexOutOfBoundsException e) 271 { 272 throw new DataFormatException(e.getMessage()); 273 } 274 } 275 276 /** 277 * Reads the next DN. 278 * 279 * @return the next DN. 280 * @throws DataFormatException 281 * if DN was incorrectly encoded or no more data can be read from 282 * the input 283 */ 284 public DN nextDN() throws DataFormatException 285 { 286 try 287 { 288 return DN.valueOf(nextString()); 289 } 290 catch (LocalizedIllegalArgumentException e) 291 { 292 throw new DataFormatException(e.getLocalizedMessage()); 293 } 294 } 295 296 /** 297 * Return a new byte array containing all remaining bytes in this 298 * ByteArrayScanner. 299 * 300 * @return new byte array containing all remaining bytes 301 */ 302 public byte[] remainingBytes() 303 { 304 final int length = byteArray.length - bytes.position(); 305 return nextByteArray(length); 306 } 307 308 /** 309 * Return a new byte array containing all remaining bytes in this 310 * ByteArrayScanner bar the last one which is a zero terminated byte 311 * (compatible with legacy code). 312 * 313 * @return new byte array containing all remaining bytes bar the last one 314 */ 315 public byte[] remainingBytesZeroTerminated() 316 { 317 /* do not copy stupid legacy zero separator */ 318 final int length = byteArray.length - (bytes.position() + 1); 319 final byte[] result = nextByteArray(length); 320 bytes.skip(1); // ignore last (supposedly) zero byte 321 return result; 322 } 323 324 /** 325 * Return a new byte array containing the requested number of bytes. 326 * 327 * @param length 328 * the number of bytes to be read and copied to the new byte array. 329 * @return new byte array containing the requested number of bytes. 330 */ 331 public byte[] nextByteArray(final int length) 332 { 333 final byte[] result = new byte[length]; 334 System.arraycopy(byteArray, bytes.position(), result, 0, length); 335 bytes.skip(length); 336 return result; 337 } 338 339 /** 340 * Reads the next ServerState. 341 * <p> 342 * Caution: ServerState MUST be the last field (see 343 * {@link ByteArrayBuilder#appendServerStateMustComeLast(ServerState)} javadoc). 344 * <p> 345 * Note: the super long method name it is intentional: 346 * nobody will want to use it, which is good because nobody should. 347 * 348 * @return the next ServerState. 349 * @throws DataFormatException 350 * if ServerState was incorrectly encoded or no more data can be 351 * read from the input 352 * @see ByteArrayBuilder#appendServerStateMustComeLast(ServerState) 353 */ 354 public ServerState nextServerStateMustComeLast() throws DataFormatException 355 { 356 final ServerState result = new ServerState(); 357 358 final int maxPos = byteArray.length - 1 /* stupid legacy zero separator */; 359 while (bytes.position() < maxPos) 360 { 361 final int serverId = nextIntUTF8(); 362 final CSN csn = nextCSNUTF8(); 363 if (serverId != csn.getServerId()) 364 { 365 throw new DataFormatException("Expected serverId=" + serverId 366 + " to be the same as serverId for CSN=" + csn); 367 } 368 result.update(csn); 369 } 370 skipZeroSeparator(); 371 return result; 372 } 373 374 /** 375 * Skips the next byte and verifies it is effectively the zero separator. 376 * 377 * @throws DataFormatException 378 * if the next byte is not the zero separator. 379 */ 380 public void skipZeroSeparator() throws DataFormatException 381 { 382 if (bytes.peek() != (byte) 0) 383 { 384 throw new DataFormatException("Expected a zero separator at position " 385 + bytes.position() + " but found byte " + bytes.peek()); 386 } 387 bytes.skip(1); 388 } 389 390 /** 391 * Returns a new ASN1Reader that will read bytes from this ByteArrayScanner. 392 * 393 * @return a new ASN1Reader that will read bytes from this ByteArrayScanner. 394 */ 395 public ASN1Reader getASN1Reader() 396 { 397 return ASN1.getReader(bytes); 398 } 399 400 /** 401 * Returns whether the scanner has more bytes to consume. 402 * 403 * @return true if the scanner has more bytes to consume, false otherwise. 404 */ 405 public boolean isEmpty() 406 { 407 return bytes.remaining() == 0; 408 } 409 410 /** {@inheritDoc} */ 411 @Override 412 public String toString() 413 { 414 return bytes.toString(); 415 } 416}