Skip to content

Commit

Permalink
New central API class (Zchunk).
Browse files Browse the repository at this point in the history
  - Header validation.
  - Single chunk validation.
  - Total data validation.
  • Loading branch information
bmarwell committed May 18, 2019
1 parent 25fc29f commit 7274f81
Show file tree
Hide file tree
Showing 15 changed files with 284 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,69 @@

package de.bmarwell.zchunk.fileformat;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.StringJoiner;
import java.util.logging.Logger;

/**
* 0 = SHA-1
* 1 = SHA-256
*/
public enum HeaderChecksumType {
SHA1("SHA-1"),
SHA256("SHA-256");
UNKNOWN("unknown", -1L, 0),
SHA1("SHA-1", 0L, -1),
SHA256("SHA-256", 1L, -1);

private final String digestAlgorithm;
private final int digestLength;
/**
* Constant and unique value as from {@code codezchunk_format.txt}.
*/
private final long identifier;

HeaderChecksumType(final String digestAlgorithm) {
HeaderChecksumType(final String digestAlgorithm, final long identifier, final int manualDigestLength) {
try {
this.digestAlgorithm = digestAlgorithm;
this.digestLength = MessageDigest.getInstance(digestAlgorithm).getDigestLength();
if (manualDigestLength <= -1) {
// use default
this.digestLength = MessageDigest.getInstance(digestAlgorithm).getDigestLength();
} else {
this.digestLength = manualDigestLength;
}

this.identifier = identifier;
} catch (final NoSuchAlgorithmException algoEx) {
throw new IllegalArgumentException("Unable to create hashing algorithm: [" + digestAlgorithm + "]. Check your JVM settings.", algoEx);
}
}

public static HeaderChecksumType find(final BigInteger unsignedLongValue) {
if (unsignedLongValue.compareTo(BigInteger.valueOf(Integer.MAX_VALUE)) >= 1) {
final String message = String.format("Unknown Checksum type: [%s], exeeds [%d]!", unsignedLongValue.toString(), Integer.MAX_VALUE);
Logger.getLogger(HeaderChecksumType.class.getCanonicalName()).warning(message);
return UNKNOWN;
}

final long requestedId = unsignedLongValue.longValue();

return Arrays.stream(values())
.filter(algo -> algo.identifier == requestedId)
.findFirst()
.orElse(UNKNOWN);
}

public int getDigestLength() {
return this.digestLength;
}

public MessageDigest digest() {
public long getIdentifier() {
return this.identifier;
}

public MessageDigest getMessageDigest() {
try {
return MessageDigest.getInstance(this.digestAlgorithm);
} catch (final NoSuchAlgorithmException algoEx) {
Expand All @@ -57,6 +91,7 @@ public String toString() {
return new StringJoiner(", ", HeaderChecksumType.class.getSimpleName() + "[", "]")
.add("digestAlgorithm=" + this.digestAlgorithm)
.add("digestLength=" + this.digestLength)
.add("identifier=" + this.identifier)
.add("ordinal=" + this.ordinal())
.toString();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ public enum IndexChecksumType {
SHA512("SHA-512", -1),
SHA512_128("SHA-512", 16);

private final MessageDigest digestAlgorithm;
private final String digestAlgorithm;
private final int length;

IndexChecksumType(final String digestAlgorithm, final int length) {
try {
this.digestAlgorithm = MessageDigest.getInstance(digestAlgorithm);
this.digestAlgorithm = digestAlgorithm;
if (length != -1) {
this.length = length;
} else {
this.length = this.digestAlgorithm.getDigestLength();
this.length = MessageDigest.getInstance(digestAlgorithm).getDigestLength();
}
} catch (final NoSuchAlgorithmException algoEx) {
throw new IllegalArgumentException("Unable to create hashing algorithm: [" + digestAlgorithm + "]. Check your JVM settings.", algoEx);
Expand All @@ -54,9 +54,9 @@ public int actualChecksumLength() {
}

public byte[] digest(final byte[] input) {
final byte[] digest = this.digestAlgorithm.digest(input);
final byte[] digest = this.getMessageDigest().digest(input);

if (this.length != this.digestAlgorithm.getDigestLength()) {
if (this.length != getMessageDigest().getDigestLength()) {
final byte[] actualDigest = new byte[this.length];
System.arraycopy(digest, 0, actualDigest, 0, this.length);
return actualDigest;
Expand All @@ -65,10 +65,18 @@ public byte[] digest(final byte[] input) {
return digest;
}

public MessageDigest getMessageDigest() {
try {
return MessageDigest.getInstance(this.digestAlgorithm);
} catch (final NoSuchAlgorithmException algoEx) {
throw new IllegalStateException("Unable to create message getMessageDigest instance!", algoEx);
}
}

@Override
public String toString() {
return new StringJoiner(", ", IndexChecksumType.class.getSimpleName() + "[", "]")
.add("digestAlgorithm=" + this.digestAlgorithm.getAlgorithm())
.add("digestAlgorithm=" + this.digestAlgorithm)
.add("actualChecksumLength=" + this.length)
.add("ordinal=" + this.ordinal())
.toString();
Expand Down
55 changes: 55 additions & 0 deletions fileformat/src/main/java/de/bmarwell/zchunk/fileformat/ZChunk.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2019, the zchunk-java contributors.
*
* 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 de.bmarwell.zchunk.fileformat;

import de.bmarwell.zchunk.fileformat.err.InvalidFileException;
import de.bmarwell.zchunk.fileformat.util.ChecksumUtil;
import java.io.File;

public class ZChunk {

/**
* Reads in a zchunk file.
*
* <p>The header part will stay in memory (heap).<br>
* The data streams and/or chunks will be available as inputstream, but are not
* eagerly loaded into memory.</p>
*
* @param input
* the input file.
* @return a {@link ZChunkFile} instance.
* @throws InvalidFileException
* if the input file is not a zchunk file.
* @throws NullPointerException
* if the input file is {@code null}.
*/
public static ZChunkFile fromFile(final File input) {
final ZChunkHeader header = ZChunkHeaderFactory.getZChunkFileHeader(input);

return ImmutableZChunkFile.builder().header(header).build();
}

public static boolean validateFile(final File file) {
final ZChunkFile zChunkFile = fromFile(file);
final ZChunkHeader header = zChunkFile.getHeader();

return ChecksumUtil.isValidHeader(header)
&& ChecksumUtil.allChunksAreValid(header, file)
&& ChecksumUtil.isValidData(header, file);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import de.bmarwell.zchunk.compressedint.CompressedInt;
import de.bmarwell.zchunk.fileformat.util.ByteUtils;
import java.util.Comparator;
import java.util.Optional;
import java.util.StringJoiner;
import org.immutables.value.Value;
Expand All @@ -38,6 +39,8 @@
@Value.Immutable
public abstract class ZChunkHeaderChunkInfo {

public static final Comparator<ZChunkHeaderChunkInfo> INDEX_COMPARATOR = Comparator.comparing(ZChunkHeaderChunkInfo::getCurrentIndex);

public abstract long getCurrentIndex();

public abstract Optional<byte[]> getChunkStream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package de.bmarwell.zchunk.fileformat;

import static de.bmarwell.zchunk.fileformat.ZChunkConstants.Header.MAX_LEAD_SIZE;
import static java.util.stream.Collectors.toConcurrentMap;

import de.bmarwell.zchunk.compressedint.CompressedInt;
import de.bmarwell.zchunk.compressedint.CompressedIntFactory;
Expand All @@ -36,6 +37,7 @@
import java.math.BigInteger;
import java.nio.channels.Channels;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Logger;

public final class ZChunkHeaderFactory {
Expand All @@ -46,29 +48,11 @@ private ZChunkHeaderFactory() {
//
}

/**
* Reads in a zchunk file.
*
* <p>The header part will stay in memory (heap).<br>
* The data streams and/or chunks will be available as inputstream, but are not
* eagerly loaded into memory.</p>
*
* @param input
* the input file.
* @return a {@link ZChunkFile} instance.
* @throws InvalidFileException
* if the input file is not a zchunk file.
* @throws NullPointerException
* if the input file is {@code null}.
*/
public static ZChunkFile fromFile(final File input) {
final ZChunkHeader header = getZChunkFileHeader(input);

return ImmutableZChunkFile.builder().header(header).build();
}

private static ZChunkHeader getZChunkFileHeader(final File input) {
public static ZChunkHeader getZChunkFileHeader(final File input) {
final ZChunkHeaderLead lead = readFileHeaderLead(input);
if (lead.getChecksumType() == HeaderChecksumType.UNKNOWN) {
throw new UnsupportedOperationException("Unknown getMessageDigest type: [" + lead.getChecksumType() + "].");
}
final byte[] completeHeader = readCompleteHeader(input, lead);
final ZChunkHeaderPreface preface = readHeaderPreface(completeHeader, lead);
final ZChunkHeaderIndex index = readHeaderIndex(completeHeader, lead, preface);
Expand Down Expand Up @@ -235,7 +219,8 @@ private static ZChunkHeaderIndex readHeaderIndex(final byte[] completeHeader, fi
.dictChecksum(parser.readDictChecksum())
.dictLength(parser.readDictLength())
.uncompressedDictLength(parser.readUncompressedDictLength())
.addAllChunkInfo(parser.readChunkInfos())
.putAllChunkInfo(parser.readChunkInfos().stream()
.collect(toConcurrentMap(ZChunkHeaderChunkInfo::getChunkChecksum, Function.identity())))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@

import de.bmarwell.zchunk.compressedint.CompressedInt;
import de.bmarwell.zchunk.fileformat.util.ByteUtils;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
import java.util.TreeSet;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.immutables.value.Value;

/**
Expand Down Expand Up @@ -80,7 +84,17 @@ public IndexChecksumType getChunkChecksumType() {

public abstract CompressedInt getUncompressedDictLength();

public abstract List<ZChunkHeaderChunkInfo> getChunkInfo();
public abstract Map<byte[], ZChunkHeaderChunkInfo> getChunkInfo();

@Value.Lazy
public Set<ZChunkHeaderChunkInfo> getChunkInfoSortedByIndex() {
final Supplier<Set<ZChunkHeaderChunkInfo>> IndexSortedList = () -> new TreeSet<>(ZChunkHeaderChunkInfo.INDEX_COMPARATOR);

return getChunkInfo().values()
.stream()
.sorted(ZChunkHeaderChunkInfo.INDEX_COMPARATOR)
.collect(Collectors.toCollection(IndexSortedList));
}

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public abstract class ZChunkHeaderLead {

@Value.Derived
public HeaderChecksumType getChecksumType() {
return HeaderChecksumType.values()[getChecksumTypeInt().getIntValue()];
return HeaderChecksumType.find(getChecksumTypeInt().getValue());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public CompressedInt readUncompressedDictLength() {
}
}

public Iterable<? extends ZChunkHeaderChunkInfo> readChunkInfos() {
public List<? extends ZChunkHeaderChunkInfo> readChunkInfos() {
if (this.chunkStreamOffset == -1L) {
readUncompressedDictLength();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public CompressedInt readLeadCksumType() {

final CompressedInt checksumType = CompressedIntFactory.readCompressedInt(bis);
this.headerSizeOffset = FILE_MAGIC.length + (long) checksumType.getCompressedBytes().length;
this.checksumType = HeaderChecksumType.values()[checksumType.getIntValue()];
this.checksumType = HeaderChecksumType.find(checksumType.getValue());

return checksumType;
} catch (final IOException ioEx) {
Expand Down
Loading

0 comments on commit 7274f81

Please # to comment.