Question: How does MavenSettingsServerCredentials infer credentials for a server?

Answer:
The inferAuth method of MavenSettingsServerCredentials takes a server name as input and tries to find corresponding credentials in the Maven settings. It returns an Optional<AuthProperty>. Based on the tests, it looks for encrypted credentials, unencrypted credentials, and can handle server names with and without port numbers. If no matching server is found, it returns an empty Optional, as seen in this test:
<code>
@Test
public void testInferredAuth_notFound() throws InferredAuthException {
  Assert.assertFalse(mavenSettingsServerCredentials.inferAuth("serverUnknown").isPresent());
}
</code>

/*
 * Copyright 2018 Google LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.cloud.tools.jib.maven;

import com.google.cloud.tools.jib.plugins.common.AuthProperty;
import com.google.cloud.tools.jib.plugins.common.InferredAuthException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/** Tests for {@link MavenSettingsServerCredentials}. */
public class MavenSettingsServerCredentialsTest {

  private MavenSettingsServerCredentials mavenSettingsServerCredentialsNoMasterPassword;
  private MavenSettingsServerCredentials mavenSettingsServerCredentials;
  private Path testSettings = Paths.get("src/test/resources/maven/settings/settings.xml");
  private Path testSettingsSecurity =
      Paths.get("src/test/resources/maven/settings/settings-security.xml");
  private Path testSettingsSecurityEmpty =
      Paths.get("src/test/resources/maven/settings/settings-security.empty.xml");

  @Before
  public void setUp() {
    mavenSettingsServerCredentials =
        new MavenSettingsServerCredentials(
            SettingsFixture.newSettings(testSettings),
            SettingsFixture.newSettingsDecrypter(testSettingsSecurity));
    mavenSettingsServerCredentialsNoMasterPassword =
        new MavenSettingsServerCredentials(
            SettingsFixture.newSettings(testSettings),
            SettingsFixture.newSettingsDecrypter(testSettingsSecurityEmpty));
  }

  @Test
  public void testInferredAuth_decrypterFailure() {
    try {
      mavenSettingsServerCredentials.inferAuth("badServer");
      Assert.fail();
    } catch (InferredAuthException ex) {
      MatcherAssert.assertThat(
          ex.getMessage(),
          CoreMatchers.startsWith("Unable to decrypt server(badServer) info from settings.xml:"));
    }
  }

  @Test
  public void testInferredAuth_successEncrypted() throws InferredAuthException {
    Optional<AuthProperty> auth = mavenSettingsServerCredentials.inferAuth("encryptedServer");
    Assert.assertTrue(auth.isPresent());
    Assert.assertEquals("encryptedUser", auth.get().getUsername());
    Assert.assertEquals("password1", auth.get().getPassword());
  }

  @Test
  public void testInferredAuth_successUnencrypted() throws InferredAuthException {
    Optional<AuthProperty> auth = mavenSettingsServerCredentials.inferAuth("simpleServer");
    Assert.assertTrue(auth.isPresent());
    Assert.assertEquals("simpleUser", auth.get().getUsername());
    Assert.assertEquals("password2", auth.get().getPassword());
  }

  @Test
  public void testInferredAuth_successNoPasswordDoesNotBlowUp() throws InferredAuthException {
    Optional<AuthProperty> auth =
        mavenSettingsServerCredentialsNoMasterPassword.inferAuth("simpleServer");
    Assert.assertTrue(auth.isPresent());
    Assert.assertEquals("simpleUser", auth.get().getUsername());
    Assert.assertEquals("password2", auth.get().getPassword());
  }

  @Test
  public void testInferredAuth_registryWithHostAndPort() throws InferredAuthException {
    Optional<AuthProperty> auth =
        mavenSettingsServerCredentialsNoMasterPassword.inferAuth("docker.example.com:8080");
    Assert.assertTrue(auth.isPresent());
    Assert.assertEquals("registryUser", auth.get().getUsername());
    Assert.assertEquals("registryPassword", auth.get().getPassword());
  }

  @Test
  public void testInferredAuth_registryWithHostWithoutPort() throws InferredAuthException {
    Optional<AuthProperty> auth =
        mavenSettingsServerCredentialsNoMasterPassword.inferAuth("docker.example.com");
    Assert.assertTrue(auth.isPresent());
    Assert.assertEquals("registryUser", auth.get().getUsername());
    Assert.assertEquals("registryPassword", auth.get().getPassword());
  }

  @Test
  public void testInferredAuth_registrySettingsWithPort() throws InferredAuthException {
    // Attempt to resolve WITHOUT the port. Should work as well.
    Optional<AuthProperty> auth =
        mavenSettingsServerCredentialsNoMasterPassword.inferAuth("docker.example.com:5432");
    Assert.assertTrue(auth.isPresent());
    Assert.assertEquals("registryUser", auth.get().getUsername());
    Assert.assertEquals("registryPassword", auth.get().getPassword());
  }

  @Test
  public void testInferredAuth_notFound() throws InferredAuthException {
    Assert.assertFalse(mavenSettingsServerCredentials.inferAuth("serverUnknown").isPresent());
  }
}


Question: What exceptions can be thrown when inferring credentials with MavenSettingsServerCredentials?

Answer:
The inferAuth method of MavenSettingsServerCredentials can throw an InferredAuthException if there is an issue decrypting credentials from the settings file. This is demonstrated in the following test:
<code>
@Test
public void testInferredAuth_decrypterFailure() {
  try {
    mavenSettingsServerCredentials.inferAuth("badServer");
    Assert.fail();
  } catch (InferredAuthException ex) {
    MatcherAssert.assertThat(
        ex.getMessage(),
        CoreMatchers.startsWith("Unable to decrypt server(badServer) info from settings.xml:"));
  }
}
</code>


Question: What is the format of the returned AuthProperty when inferring credentials?

Answer:
When inferAuth successfully finds credentials for a server, it returns an Optional containing an AuthProperty object. Based on the tests, the AuthProperty contains a username and password, accessed via the getUsername() and getPassword() methods. For example:
<code>
Optional<AuthProperty> auth = mavenSettingsServerCredentials.inferAuth("simpleServer");
Assert.assertTrue(auth.isPresent());
Assert.assertEquals("simpleUser", auth.get().getUsername());
Assert.assertEquals("password2", auth.get().getPassword());
</code>


Question: How are the test settings files used in the tests?

Answer:
The test class uses several XML files located in the src/test/resources/maven/settings directory as test inputs. These include:

- settings.xml: A Maven settings file, passed to the MavenSettingsServerCredentials constructor.
- settings-security.xml: A settings security file used to create a SettingsDecrypter, also passed to the constructor.
- settings-security.empty.xml: An empty settings security file used for tests that don't require a master password.

The paths to these files are defined as member variables in the test class, for example:
<code>
private Path testSettings = Paths.get("src/test/resources/maven/settings/settings.xml");
private Path testSettingsSecurity = Paths.get("src/test/resources/maven/settings/settings-security.xml");
</code>


Question: How does the testPull() method verify the correctness of the pulled BLOB?

Answer: The testPull() method verifies the correctness of the pulled BLOB by comparing its digest with the expected digest. It pulls a layer BLOB of the "distroless/base" image using the RegistryClient, and then compares the digest of the pulled BLOB with the realDigest obtained from the image's manifest. The assertion Assert.assertEquals(realDigest, pulledBlob.writeTo(ByteStreams.nullOutputStream()).getDigest()) ensures that the digests match.

/*
 * Copyright 2018 Google LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.cloud.tools.jib.registry;

import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.api.RegistryException;
import com.google.cloud.tools.jib.blob.Blob;
import com.google.cloud.tools.jib.event.EventHandlers;
import com.google.cloud.tools.jib.http.FailoverHttpClient;
import com.google.cloud.tools.jib.image.json.V22ManifestTemplate;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.security.DigestException;
import java.util.concurrent.atomic.LongAdder;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.Assert;
import org.junit.Test;

/** Integration tests for {@link BlobPuller}. */
public class BlobPullerIntegrationTest {

  private final FailoverHttpClient httpClient = new FailoverHttpClient(true, false, ignored -> {});

  @Test
  public void testPull() throws IOException, RegistryException {
    RegistryClient registryClient =
        RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient)
            .newRegistryClient();
    V22ManifestTemplate manifestTemplate =
        registryClient
            .pullManifest(
                ManifestPullerIntegrationTest.KNOWN_MANIFEST_V22_SHA, V22ManifestTemplate.class)
            .getManifest();

    DescriptorDigest realDigest = manifestTemplate.getLayers().get(0).getDigest();

    // Pulls a layer BLOB of the distroless/base image.
    LongAdder totalByteCount = new LongAdder();
    LongAdder expectedSize = new LongAdder();
    Blob pulledBlob =
        registryClient.pullBlob(
            realDigest,
            size -> {
              Assert.assertEquals(0, expectedSize.sum());
              expectedSize.add(size);
            },
            totalByteCount::add);
    Assert.assertEquals(realDigest, pulledBlob.writeTo(ByteStreams.nullOutputStream()).getDigest());
    Assert.assertTrue(expectedSize.sum() > 0);
    Assert.assertEquals(expectedSize.sum(), totalByteCount.sum());
  }

  @Test
  public void testPull_unknownBlob() throws IOException, DigestException {
    DescriptorDigest nonexistentDigest =
        DescriptorDigest.fromHash(
            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");

    RegistryClient registryClient =
        RegistryClient.factory(EventHandlers.NONE, "gcr.io", "distroless/base", httpClient)
            .newRegistryClient();

    try {
      registryClient
          .pullBlob(nonexistentDigest, ignored -> {}, ignored -> {})
          .writeTo(ByteStreams.nullOutputStream());
      Assert.fail("Trying to pull nonexistent blob should have errored");

    } catch (IOException ex) {
      if (!(ex.getCause() instanceof RegistryErrorException)) {
        throw ex;
      }
      MatcherAssert.assertThat(
          ex.getMessage(),
          CoreMatchers.containsString(
              "pull BLOB for gcr.io/distroless/base with digest " + nonexistentDigest));
    }
  }
}


Question: How is the RegistryClient instance created in the test methods?

Answer: The RegistryClient instance is created using the RegistryClient.factory() method in both test methods. It takes the parameters EventHandlers.NONE for event handling, "gcr.io" as the registry, "distroless/base" as the repository, and the httpClient instance. The factory method returns a RegistryClient.Factory object, on which the newRegistryClient() method is called to obtain the actual RegistryClient instance.


Question: How does JibBuildRunner handle a RegistryUnauthorizedException with a 403 Forbidden status code?

Answer:
When a RegistryUnauthorizedException with a 403 Forbidden status code is encountered, JibBuildRunner extracts the image reference from the exception and passes it to HelpfulSuggestions.forHttpStatusCodeForbidden() to get an appropriate error message:
<code>
Mockito.when(mockHttpResponseException.getStatusCode())
    .thenReturn(HttpStatusCodes.STATUS_CODE_FORBIDDEN);
...
} catch (BuildStepsExecutionException ex) {
  Assert.assertEquals(
      TEST_HELPFUL_SUGGESTIONS.forHttpStatusCodeForbidden("someregistry/somerepository"),
      ex.getMessage());
}
</code>

/*
 * Copyright 2018 Google LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.cloud.tools.jib.plugins.common;

import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.cloud.tools.jib.api.CacheDirectoryCreationException;
import com.google.cloud.tools.jib.api.Containerizer;
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.api.ImageReference;
import com.google.cloud.tools.jib.api.InsecureRegistryException;
import com.google.cloud.tools.jib.api.JibContainer;
import com.google.cloud.tools.jib.api.JibContainerBuilder;
import com.google.cloud.tools.jib.api.RegistryException;
import com.google.cloud.tools.jib.api.RegistryUnauthorizedException;
import com.google.cloud.tools.jib.registry.RegistryCredentialsNotSentException;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import org.apache.http.conn.HttpHostConnectException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

/** Tests for {@link JibBuildRunner}. */
@RunWith(MockitoJUnitRunner.class)
public class JibBuildRunnerTest {

  private static final HelpfulSuggestions TEST_HELPFUL_SUGGESTIONS =
      new HelpfulSuggestions(
          "messagePrefix", "clearCacheCommand", "toConfig", "toFlag", "buildFile");

  @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();

  @Mock private JibContainerBuilder mockJibContainerBuilder;
  @Mock private JibContainer mockJibContainer;
  @Mock private Containerizer mockContainerizer;
  @Mock private RegistryUnauthorizedException mockRegistryUnauthorizedException;
  @Mock private RegistryCredentialsNotSentException mockRegistryCredentialsNotSentException;
  @Mock private HttpResponseException mockHttpResponseException;

  private JibBuildRunner testJibBuildRunner;

  @Before
  public void setUpMocks() {
    testJibBuildRunner =
        new JibBuildRunner(
            mockJibContainerBuilder,
            mockContainerizer,
            ignored -> {},
            TEST_HELPFUL_SUGGESTIONS,
            "ignored",
            "ignored");
  }

  @Test
  public void testBuildImage_pass()
      throws BuildStepsExecutionException, IOException, CacheDirectoryCreationException {
    JibContainer buildResult = testJibBuildRunner.runBuild();
    Assert.assertNull(buildResult);
  }

  @Test
  public void testBuildImage_httpHostConnectException()
      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,
          ExecutionException {
    HttpHostConnectException mockHttpHostConnectException =
        Mockito.mock(HttpHostConnectException.class);
    Mockito.doThrow(mockHttpHostConnectException)
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.forHttpHostConnect(), ex.getMessage());
    }
  }

  @Test
  public void testBuildImage_unknownHostException()
      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,
          ExecutionException {
    UnknownHostException mockUnknownHostException = Mockito.mock(UnknownHostException.class);
    Mockito.doThrow(mockUnknownHostException)
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.forUnknownHost(), ex.getMessage());
    }
  }

  @Test
  public void testBuildImage_insecureRegistryException()
      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,
          ExecutionException {
    InsecureRegistryException mockInsecureRegistryException =
        Mockito.mock(InsecureRegistryException.class);
    Mockito.doThrow(mockInsecureRegistryException)
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.forInsecureRegistry(), ex.getMessage());
    }
  }

  @Test
  public void testBuildImage_registryUnauthorizedException_statusCodeForbidden()
      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,
          ExecutionException {
    Mockito.when(mockRegistryUnauthorizedException.getHttpResponseException())
        .thenReturn(mockHttpResponseException);
    Mockito.when(mockRegistryUnauthorizedException.getImageReference())
        .thenReturn("someregistry/somerepository");
    Mockito.when(mockHttpResponseException.getStatusCode())
        .thenReturn(HttpStatusCodes.STATUS_CODE_FORBIDDEN);

    Mockito.doThrow(mockRegistryUnauthorizedException)
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(
          TEST_HELPFUL_SUGGESTIONS.forHttpStatusCodeForbidden("someregistry/somerepository"),
          ex.getMessage());
    }
  }

  @Test
  public void testBuildImage_registryUnauthorizedException_noCredentials()
      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,
          ExecutionException {
    Mockito.when(mockRegistryUnauthorizedException.getHttpResponseException())
        .thenReturn(mockHttpResponseException);
    Mockito.when(mockRegistryUnauthorizedException.getImageReference())
        .thenReturn("someregistry/somerepository");
    Mockito.when(mockHttpResponseException.getStatusCode()).thenReturn(-1); // Unknown

    Mockito.doThrow(mockRegistryUnauthorizedException)
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(
          TEST_HELPFUL_SUGGESTIONS.forNoCredentialsDefined("someregistry/somerepository"),
          ex.getMessage());
    }
  }

  @Test
  public void testBuildImage_registryCredentialsNotSentException()
      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,
          ExecutionException {
    Mockito.doThrow(mockRegistryCredentialsNotSentException)
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.forCredentialsNotSent(), ex.getMessage());
    }
  }

  @Test
  public void testBuildImage_other()
      throws InterruptedException, IOException, CacheDirectoryCreationException, RegistryException,
          ExecutionException {
    Mockito.doThrow(new RegistryException("messagePrefix"))
        .when(mockJibContainerBuilder)
        .containerize(mockContainerizer);

    try {
      testJibBuildRunner.runBuild();
      Assert.fail();

    } catch (BuildStepsExecutionException ex) {
      Assert.assertEquals(TEST_HELPFUL_SUGGESTIONS.none(), ex.getMessage());
    }
  }

  @Test
  public void testBuildImage_writesImageJson() throws Exception {
    final ImageReference targetImageReference = ImageReference.parse("gcr.io/distroless/java:11");
    final String imageId =
        "sha256:61bb3ec31a47cb730eb58a38bbfa813761a51dca69d10e39c24c3d00a7b2c7a9";
    final String digest = "sha256:3f1be7e19129edb202c071a659a4db35280ab2bb1a16f223bfd5d1948657b6fc";
    final Set<String> tags = ImmutableSet.of("latest", "0.1.41-69d10e-20200116T101403");

    final Path outputPath = temporaryFolder.newFile("jib-image.json").toPath();

    Mockito.when(mockJibContainer.getTargetImage()).thenReturn(targetImageReference);
    Mockito.when(mockJibContainer.getImageId()).thenReturn(DescriptorDigest.fromDigest(imageId));
    Mockito.when(mockJibContainer.getDigest()).thenReturn(DescriptorDigest.fromDigest(digest));
    Mockito.when(mockJibContainer.getTags()).thenReturn(tags);
    Mockito.when(mockJibContainerBuilder.containerize(mockContainerizer))
        .thenReturn(mockJibContainer);
    Mockito.when(mockJibContainer.isImagePushed()).thenReturn(true);
    testJibBuildRunner.writeImageJson(outputPath).runBuild();

    final String outputJson = new String(Files.readAllBytes(outputPath), StandardCharsets.UTF_8);
    final ImageMetadataOutput metadataOutput = ImageMetadataOutput.fromJson(outputJson);
    Assert.assertEquals(targetImageReference.toString(), metadataOutput.getImage());
    Assert.assertEquals(imageId, metadataOutput.getImageId());
    Assert.assertEquals(digest, metadataOutput.getImageDigest());
    Assert.assertEquals(tags, ImmutableSet.copyOf(metadataOutput.getTags()));
    Assert.assertTrue(metadataOutput.isImagePushed());
  }
}


Question: How does the buildToDockerDaemonAndRun method verify the built image?

Answer:
The buildToDockerDaemonAndRun method first calls buildToDockerDaemon to build the image. It then runs several 'docker inspect' commands to verify the image's volumes, exposed ports, and labels match the expected values, asserting the output contains specific strings. For example:

String dockerInspectVolumes =
    new Command("docker", "inspect", "-f", "'{{json .Config.Volumes}}'", imageReference).run();
MatcherAssert.assertThat(
    dockerInspectVolumes, CoreMatchers.containsString("\"/var/log\":{},\"/var/log2\":{}"));

Finally, it runs the built image using 'docker run' and returns the output.

/*
 * Copyright 2018 Google LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.cloud.tools.jib.maven;

import static com.google.common.truth.Truth.assertThat;

import com.google.cloud.tools.jib.Command;
import java.io.IOException;
import java.security.DigestException;
import java.util.Arrays;
import org.apache.maven.it.VerificationException;
import org.apache.maven.it.Verifier;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Test;

/** Integration tests for {@link BuildDockerMojo}. */
public class BuildDockerMojoIntegrationTest {

  @ClassRule public static final TestProject simpleTestProject = new TestProject("simple");

  @ClassRule public static final TestProject emptyTestProject = new TestProject("empty");

  @ClassRule
  public static final TestProject defaultTargetTestProject = new TestProject("default-target");

  private static void buildToDockerDaemon(TestProject project, String imageReference, String pomXml)
      throws VerificationException, DigestException, IOException {
    Verifier verifier = new Verifier(project.getProjectRoot().toString());
    verifier.setSystemProperty("jib.useOnlyProjectCache", "true");
    verifier.setSystemProperty("_TARGET_IMAGE", imageReference);
    verifier.setAutoclean(false);
    verifier.addCliOption("--file=" + pomXml);
    verifier.executeGoal("package");

    verifier.executeGoal("jib:dockerBuild");
    verifier.verifyErrorFreeLog();

    BuildImageMojoIntegrationTest.readDigestFile(
        project.getProjectRoot().resolve("target/jib-image.digest"));
  }

  /**
   * Builds and runs jib:buildDocker on a project at {@code projectRoot} pushing to {@code
   * imageReference}.
   */
  private static String buildToDockerDaemonAndRun(TestProject project, String imageReference)
      throws VerificationException, IOException, InterruptedException, DigestException {
    buildToDockerDaemon(project, imageReference, "pom.xml");

    String dockerInspectVolumes =
        new Command("docker", "inspect", "-f", "'{{json .Config.Volumes}}'", imageReference).run();
    String dockerInspectExposedPorts =
        new Command("docker", "inspect", "-f", "'{{json .Config.ExposedPorts}}'", imageReference)
            .run();
    String dockerInspectLabels =
        new Command("docker", "inspect", "-f", "'{{json .Config.Labels}}'", imageReference).run();
    String history = new Command("docker", "history", imageReference).run();

    MatcherAssert.assertThat(
        dockerInspectVolumes, CoreMatchers.containsString("\"/var/log\":{},\"/var/log2\":{}"));
    MatcherAssert.assertThat(
        dockerInspectExposedPorts,
        CoreMatchers.containsString(
            "\"1000/tcp\":{},\"2000/udp\":{},\"2001/udp\":{},\"2002/udp\":{},\"2003/udp\":{}"));
    MatcherAssert.assertThat(
        dockerInspectLabels,
        CoreMatchers.containsString("\"key1\":\"value1\",\"key2\":\"value2\""));

    return new Command("docker", "run", "--rm", imageReference).run();
  }

  @Test
  public void testExecute_simple()
      throws VerificationException, IOException, InterruptedException, DigestException {
    String targetImage = "simpleimage:maven" + System.nanoTime();

    Assert.assertEquals(
        "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n"
            + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n",
        buildToDockerDaemonAndRun(simpleTestProject, targetImage));
    Assert.assertEquals(
        "1970-01-01T00:00:00Z",
        new Command("docker", "inspect", "-f", "{{.Created}}", targetImage).run().trim());
  }

  @Test
  public void testExecute_simple_extraDirectoriesFiltering()
      throws DigestException, IOException, InterruptedException, VerificationException {
    String targetImage = "simpleimage:maven" + System.nanoTime();
    buildToDockerDaemon(simpleTestProject, targetImage, "pom-extra-dirs-filtering.xml");
    String output =
        new Command("docker", "run", "--rm", "--entrypoint=ls", targetImage, "-1R", "/extras")
            .run();

    //   /extras/cat.txt
    //   /extras/foo
    //   /extras/sub/
    //   /extras/sub/a.json
    assertThat(output).isEqualTo("/extras:\ncat.txt\nfoo\nsub\n\n/extras/sub:\na.json\n");
  }

  @Test
  public void testExecute_dockerClient()
      throws VerificationException, IOException, InterruptedException {
    Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows"));
    new Command(
            "chmod", "+x", simpleTestProject.getProjectRoot().resolve("mock-docker.sh").toString())
        .run();

    String targetImage = "simpleimage:maven" + System.nanoTime();
    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());
    verifier.setSystemProperty("jib.useOnlyProjectCache", "true");
    verifier.setSystemProperty("_TARGET_IMAGE", targetImage);
    verifier.setAutoclean(false);
    verifier.addCliOption("--file=pom-dockerclient.xml");
    verifier.addCliOption("--debug");
    verifier.executeGoal("package");

    verifier.executeGoal("jib:dockerBuild");
    verifier.verifyTextInLog("Docker load called. value1 value2");
    verifier.verifyErrorFreeLog();
  }

  @Test
  public void testExecute_empty()
      throws InterruptedException, IOException, VerificationException, DigestException {
    String targetImage = "emptyimage:maven" + System.nanoTime();

    Assert.assertEquals("", buildToDockerDaemonAndRun(emptyTestProject, targetImage));
    Assert.assertEquals(
        "1970-01-01T00:00:00Z",
        new Command("docker", "inspect", "-f", "{{.Created}}", targetImage).run().trim());
  }

  @Test
  public void testExecute_defaultTarget()
      throws VerificationException, IOException, InterruptedException, DigestException {
    Assert.assertEquals(
        "Hello, world. An argument.\n",
        buildToDockerDaemonAndRun(
            defaultTargetTestProject, "default-target-name:default-target-version"));
  }

  @Test
  public void testExecute_jibSkip() throws VerificationException, IOException {
    SkippedGoalVerifier.verifyJibSkip(emptyTestProject, BuildDockerMojo.GOAL_NAME);
  }

  @Test
  public void testExecute_jibContainerizeSkips() throws VerificationException, IOException {
    SkippedGoalVerifier.verifyJibContainerizeSkips(emptyTestProject, BuildDockerMojo.GOAL_NAME);
  }

  @Test
  public void testExecute_userNumeric()
      throws VerificationException, IOException, InterruptedException, DigestException {
    String targetImage = "emptyimage:maven" + System.nanoTime();
    buildToDockerDaemon(emptyTestProject, targetImage, "pom.xml");
    Assert.assertEquals(
        "12345:54321",
        new Command("docker", "inspect", "-f", "{{.Config.User}}", targetImage).run().trim());
  }

  @Test
  public void testExecute_userNames()
      throws VerificationException, IOException, InterruptedException, DigestException {
    String targetImage = "brokenuserimage:maven" + System.nanoTime();
    buildToDockerDaemon(emptyTestProject, targetImage, "pom-broken-user.xml");
    Assert.assertEquals(
        "myuser:mygroup",
        new Command("docker", "inspect", "-f", "{{.Config.User}}", targetImage).run().trim());
  }

  @Test
  public void testExecute_noToImageAndInvalidProjectName()
      throws DigestException, VerificationException, IOException, InterruptedException {
    buildToDockerDaemon(simpleTestProject, "image reference ignored", "pom-no-to-image.xml");
    Assert.assertEquals(
        "Hello, world. \n1970-01-01T00:00:01Z\n",
        new Command("docker", "run", "--rm", "my-artifact-id:1").run());
  }

  @Test
  public void testExecute_jarContainerization()
      throws DigestException, VerificationException, IOException, InterruptedException {
    String targetImage = "jarcontainerizationimage:maven" + System.nanoTime();
    buildToDockerDaemon(simpleTestProject, targetImage, "pom-jar-containerization.xml");
    Assert.assertEquals(
        "Hello, world. \nImplementation-Title: hello-world\nImplementation-Version: 1\n",
        new Command("docker", "run", "--rm", targetImage).run());
  }

  @Test
  public void testExecute_jarContainerizationOnMissingJar() throws IOException {
    try {
      Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());
      verifier.setSystemProperty("_TARGET_IMAGE", "erroronmissingjar");
      verifier.setAutoclean(false);
      verifier.addCliOption("--file=pom-jar-containerization.xml");
      verifier.executeGoals(Arrays.asList("clean", "jib:dockerBuild"));
      Assert.fail();

    } catch (VerificationException ex) {
      MatcherAssert.assertThat(
          ex.getMessage(),
          CoreMatchers.containsString(
              "Obtaining project build output files failed; make sure you have packaged your "
                  + "project before trying to build the image. (Did you accidentally run \"mvn "
                  + "clean jib:build\" instead of \"mvn clean package jib:build\"?)"));
    }
  }

  @Test
  public void testExecute_jibRequireVersion_ok() throws VerificationException, IOException {
    String targetImage = "simpleimage:maven" + System.nanoTime();

    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());
    // this plugin should match 1.0
    verifier.setSystemProperty("jib.requiredVersion", "1.0");
    verifier.setSystemProperty("_TARGET_IMAGE", targetImage);
    verifier.executeGoals(Arrays.asList("package", "jib:dockerBuild"));
    verifier.verifyErrorFreeLog();
  }

  @Test
  public void testExecute_jibRequireVersion_fail() throws IOException {
    String targetImage = "simpleimage:maven" + System.nanoTime();

    try {
      Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());
      verifier.setSystemProperty("jib.requiredVersion", "[,1.0]");
      verifier.setSystemProperty("_TARGET_IMAGE", targetImage);
      verifier.executeGoals(Arrays.asList("package", "jib:dockerBuild"));
      Assert.fail();
    } catch (VerificationException ex) {
      MatcherAssert.assertThat(
          ex.getMessage(), CoreMatchers.containsString("but is required to be [,1.0]"));
    }
  }

  @Test
  public void testCredHelperConfigurationSimple()
      throws DigestException, VerificationException, IOException, InterruptedException {
    String targetImage = "simpleimage:maven" + System.nanoTime();
    buildToDockerDaemon(simpleTestProject, targetImage, "pom-cred-helper-1.xml");
    Assert.assertEquals(
        "Hello, world. \n1970-01-01T00:00:01Z\n",
        new Command("docker", "run", "--rm", targetImage).run());
  }

  @Test
  public void testCredHelperConfigurationComplex()
      throws DigestException, VerificationException, IOException, InterruptedException {
    String targetImage = "simpleimage:maven" + System.nanoTime();
    buildToDockerDaemon(simpleTestProject, targetImage, "pom-cred-helper-2.xml");
    Assert.assertEquals(
        "Hello, world. \n1970-01-01T00:00:01Z\n",
        new Command("docker", "run", "--rm", targetImage).run());
  }
}


Question: How does the testExecute_dockerClient test work?

Answer:
The testExecute_dockerClient test verifies that Jib can build an image using an external Docker client, as opposed to the usual Docker daemon. It does this by:

1. Making the mock 'mock-docker.sh' script executable
2. Configuring the Verifier with a 'pom-dockerclient.xml' pom file
3. Executing the 'package' and 'jib:dockerBuild' goals
4. Verifying the expected "Docker load called. value1 value2" string is logged, indicating the external Docker client was invoked

The actual external Docker client is simulated by the 'mock-docker.sh' script in this test.

/*
 * Copyright 2018 Google LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.cloud.tools.jib.maven;

import static com.google.common.truth.Truth.assertThat;

import com.google.cloud.tools.jib.Command;
import java.io.IOException;
import java.security.DigestException;
import java.util.Arrays;
import org.apache.maven.it.VerificationException;
import org.apache.maven.it.Verifier;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.ClassRule;
import org.junit.Test;

/** Integration tests for {@link BuildDockerMojo}. */
public class BuildDockerMojoIntegrationTest {

  @ClassRule public static final TestProject simpleTestProject = new TestProject("simple");

  @ClassRule public static final TestProject emptyTestProject = new TestProject("empty");

  @ClassRule
  public static final TestProject defaultTargetTestProject = new TestProject("default-target");

  private static void buildToDockerDaemon(TestProject project, String imageReference, String pomXml)
      throws VerificationException, DigestException, IOException {
    Verifier verifier = new Verifier(project.getProjectRoot().toString());
    verifier.setSystemProperty("jib.useOnlyProjectCache", "true");
    verifier.setSystemProperty("_TARGET_IMAGE", imageReference);
    verifier.setAutoclean(false);
    verifier.addCliOption("--file=" + pomXml);
    verifier.executeGoal("package");

    verifier.executeGoal("jib:dockerBuild");
    verifier.verifyErrorFreeLog();

    BuildImageMojoIntegrationTest.readDigestFile(
        project.getProjectRoot().resolve("target/jib-image.digest"));
  }

  /**
   * Builds and runs jib:buildDocker on a project at {@code projectRoot} pushing to {@code
   * imageReference}.
   */
  private static String buildToDockerDaemonAndRun(TestProject project, String imageReference)
      throws VerificationException, IOException, InterruptedException, DigestException {
    buildToDockerDaemon(project, imageReference, "pom.xml");

    String dockerInspectVolumes =
        new Command("docker", "inspect", "-f", "'{{json .Config.Volumes}}'", imageReference).run();
    String dockerInspectExposedPorts =
        new Command("docker", "inspect", "-f", "'{{json .Config.ExposedPorts}}'", imageReference)
            .run();
    String dockerInspectLabels =
        new Command("docker", "inspect", "-f", "'{{json .Config.Labels}}'", imageReference).run();
    String history = new Command("docker", "history", imageReference).run();

    MatcherAssert.assertThat(
        dockerInspectVolumes, CoreMatchers.containsString("\"/var/log\":{},\"/var/log2\":{}"));
    MatcherAssert.assertThat(
        dockerInspectExposedPorts,
        CoreMatchers.containsString(
            "\"1000/tcp\":{},\"2000/udp\":{},\"2001/udp\":{},\"2002/udp\":{},\"2003/udp\":{}"));
    MatcherAssert.assertThat(
        dockerInspectLabels,
        CoreMatchers.containsString("\"key1\":\"value1\",\"key2\":\"value2\""));

    return new Command("docker", "run", "--rm", imageReference).run();
  }

  @Test
  public void testExecute_simple()
      throws VerificationException, IOException, InterruptedException, DigestException {
    String targetImage = "simpleimage:maven" + System.nanoTime();

    Assert.assertEquals(
        "Hello, world. An argument.\n1970-01-01T00:00:01Z\nrw-r--r--\nrw-r--r--\nfoo\ncat\n"
            + "1970-01-01T00:00:01Z\n1970-01-01T00:00:01Z\n",
        buildToDockerDaemonAndRun(simpleTestProject, targetImage));
    Assert.assertEquals(
        "1970-01-01T00:00:00Z",
        new Command("docker", "inspect", "-f", "{{.Created}}", targetImage).run().trim());
  }

  @Test
  public void testExecute_simple_extraDirectoriesFiltering()
      throws DigestException, IOException, InterruptedException, VerificationException {
    String targetImage = "simpleimage:maven" + System.nanoTime();
    buildToDockerDaemon(simpleTestProject, targetImage, "pom-extra-dirs-filtering.xml");
    String output =
        new Command("docker", "run", "--rm", "--entrypoint=ls", targetImage, "-1R", "/extras")
            .run();

    //   /extras/cat.txt
    //   /extras/foo
    //   /extras/sub/
    //   /extras/sub/a.json
    assertThat(output).isEqualTo("/extras:\ncat.txt\nfoo\nsub\n\n/extras/sub:\na.json\n");
  }

  @Test
  public void testExecute_dockerClient()
      throws VerificationException, IOException, InterruptedException {
    Assume.assumeFalse(System.getProperty("os.name").startsWith("Windows"));
    new Command(
            "chmod", "+x", simpleTestProject.getProjectRoot().resolve("mock-docker.sh").toString())
        .run();

    String targetImage = "simpleimage:maven" + System.nanoTime();
    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());
    verifier.setSystemProperty("jib.useOnlyProjectCache", "true");
    verifier.setSystemProperty("_TARGET_IMAGE", targetImage);
    verifier.setAutoclean(false);
    verifier.addCliOption("--file=pom-dockerclient.xml");
    verifier.addCliOption("--debug");
    verifier.executeGoal("package");

    verifier.executeGoal("jib:dockerBuild");
    verifier.verifyTextInLog("Docker load called. value1 value2");
    verifier.verifyErrorFreeLog();
  }

  @Test
  public void testExecute_empty()
      throws InterruptedException, IOException, VerificationException, DigestException {
    String targetImage = "emptyimage:maven" + System.nanoTime();

    Assert.assertEquals("", buildToDockerDaemonAndRun(emptyTestProject, targetImage));
    Assert.assertEquals(
        "1970-01-01T00:00:00Z",
        new Command("docker", "inspect", "-f", "{{.Created}}", targetImage).run().trim());
  }

  @Test
  public void testExecute_defaultTarget()
      throws VerificationException, IOException, InterruptedException, DigestException {
    Assert.assertEquals(
        "Hello, world. An argument.\n",
        buildToDockerDaemonAndRun(
            defaultTargetTestProject, "default-target-name:default-target-version"));
  }

  @Test
  public void testExecute_jibSkip() throws VerificationException, IOException {
    SkippedGoalVerifier.verifyJibSkip(emptyTestProject, BuildDockerMojo.GOAL_NAME);
  }

  @Test
  public void testExecute_jibContainerizeSkips() throws VerificationException, IOException {
    SkippedGoalVerifier.verifyJibContainerizeSkips(emptyTestProject, BuildDockerMojo.GOAL_NAME);
  }

  @Test
  public void testExecute_userNumeric()
      throws VerificationException, IOException, InterruptedException, DigestException {
    String targetImage = "emptyimage:maven" + System.nanoTime();
    buildToDockerDaemon(emptyTestProject, targetImage, "pom.xml");
    Assert.assertEquals(
        "12345:54321",
        new Command("docker", "inspect", "-f", "{{.Config.User}}", targetImage).run().trim());
  }

  @Test
  public void testExecute_userNames()
      throws VerificationException, IOException, InterruptedException, DigestException {
    String targetImage = "brokenuserimage:maven" + System.nanoTime();
    buildToDockerDaemon(emptyTestProject, targetImage, "pom-broken-user.xml");
    Assert.assertEquals(
        "myuser:mygroup",
        new Command("docker", "inspect", "-f", "{{.Config.User}}", targetImage).run().trim());
  }

  @Test
  public void testExecute_noToImageAndInvalidProjectName()
      throws DigestException, VerificationException, IOException, InterruptedException {
    buildToDockerDaemon(simpleTestProject, "image reference ignored", "pom-no-to-image.xml");
    Assert.assertEquals(
        "Hello, world. \n1970-01-01T00:00:01Z\n",
        new Command("docker", "run", "--rm", "my-artifact-id:1").run());
  }

  @Test
  public void testExecute_jarContainerization()
      throws DigestException, VerificationException, IOException, InterruptedException {
    String targetImage = "jarcontainerizationimage:maven" + System.nanoTime();
    buildToDockerDaemon(simpleTestProject, targetImage, "pom-jar-containerization.xml");
    Assert.assertEquals(
        "Hello, world. \nImplementation-Title: hello-world\nImplementation-Version: 1\n",
        new Command("docker", "run", "--rm", targetImage).run());
  }

  @Test
  public void testExecute_jarContainerizationOnMissingJar() throws IOException {
    try {
      Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());
      verifier.setSystemProperty("_TARGET_IMAGE", "erroronmissingjar");
      verifier.setAutoclean(false);
      verifier.addCliOption("--file=pom-jar-containerization.xml");
      verifier.executeGoals(Arrays.asList("clean", "jib:dockerBuild"));
      Assert.fail();

    } catch (VerificationException ex) {
      MatcherAssert.assertThat(
          ex.getMessage(),
          CoreMatchers.containsString(
              "Obtaining project build output files failed; make sure you have packaged your "
                  + "project before trying to build the image. (Did you accidentally run \"mvn "
                  + "clean jib:build\" instead of \"mvn clean package jib:build\"?)"));
    }
  }

  @Test
  public void testExecute_jibRequireVersion_ok() throws VerificationException, IOException {
    String targetImage = "simpleimage:maven" + System.nanoTime();

    Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());
    // this plugin should match 1.0
    verifier.setSystemProperty("jib.requiredVersion", "1.0");
    verifier.setSystemProperty("_TARGET_IMAGE", targetImage);
    verifier.executeGoals(Arrays.asList("package", "jib:dockerBuild"));
    verifier.verifyErrorFreeLog();
  }

  @Test
  public void testExecute_jibRequireVersion_fail() throws IOException {
    String targetImage = "simpleimage:maven" + System.nanoTime();

    try {
      Verifier verifier = new Verifier(simpleTestProject.getProjectRoot().toString());
      verifier.setSystemProperty("jib.requiredVersion", "[,1.0]");
      verifier.setSystemProperty("_TARGET_IMAGE", targetImage);
      verifier.executeGoals(Arrays.asList("package", "jib:dockerBuild"));
      Assert.fail();
    } catch (VerificationException ex) {
      MatcherAssert.assertThat(
          ex.getMessage(), CoreMatchers.containsString("but is required to be [,1.0]"));
    }
  }

  @Test
  public void testCredHelperConfigurationSimple()
      throws DigestException, VerificationException, IOException, InterruptedException {
    String targetImage = "simpleimage:maven" + System.nanoTime();
    buildToDockerDaemon(simpleTestProject, targetImage, "pom-cred-helper-1.xml");
    Assert.assertEquals(
        "Hello, world. \n1970-01-01T00:00:01Z\n",
        new Command("docker", "run", "--rm", targetImage).run());
  }

  @Test
  public void testCredHelperConfigurationComplex()
      throws DigestException, VerificationException, IOException, InterruptedException {
    String targetImage = "simpleimage:maven" + System.nanoTime();
    buildToDockerDaemon(simpleTestProject, targetImage, "pom-cred-helper-2.xml");
    Assert.assertEquals(
        "Hello, world. \n1970-01-01T00:00:01Z\n",
        new Command("docker", "run", "--rm", targetImage).run());
  }
}


Question: What exception is thrown when registry authentication fails?

Answer:
When registry authentication fails, a `RegistryAuthenticationFailedException` is thrown. This is evident from the class declaration:
```java
public class RegistryAuthenticationFailedException extends RegistryException {
  // ...
}
```

/*
 * Copyright 2017 Google LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.cloud.tools.jib.api;

import java.text.MessageFormat;

/** Thrown because registry authentication failed. */
public class RegistryAuthenticationFailedException extends RegistryException {

  private static final String REASON = "Failed to authenticate with registry {0}/{1} because: {2}";
  private final String serverUrl;
  private final String imageName;

  /**
   * Creates a new exception with a human readable message.
   *
   * @param serverUrl the registry server url
   * @param imageName the image name that requires authentication
   * @param cause the underlying cause that triggered this exception
   */
  public RegistryAuthenticationFailedException(
      String serverUrl, String imageName, Throwable cause) {
    super(MessageFormat.format(REASON, serverUrl, imageName, cause.getMessage()), cause);
    this.serverUrl = serverUrl;
    this.imageName = imageName;
  }

  /**
   * Creates a new exception with a human readable message.
   *
   * @param serverUrl the registry server url
   * @param imageName the image name that requires authentication
   * @param reason the underlying reason that triggered this exception
   */
  public RegistryAuthenticationFailedException(String serverUrl, String imageName, String reason) {
    super(MessageFormat.format(REASON, serverUrl, imageName, reason));
    this.serverUrl = serverUrl;
    this.imageName = imageName;
  }

  /**
   * The server being authenticated.
   *
   * @return the server being authenticated
   */
  public String getServerUrl() {
    return serverUrl;
  }

  /**
   * The image being authenticated.
   *
   * @return the image being authenticated
   */
  public String getImageName() {
    return imageName;
  }
}


Question: What information is included in the exception message when a `RegistryAuthenticationFailedException` is thrown?

Answer:
The exception message includes the registry server URL, the image name that requires authentication, and the reason for the authentication failure. This can be seen in the `REASON` constant and the constructor implementations:
```java
private static final String REASON = "Failed to authenticate with registry {0}/{1} because: {2}";

public RegistryAuthenticationFailedException(
    String serverUrl, String imageName, Throwable cause) {
  super(MessageFormat.format(REASON, serverUrl, imageName, cause.getMessage()), cause);
  // ...
}

public RegistryAuthenticationFailedException(String serverUrl, String imageName, String reason) {
  super(MessageFormat.format(REASON, serverUrl, imageName, reason));
  // ...
}
```


Question: What is the default length of a generated pepper when no length is specified?

Answer:
As per the testSaltNoLength() test method, when generating a pepper without specifying a length, the default length is expected to be 24. This is verified by the assertion:
<code>
Assert.assertEquals(24, pepper.length());
</code>

package com.password4j;

import org.junit.Assert;
import org.junit.Test;

public class PepperGeneratorTest
{

    @Test
    public void testSaltLength()
    {
        // GIVEN
        int length = 23;

        // WHEN
        String pepper = PepperGenerator.generate(length);

        // THEN
        Assert.assertNotNull(pepper);
        Assert.assertEquals(length, pepper.length());
    }

    @Test
    public void testSaltNoLength()
    {
        // GIVEN

        // WHEN
        String pepper = PepperGenerator.generate();

        // THEN
        Assert.assertNotNull(pepper);
        Assert.assertEquals(24, pepper.length());
    }

    @Test(expected = BadParametersException.class)
    public void testSaltNegativeLength()
    {
        // GIVEN

        // WHEN
        PepperGenerator.generate(-3);

        // THEN

    }

    @Test
    public void testSaltZeroLength()
    {
        // GIVEN
        int length = 0;

        // WHEN
        String pepper = PepperGenerator.generate(length);

        // THEN
        Assert.assertNotNull(pepper);
        Assert.assertEquals(length, pepper.length());
    }

    @Test
    public void testAlice()
    {
        // GIVEN

        // WHEN
        String pepper = PepperGenerator.get();

        // THEN
        Assert.assertEquals("AlicePepper", pepper);
    }
}


Question: Is it possible to generate a pepper with a length of zero?

Answer:
Yes, based on the testSaltZeroLength() test method, it is possible to generate a pepper with a length of zero. The test verifies that the generated pepper is not null and has a length of zero, as asserted by:
<code>
Assert.assertNotNull(pepper);
Assert.assertEquals(length, pepper.length());
</code>


Question: What is the expected value of the pepper returned by the PepperGenerator.get() method?

Answer:
The testAlice() test method indicates that when calling the PepperGenerator.get() method, it is expected to return the value "AlicePepper". This is verified by the assertion:
<code>
Assert.assertEquals("AlicePepper", pepper);
</code>


Question: What is the purpose of the slowEquals method that takes two CharSequence objects?

Answer: The slowEquals method that takes two CharSequence objects is used to compare them as byte arrays in length-constant time. As stated in the code comments, "This comparison method is used so that password hashes cannot be extracted from an on-line system using a timing attack and then attacked off-line." It converts the CharSequences to byte arrays using Utils.fromCharSequenceToBytes(a) and Utils.fromCharSequenceToBytes(b), and then calls the slowEquals method that compares byte arrays.

/*
 *  (C) Copyright 2020 Password4j (http://password4j.com/).
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package com.password4j;

/**
 * Class in the hierarchy to avoid code duplication.
 *
 * @author David Bertoldi
 * @since 0.1.0
 */
public abstract class AbstractHashingFunction implements HashingFunction
{

    /**
     * Compares two {@link CharSequence}s as byte arrays in length-constant time. This comparison method
     * is used so that password hashes cannot be extracted from an on-line
     * system using a timing attack and then attacked off-line.
     *
     * @param a the first CharSequence
     * @param b the second CharSequence
     * @return true if both {@link CharSequence}s are the same, false if not
     */
    protected static boolean slowEquals(CharSequence a, CharSequence b)
    {
        return slowEquals(Utils.fromCharSequenceToBytes(a), Utils.fromCharSequenceToBytes(b));
    }

    /**
     * Compares two byte arrays in length-constant time. This comparison method
     * is used so that password hashes cannot be extracted from an on-line
     * system using a timing attack and then attacked off-line.
     *
     * @param a the first byte array
     * @param b the second byte array
     * @return true if both byte arrays are the same, false if not
     */
    protected static boolean slowEquals(byte[] a, byte[] b)
    {
        int diff = a.length ^ b.length;
        for (int i = 0; i < a.length && i < b.length; i++)
        {
            diff |= a[i] ^ b[i];
        }
        return diff == 0;
    }

    @Override
    public Hash hash(CharSequence plainTextPassword, String salt, CharSequence pepper)
    {
        CharSequence peppered = Utils.append(pepper, plainTextPassword);
        Hash result;
        if (salt == null)
        {
            result = hash(peppered);
        }
        else
        {
            result = hash(peppered, salt);
        }

        result.setPepper(pepper);
        return result;
    }

    @Override
    public Hash hash(byte[] plainTextPassword, byte[] salt, CharSequence pepper)
    {
        byte[] pepperAsBytes = Utils.fromCharSequenceToBytes(pepper);
        byte[] peppered = Utils.append(pepperAsBytes, plainTextPassword);
        Hash result;
        if (salt == null)
        {
            result = hash(peppered);
        }
        else
        {
            result = hash(peppered, salt);
        }

        result.setPepper(pepper);
        return result;
    }

    /**
     * Just calls {@link #check(CharSequence, String)} without salt
     * parameter.
     *

     * Do not override this if the algorithm doesn't need a manually
     * provided salt.
     *
     * @param plainTextPassword the plaintext password
     * @param hashed            the hash
     * @param salt              the salt used to produce the hash
     * @return true if the hash is generated from the plaintext; false otherwise
     * @since 0.1.0
     */
    @Override
    public boolean check(CharSequence plainTextPassword, String hashed, String salt)
    {
        return check(plainTextPassword, hashed);
    }

    /**
     * Just calls {@link #check(CharSequence, String)} without salt
     * parameter.
     *

     * Do not override this if the algorithm doesn't need a manually
     * provided salt.
     *
     * @param plainTextPassword the plaintext password as bytes array
     * @param hashed            the hash as bytes array
     * @param salt              the salt as bytes array used to produce the hash
     * @return true if the hash is generated from the plaintext; false otherwise
     * @since 1.7.0
     */
    @Override
    public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt)
    {
        return check(plainTextPassword, hashed);
    }

    /**
     * Just calls {@link #check(CharSequence, String, String)}, with a prepended pepper.
     *
     * @param plainTextPassword the plaintext password
     * @param hashed            the hash
     * @param salt              the salt used to produce the hash
     * @return true if the hash is generated from the plaintext; false otherwise
     * @since 1.5.0
     */
    @Override
    public boolean check(CharSequence plainTextPassword, String hashed, String salt, CharSequence pepper)
    {
        return check(Utils.append(pepper, plainTextPassword), hashed, salt);
    }

    /**
     * Just calls {@link #check(CharSequence, String, String)}, with a prepended pepper.
     *
     * @param plainTextPassword the plaintext password
     * @param hashed            the hash
     * @param salt              the salt used to produce the hash
     * @return true if the hash is generated from the plaintext; false otherwise
     * @since 1.7.0
     */
    @Override
    public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt, CharSequence pepper)
    {
        byte[] pepperAsBytes = Utils.fromCharSequenceToBytes(pepper);
        return check(Utils.append(pepperAsBytes, plainTextPassword), hashed, salt);
    }

}

    /**
     * Just calls {@link #check(CharSequence, String)} without salt
     * parameter.
     * <p>
     * Do not override this if the algorithm doesn't need a manually
     * provided salt.
     *
     * @param plainTextPassword the plaintext password
     * @param hashed            the hash
     * @param salt              the salt used to produce the hash
     * @return true if the hash is generated from the plaintext; false otherwise
     * @since 0.1.0
     */
    @Override
    public boolean check(CharSequence plainTextPassword, String hashed, String salt)
    {
        return check(plainTextPassword, hashed);
    }

    /**
     * Just calls {@link #check(CharSequence, String)} without salt
     * parameter.
     * <p>
     * Do not override this if the algorithm doesn't need a manually
     * provided salt.
     *
     * @param plainTextPassword the plaintext password as bytes array
     * @param hashed            the hash as bytes array
     * @param salt              the salt as bytes array used to produce the hash
     * @return true if the hash is generated from the plaintext; false otherwise
     * @since 1.7.0
     */
    @Override
    public boolean check(byte[] plainTextPassword, byte[] hashed, byte[] salt)
    {
        return check(plainTextPassword, hashed);
    }


Question: How does the hash method handle the presence or absence of a salt value?

Answer: The hash method checks if the salt parameter is null. If it is null, it calls the hash(peppered) method, passing only the peppered password (plaintext password appended with pepper). If salt is not null, it calls the hash(peppered, salt) method, passing both the peppered password and the salt. This allows the hash method to work with or without a salt value.


Question: What are the input parameters for the BalloonHashingFunction constructor?

Answer:
The BalloonHashingFunction constructor takes the following parameters:
- hashAlgorithm: A String representing the hash algorithm to use (e.g., "SHA-256").
- space: An integer representing the space cost.
- time: An integer representing the time cost.
- parallelism: An integer representing the parallelism factor.
- delta: An integer representing the delta value.
This can be seen in the constructor definition: `public BalloonHashingFunction(String hashAlgorithm, int space, int time, int parallelism, int delta)`.

/*
 *  (C) Copyright 2023 Password4j (http://password4j.com/).
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */

package com.password4j;

import com.password4j.types.Argon2;
import com.password4j.types.Bcrypt;
import org.junit.Assert;
import org.junit.Test;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class BalloonHashingFunctionTest
{

    private static final Object[][] TEST_VECTORS = new Object[][]{
            // Single thread
            new Object[]{"hunter42", "examplesalt", "SHA-256", 1024, 3, 0, 3, "716043dff777b44aa7b88dcbab12c078abecfac9d289c5b5195967aa63440dfb"},
            new Object[]{"", "salt", "SHA-256", 3, 3, 0, 3, "5f02f8206f9cd212485c6bdf85527b698956701ad0852106f94b94ee94577378"},
            new Object[]{"password", "", "SHA-256", 3, 3, 0, 3, "20aa99d7fe3f4df4bd98c655c5480ec98b143107a331fd491deda885c4d6a6cc"},
            new Object[]{"\0", "\0", "SHA-256", 3, 3, 0, 3, "4fc7e302ffa29ae0eac31166cee7a552d1d71135f4e0da66486fb68a749b73a4"},
            new Object[]{"password", "salt", "SHA-256", 1, 1, 0, 3, "eefda4a8a75b461fa389c1dcfaf3e9dfacbc26f81f22e6f280d15cc18c417545"},

            // Multiple threads
            new Object[]{"hunter42", "examplesalt", "SHA-256", 1024, 3, 4, 3, "1832bd8e5cbeba1cb174a13838095e7e66508e9bf04c40178990adbc8ba9eb6f"},
            new Object[]{"", "salt", "SHA-256", 3, 3, 2, 3, "f8767fe04059cef67b4427cda99bf8bcdd983959dbd399a5e63ea04523716c23"},
            new Object[]{"password", "", "SHA-256", 3, 3, 3, 3, "bcad257eff3d1090b50276514857e60db5d0ec484129013ef3c88f7d36e438d6"},
            new Object[]{"password", "", "SHA-256", 3, 3, 1, 3, "498344ee9d31baf82cc93ebb3874fe0b76e164302c1cefa1b63a90a69afb9b4d"},
            new Object[]{"\000", "\000", "SHA-256", 3, 3, 4, 3, "8a665611e40710ba1fd78c181549c750f17c12e423c11930ce997f04c7153e0c"},
            new Object[]{"\000", "\000", "SHA-256", 3, 3, 1, 3, "d9e33c683451b21fb3720afbd78bf12518c1d4401fa39f054b052a145c968bb1"},
            new Object[]{"password", "salt", "SHA-256", 1, 1, 16, 3, "a67b383bb88a282aef595d98697f90820adf64582a4b3627c76b7da3d8bae915"},
            new Object[]{"password", "salt", "SHA-256", 1, 1, 1, 3, "97a11df9382a788c781929831d409d3599e0b67ab452ef834718114efdcd1c6d"},

    };

    @Test
    public void test()
    {

        BalloonHashingFunction balloonHashingFunction;
        for (Object[] testVector : TEST_VECTORS)
        {
            balloonHashingFunction = new BalloonHashingFunction((String) testVector[2], (Integer) testVector[3], (Integer) testVector[4], (Integer) testVector[5], (Integer) testVector[6]);
            Assert.assertEquals(testVector[7], balloonHashingFunction.hash((String) testVector[0], (String) testVector[1]).getResult());

            Assert.assertTrue(balloonHashingFunction.check((String) testVector[0], (String) testVector[7], (String) testVector[1]));
        }

    }

    @Test
    public void testInstance()
    {

        BalloonHashingFunction balloonHashingFunction;
        for (Object[] testVector : TEST_VECTORS)
        {
            balloonHashingFunction = BalloonHashingFunction.getInstance((String) testVector[2], (Integer) testVector[3], (Integer) testVector[4], (Integer) testVector[5], (Integer) testVector[6]);
            Assert.assertEquals(testVector[7], balloonHashingFunction.hash((String) testVector[0], (String) testVector[1]).getResult());
            Assert.assertEquals(testVector[7], balloonHashingFunction.hash(((String) testVector[0]).getBytes(), ((String) testVector[1]).getBytes()).getResult());

            Assert.assertTrue(balloonHashingFunction.check((String) testVector[0], (String) testVector[7], (String) testVector[1]));
            Assert.assertTrue(balloonHashingFunction.check(((String) testVector[0]).getBytes(), ((String) testVector[7]).getBytes(), ((String) testVector[1]).getBytes()));
        }

    }

    @Test
    public void testEquality()
    {
        // GIVEN
        String m = "SHA-256";
        int i = 2;
        int p = 3;
        int l = 4;
        int v = 5;
        BalloonHashingFunction balloonHashingFunction = BalloonHashingFunction.getInstance(m, i, p, l, v);

        // THEN
        boolean eqNull = balloonHashingFunction.equals(null);
        boolean eqClass = balloonHashingFunction.equals(new BcryptFunction(Bcrypt.A, 10));
        boolean sameInst = balloonHashingFunction.equals(BalloonHashingFunction.getInstance(m, i, p, l, v));
        boolean sameInst2 = balloonHashingFunction.equals(new BalloonHashingFunction(m, i, p, l, v));
        String toString = balloonHashingFunction.toString();
        int hashCode = balloonHashingFunction.hashCode();
        boolean notSameInst1 = balloonHashingFunction.equals(new BalloonHashingFunction("SHA-512", i, p, l, v));
        boolean notSameInst2 = balloonHashingFunction.equals(new BalloonHashingFunction(m, i+1, p, l, v));
        boolean notSameInst3 = balloonHashingFunction.equals(new BalloonHashingFunction(m, i, p+1, l, v));
        boolean notSameInst4 = balloonHashingFunction.equals(new BalloonHashingFunction(m, i, p, l+1, v));
        boolean notSameInst6 = balloonHashingFunction.equals(new BalloonHashingFunction(m, i, p, l,  v+1));

        // END
        Assert.assertFalse(eqNull);
        Assert.assertFalse(eqClass);
        Assert.assertTrue(sameInst);
        Assert.assertTrue(sameInst2);
        Assert.assertNotEquals(toString, new BalloonHashingFunction(m, i+1, p, l, v).toString());
        Assert.assertNotEquals(hashCode, new BalloonHashingFunction(m, i, p, l, v+1).hashCode());
        Assert.assertFalse(notSameInst1);
        Assert.assertFalse(notSameInst2);
        Assert.assertFalse(notSameInst3);
        Assert.assertFalse(notSameInst4);
        Assert.assertFalse(notSameInst6);
    }

}


Question: How can I obtain an instance of the BalloonHashingFunction using the factory method?

Answer:
You can obtain an instance of the BalloonHashingFunction using the `getInstance` factory method. It takes the same parameters as the constructor:
`BalloonHashingFunction.getInstance(String hashAlgorithm, int space, int time, int parallelism, int delta)`
This is demonstrated in the `testInstance` method of the provided test class.


Question: What are the test vectors used in the `TEST_VECTORS` array?

Answer:
The `TEST_VECTORS` array contains a set of test vectors for the BalloonHashingFunction. Each test vector is an array of objects with the following elements:
- password: A String representing the password to hash.
- salt: A String representing the salt value.
- hashAlgorithm: A String representing the hash algorithm to use (e.g., "SHA-256").
- space: An integer representing the space cost.
- time: An integer representing the time cost.
- parallelism: An integer representing the parallelism factor.
- delta: An integer representing the delta value.
- expectedHash: A String representing the expected hash value for the given input parameters.
These test vectors are used in the `test` and `testInstance` methods to verify the correctness of the BalloonHashingFunction implementation.


Question: What is the maximum digest size supported by this Blake2b implementation?

Answer:
The maximum digest size supported is 64 bytes. This can be seen in the constructor:
<code>
if (digestSize < 1 || digestSize > 64)
{
    throw new BadParametersException("BLAKE2b digest bytes length must be not greater than 64");
}
</code>

/*
 *  (C) Copyright 2020 Password4j (http://password4j.com/).
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package com.password4j;

import java.util.Arrays;

class Blake2b
{
    private static final long[] IV = { 0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL, 0xa54ff53a5f1d36f1L,
            0x510e527fade682d1L, 0x9b05688c2b3e6c1fL, 0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L };

    private static final byte[][] SIGMA = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
            { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
            { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
            { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
            { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
            { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
            { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } };

    private static final int ROUNDS = 12;

    private static final int BLOCK_LENGTH_BYTES = 128;

    private final int digestLength;

    private final int keyLength;

    private final byte[] buffer;

    private final long[] internalState = new long[16];

    private int bufferPos = 0;

    private long[] chainValue = null;

    private long t0 = 0L;

    private long t1 = 0L;

    private long f0 = 0L;

    /**
     * Basic sized constructor - size in bytes.
     *
     * @param digestSize size of the digest in bytes
     */
    Blake2b(int digestSize)
    {
        if (digestSize < 1 || digestSize > 64)
        {
            throw new BadParametersException("BLAKE2b digest bytes length must be not greater than 64");
        }

        buffer = new byte[BLOCK_LENGTH_BYTES];
        keyLength = 0;
        this.digestLength = digestSize;
        init();
    }

    // initialize chainValue
    private void init()
    {
        chainValue = new long[8];
        chainValue[0] = IV[0] ^ (digestLength | ((long) keyLength << 8) | 0x1010000);
        chainValue[1] = IV[1];
        chainValue[2] = IV[2];
        chainValue[3] = IV[3];
        chainValue[4] = IV[4];
        chainValue[5] = IV[5];
        chainValue[6] = IV[6];
        chainValue[7] = IV[7];
    }

    private void initializeInternalState()
    {
        System.arraycopy(chainValue, 0, internalState, 0, chainValue.length);
        System.arraycopy(IV, 0, internalState, chainValue.length, 4);
        internalState[12] = t0 ^ IV[4];
        internalState[13] = t1 ^ IV[5];
        internalState[14] = f0 ^ IV[6];
        internalState[15] = IV[7];// ^ f1 with f1 = 0
    }

    void update(byte[] message)
    {
        if (message == null)
        {
            return;
        }
        update(message, 0, message.length);
    }

    /**
     * update the message digest with a block of bytes.
     *
     * @param message the byte array containing the data.
     * @param offset  the offset into the byte array where the data starts.
     * @param len     the length of the data.
     */
    void update(byte[] message, int offset, int len)
    {
        int remainingLength = 0;

        if (bufferPos != 0)
        {
            remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
            if (remainingLength < len)
            {
                System.arraycopy(message, offset, buffer, bufferPos, remainingLength);
                t0 += BLOCK_LENGTH_BYTES;
                if (t0 == 0)
                {
                    t1++;
                }
                compress(buffer, 0);
                bufferPos = 0;
                Arrays.fill(buffer, (byte) 0);// clear buffer
            }
            else
            {
                System.arraycopy(message, offset, buffer, bufferPos, len);
                bufferPos += len;
                return;
            }
        }

        int messagePos;
        int blockWiseLastPos = offset + len - BLOCK_LENGTH_BYTES;
        for (messagePos = offset + remainingLength; messagePos < blockWiseLastPos; messagePos += BLOCK_LENGTH_BYTES)
        {
            t0 += BLOCK_LENGTH_BYTES;
            if (t0 == 0)
            {
                t1++;
            }
            compress(message, messagePos);
        }

        // fill the buffer with left bytes, this might be a full block
        System.arraycopy(message, messagePos, buffer, 0, offset + len - messagePos);
        bufferPos += offset + len - messagePos;
    }

    /**
     * close the digest, producing the final digest value. The doFinal
     * call leaves the digest reset.
     * Key, salt and personal string remain.
     *
     * @param out       the array the digest is to be copied into.
     * @param outOffset the offset into the out array the digest is to start at.
     */
    void doFinal(byte[] out, int outOffset)
    {

        f0 = 0xFFFFFFFFFFFFFFFFL;
        t0 += bufferPos;
        if (bufferPos > 0 && t0 == 0)
        {
            t1++;
        }
        compress(buffer, 0);
        Arrays.fill(buffer, (byte) 0);// Holds eventually the key if input is null
        Arrays.fill(internalState, 0L);

        for (int i = 0; i < chainValue.length && (i * 8 < digestLength); i++)
        {
            byte[] bytes = Utils.longToLittleEndian(chainValue[i]);

            if (i * 8 < digestLength - 8)
            {
                System.arraycopy(bytes, 0, out, outOffset + i * 8, 8);
            }
            else
            {
                System.arraycopy(bytes, 0, out, outOffset + i * 8, digestLength - (i * 8));
            }
        }

        Arrays.fill(chainValue, 0L);

        reset();
    }

    /**
     * Reset the digest back to it's initial state.
     * The key, the salt and the personal string will
     * remain for further computations.
     */
    void reset()
    {
        bufferPos = 0;
        f0 = 0L;
        t0 = 0L;
        t1 = 0L;
        chainValue = null;
        Arrays.fill(buffer, (byte) 0);
        init();
    }

    private void compress(byte[] message, int messagePos)
    {

        initializeInternalState();

        long[] m = new long[16];
        for (int j = 0; j < 16; j++)
        {
            m[j] = Utils.littleEndianToLong(message, messagePos + j * 8);
        }

        for (int round = 0; round < ROUNDS; round++)
        {

            // G apply to columns of internalState:m[blake2b_sigma[round][2 *
            // blockPos]] /+1
            functionG(m[SIGMA[round][0]], m[SIGMA[round][1]], 0, 4, 8, 12);
            functionG(m[SIGMA[round][2]], m[SIGMA[round][3]], 1, 5, 9, 13);
            functionG(m[SIGMA[round][4]], m[SIGMA[round][5]], 2, 6, 10, 14);
            functionG(m[SIGMA[round][6]], m[SIGMA[round][7]], 3, 7, 11, 15);
            // G apply to diagonals of internalState:
            functionG(m[SIGMA[round][8]], m[SIGMA[round][9]], 0, 5, 10, 15);
            functionG(m[SIGMA[round][10]], m[SIGMA[round][11]], 1, 6, 11, 12);
            functionG(m[SIGMA[round][12]], m[SIGMA[round][13]], 2, 7, 8, 13);
            functionG(m[SIGMA[round][14]], m[SIGMA[round][15]], 3, 4, 9, 14);
        }

        // update chain values:
        for (int offset = 0; offset < chainValue.length; offset++)
        {
            chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
        }
    }

    private void functionG(long m1, long m2, int posA, int posB, int posC, int posD)
    {

        internalState[posA] = internalState[posA] + internalState[posB] + m1;
        internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 32);
        internalState[posC] = internalState[posC] + internalState[posD];
        internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 24); // replaces 25 of BLAKE
        internalState[posA] = internalState[posA] + internalState[posB] + m2;
        internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 16);
        internalState[posC] = internalState[posC] + internalState[posD];
        internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 63); // replaces 11 of BLAKE
    }
}


Question: How do you reset the hasher to its initial state?

Answer:
To reset the hasher to its initial state, call the `reset` method on the Blake2b instance:
<code>
blake2b.reset();
</code>
This will clear the internal buffers and state, preparing the hasher for a new hashing operation. Any previously configured parameters such as the key, salt, and personal string will remain intact for further computations.

/*
 *  (C) Copyright 2020 Password4j (http://password4j.com/).
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package com.password4j;

import java.util.Arrays;

class Blake2b
{
    private static final long[] IV = { 0x6a09e667f3bcc908L, 0xbb67ae8584caa73bL, 0x3c6ef372fe94f82bL, 0xa54ff53a5f1d36f1L,
            0x510e527fade682d1L, 0x9b05688c2b3e6c1fL, 0x1f83d9abfb41bd6bL, 0x5be0cd19137e2179L };

    private static final byte[][] SIGMA = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
            { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 }, { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 },
            { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 }, { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 },
            { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 }, { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 },
            { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 }, { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 },
            { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 }, { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
            { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } };

    private static final int ROUNDS = 12;

    private static final int BLOCK_LENGTH_BYTES = 128;

    private final int digestLength;

    private final int keyLength;

    private final byte[] buffer;

    private final long[] internalState = new long[16];

    private int bufferPos = 0;

    private long[] chainValue = null;

    private long t0 = 0L;

    private long t1 = 0L;

    private long f0 = 0L;

    /**
     * Basic sized constructor - size in bytes.
     *
     * @param digestSize size of the digest in bytes
     */
    Blake2b(int digestSize)
    {
        if (digestSize < 1 || digestSize > 64)
        {
            throw new BadParametersException("BLAKE2b digest bytes length must be not greater than 64");
        }

        buffer = new byte[BLOCK_LENGTH_BYTES];
        keyLength = 0;
        this.digestLength = digestSize;
        init();
    }

    // initialize chainValue
    private void init()
    {
        chainValue = new long[8];
        chainValue[0] = IV[0] ^ (digestLength | ((long) keyLength << 8) | 0x1010000);
        chainValue[1] = IV[1];
        chainValue[2] = IV[2];
        chainValue[3] = IV[3];
        chainValue[4] = IV[4];
        chainValue[5] = IV[5];
        chainValue[6] = IV[6];
        chainValue[7] = IV[7];
    }

    private void initializeInternalState()
    {
        System.arraycopy(chainValue, 0, internalState, 0, chainValue.length);
        System.arraycopy(IV, 0, internalState, chainValue.length, 4);
        internalState[12] = t0 ^ IV[4];
        internalState[13] = t1 ^ IV[5];
        internalState[14] = f0 ^ IV[6];
        internalState[15] = IV[7];// ^ f1 with f1 = 0
    }

    void update(byte[] message)
    {
        if (message == null)
        {
            return;
        }
        update(message, 0, message.length);
    }

    /**
     * update the message digest with a block of bytes.
     *
     * @param message the byte array containing the data.
     * @param offset  the offset into the byte array where the data starts.
     * @param len     the length of the data.
     */
    void update(byte[] message, int offset, int len)
    {
        int remainingLength = 0;

        if (bufferPos != 0)
        {
            remainingLength = BLOCK_LENGTH_BYTES - bufferPos;
            if (remainingLength < len)
            {
                System.arraycopy(message, offset, buffer, bufferPos, remainingLength);
                t0 += BLOCK_LENGTH_BYTES;
                if (t0 == 0)
                {
                    t1++;
                }
                compress(buffer, 0);
                bufferPos = 0;
                Arrays.fill(buffer, (byte) 0);// clear buffer
            }
            else
            {
                System.arraycopy(message, offset, buffer, bufferPos, len);
                bufferPos += len;
                return;
            }
        }

        int messagePos;
        int blockWiseLastPos = offset + len - BLOCK_LENGTH_BYTES;
        for (messagePos = offset + remainingLength; messagePos < blockWiseLastPos; messagePos += BLOCK_LENGTH_BYTES)
        {
            t0 += BLOCK_LENGTH_BYTES;
            if (t0 == 0)
            {
                t1++;
            }
            compress(message, messagePos);
        }

        // fill the buffer with left bytes, this might be a full block
        System.arraycopy(message, messagePos, buffer, 0, offset + len - messagePos);
        bufferPos += offset + len - messagePos;
    }

    /**
     * close the digest, producing the final digest value. The doFinal
     * call leaves the digest reset.
     * Key, salt and personal string remain.
     *
     * @param out       the array the digest is to be copied into.
     * @param outOffset the offset into the out array the digest is to start at.
     */
    void doFinal(byte[] out, int outOffset)
    {

        f0 = 0xFFFFFFFFFFFFFFFFL;
        t0 += bufferPos;
        if (bufferPos > 0 && t0 == 0)
        {
            t1++;
        }
        compress(buffer, 0);
        Arrays.fill(buffer, (byte) 0);// Holds eventually the key if input is null
        Arrays.fill(internalState, 0L);

        for (int i = 0; i < chainValue.length && (i * 8 < digestLength); i++)
        {
            byte[] bytes = Utils.longToLittleEndian(chainValue[i]);

            if (i * 8 < digestLength - 8)
            {
                System.arraycopy(bytes, 0, out, outOffset + i * 8, 8);
            }
            else
            {
                System.arraycopy(bytes, 0, out, outOffset + i * 8, digestLength - (i * 8));
            }
        }

        Arrays.fill(chainValue, 0L);

        reset();
    }

    /**
     * Reset the digest back to it's initial state.
     * The key, the salt and the personal string will
     * remain for further computations.
     */
    void reset()
    {
        bufferPos = 0;
        f0 = 0L;
        t0 = 0L;
        t1 = 0L;
        chainValue = null;
        Arrays.fill(buffer, (byte) 0);
        init();
    }

    private void compress(byte[] message, int messagePos)
    {

        initializeInternalState();

        long[] m = new long[16];
        for (int j = 0; j < 16; j++)
        {
            m[j] = Utils.littleEndianToLong(message, messagePos + j * 8);
        }

        for (int round = 0; round < ROUNDS; round++)
        {

            // G apply to columns of internalState:m[blake2b_sigma[round][2 *
            // blockPos]] /+1
            functionG(m[SIGMA[round][0]], m[SIGMA[round][1]], 0, 4, 8, 12);
            functionG(m[SIGMA[round][2]], m[SIGMA[round][3]], 1, 5, 9, 13);
            functionG(m[SIGMA[round][4]], m[SIGMA[round][5]], 2, 6, 10, 14);
            functionG(m[SIGMA[round][6]], m[SIGMA[round][7]], 3, 7, 11, 15);
            // G apply to diagonals of internalState:
            functionG(m[SIGMA[round][8]], m[SIGMA[round][9]], 0, 5, 10, 15);
            functionG(m[SIGMA[round][10]], m[SIGMA[round][11]], 1, 6, 11, 12);
            functionG(m[SIGMA[round][12]], m[SIGMA[round][13]], 2, 7, 8, 13);
            functionG(m[SIGMA[round][14]], m[SIGMA[round][15]], 3, 4, 9, 14);
        }

        // update chain values:
        for (int offset = 0; offset < chainValue.length; offset++)
        {
            chainValue[offset] = chainValue[offset] ^ internalState[offset] ^ internalState[offset + 8];
        }
    }

    private void functionG(long m1, long m2, int posA, int posB, int posC, int posD)
    {

        internalState[posA] = internalState[posA] + internalState[posB] + m1;
        internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 32);
        internalState[posC] = internalState[posC] + internalState[posD];
        internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 24); // replaces 25 of BLAKE
        internalState[posA] = internalState[posA] + internalState[posB] + m2;
        internalState[posD] = Long.rotateRight(internalState[posD] ^ internalState[posA], 16);
        internalState[posC] = internalState[posC] + internalState[posD];
        internalState[posB] = Long.rotateRight(internalState[posB] ^ internalState[posC], 63); // replaces 11 of BLAKE
    }
}


Question: What class does BadParametersException extend?

Answer: BadParametersException extends the IllegalArgumentException class, as seen in the class declaration: "public class BadParametersException extends IllegalArgumentException".

/*
 *  (C) Copyright 2020 Password4j (http://password4j.com/).
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package com.password4j;

/**
 * This exception is normally thrown when a not well formed parameter
 * is passed as argument to a function.
 * <p>
 * This exception covers all the exceptions raised by underlying logic,
 * grouping them as one exception.
 *
 * @author David Bertoldi
 * @since 0.1.0
 */
public class BadParametersException extends IllegalArgumentException
{

    private static final long serialVersionUID = 9204720180786210237L;

    /**
     * Constructs the exception.
     *
     * @param message the message describing the cause of the exception
     * @since 0.1.0
     */
    public BadParametersException(String message)
    {
        super(message);
    }

    /**
     * Constructs the exception.
     *
     * @param message   the message describing the cause of the exception
     * @param exception the exception masked by this object
     * @since 0.1.0
     */
    public BadParametersException(String message, Throwable exception)
    {
        super(message, exception);
    }
}


Question: What package does the BadParametersException class belong to?

Answer: The BadParametersException class belongs to the "com.password4j" package, as specified in the package declaration at the top of the file: "package com.password4j;".


Question: Who is the author of the BadParametersException class?

Answer: According to the Javadoc comment, the author of the BadParametersException class is David Bertoldi, as stated in the "@author David Bertoldi" tag.


Question: Since which version has the BadParametersException class been available?

Answer: The BadParametersException class has been available since version 0.1.0, as indicated by the "@since 0.1.0" tags in the Javadoc comments for the class and its constructors.