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 2008-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.replication.protocol;
028
029import java.util.*;
030import java.util.zip.DataFormatException;
031
032import org.forgerock.opendj.io.ASN1;
033import org.forgerock.opendj.io.ASN1Reader;
034import org.forgerock.opendj.io.ASN1Writer;
035import org.opends.server.replication.common.AssuredMode;
036import org.opends.server.replication.common.ServerStatus;
037import org.forgerock.opendj.ldap.ByteSequenceReader;
038import org.forgerock.opendj.ldap.ByteString;
039import org.forgerock.opendj.ldap.ByteStringBuilder;
040import org.forgerock.util.Utils;
041
042/**
043 * This message is used by DS to confirm a RS he wants to connect to him (open
044 * a session):
045 * Handshake sequence between DS and RS is like this:
046 * DS --- ServerStartMsg ---> RS
047 * DS <--- ReplServerStartMsg --- RS
048 * DS --- StartSessionMsg ---> RS
049 * DS <--- TopologyMsg --- RS
050 *
051 * This message contains:
052 * - status: the status we are entering the topology with
053 * - referrals URLs: the referrals URLs we allow peer DSs to use to refer to
054 * our domain when needed.
055 */
056public class StartSessionMsg extends ReplicationMsg
057{
058  /** The list of referrals URLs to the sending DS. */
059  private final List<String> referralsURLs = new ArrayList<>();
060  /** The initial status the DS starts with. */
061  private ServerStatus status = ServerStatus.INVALID_STATUS;
062  /** Assured replication enabled on DS or not. */
063  private boolean assuredFlag;
064  /** DS assured mode (relevant if assured replication enabled). */
065  private AssuredMode assuredMode = AssuredMode.SAFE_DATA_MODE;
066  /** DS safe data level (relevant if assured mode is safe data). */
067  private byte safeDataLevel = 1;
068
069  private Set<String> eclIncludes = new HashSet<>();
070  private Set<String> eclIncludesForDeletes = new HashSet<>();
071
072  /**
073   * Creates a new StartSessionMsg message from its encoded form.
074   *
075   * @param in The byte array containing the encoded form of the message.
076   * @param version The protocol version to use to decode the msg.
077   * @throws java.util.zip.DataFormatException If the byte array does not
078   * contain a valid encoded form of the message.
079   */
080  StartSessionMsg(byte[] in, short version) throws DataFormatException
081  {
082    if (version <= ProtocolVersion.REPLICATION_PROTOCOL_V3)
083    {
084      decode_V23(in);
085    }
086    else
087    {
088      decode_V45(in, version);
089    }
090  }
091
092  /**
093   * Creates a new  message with the given required parameters.
094   * @param status Status we are starting with
095   * @param referralsURLs Referrals URLs to be used by peer DSs
096   * @param assuredFlag If assured mode is enabled or not
097   * @param assuredMode Assured type
098   * @param safeDataLevel Assured mode safe data level
099   */
100  public StartSessionMsg(ServerStatus status, Collection<String> referralsURLs,
101    boolean assuredFlag, AssuredMode assuredMode, byte safeDataLevel)
102  {
103    this.referralsURLs.addAll(referralsURLs);
104    this.status = status;
105    this.assuredFlag = assuredFlag;
106    this.assuredMode = assuredMode;
107    this.safeDataLevel = safeDataLevel;
108  }
109
110  // ============
111  // Msg encoding
112  // ============
113
114  /** {@inheritDoc} */
115  @Override
116  public byte[] getBytes(short protocolVersion)
117  {
118    if (protocolVersion <= ProtocolVersion.REPLICATION_PROTOCOL_V3)
119    {
120      return getBytes_V23();
121    }
122    else
123    {
124      return getBytes_V45(protocolVersion);
125    }
126  }
127
128  private byte[] getBytes_V45(short version)
129  {
130    try
131    {
132      ByteStringBuilder byteBuilder = new ByteStringBuilder();
133      ASN1Writer writer = ASN1.getWriter(byteBuilder);
134
135      byteBuilder.appendByte(MSG_TYPE_START_SESSION);
136      byteBuilder.appendByte(status.getValue());
137      byteBuilder.appendByte(assuredFlag ? 1 : 0);
138      byteBuilder.appendByte(assuredMode.getValue());
139      byteBuilder.appendByte(safeDataLevel);
140
141      writer.writeStartSequence();
142      for (String url : referralsURLs)
143      {
144        writer.writeOctetString(url);
145      }
146      writer.writeEndSequence();
147
148      writer.writeStartSequence();
149      for (String attrDef : eclIncludes)
150      {
151        writer.writeOctetString(attrDef);
152      }
153      writer.writeEndSequence();
154
155      if (version >= ProtocolVersion.REPLICATION_PROTOCOL_V5)
156      {
157        writer.writeStartSequence();
158        for (String attrDef : eclIncludesForDeletes)
159        {
160          writer.writeOctetString(attrDef);
161        }
162        writer.writeEndSequence();
163      }
164
165      return byteBuilder.toByteArray();
166    }
167    catch (Exception e)
168    {
169      throw new RuntimeException(e);
170    }
171  }
172
173  private byte[] getBytes_V23()
174  {
175    /*
176     * The message is stored in the form:
177     * <message type><status><assured flag><assured mode><safe data level>
178     * <list of referrals urls>
179     * (each referral url terminates with 0)
180     */
181    final ByteArrayBuilder builder = new ByteArrayBuilder();
182    builder.appendByte(MSG_TYPE_START_SESSION);
183    builder.appendByte(status.getValue());
184    builder.appendBoolean(assuredFlag);
185    builder.appendByte(assuredMode.getValue());
186    builder.appendByte(safeDataLevel);
187
188    if (referralsURLs.size() >= 1)
189    {
190      for (String url : referralsURLs)
191      {
192        builder.appendString(url);
193      }
194    }
195    return builder.toByteArray();
196  }
197
198  // ============
199  // Msg decoding
200  // ============
201
202  private void decode_V45(byte[] in, short version) throws DataFormatException
203  {
204    ByteSequenceReader reader = ByteString.wrap(in).asReader();
205    try
206    {
207      if (reader.readByte() != MSG_TYPE_START_SESSION)
208      {
209        throw new DataFormatException("input is not a valid "
210            + getClass().getCanonicalName());
211      }
212
213      /*
214      status = ServerStatus.valueOf(asn1Reader.readOctetString().byteAt(0));
215      assuredFlag = (asn1Reader.readOctetString().byteAt(0) == 1);
216      assuredMode=AssuredMode.valueOf((asn1Reader.readOctetString().byteAt(0)));
217      safeDataLevel = asn1Reader.readOctetString().byteAt(0);
218      */
219      status = ServerStatus.valueOf(reader.readByte());
220      assuredFlag = reader.readByte() == 1;
221      assuredMode = AssuredMode.valueOf(reader.readByte());
222      safeDataLevel = reader.readByte();
223
224      ASN1Reader asn1Reader = ASN1.getReader(reader);
225
226      asn1Reader.readStartSequence();
227      while(asn1Reader.hasNextElement())
228      {
229        String s = asn1Reader.readOctetStringAsString();
230        this.referralsURLs.add(s);
231      }
232      asn1Reader.readEndSequence();
233
234      asn1Reader.readStartSequence();
235      while(asn1Reader.hasNextElement())
236      {
237        String s = asn1Reader.readOctetStringAsString();
238        this.eclIncludes.add(s);
239      }
240      asn1Reader.readEndSequence();
241
242      if (version >= ProtocolVersion.REPLICATION_PROTOCOL_V5)
243      {
244        asn1Reader.readStartSequence();
245        while (asn1Reader.hasNextElement())
246        {
247          this.eclIncludesForDeletes.add(asn1Reader.readOctetStringAsString());
248        }
249        asn1Reader.readEndSequence();
250      }
251      else
252      {
253        // Default to using the same set of attributes for deletes.
254        this.eclIncludesForDeletes.addAll(eclIncludes);
255      }
256    }
257    catch (Exception e)
258    {
259      throw new RuntimeException(e);
260    }
261  }
262
263  private void decode_V23(byte[] in) throws DataFormatException
264  {
265    /*
266     * The message is stored in the form:
267     * <message type><status><assured flag><assured mode><safe data level>
268     * <list of referrals urls>
269     * (each referral url terminates with 0)
270     */
271    final ByteArrayScanner scanner = new ByteArrayScanner(in);
272    final byte msgType = scanner.nextByte();
273    if (msgType != MSG_TYPE_START_SESSION)
274    {
275      throw new DataFormatException(
276          "Input is not a valid " + getClass().getCanonicalName());
277    }
278
279    status = ServerStatus.valueOf(scanner.nextByte());
280    assuredFlag = scanner.nextBoolean();
281    assuredMode = AssuredMode.valueOf(scanner.nextByte());
282    safeDataLevel = scanner.nextByte();
283
284    while (!scanner.isEmpty())
285    {
286      referralsURLs.add(scanner.nextString());
287    }
288  }
289
290  /**
291   * Get the list of referrals URLs.
292   *
293   * @return The list of referrals URLs.
294   */
295  public List<String> getReferralsURLs()
296  {
297    return referralsURLs;
298  }
299
300  /**
301   * Get the status from this message.
302   * @return The status.
303   */
304  public ServerStatus getStatus()
305  {
306    return status;
307  }
308
309  /** {@inheritDoc} */
310  @Override
311  public String toString()
312  {
313    String urls = Utils.joinAsString(" | ", referralsURLs);
314    return "StartSessionMsg content:\nstatus: " + status +
315      "\nassuredFlag: " + assuredFlag +
316      "\nassuredMode: " + assuredMode +
317      "\nsafeDataLevel: " + safeDataLevel +
318      "\nreferralsURLs: " + urls +
319      "\nEclIncludes " + eclIncludes +
320      "\nEclIncludeForDeletes: " + eclIncludesForDeletes;
321  }
322
323  /**
324   * Returns true if assured mode is enabled.
325   * @return true if assured mode is enabled.
326   */
327  public boolean isAssured()
328  {
329    return assuredFlag;
330  }
331
332  /**
333   * Get the assured mode.
334   * @return the assured mode.
335   */
336  public AssuredMode getAssuredMode()
337  {
338    return assuredMode;
339  }
340
341  /**
342   * Get the safe data level.
343   * @return the safe data level.
344   */
345  public byte getSafeDataLevel()
346  {
347    return safeDataLevel;
348  }
349
350  /**
351   * Set the attributes configured on a server to be included in the ECL.
352   *
353   * @param includeAttributes
354   *          attributes to be included with all change records.
355   * @param includeAttributesForDeletes
356   *          additional attributes to be included with delete change records.
357   */
358  public void setEclIncludes(
359      Set<String> includeAttributes,
360      Set<String> includeAttributesForDeletes)
361  {
362    if (includeAttributes != null)
363    {
364      eclIncludes = includeAttributes;
365    }
366
367    if (includeAttributesForDeletes != null)
368    {
369      eclIncludesForDeletes = includeAttributesForDeletes;
370    }
371  }
372
373  /**
374   * Get the attributes to include in each change for the ECL.
375   *
376   * @return The attributes to include in each change for the ECL.
377   */
378  public Set<String> getEclIncludes()
379  {
380    return eclIncludes;
381  }
382
383
384
385  /**
386   * Get the attributes to include in each delete change for the ECL.
387   *
388   * @return The attributes to include in each delete change for the ECL.
389   */
390  public Set<String> getEclIncludesForDeletes()
391  {
392    return eclIncludesForDeletes;
393  }
394
395}