/**
   * Encodes this entry using the V3 encoding.
   *
   * @param buffer The buffer to encode into.
   * @throws DirectoryException If a problem occurs while attempting to encode the entry.
   */
  private void encodeV1(Entry entry, ByteStringBuilder buffer) throws DirectoryException {
    // The version number will be one byte.
    buffer.appendByte(0x01);

    // TODO: Can we encode the DN directly into buffer?
    byte[] dnBytes = getBytes(entry.getName().toString());
    buffer.appendBERLength(dnBytes.length);
    buffer.appendBytes(dnBytes);

    // Encode number of OCs and 0 terminated names.
    int i = 1;
    ByteStringBuilder bsb = new ByteStringBuilder();
    for (String ocName : entry.getObjectClasses().values()) {
      bsb.appendUtf8(ocName);
      if (i < entry.getObjectClasses().values().size()) {
        bsb.appendByte(0x00);
      }
      i++;
    }
    buffer.appendBERLength(bsb.length());
    buffer.appendBytes(bsb);

    // Encode the user attributes in the appropriate manner.
    encodeV1Attributes(buffer, entry.getUserAttributes());

    // The operational attributes will be encoded in the same way as
    // the user attributes.
    encodeV1Attributes(buffer, entry.getOperationalAttributes());
  }
  /** Test for escaped characters in templates, check LDIF output. */
  @Test(
      dataProvider = "templatesToTestLDIFOutput",
      dependsOnMethods = {"testParsingEscapeCharInTemplate"})
  public void testLDIFOutputFromTemplate(
      String testName, String[] lines, String attrName, String expectedValue) throws Exception {
    File tmpFile = File.createTempFile(testName, "out.ldif");
    tmpFile.deleteOnExit();
    String outLdifFilePath = tmpFile.getAbsolutePath();

    LdifFileWriter.makeLdif(outLdifFilePath, resourcePath, lines);

    LDIFImportConfig ldifConfig = new LDIFImportConfig(outLdifFilePath);
    ldifConfig.setValidateSchema(false);
    LDIFReader reader = new LDIFReader(ldifConfig);
    Entry top = reader.readEntry();
    Entry e = reader.readEntry();
    reader.close();

    assertNotNull(top);
    assertNotNull(e);
    List<Attribute> attrs = e.getAttribute(attrName);
    assertFalse(attrs.isEmpty());
    Attribute a = attrs.get(0);
    Attribute expectedRes = Attributes.create(attrName, expectedValue);
    assertEquals(a, expectedRes);
  }
  /**
   * Performs a successful LDAP bind using CRAM-MD5 using the dn: form of the authentication ID
   * using a long password (longer than 64 bytes).
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testLDAPBindSuccessWithDNAndLongPassword() throws Exception {
    TestCaseUtils.initializeTestBackend(true);

    String password = "******";

    Entry e =
        TestCaseUtils.makeEntry(
            "dn: uid=test.user,o=test",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalPerson",
            "objectClass: inetOrgPerson",
            "uid: test.user",
            "givenName: Test",
            "sn: User",
            "cn: Test User",
            "userPassword: "******"ds-pwp-password-policy-dn: cn=Clear UserPassword Policy,"
                + "cn=Password Policies,cn=config");

    InternalClientConnection conn = InternalClientConnection.getRootConnection();
    AddOperation addOperation =
        conn.processAdd(
            e.getDN(), e.getObjectClasses(),
            e.getUserAttributes(), e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);

    String[] args = {
      "--noPropertiesFile",
      "-h",
      "127.0.0.1",
      "-p",
      String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-o",
      "mech=CRAM-MD5",
      "-o",
      "authid=dn:uid=test.user,o=test",
      "-w",
      password,
      "-b",
      "",
      "-s",
      "base",
      "(objectClass=*)"
    };
    assertEquals(LDAPSearch.mainSearch(args, false, null, System.err), 0);
  }
  /**
   * Performs a failed LDAP bind using CRAM-MD5 using the dn: form of the authentication ID with the
   * DN of a user that doesn't exist.
   *
   * @throws Exception If an unexpected problem occurs.
   */
  @Test()
  public void testLDAPBindFailNoSuchUser() throws Exception {
    TestCaseUtils.initializeTestBackend(true);

    Entry e =
        TestCaseUtils.makeEntry(
            "dn: uid=test.user,o=test",
            "objectClass: top",
            "objectClass: person",
            "objectClass: organizationalPerson",
            "objectClass: inetOrgPerson",
            "uid: test.user",
            "givenName: Test",
            "sn: User",
            "cn: Test User",
            "userPassword: password");

    InternalClientConnection conn = InternalClientConnection.getRootConnection();
    AddOperation addOperation =
        conn.processAdd(
            e.getDN(), e.getObjectClasses(),
            e.getUserAttributes(), e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);

    String[] args = {
      "--noPropertiesFile",
      "-h",
      "127.0.0.1",
      "-p",
      String.valueOf(TestCaseUtils.getServerLdapPort()),
      "-o",
      "mech=CRAM-MD5",
      "-o",
      "authid=dn:uid=doesntexist,o=test",
      "-w",
      "password",
      "-b",
      "",
      "-s",
      "base",
      "(objectClass=*)"
    };
    assertFalse(LDAPSearch.mainSearch(args, false, null, null) == 0);
  }
  /**
   * Tests the entry encoding and decoding process the version 1 encoding.
   *
   * @throws Exception If the test failed unexpectedly.
   */
  @Test(dataProvider = "encodeConfigs")
  public void testEntryToAndFromDatabaseV2(EntryEncodeConfig config) throws Exception {
    ensureServerIsUpAndRunning();

    // Convert the test LDIF string to a byte array
    byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString);

    try (final LDIFReader reader =
        new LDIFReader(new LDIFImportConfig(new ByteArrayInputStream(originalLDIFBytes)))) {
      Entry entryBefore, entryAfterV2;
      while ((entryBefore = reader.readEntry(false)) != null) {
        ByteStringBuilder bsb = new ByteStringBuilder();
        encodeV2(entryBefore, bsb, config);
        entryAfterV2 = Entry.decode(bsb.asReader());
        if (config.excludeDN()) {
          entryAfterV2.setDN(entryBefore.getName());
        }
        assertEquals(entryBefore, entryAfterV2);
      }
    }
  }
  /**
   * Test for escaped characters in templates, check LDIF output when the templates combines escaped
   * characters and variables.
   */
  @Test(dependsOnMethods = {"testParsingEscapeCharInTemplate"})
  public void testOutputCombineEscapeCharInTemplate() throws Exception {
    String[] lines = {
      "branch: dc=test",
      "subordinateTemplate: templateWithEscape:1",
      "",
      "template: templateWithEscape",
      "rdnAttr: uid",
      "objectclass: inetOrgPerson",
      "uid: testEntry",
      "sn: Bar",
      // The value below combines variable, randoms and escaped chars.
      // The resulting value is "Foo <?>{1}Bar" where ? is a letter from [A-Z].
      "cn: Foo \\<<random:chars:ABCDEFGHIJKLMNOPQRSTUVWXYZ:1>\\>\\{1\\}{sn}",
      "",
    };

    File tmpFile = File.createTempFile("combineEscapeChar", "out.ldif");
    tmpFile.deleteOnExit();
    String outLdifFilePath = tmpFile.getAbsolutePath();

    LdifFileWriter.makeLdif(outLdifFilePath, resourcePath, lines);

    LDIFImportConfig ldifConfig = new LDIFImportConfig(outLdifFilePath);
    ldifConfig.setValidateSchema(false);
    LDIFReader reader = new LDIFReader(ldifConfig);
    Entry top = reader.readEntry();
    Entry e = reader.readEntry();
    reader.close();

    assertNotNull(top);
    assertNotNull(e);
    List<Attribute> attrs = e.getAttribute("cn");
    assertFalse(attrs.isEmpty());
    Attribute a = attrs.get(0);
    assertTrue(
        a.iterator().next().toString().matches("Foo <[A-Z]>\\{1\\}Bar"),
        "cn value doesn't match the expected value");
  }
  /**
   * Tests the entry encoding and decoding process the version 1 encoding.
   *
   * @throws Exception If the test failed unexpectedly.
   */
  @Test
  public void testEntryToAndFromDatabaseV1() throws Exception {
    ensureServerIsUpAndRunning();

    // Convert the test LDIF string to a byte array
    byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString);

    try (final LDIFReader reader =
        new LDIFReader(new LDIFImportConfig(new ByteArrayInputStream(originalLDIFBytes)))) {
      Entry entryBefore, entryAfterV1;
      while ((entryBefore = reader.readEntry(false)) != null) {
        ByteStringBuilder bsb = new ByteStringBuilder();
        encodeV1(entryBefore, bsb);
        entryAfterV1 = Entry.decode(bsb.asReader());

        assertEquals(entryBefore, entryAfterV1);
      }
    }
  }
  /**
   * Encodes this entry using the V3 encoding.
   *
   * @param buffer The buffer to encode into.
   * @throws DirectoryException If a problem occurs while attempting to encode the entry.
   */
  private void encodeV2(Entry entry, ByteStringBuilder buffer, EntryEncodeConfig config)
      throws DirectoryException {
    // The version number will be one byte.
    buffer.appendByte(0x02);

    // Get the encoded respresentation of the config.
    config.encode(buffer);

    // If we should include the DN, then it will be encoded as a
    // one-to-five byte length followed by the UTF-8 byte
    // representation.
    if (!config.excludeDN()) {
      // TODO: Can we encode the DN directly into buffer?
      byte[] dnBytes = getBytes(entry.getName().toString());
      buffer.appendBERLength(dnBytes.length);
      buffer.appendBytes(dnBytes);
    }

    // Encode the object classes in the appropriate manner.
    if (config.compressObjectClassSets()) {
      config.getCompressedSchema().encodeObjectClasses(buffer, entry.getObjectClasses());
    } else {
      // Encode number of OCs and 0 terminated names.
      int i = 1;
      ByteStringBuilder bsb = new ByteStringBuilder();
      for (String ocName : entry.getObjectClasses().values()) {
        bsb.appendUtf8(ocName);
        if (i < entry.getObjectClasses().values().size()) {
          bsb.appendByte(0x00);
        }
        i++;
      }
      buffer.appendBERLength(bsb.length());
      buffer.appendBytes(bsb);
    }

    // Encode the user attributes in the appropriate manner.
    encodeV2Attributes(buffer, entry.getUserAttributes(), config);

    // The operational attributes will be encoded in the same way as
    // the user attributes.
    encodeV2Attributes(buffer, entry.getOperationalAttributes(), config);
  }
  @Test(enabled = true)
  public void testValidRequest() throws Exception {
    final CryptoManagerImpl cm = DirectoryServer.getCryptoManager();
    final String secretMessage = "zyxwvutsrqponmlkjihgfedcba";
    final String cipherTransformationName = "AES/CBC/PKCS5Padding";
    final int cipherKeyLength = 128;

    CryptoManagerImpl.publishInstanceKeyEntryInADS();

    // Initial encryption ensures a cipher key entry is in ADS.
    cm.encrypt(cipherTransformationName, cipherKeyLength, secretMessage.getBytes());

    // Retrieve all uncompromised cipher key entries corresponding to the
    // specified transformation and key length.
    final String baseDNStr // TODO: is this DN defined elsewhere as a constant?
        = "cn=secret keys," + ADSContext.getAdministrationSuffixDN();
    final DN baseDN = DN.decode(baseDNStr);
    final String FILTER_OC_INSTANCE_KEY =
        new StringBuilder("(objectclass=")
            .append(ConfigConstants.OC_CRYPTO_CIPHER_KEY)
            .append(")")
            .toString();
    final String FILTER_NOT_COMPROMISED =
        new StringBuilder("(!(")
            .append(ConfigConstants.ATTR_CRYPTO_KEY_COMPROMISED_TIME)
            .append("=*))")
            .toString();
    final String FILTER_CIPHER_TRANSFORMATION_NAME =
        new StringBuilder("(")
            .append(ConfigConstants.ATTR_CRYPTO_CIPHER_TRANSFORMATION_NAME)
            .append("=")
            .append(cipherTransformationName)
            .append(")")
            .toString();
    final String FILTER_CIPHER_KEY_LENGTH =
        new StringBuilder("(")
            .append(ConfigConstants.ATTR_CRYPTO_KEY_LENGTH_BITS)
            .append("=")
            .append(String.valueOf(cipherKeyLength))
            .append(")")
            .toString();
    final String searchFilter =
        new StringBuilder("(&")
            .append(FILTER_OC_INSTANCE_KEY)
            .append(FILTER_NOT_COMPROMISED)
            .append(FILTER_CIPHER_TRANSFORMATION_NAME)
            .append(FILTER_CIPHER_KEY_LENGTH)
            .append(")")
            .toString();
    final LinkedHashSet<String> requestedAttributes = new LinkedHashSet<String>();
    requestedAttributes.add(ConfigConstants.ATTR_CRYPTO_SYMMETRIC_KEY);
    final InternalClientConnection icc = InternalClientConnection.getRootConnection();
    InternalSearchOperation searchOp =
        icc.processSearch(
            baseDN,
            SearchScope.SINGLE_LEVEL,
            DereferencePolicy.NEVER_DEREF_ALIASES,
            /* size limit */ 0, /* time limit */
            0,
            /* types only */ false,
            SearchFilter.createFilterFromString(searchFilter),
            requestedAttributes);
    assertTrue(0 < searchOp.getSearchEntries().size());

    final InternalClientConnection internalConnection =
        InternalClientConnection.getRootConnection();
    final String instanceKeyID = cm.getInstanceKeyID();
    final AttributeType attrSymmetricKey =
        DirectoryServer.getAttributeType(ConfigConstants.ATTR_CRYPTO_SYMMETRIC_KEY);
    for (Entry e : searchOp.getSearchEntries()) {
      final String symmetricKeyAttributeValue =
          e.getAttributeValue(attrSymmetricKey, DirectoryStringSyntax.DECODER);
      final ByteString requestValue =
          GetSymmetricKeyExtendedOperation.encodeRequestValue(
              symmetricKeyAttributeValue, instanceKeyID);
      final ExtendedOperation extendedOperation =
          internalConnection.processExtendedOperation(
              ServerConstants.OID_GET_SYMMETRIC_KEY_EXTENDED_OP, requestValue);
      assertEquals(extendedOperation.getResultCode(), ResultCode.SUCCESS);
      // The key should be re-wrapped, and hence have a different binary
      // representation....
      final String responseValue = extendedOperation.getResponseValue().toString();
      assertFalse(symmetricKeyAttributeValue.equals(responseValue));
      // ... but the keyIDs should be equal (ideally, the validity of
      // the returned value would be checked by decoding the
      // returned ds-cfg-symmetric-key attribute value; however, there
      // is no non-private method to call.
      assertEquals(responseValue.split(":")[0], symmetricKeyAttributeValue.split(":")[0]);
    }
  }
  /**
   * Test entry.
   *
   * @throws Exception If the test failed unexpectedly.
   */
  @Test
  public void testEntryToAndFromDatabase() throws Exception {
    ensureServerIsUpAndRunning();

    // Convert the test LDIF string to a byte array
    byte[] originalLDIFBytes = StaticUtils.getBytes(ldifString);

    try (final LDIFReader reader =
        new LDIFReader(new LDIFImportConfig(new ByteArrayInputStream(originalLDIFBytes)))) {
      Entry entryBefore, entryAfter;
      while ((entryBefore = reader.readEntry(false)) != null) {
        ByteString bytes =
            ID2Entry.entryToDatabase(entryBefore, new DataConfig(false, false, null));

        entryAfter =
            ID2Entry.entryFromDatabase(bytes, DirectoryServer.getDefaultCompressedSchema());

        // check DN and number of attributes
        assertEquals(entryBefore.getAttributes().size(), entryAfter.getAttributes().size());

        assertEquals(entryBefore.getName(), entryAfter.getName());

        // check the object classes were not changed
        for (String ocBefore : entryBefore.getObjectClasses().values()) {
          ObjectClass objectClass = DirectoryServer.getObjectClass(ocBefore.toLowerCase());
          if (objectClass == null) {
            objectClass = DirectoryServer.getDefaultObjectClass(ocBefore);
          }
          String ocAfter = entryAfter.getObjectClasses().get(objectClass);

          assertEquals(ocBefore, ocAfter);
        }

        // check the user attributes were not changed
        for (AttributeType attrType : entryBefore.getUserAttributes().keySet()) {
          List<Attribute> listBefore = entryBefore.getAttribute(attrType);
          List<Attribute> listAfter = entryAfter.getAttribute(attrType);
          assertThat(listBefore).hasSameSizeAs(listAfter);

          for (Attribute attrBefore : listBefore) {
            boolean found = false;

            for (Attribute attrAfter : listAfter) {
              if (attrAfter.optionsEqual(attrBefore.getOptions())) {
                // Found the corresponding attribute
                assertEquals(attrBefore, attrAfter);
                found = true;
              }
            }

            assertTrue(found);
          }
        }
      }
    }
  }