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 * 024 * Copyright 2006-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2011-2015 ForgeRock AS 026 */ 027package org.opends.server.replication.protocol; 028 029import java.io.IOException; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.zip.DataFormatException; 033 034import org.forgerock.opendj.io.ASN1; 035import org.forgerock.opendj.ldap.DecodeException; 036import org.forgerock.opendj.io.ASN1Reader; 037import org.forgerock.opendj.io.ASN1Writer; 038import org.opends.server.protocols.internal.InternalClientConnection; 039import org.opends.server.protocols.ldap.LDAPAttribute; 040import org.opends.server.replication.common.AssuredMode; 041import org.opends.server.replication.common.CSN; 042import org.opends.server.types.*; 043import org.forgerock.opendj.ldap.ByteString; 044import org.forgerock.opendj.ldap.ByteStringBuilder; 045import org.forgerock.opendj.ldap.ByteSequenceReader; 046import org.opends.server.types.operation.*; 047 048/** 049 * Abstract class that must be extended to define a message 050 * used for sending Updates between servers. 051 */ 052public abstract class LDAPUpdateMsg extends UpdateMsg 053{ 054 /** 055 * The DN on which the update was originally done. 056 */ 057 protected DN dn; 058 059 /** 060 * The entryUUID of the entry that was updated. 061 */ 062 protected String entryUUID; 063 064 /** 065 * Encoded form of the LDAPUpdateMsg. 066 */ 067 protected byte[] bytes; 068 069 /** 070 * Encoded form of entry attributes. 071 */ 072 protected byte[] encodedEclIncludes = new byte[0]; 073 074 /** 075 * Creates a new UpdateMsg. 076 */ 077 protected LDAPUpdateMsg() 078 { 079 } 080 081 /** 082 * Creates a new UpdateMsg with the given information. 083 * 084 * @param ctx The replication Context of the operation for which the 085 * update message must be created,. 086 * @param dn The DN of the entry on which the change 087 * that caused the creation of this object happened 088 */ 089 LDAPUpdateMsg(OperationContext ctx, DN dn) 090 { 091 this.protocolVersion = ProtocolVersion.getCurrentVersion(); 092 this.csn = ctx.getCSN(); 093 this.entryUUID = ctx.getEntryUUID(); 094 this.dn = dn; 095 } 096 097 /** 098 * Creates a new UpdateMessage with the given information. 099 * 100 * @param csn The CSN of the operation for which the 101 * UpdateMessage is created. 102 * @param entryUUID The Unique identifier of the entry that is updated 103 * by the operation for which the UpdateMessage is created. 104 * @param dn The DN of the entry on which the change 105 * that caused the creation of this object happened 106 */ 107 LDAPUpdateMsg(CSN csn, String entryUUID, DN dn) 108 { 109 this.protocolVersion = ProtocolVersion.getCurrentVersion(); 110 this.csn = csn; 111 this.entryUUID = entryUUID; 112 this.dn = dn; 113 } 114 115 /** 116 * Generates an Update message with the provided information. 117 * 118 * @param op The operation for which the message must be created. 119 * @return The generated message. 120 */ 121 public static LDAPUpdateMsg generateMsg(PostOperationOperation op) 122 { 123 switch (op.getOperationType()) 124 { 125 case MODIFY : 126 return new ModifyMsg((PostOperationModifyOperation) op); 127 case ADD: 128 return new AddMsg((PostOperationAddOperation) op); 129 case DELETE : 130 return new DeleteMsg((PostOperationDeleteOperation) op); 131 case MODIFY_DN : 132 return new ModifyDNMsg( (PostOperationModifyDNOperation) op); 133 default: 134 return null; 135 } 136 } 137 138 /** 139 * Get the DN on which the operation happened. 140 * 141 * @return The DN on which the operations happened. 142 */ 143 public DN getDN() 144 { 145 return dn; 146 } 147 148 /** 149 * Set the DN. 150 * @param dn The dn that must now be used for this message. 151 */ 152 public void setDN(DN dn) 153 { 154 this.dn = dn; 155 } 156 157 /** 158 * Get the entryUUID of the entry on which the operation happened. 159 * 160 * @return The entryUUID of the entry on which the operation happened. 161 */ 162 public String getEntryUUID() 163 { 164 return entryUUID; 165 } 166 167 /** 168 * Create and Operation from the message. 169 * 170 * @param conn connection to use when creating the message 171 * @return the created Operation 172 * @throws LDAPException In case of LDAP decoding exception. 173 * @throws IOException In case of ASN1 decoding exception. 174 * @throws DataFormatException In case of bad msg format. 175 */ 176 public Operation createOperation(InternalClientConnection conn) 177 throws LDAPException, IOException, DataFormatException 178 { 179 return createOperation(conn, dn); 180 } 181 182 183 /** 184 * Create and Operation from the message using the provided DN. 185 * 186 * @param conn connection to use when creating the message. 187 * @param newDN the DN to use when creating the operation. 188 * @return the created Operation. 189 * @throws LDAPException In case of LDAP decoding exception. 190 * @throws IOException In case of ASN1 decoding exception. 191 * @throws DataFormatException In case of bad msg format. 192 */ 193 public abstract Operation createOperation(InternalClientConnection conn, 194 DN newDN) throws LDAPException, IOException, DataFormatException; 195 196 197 // ============ 198 // Msg encoding 199 // ============ 200 201 /** 202 * Do all the work necessary for the encoding. 203 * 204 * This is useful in case when one wants to perform this outside 205 * of a synchronized portion of code. 206 * 207 * This method is not synchronized and therefore not MT safe. 208 */ 209 public void encode() 210 { 211 bytes = getBytes(ProtocolVersion.getCurrentVersion()); 212 } 213 214 /** {@inheritDoc} */ 215 @Override 216 public ByteArrayBuilder encodeHeader(byte msgType, short protocolVersion) 217 { 218 /* The message header is stored in the form : 219 * <operation type><protocol version><CSN><dn><entryuuid><assured> 220 * <assured mode> <safe data level> 221 */ 222 final ByteArrayBuilder builder = new ByteArrayBuilder(); 223 builder.appendByte(msgType); 224 builder.appendByte(protocolVersion); 225 builder.appendCSNUTF8(csn); 226 builder.appendDN(dn); 227 builder.appendString(entryUUID); 228 builder.appendBoolean(assuredFlag); 229 builder.appendByte(assuredMode.getValue()); 230 builder.appendByte(safeDataLevel); 231 return builder; 232 } 233 234 /** 235 * Encode the common header for all the UpdateMessage. This uses the version 236 * 1 of the replication protocol (used for compatibility purpose). 237 * 238 * @param msgType the type of UpdateMessage to encode. 239 * @return a byte array builder containing the common header 240 */ 241 ByteArrayBuilder encodeHeader_V1(byte msgType) 242 { 243 /* The message header is stored in the form : 244 * <operation type><CSN><dn><assured><entryuuid><change> 245 */ 246 final ByteArrayBuilder builder = new ByteArrayBuilder(); 247 builder.appendByte(msgType); 248 builder.appendCSNUTF8(csn); 249 builder.appendBoolean(assuredFlag); 250 builder.appendDN(dn); 251 builder.appendString(entryUUID); 252 return builder; 253 } 254 255 /** {@inheritDoc} */ 256 @Override 257 public byte[] getBytes(short protocolVersion) 258 { 259 if (protocolVersion == ProtocolVersion.REPLICATION_PROTOCOL_V1) 260 { 261 return getBytes_V1(); 262 } 263 else if (protocolVersion <= ProtocolVersion.REPLICATION_PROTOCOL_V3) 264 { 265 return getBytes_V23(); 266 } 267 else 268 { 269 // Encode in the current protocol version 270 if (bytes == null) 271 { 272 // this is the current version of the protocol 273 bytes = getBytes_V45(protocolVersion); 274 } 275 return bytes; 276 } 277 } 278 279 /** 280 * Get the byte array representation of this message. This uses the version 281 * 1 of the replication protocol (used for compatibility purpose). 282 * 283 * @return The byte array representation of this message. 284 */ 285 protected abstract byte[] getBytes_V1(); 286 287 /** 288 * Get the byte array representation of this message. This uses the version 289 * 2 of the replication protocol (used for compatibility purpose). 290 * 291 * @return The byte array representation of this message. 292 */ 293 protected abstract byte[] getBytes_V23(); 294 295 /** 296 * Get the byte array representation of this message. This uses the provided 297 * version number which must be version 4 or newer. 298 * 299 * @param protocolVersion the actual protocol version to encode into 300 * @return The byte array representation of this Message. 301 */ 302 protected abstract byte[] getBytes_V45(short protocolVersion); 303 304 /** 305 * Encode a list of attributes. 306 */ 307 private static byte[] encodeAttributes(Collection<Attribute> attributes) 308 { 309 if (attributes==null) 310 { 311 return new byte[0]; 312 } 313 try 314 { 315 ByteStringBuilder byteBuilder = new ByteStringBuilder(); 316 ASN1Writer writer = ASN1.getWriter(byteBuilder); 317 for (Attribute a : attributes) 318 { 319 new LDAPAttribute(a).write(writer); 320 } 321 return byteBuilder.toByteArray(); 322 } 323 catch (Exception e) 324 { 325 return null; 326 } 327 } 328 329 // ============ 330 // Msg decoding 331 // ============ 332 333 /** 334 * Decode the Header part of this Update message, and check its type. 335 * 336 * @param scanner the encoded form of the UpdateMsg. 337 * @param allowedTypes The allowed types of this Update Message. 338 * @throws DataFormatException if the encodedMsg does not contain a valid 339 * common header. 340 */ 341 void decodeHeader(ByteArrayScanner scanner, byte... allowedTypes) 342 throws DataFormatException 343 { 344 final byte msgType = scanner.nextByte(); 345 if (!isTypeAllowed(msgType, allowedTypes)) 346 { 347 throw new DataFormatException("byte[] is not a valid update msg: " 348 + msgType); 349 } 350 351 if (msgType == MSG_TYPE_ADD_V1 352 || msgType == MSG_TYPE_DELETE_V1 353 || msgType == MSG_TYPE_MODIFYDN_V1 354 || msgType == MSG_TYPE_MODIFY_V1) 355 { 356 /* 357 * For older protocol versions, decode the matching version header instead 358 */ 359 // Force version to V1 (other new parameters take their default values 360 // (assured stuff...)) 361 protocolVersion = ProtocolVersion.REPLICATION_PROTOCOL_V1; 362 csn = scanner.nextCSNUTF8(); 363 assuredFlag = scanner.nextBoolean(); 364 dn = scanner.nextDN(); 365 entryUUID = scanner.nextString(); 366 } 367 else 368 { 369 protocolVersion = scanner.nextByte(); 370 csn = scanner.nextCSNUTF8(); 371 dn = scanner.nextDN(); 372 entryUUID = scanner.nextString(); 373 assuredFlag = scanner.nextBoolean(); 374 assuredMode = AssuredMode.valueOf(scanner.nextByte()); 375 safeDataLevel = scanner.nextByte(); 376 } 377 } 378 379 private boolean isTypeAllowed(final byte msgType, byte... allowedTypes) 380 { 381 for (byte allowedType : allowedTypes) 382 { 383 if (msgType == allowedType) 384 { 385 return true; 386 } 387 } 388 return false; 389 } 390 391 /** {@inheritDoc} */ 392 @Override 393 public abstract int size(); 394 395 /** 396 * Return the number of bytes used by the header. 397 * @return The number of bytes used by the header. 398 */ 399 protected int headerSize() 400 { 401 return 100; // 100 let's assume header size is 100 402 } 403 404 /** 405 * Set a provided list of entry attributes. 406 * @param entryAttrs The provided list of entry attributes. 407 */ 408 public void setEclIncludes(Collection<Attribute> entryAttrs) 409 { 410 this.encodedEclIncludes = encodeAttributes(entryAttrs); 411 } 412 413 /** 414 * Returns the list of entry attributes. 415 * @return The list of entry attributes. 416 */ 417 public ArrayList<RawAttribute> getEclIncludes() 418 { 419 try 420 { 421 return decodeRawAttributes(this.encodedEclIncludes); 422 } 423 catch(Exception e) 424 { 425 return null; 426 } 427 } 428 429 /** 430 * Decode a provided byte array as a list of RawAttribute. 431 * @param in The provided byte array. 432 * @return The list of RawAttribute objects. 433 * @throws LDAPException when it occurs. 434 * @throws DecodeException when it occurs. 435 */ 436 ArrayList<RawAttribute> decodeRawAttributes(byte[] in) 437 throws LDAPException, DecodeException 438 { 439 ArrayList<RawAttribute> rattr = new ArrayList<>(); 440 try 441 { 442 ByteSequenceReader reader = 443 ByteString.wrap(in).asReader(); 444 ASN1Reader asn1Reader = ASN1.getReader(reader); 445 // loop on attributes 446 while(asn1Reader.hasNextElement()) 447 { 448 rattr.add(LDAPAttribute.decode(asn1Reader)); 449 } 450 return rattr; 451 } 452 catch(Exception e) 453 { 454 return null; 455 } 456 } 457 458 /** 459 * Decode a provided byte array as a list of Attribute. 460 * @param in The provided byte array. 461 * @return The list of Attribute objects. 462 * @throws LDAPException when it occurs. 463 * @throws DecodeException when it occurs. 464 */ 465 ArrayList<Attribute> decodeAttributes(byte[] in) 466 throws LDAPException, DecodeException 467 { 468 ArrayList<Attribute> lattr = new ArrayList<>(); 469 try 470 { 471 ByteSequenceReader reader = 472 ByteString.wrap(in).asReader(); 473 ASN1Reader asn1Reader = ASN1.getReader(reader); 474 // loop on attributes 475 while(asn1Reader.hasNextElement()) 476 { 477 lattr.add(LDAPAttribute.decode(asn1Reader).toAttribute()); 478 } 479 return lattr; 480 } 481 catch(Exception e) 482 { 483 return null; 484 } 485 } 486}