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