aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/cipherpack/cipherpack.hpp9
-rw-r--r--java/jni/cipherpack/Cipherpack.cxx34
-rw-r--r--java/org/cipherpack/Cipherpack.java19
-rw-r--r--src/cipherpack/crypto0.cpp73
-rw-r--r--test/cipherpack/test_01_cipherpack.cpp57
-rw-r--r--test/java/test/org/cipherpack/Test01Cipherpack.java138
6 files changed, 290 insertions, 40 deletions
diff --git a/include/cipherpack/cipherpack.hpp b/include/cipherpack/cipherpack.hpp
index 8630d07..682f351 100644
--- a/include/cipherpack/cipherpack.hpp
+++ b/include/cipherpack/cipherpack.hpp
@@ -629,6 +629,15 @@ namespace cipherpack {
* @return the calculated hash value or nullptr in case of error
*/
std::unique_ptr<std::vector<uint8_t>> calc(const std::string_view& algo, jau::io::ByteInStream& source) noexcept;
+
+ /**
+ * Return the calculated hash value using given algo name and all actual files (not symbolic links) within the given path.
+ * @param algo the hash algo name
+ * @param path source path, either a single file or directory for which all files (not symbolic links) are considered
+ * @param bytes_hashed returns overall bytes hashed
+ * @return the calculated hash value or nullptr in case of error
+ */
+ std::unique_ptr<std::vector<uint8_t>> calc(const std::string_view& algo, const std::string& path, uint64_t& bytes_hashed) noexcept;
}
/**@}*/
diff --git a/java/jni/cipherpack/Cipherpack.cxx b/java/jni/cipherpack/Cipherpack.cxx
index c9ee478..685f710 100644
--- a/java/jni/cipherpack/Cipherpack.cxx
+++ b/java/jni/cipherpack/Cipherpack.cxx
@@ -101,7 +101,7 @@ jobject Java_org_cipherpack_Cipherpack_checkSignThenDecrypt1(JNIEnv *env, jclass
return nullptr;
}
-jbyteArray Java_org_cipherpack_Cipherpack_00024HashUtil_calc(JNIEnv *env, jclass jclazz, jstring jalgo, jobject jsource_feed) {
+jbyteArray Java_org_cipherpack_Cipherpack_00024HashUtil_calcImpl1(JNIEnv *env, jclass jclazz, jstring jalgo, jobject jsource_feed) {
try {
jau::jni::shared_ptr_ref<jau::io::ByteInStream> refSource(env, jsource_feed); // hold until done
std::string algo = jau::jni::from_jstring_to_string(env, jalgo);
@@ -119,3 +119,35 @@ jbyteArray Java_org_cipherpack_Cipherpack_00024HashUtil_calc(JNIEnv *env, jclass
}
return nullptr;
}
+
+jbyteArray Java_org_cipherpack_Cipherpack_00024HashUtil_calcImpl2(JNIEnv *env, jclass jclazz, jstring jalgo, jstring jpath, jlongArray jbytes_hashed) {
+ try {
+ std::string algo = jau::jni::from_jstring_to_string(env, jalgo);
+ std::string path = jau::jni::from_jstring_to_string(env, jpath);
+
+ if( nullptr == jbytes_hashed ) {
+ throw jau::IllegalArgumentException("bytes_hashed null", E_FILE_LINE);
+ }
+ const size_t bh_size = env->GetArrayLength(jbytes_hashed);
+ if( 1 > bh_size ) {
+ throw jau::IllegalArgumentException("bytes_hashed size "+std::to_string(bh_size)+" < 1", E_FILE_LINE);
+ }
+ jau::jni::JNICriticalArray<uint64_t, jlongArray> criticalArray(env); // RAII - release
+ uint64_t * bh_ptr = criticalArray.get(jbytes_hashed, criticalArray.Mode::UPDATE_AND_RELEASE);
+ if( NULL == bh_ptr ) {
+ throw jau::InternalError("GetPrimitiveArrayCritical(address byte array) is null", E_FILE_LINE);
+ }
+
+ std::unique_ptr<std::vector<uint8_t>> hash = cipherpack::hash_util::calc(algo, path, *bh_ptr);
+ if( nullptr == hash ) {
+ return nullptr;
+ }
+ jbyteArray jhash = env->NewByteArray((jsize)hash->size());
+ env->SetByteArrayRegion(jhash, 0, (jsize)hash->size(), (const jbyte *)hash->data());
+ jau::jni::java_exception_check_and_throw(env, E_FILE_LINE);
+ return jhash;
+ } catch(...) {
+ rethrow_and_raise_java_exception(env);
+ }
+ return nullptr;
+}
diff --git a/java/org/cipherpack/Cipherpack.java b/java/org/cipherpack/Cipherpack.java
index 5d69a5c..32e1401 100644
--- a/java/org/cipherpack/Cipherpack.java
+++ b/java/org/cipherpack/Cipherpack.java
@@ -250,7 +250,7 @@ public final class Cipherpack {
* Hash utility functions to produce a hash file compatible to `sha256sum`
* as well as to produce the hash value itself for validation.
*/
- public static class HashUtil {
+ public static final class HashUtil {
/** Return a lower-case file suffix used to store a `sha256sum` compatible hash signature w/o dot and w/o dashes. */
public static String fileSuffix(final String algo) {
return algo.toLowerCase().replace("-", "");
@@ -287,6 +287,21 @@ public final class Cipherpack {
* @param source the byte input stream
* @return the calculated hash value or null in case of error
*/
- public static native byte[] calc(final String algo, final ByteInStream source);
+ public static byte[] calc(final String algo, final ByteInStream source) {
+ return calcImpl1(algo, source);
+ }
+ private static native byte[] calcImpl1(final String algo, final ByteInStream source);
+
+ /**
+ * Return the calculated hash value using given algo name and all actual files (not symbolic links) within the given path.
+ * @param algo the hash algo name
+ * @param path source path, either a single file or directory for which all files (not symbolic links) are considered
+ * @param bytes_hashed returns overall bytes hashed, an array of length 1
+ * @return the calculated hash value or nullptr in case of error
+ */
+ public static byte[] calc(final String algo, final String path, final long bytes_hashed[/*0*/]) {
+ return calcImpl2(algo, path, bytes_hashed);
+ }
+ private static native byte[] calcImpl2(final String algo, final String path, final long bytes_hashed[/*0*/]);
}
}
diff --git a/src/cipherpack/crypto0.cpp b/src/cipherpack/crypto0.cpp
index ea49b71..b3f9b4f 100644
--- a/src/cipherpack/crypto0.cpp
+++ b/src/cipherpack/crypto0.cpp
@@ -391,3 +391,76 @@ std::unique_ptr<std::vector<uint8_t>> cipherpack::hash_util::calc(const std::str
hash_func->final(res->data());
return res;
}
+
+std::unique_ptr<std::vector<uint8_t>> cipherpack::hash_util::calc(const std::string_view& algo, const std::string& path, uint64_t& bytes_hashed) noexcept {
+ bytes_hashed = 0;
+ jau::fs::file_stats source_stats(path);
+ if( source_stats.is_file() ) {
+ jau::io::ByteInStream_File in(path);
+ if( in.error() ) {
+ return nullptr;
+ }
+ return calc(algo, in);
+ }
+ if( !source_stats.is_dir() ) {
+ ERR_PRINT("path is neither file nor dir: %s", source_stats.to_string());
+ return nullptr;
+ }
+
+ //
+ // directory handling
+ //
+ struct context_t {
+ std::vector<int> dirfds;
+ std::unique_ptr<Botan::HashFunction> hash_func;
+ jau::io::secure_vector<uint8_t> io_buffer;
+ jau::io::StreamConsumerFunc consume_data;
+ uint64_t bytes_hashed;
+ };
+ context_t ctx { std::vector<int>(), nullptr, jau::io::secure_vector<uint8_t>(), nullptr, 0 };
+ {
+ const std::string algo_s(algo);
+ ctx.hash_func = Botan::HashFunction::create(algo_s);
+ if( nullptr == ctx.hash_func ) {
+ ERR_PRINT2("Hash failed: Algo %s not available", algo_s.c_str());
+ return nullptr;
+ }
+ }
+ ctx.consume_data = [&](jau::io::secure_vector<uint8_t>& data, bool is_final) -> bool {
+ (void) is_final;
+ ctx.hash_func->update(data);
+ return true;
+ };
+ ctx.io_buffer.reserve(Constants::buffer_size);
+
+ const jau::fs::path_visitor pv = jau::bindCaptureRefFunc<bool, context_t, jau::fs::traverse_event, const jau::fs::file_stats&>(&ctx,
+ ( bool(*)(context_t*, jau::fs::traverse_event, const jau::fs::file_stats&) ) /* help template type deduction of function-ptr */
+ ( [](context_t* ctx_ptr, jau::fs::traverse_event tevt, const jau::fs::file_stats& element_stats) -> bool {
+ if( is_set(tevt, jau::fs::traverse_event::file) && !is_set(tevt, jau::fs::traverse_event::symlink) ) {
+ // FIXME: It would be desirable to have ByteInStream_File handle dirfd,
+ // i.e. implementation based on OS level I/O.
+ //
+ // const int dirfd = ctx_ptr->dirfds.back();
+ // const std::string& basename_ = element_stats.item().basename();
+ jau::io::ByteInStream_File in(element_stats.path());
+ if( in.error() ) {
+ return false;
+ }
+ const uint64_t in_bytes_total = jau::io::read_stream(in, ctx_ptr->io_buffer, ctx_ptr->consume_data);
+ in.close();
+ if( in.has_content_size() && in_bytes_total != in.content_size() ) {
+ ERR_PRINT2("Hash failed: Only read %" PRIu64 " bytes of %s", in_bytes_total, in.to_string().c_str());
+ return false;
+ }
+ ctx_ptr->bytes_hashed += in_bytes_total;
+ }
+ return true;
+ } ) );
+ if( jau::fs::visit(source_stats, jau::fs::traverse_options::recursive, pv, &ctx.dirfds) ) {
+ std::unique_ptr<std::vector<uint8_t>> res = std::make_unique<std::vector<uint8_t>>(ctx.hash_func->output_length());
+ ctx.hash_func->final(res->data());
+ bytes_hashed = ctx.bytes_hashed;
+ return res;
+ }
+ return nullptr;
+}
diff --git a/test/cipherpack/test_01_cipherpack.cpp b/test/cipherpack/test_01_cipherpack.cpp
index b3f2c0e..9b6f682 100644
--- a/test/cipherpack/test_01_cipherpack.cpp
+++ b/test/cipherpack/test_01_cipherpack.cpp
@@ -239,7 +239,7 @@ class Test01Cipherpack : public TestData {
REQUIRE( hash_value == *orig_hash_value );
const jau::fraction_i64 _td = ( jau::getMonotonicTime() - _t0 ).to_fraction_i64();
- jau::io::print_stats("Hash "+hash_algo, orig_in->content_size(), _td);
+ jau::io::print_stats("Hash '"+hash_algo+"'", orig_in->content_size(), _td);
jau::PLAIN_PRINT(true, "");
}
@@ -647,6 +647,59 @@ class Test01Cipherpack : public TestData {
}
}
+ const std::string root = "test_data";
+ // submodule location with jaulib directly hosted below main project
+ const std::string project_root2 = "../../../jaulib/test_data";
+
+ void test50_copy_and_verify() {
+ const std::string title("test50_copy_and_verify");
+ const std::string hash_file = title+".hash";
+
+ jau::fprintf_td(stderr, "\n");
+ jau::fprintf_td(stderr, "%s\n", title.c_str());
+
+ jau::fs::remove(hash_file);
+
+ jau::fs::file_stats source_stats(project_root2);
+ REQUIRE( true == source_stats.exists() );
+ REQUIRE( true == source_stats.is_dir() );
+
+ uint64_t source_bytes_hashed = 0;
+ std::unique_ptr<std::vector<uint8_t>> source_hash = cipherpack::hash_util::calc(cipherpack::default_hash_algo(), source_stats.path(), source_bytes_hashed);
+ REQUIRE( nullptr != source_hash );
+ REQUIRE( true == cipherpack::hash_util::append_to_file(hash_file, source_stats.path(), *source_hash));
+
+ // copy folder
+ const std::string dest = root+"_copy_verify_test50";
+ {
+ const jau::fs::copy_options copts = jau::fs::copy_options::recursive |
+ jau::fs::copy_options::preserve_all |
+ jau::fs::copy_options::sync |
+ jau::fs::copy_options::verbose;
+ jau::fs::remove(dest, jau::fs::traverse_options::recursive);
+ REQUIRE( true == jau::fs::copy(source_stats.path(), dest, copts) );
+ }
+ jau::fs::file_stats dest_stats(dest);
+ REQUIRE( true == dest_stats.exists() );
+ REQUIRE( true == dest_stats.ok() );
+ REQUIRE( true == dest_stats.is_dir() );
+
+ uint64_t dest_bytes_hashed = 0;
+ std::unique_ptr<std::vector<uint8_t>> dest_hash = cipherpack::hash_util::calc(cipherpack::default_hash_algo(), dest_stats.path(), dest_bytes_hashed);
+ REQUIRE( nullptr != dest_hash );
+ REQUIRE( true == cipherpack::hash_util::append_to_file(hash_file, dest_stats.path(), *dest_hash));
+
+ // actual validation of hash values, i.e. same content
+ REQUIRE( *source_hash == *dest_hash );
+ REQUIRE( source_bytes_hashed == dest_bytes_hashed );
+
+ jau::fprintf_td(stderr, "%s: bytes %s, '%s'\n", title.c_str(),
+ jau::to_decstring(dest_bytes_hashed).c_str(),
+ jau::bytesHexString(dest_hash->data(), 0, dest_hash->size(), true /* lsbFirst */, true /* lowerCase */).c_str());
+
+ REQUIRE( true == jau::fs::remove(dest, jau::fs::traverse_options::recursive) );
+ }
+
};
std::vector<std::string> Test01Cipherpack::fname_payload_lst;
@@ -663,4 +716,4 @@ METHOD_AS_TEST_CASE( Test01Cipherpack::test13_dec_http_error, "CipherPack 02
METHOD_AS_TEST_CASE( Test01Cipherpack::test21_enc_dec_fed_ok, "CipherPack 03 test21_enc_dec_fed_ok");
METHOD_AS_TEST_CASE( Test01Cipherpack::test22_enc_dec_fed_irq, "CipherPack 03 test22_enc_dec_fed_irq");
-
+METHOD_AS_TEST_CASE( Test01Cipherpack::test50_copy_and_verify, "CipherPack 03 test50_copy_and_verify");
diff --git a/test/java/test/org/cipherpack/Test01Cipherpack.java b/test/java/test/org/cipherpack/Test01Cipherpack.java
index d4658ea..f5ebb9d 100644
--- a/test/java/test/org/cipherpack/Test01Cipherpack.java
+++ b/test/java/test/org/cipherpack/Test01Cipherpack.java
@@ -34,6 +34,8 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -45,12 +47,17 @@ import org.cipherpack.Cipherpack;
import org.cipherpack.CipherpackListener;
import org.cipherpack.CryptoConfig;
import org.cipherpack.PackHeader;
+import org.jau.fs.CopyOptions;
+import org.jau.fs.FileStats;
+import org.jau.fs.FileUtil;
+import org.jau.fs.TraverseOptions;
import org.jau.io.ByteInStream;
import org.jau.io.ByteInStreamUtil;
import org.jau.io.ByteInStream_Feed;
import org.jau.io.ByteInStream_File;
import org.jau.io.ByteInStream_URL;
import org.jau.io.PrintUtil;
+import org.jau.sys.Clock;
import org.jau.util.BasicTypes;
import org.junit.AfterClass;
import org.junit.Assert;
@@ -158,7 +165,7 @@ public class Test01Cipherpack extends data_test {
CipherpackListener silentListener = new CipherpackListener();
- @Test(timeout = 10000)
+ @Test(timeout = 20000)
public final void test01_enc_dec_file_ok() {
CPFactory.checkInitialized();
final List<String> enc_pub_keys = Arrays.asList(enc_pub_key1_fname, enc_pub_key2_fname, enc_pub_key3_fname);
@@ -235,20 +242,7 @@ public class Test01Cipherpack extends data_test {
PrintUtil.fprintf_td(System.err, "test01_enc_dec_file_ok: Decypted %s to %s\n", fname_payload_encrypted_lst.get(file_idx), fname_payload_decrypted_lst.get(file_idx));
PrintUtil.fprintf_td(System.err, "test01_enc_dec_file_ok: %s\n", ph2.toString(true, true));
Assert.assertTrue( ph2.isValid() );
- {
- final String hashedDescryptedFile = fname_payload_decrypted_lst.get(file_idx);
- final String suffix = Cipherpack.HashUtil.fileSuffix(ph2.payload_hash_algo);
- final String outFile = hashedDescryptedFile + "." + suffix;
- remove_file( outFile );
- Assert.assertTrue( Cipherpack.HashUtil.appendToFile(outFile, hashedDescryptedFile, ph2.payload_hash) );
-
- final String origFile = fname_payload_lst.get(file_idx);
- final ByteInStream origIn = ByteInStreamUtil.to_ByteInStream(origFile);
- Assert.assertNotNull( origIn );
- final byte[] origHashValue = Cipherpack.HashUtil.calc(ph2.payload_hash_algo, origIn);
- Assert.assertNotNull( origHashValue );
- Assert.assertArrayEquals(ph2.payload_hash, origHashValue);
- }
+ hash_retest(fname_payload_lst.get(file_idx), fname_payload_decrypted_lst.get(file_idx), ph2.payload_hash_algo, ph2.payload_hash);
}
{
final ByteInStream_File enc_stream = new ByteInStream_File(fname_payload_encrypted_lst.get(file_idx));
@@ -258,25 +252,44 @@ public class Test01Cipherpack extends data_test {
PrintUtil.fprintf_td(System.err, "test01_enc_dec_file_ok: Decypted %s to %s\n", fname_payload_encrypted_lst.get(file_idx), fname_payload_decrypted_lst.get(file_idx));
PrintUtil.fprintf_td(System.err, "test01_enc_dec_file_ok: %s\n", ph2.toString(true, true));
Assert.assertTrue( ph2.isValid() );
- {
- final String hashedDescryptedFile = fname_payload_decrypted_lst.get(file_idx);
- final String suffix = Cipherpack.HashUtil.fileSuffix(ph2.payload_hash_algo);
- final String outFile = hashedDescryptedFile + "." + suffix;
- remove_file( outFile );
- Assert.assertTrue( Cipherpack.HashUtil.appendToFile(outFile, hashedDescryptedFile, ph2.payload_hash) );
-
- final String origFile = fname_payload_lst.get(file_idx);
- final ByteInStream origIn = ByteInStreamUtil.to_ByteInStream(origFile);
- Assert.assertNotNull( origIn );
- final byte[] origHashValue = Cipherpack.HashUtil.calc(ph2.payload_hash_algo, origIn);
- Assert.assertNotNull( origHashValue );
- Assert.assertArrayEquals(ph2.payload_hash, origHashValue);
- }
+ hash_retest(fname_payload_lst.get(file_idx), fname_payload_decrypted_lst.get(file_idx), ph2.payload_hash_algo, ph2.payload_hash);
+ }
+ {
+ final ByteInStream_File enc_stream = new ByteInStream_File(fname_payload_encrypted_lst.get(file_idx));
+ final PackHeader ph2 = Cipherpack.checkSignThenDecrypt(sign_pub_keys, dec_sec_key3_fname, dec_sec_key_passphrase,
+ enc_stream,
+ silentListener, "BLAKE2b(512)", fname_payload_decrypted_lst.get(file_idx));
+ PrintUtil.fprintf_td(System.err, "test01_enc_dec_file_ok: Decypted %s to %s\n", fname_payload_encrypted_lst.get(file_idx), fname_payload_decrypted_lst.get(file_idx));
+ PrintUtil.fprintf_td(System.err, "test01_enc_dec_file_ok: %s\n", ph2.toString(true, true));
+ Assert.assertTrue( ph2.isValid() );
+ hash_retest(fname_payload_lst.get(file_idx), fname_payload_decrypted_lst.get(file_idx), ph2.payload_hash_algo, ph2.payload_hash);
}
}
}
- @Test(timeout = 10000)
+ public static void hash_retest(final String origFile, final String hashedDescryptedFile,
+ final String hashAlgo, final byte[] hashValue)
+ {
+ final String suffix = Cipherpack.HashUtil.fileSuffix(hashAlgo);
+ final String outFile = hashedDescryptedFile + "." + suffix;
+ FileUtil.remove(outFile, TraverseOptions.none);
+
+ Assert.assertTrue( Cipherpack.HashUtil.appendToFile(outFile, hashedDescryptedFile, hashValue) );
+
+ final ByteInStream origIn = ByteInStreamUtil.to_ByteInStream(origFile);
+ Assert.assertNotNull( origIn );
+ final Instant t0 = Clock.getMonotonicTime();
+ final byte[] origHashValue = Cipherpack.HashUtil.calc(hashAlgo, origIn);
+ Assert.assertNotNull( origHashValue );
+ Assert.assertArrayEquals(hashValue, origHashValue);
+
+ final Instant t1 = Clock.getMonotonicTime();
+ final long td_ms = t0.until(t1, ChronoUnit.MILLIS);
+ ByteInStreamUtil.print_stats("Hash '"+hashAlgo+"'", origIn.content_size(), td_ms);
+ PrintUtil.fprintf_td(System.err, "\n");
+ }
+
+ @Test(timeout = 20000)
public final void test02_enc_dec_file_error() {
CPFactory.checkInitialized();
@@ -317,7 +330,7 @@ public class Test01Cipherpack extends data_test {
}
}
- @Test(timeout = 10000)
+ @Test(timeout = 20000)
public final void test11_dec_http_ok() {
CPFactory.checkInitialized();
if( !org.jau.io.UriTk.protocol_supported("http:") ) {
@@ -372,7 +385,7 @@ public class Test01Cipherpack extends data_test {
}
}
- @Test(timeout = 10000)
+ @Test(timeout = 20000)
public final void test12_dec_http_ok() {
CPFactory.checkInitialized();
if( !org.jau.io.UriTk.protocol_supported("http:") ) {
@@ -409,7 +422,7 @@ public class Test01Cipherpack extends data_test {
}
}
- @Test(timeout = 10000)
+ @Test(timeout = 20000)
public final void test13_dec_http_error() {
CPFactory.checkInitialized();
if( !org.jau.io.UriTk.protocol_supported("http:") ) {
@@ -625,7 +638,7 @@ public class Test01Cipherpack extends data_test {
}
}
- @Test(timeout = 10000)
+ @Test(timeout = 20000)
public final void test21_enc_dec_fed_ok() {
CPFactory.checkInitialized();
final List<String> enc_pub_keys = Arrays.asList(enc_pub_key1_fname, enc_pub_key2_fname, enc_pub_key3_fname);
@@ -710,7 +723,7 @@ public class Test01Cipherpack extends data_test {
}
}
- @Test(timeout = 10000)
+ @Test(timeout = 20000)
public final void test22_enc_dec_fed_irq() {
CPFactory.checkInitialized();
final List<String> enc_pub_keys = Arrays.asList(enc_pub_key1_fname, enc_pub_key2_fname, enc_pub_key3_fname);
@@ -764,6 +777,61 @@ public class Test01Cipherpack extends data_test {
}
}
+ final static String root = "test_data";
+ // submodule location with jaulib directly hosted below main project
+ final static String project_root2 = "../../../jaulib/test_data";
+
+ @Test(timeout = 20000)
+ public final void test50_copy_and_verify() {
+ final String title = "test50_copy_and_verify";
+ final String hash_file = title+".hash";
+
+ PrintUtil.fprintf_td(System.err, "\n");
+ PrintUtil.fprintf_td(System.err, "%s\n", title);
+
+ FileUtil.remove(hash_file, TraverseOptions.none);
+
+ final FileStats source_stats = new FileStats(project_root2);
+ Assert.assertTrue( source_stats.exists() );
+ Assert.assertTrue( source_stats.is_dir() );
+
+ final long[] source_bytes_hashed = { 0 };
+ final byte[] source_hash = Cipherpack.HashUtil.calc(Cipherpack.default_hash_algo(), source_stats.path(), source_bytes_hashed);
+ Assert.assertNotNull( source_hash );
+ Assert.assertTrue( Cipherpack.HashUtil.appendToFile(hash_file, source_stats.path(), source_hash));
+
+ // copy folder
+ final String dest = root+"_copy_verify_test50";
+ {
+ final CopyOptions copts = new CopyOptions();
+ copts.set(CopyOptions.Bit.recursive);
+ copts.set(CopyOptions.Bit.preserve_all);
+ copts.set(CopyOptions.Bit.sync);
+ copts.set(CopyOptions.Bit.verbose);
+
+ FileUtil.remove(dest, TraverseOptions.recursive);
+ Assert.assertTrue( true == FileUtil.copy(source_stats.path(), dest, copts) );
+ }
+ final FileStats dest_stats = new FileStats(dest);
+ Assert.assertTrue( source_stats.exists() );
+ Assert.assertTrue( source_stats.ok() );
+ Assert.assertTrue( source_stats.is_dir() );
+
+ final long[] dest_bytes_hashed = { 0 };
+ final byte[] dest_hash = Cipherpack.HashUtil.calc(Cipherpack.default_hash_algo(), dest_stats.path(), dest_bytes_hashed);
+ Assert.assertNotNull( dest_hash );
+ Assert.assertTrue( Cipherpack.HashUtil.appendToFile(hash_file, dest_stats.path(), dest_hash));
+
+ // actual validation of hash values, i.e. same content
+ Assert.assertArrayEquals(source_hash, dest_hash);
+ Assert.assertEquals( source_bytes_hashed[0], dest_bytes_hashed[0] );
+
+ PrintUtil.fprintf_td(System.err, "%s: bytes %,d, '%s'\n", title, dest_bytes_hashed[0],
+ BasicTypes.bytesHexString(dest_hash, 0, dest_hash.length, true /* lsbFirst */));
+
+ Assert.assertTrue( FileUtil.remove(dest, TraverseOptions.recursive) );
+ }
+
public static void main(final String args[]) {
org.junit.runner.JUnitCore.main(Test01Cipherpack.class.getName());
}