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}