Package com.google.k2crypto.storage.driver.impl

Source Code of com.google.k2crypto.storage.driver.impl.K2FileSystemDriverTest

/*
* Copyright 2014 Google. Inc.
*
* 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.k2crypto.storage.driver.impl;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static com.google.k2crypto.storage.driver.impl.K2FileSystemDriver.FILE_EXTENSION;
import static com.google.k2crypto.storage.driver.impl.K2FileSystemDriver.MAX_FILENAME_LENGTH;
import static com.google.k2crypto.storage.driver.impl.K2FileSystemDriver.NATIVE_SCHEME;
import static com.google.k2crypto.storage.driver.impl.K2FileSystemDriver.TEMP_A_EXTENSION;
import static com.google.k2crypto.storage.driver.impl.K2FileSystemDriver.TEMP_B_EXTENSION;
import static com.google.k2crypto.storage.driver.impl.K2FileSystemDriver.TEMP_PREFIX;

import com.google.k2crypto.K2Exception;
import com.google.k2crypto.storage.IllegalAddressException;
import com.google.k2crypto.storage.StoreIOException;
import com.google.k2crypto.storage.driver.Driver;
import com.google.k2crypto.storage.driver.FileBasedDriverTest;

import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.net.URI;

/**
* Unit tests for the K2 native file-system driver.
*
* @author darylseah@gmail.com (Daryl Seah)
*/
public class K2FileSystemDriverTest
    extends FileBasedDriverTest<K2FileSystemDriver> {

  // Limit to prevent tests from stalling completely if something goes wrong
  // during random generation of test files
  private static final int MAX_TRIPLE_GENERATION_ATTEMPTS = 10;

  // File scheme prefix to add to addresses
  private static final String FILE_PREFIX = "file:";
 
  // Native scheme prefix to add to addresses
  private static final String NATIVE_PREFIX = NATIVE_SCHEME + ':';
 
  // Native file extension to add to addresses
  private static final String NATIVE_POSTFIX = '.' + FILE_EXTENSION;
 
  /**
   * Constructs the driver test class.
   */
  public K2FileSystemDriverTest() {
    super(K2FileSystemDriver.class);
  }
 
  /**
   * Tests that the open() method rejects all syntactically invalid
   * URI addresses.
   */
  @Test public final void testRejectBadAddresses() {
    final String testingDirPath = getTestingDirPath();
   
    // Test unsupported components
    checkRejectAddress(
        FILE_PREFIX + "//host/path",
        IllegalAddressException.Reason.AUTHORITY_UNSUPPORTED);
    checkRejectAddress(
        FILE_PREFIX + "//user@localhost:80/path",
        IllegalAddressException.Reason.AUTHORITY_UNSUPPORTED);
    checkRejectAddress(
        FILE_PREFIX + "/path?que",
        IllegalAddressException.Reason.QUERY_UNSUPPORTED);
    checkRejectAddress(
        FILE_PREFIX + "/path#frag",
        IllegalAddressException.Reason.FRAGMENT_UNSUPPORTED);
   
    // Test invalid schemes
    checkRejectAddress(
        "k3:/path",
        IllegalAddressException.Reason.INVALID_SCHEME);
    checkRejectAddress(
        "keyczar:/path",
        IllegalAddressException.Reason.INVALID_SCHEME);
   
    // Test generic schemes without the proper lowercase ".k2k" extension
    checkRejectAddress(
        testingDirPath + "myfile",
        IllegalAddressException.Reason.INVALID_PATH);
    checkRejectAddress(
        testingDirPath + "myfile" + NATIVE_POSTFIX.toUpperCase(),
        IllegalAddressException.Reason.INVALID_PATH);
    checkRejectAddress(
        FILE_PREFIX + testingDirPath + "myfile",
        IllegalAddressException.Reason.INVALID_PATH);
    checkRejectAddress(
        FILE_PREFIX + testingDirPath + "myfile" + NATIVE_POSTFIX.toUpperCase(),
        IllegalAddressException.Reason.INVALID_PATH);
    checkRejectAddress(
        FILE_PREFIX + testingDirPath + "myfile%2e%6B%32%4B",
        IllegalAddressException.Reason.INVALID_PATH);
   
    // Test no path
    checkRejectAddress(
        NATIVE_PREFIX + "?",
        IllegalAddressException.Reason.MISSING_PATH);
   
    // Test relative path going beyond the logical URI root
    checkRejectAddress(
        "/../my%20key",
        IllegalAddressException.Reason.INVALID_PATH);
   
    final String testingAddress = NATIVE_PREFIX + testingDirPath;
   
    // Test all illegal filename characters
    for (char illegal : new char[] {
        '\0', '\n', '\r', '\t', '\f', '\b', '\u007F',
        '\\', '/', '*', '?', '|', '<', '>', ':', ';', '"'
    }) {
      String encoded = String.format("%%%02X", (int)illegal);
      assertEquals(3, encoded.length()); // sanity check
      checkRejectAddress(
          testingAddress + 'A' + encoded + 'Z',
          IllegalAddressException.Reason.INVALID_PATH);
      checkRejectAddress(
          testingAddress + encoded,
          IllegalAddressException.Reason.INVALID_PATH);
    }
   
    // Test illegal filename prefixes
    for (String illegalPrefix : new String[] { "~", ".", "%20" }) {
      checkRejectAddress(
          testingAddress + illegalPrefix + "abc",
          IllegalAddressException.Reason.INVALID_PATH);
    }

    // Test illegal filename postfixes
    for (String illegalPostfix : new String[] { ".", "%20" }) {
      checkRejectAddress(
          testingAddress + "abc" + illegalPostfix,
          IllegalAddressException.Reason.INVALID_PATH);
    }
  }
 
  /**
   * Tests that the open() method accepts a filename at maximum length and
   * rejects when it is any longer.
   */
  @Test public final void testFilenameLength() throws K2Exception {
    final String testingAddress = NATIVE_PREFIX + getTestingDirPath();

    // Test filename that is one character too long
    String oneCharTooLongName = generateString(1 + MAX_FILENAME_LENGTH);
    checkRejectAddress(
        testingAddress + oneCharTooLongName,
        IllegalAddressException.Reason.INVALID_PATH);
   
    // Test acceptance of filename at maximum length
    Driver driver = newDriver();
    try {
      driver.open(URI.create(
          testingAddress + generateString(MAX_FILENAME_LENGTH)));
    } finally {
      driver.close();
   
  }
 
  /**
   * Tests that the open() method rejects addresses pointing to a bad
   * file location (on disk).
   */
  @Test public final void testRejectBadFileLocation() throws IOException {
    // We can only run this test if there is a physical root available
    File[] roots = File.listRoots();
    if (roots != null) {
      // Should not be able to open the root path (without a filename)
      for (File root : roots) {
        checkRejectAddress(
            NATIVE_PREFIX + root.toURI().getRawPath(),
            IllegalAddressException.Reason.INVALID_PATH);
      }
    }
   
    // The parent "File" of the key file should be an existing directory
    // (and not a file)
    File parent = generateFile(getTestingDir(), "", ".tmp");
    parent.deleteOnExit();
    try {
      assertTrue(parent.createNewFile());
      checkRejectAddress(
          NATIVE_PREFIX + parent.toURI().getRawPath() + "/keys",
          IllegalAddressException.Reason.INVALID_PATH);
    } finally {
      parent.delete();
    }
   
    // Generate the main key file and two temp files for it that do not exist.
    File[] files = generateFileTriple(getTestingDir());
    deleteAllOnExit(files);
   
    // Check that the driver rejects opening the main file address
    // if any of these files is a directory.
    try {
      String mainAddress = NATIVE_PREFIX + files[0].toURI().getRawPath();
      for (File f : files) {
        assertTrue(f.mkdir());
        checkRejectAddress(
            mainAddress, IllegalAddressException.Reason.INVALID_PATH);
        assertTrue(f.delete());
      }
    } finally {
      deleteAll(files);
    }
  }
 
  /**
   * Tests that various addresses are normalized correctly.
   */
  @Test public final void testAddressNormalization() throws K2Exception {
    final String absTestingPath = getTestingDirPath();
    final String absTestingAddress = NATIVE_PREFIX + absTestingPath;
    final String relTestingPath = getRelativeTestingDirPath();
   
    final String filename = generateSafeFilename(getTestingDir());
    final String expected = absTestingAddress + filename;
   
    // Test absolute addresses (with k2: scheme), without and with extension
    checkNormalization(expected, absTestingAddress + filename);
    checkNormalization(expected, absTestingAddress + filename + NATIVE_POSTFIX);

    // Test with empty query and fragment
    checkNormalization(expected, absTestingAddress + filename + '?');
    checkNormalization(expected, absTestingAddress + filename + '#');

    // Test absolute addresses with collapsable paths, w/o and w/ extension
    checkNormalization(expected,
        absTestingAddress + "anything/./something/.././../" + filename);
    checkNormalization(expected,
        absTestingAddress + "/././" + filename + NATIVE_POSTFIX);
   
    // Test the above with absolute paths (i.e. no k2: scheme)
    checkNormalization(expected, absTestingPath + filename + NATIVE_POSTFIX);
    checkNormalization(expected,
        absTestingPath + "/./something/../" + filename + NATIVE_POSTFIX);
   
    // Test the above with relative paths
    checkNormalization(expected, relTestingPath + filename + NATIVE_POSTFIX);
    checkNormalization(expected,
        relTestingPath + "/anything/../" + filename + NATIVE_POSTFIX);
  }
 
  /**
   * Tests saving, loading and erasing keys.
   */
  @Test public final void testSaveLoadErase() throws K2Exception {
    File[] files = generateFileTriple(getTestingDir());
    URI address = files[0].toURI().normalize();
    deleteAllOnExit(files);
   
    K2FileSystemDriver driver = newDriver();
    try {
      driver.open(address);
      checkLoadSaveErase(driver);
    } finally {
      deleteAll(files);
      driver.close();
    }
  }
 
  /**
   * Tests recovering a key from any save slot.
   */
  @Test public final void testRecovery() throws K2Exception, IOException {
    File[] files = generateFileTriple(getTestingDir());
    URI address = files[0].toURI().normalize();
    deleteAllOnExit(files);
   
    K2FileSystemDriver driver = newDriver();
    try {
      // Open and save a key
      assertEquals(
          NATIVE_PREFIX + address.getSchemeSpecificPart(),
          driver.open(address) + NATIVE_POSTFIX);
      driver.save(EMPTY_KEY);
     
      // Verify that we can load when key data is in any slot
      File last = null;
      for (File current : files) {
        if (last != null) {
          // Move data to next slot
          assertTrue(last.renameTo(current));
          assertFalse(last.exists());
        }
       
        // Check that the slot is readable
        assertTrue(current.isFile());
        checkLoad(driver, EMPTY_KEY);

        // Check it is still readable with corrupted (empty)
        // files in some other slot
        for (File f : files) {
          if (f != current) {
            assertFalse(f.exists());           
            assertTrue(f.createNewFile());
            checkLoad(driver, EMPTY_KEY);
            assertTrue(f.delete());
          }
        }       
        last = current;
      }
     
    } finally {
      deleteAll(files);
      driver.close();
    }
  }
 
  /**
   * Tests precedence of recovery when there is more than one readable slot.
   */
  @Test public final void testRecoveryPrecedence()
      throws K2Exception, IOException {
    final File keyFile =
        generateFile(getTestingDir(), "key", NATIVE_POSTFIX);
    final File emptyFile =
        generateFile(getTestingDir(), "empty", NATIVE_POSTFIX);
    File[] files = generateFileTriple(getTestingDir());
    URI address = files[0].toURI().normalize();
   
    keyFile.deleteOnExit();
    emptyFile.deleteOnExit();
    deleteAllOnExit(files);

    K2FileSystemDriver driver = newDriver();
    try {
      // Open store and perform some initial setup
      assertEquals(
          NATIVE_PREFIX + address.getSchemeSpecificPart(),
          driver.open(address) + NATIVE_POSTFIX);
     
      // Save then put aside the two keys as test data
      driver.save(MOCK_KEY);
      assertTrue(files[0].renameTo(keyFile));
      assertTrue(driver.isEmpty());
      driver.save(EMPTY_KEY);
      assertTrue(files[0].renameTo(emptyFile));
      assertTrue(driver.isEmpty());

      // If both temp files are readable, the later one takes precedence
      copyData(keyFile, files[1]);
      files[1].setLastModified(Math.max(files[1].lastModified() - 5000, 0));
      copyData(emptyFile, files[2]);
      checkLoad(driver, EMPTY_KEY);
     
      // If both have the same timestamp, the larger one takes precedence
      files[1].setLastModified(files[2].lastModified());
      checkLoad(driver, MOCK_KEY);
     
      // If main file exists, it always takes precedence
      copyData(emptyFile, files[0]);
      checkLoad(driver, EMPTY_KEY);
     
      // If the main file is corrupted, we fallback to the temporary files
      files[0].delete();
      files[0].createNewFile();
      checkLoad(driver, MOCK_KEY);
     
    } finally {
      deleteAll(files);
      keyFile.delete();
      emptyFile.delete();
      driver.close();
    }
  }
 
  /**
   * Tests loading and erasing of corrupted files.
   */
  @Test public final void testLoadEraseCorrupted()
      throws K2Exception, IOException {
    File[] files = generateFileTriple(getTestingDir());
    URI address = files[0].toURI().normalize();
    deleteAllOnExit(files);
   
    K2FileSystemDriver driver = newDriver();
    try {
      // Open the driver
      assertEquals(
          NATIVE_PREFIX + address.getSchemeSpecificPart(),
          driver.open(address) + NATIVE_POSTFIX);

      // Check that loading fails with empty files in each slot
      for (File f : files) {
        assertFalse(f.exists());
        assertTrue(f.createNewFile());
        checkLoadFails(driver, StoreIOException.Reason.DESERIALIZATION_ERROR);
        assertTrue(driver.erase());
        assertTrue(driver.isEmpty());
      }

      // Check that loading fails when all slots have empty files
      for (File f : files) {
        assertFalse(f.exists());
        assertTrue(f.createNewFile());
      }
      checkLoadFails(driver, StoreIOException.Reason.DESERIALIZATION_ERROR);
     
      // Make sure that an erase wipes all the slots
      assertTrue(driver.erase());
      assertTrue(driver.isEmpty());
      for (File f : files) {
        assertFalse(f.exists());
      }     
     
    } finally {
      deleteAll(files);
      driver.close();
    }
  }

  /**
   * Generates a path to a K2 key location that does not currently exist.
   *
   * @param dir Directory that the key should be in.
   *
   * @return path to the K2 key file without the extension.
   */
  private String generateSafeFilename(File dir) {
    String name = generateFileTriple(dir)[0].getName();
    return name.substring(0, name.length() - NATIVE_POSTFIX.length());
  }
 
  /**
   * Generates a new K2 key location and returns the three (currently
   * non-existent) files that the driver will use for storing a key in it.
   * The first file in the returned array will be the main key file, while
   * the remaining two will be the temporary/backup files.
   *
   * @param dir Directory that the key should be in.
   *
   * @return a 3-element array with the main key file at index 0 and the
   *         temporary/backup files in the remaining slots.
   */
  private File[] generateFileTriple(File dir) {
    // Generate the main key file and two temp files for it that do not exist.
    File[] files = new File[3];
    int countdown = MAX_TRIPLE_GENERATION_ATTEMPTS;
    do {
      if (--countdown < 0) {
        fail("Could not generate file triple!");
      }
      // Main file
      File main = files[0] = generateFile(dir, "", NATIVE_POSTFIX);
      // Temp files
      files[1] = new File(dir, TEMP_PREFIX + main.getName() + TEMP_A_EXTENSION);
      files[2] = new File(dir, TEMP_PREFIX + main.getName() + TEMP_B_EXTENSION);     
    } while (files[1].exists() || files[2].exists());
    return files;
  }
}
TOP

Related Classes of com.google.k2crypto.storage.driver.impl.K2FileSystemDriverTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.