/*
* Copyright 2011 b1.org
*
* 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 org.b1.pack.standard.common;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Bytes;
import org.spongycastle.util.encoders.Base64;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
public class Volumes {
public static final String B1 = "b1";
public static final String V = "v";
public static final String A = "a";
public static final String N = "n";
public static final String T = "t";
public static final String C = "c";
public static final String E = "e";
public static final String X = "x";
public static final String AS = "as";
public static final String VS = "vs";
public static final char COLON = ':';
public static final String SLASH = "/";
public static final String B1_AS = "b1:as";
public static final String B1_VS = "b1:vs";
public static final String B1_AE = "b1:ae";
public static final String B1_VE = "b1:ve";
public static final byte SEPARATOR_BYTE = (byte) 0xFC;
public static final double SCHEMA_VERSION = 0.4;
private static final double NO_ENCRYPTION_SCHEMA_VERSION = 0.2;
private static final double ENCRYPTION_SCHEMA_VERSION = 0.3;
private static final byte[] SEPARATOR = new byte[]{SEPARATOR_BYTE};
private static volatile SecureRandom secureRandom;
public static byte[] generateRandomBytes(int count) {
SecureRandom random = secureRandom;
if (random == null) {
random = secureRandom = new SecureRandom();
}
byte[] buffer = new byte[count];
random.nextBytes(buffer);
return buffer;
}
public static String encodeBase64(byte[] buffer) {
try {
return new String(Base64.encode(buffer), Charsets.US_ASCII.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static byte[] decodeBase64(String s) {
return Base64.decode(s);
}
public static String createArchiveId() {
return encodeBase64(generateRandomBytes(16));
}
public static byte[] createVolumeHead(String archiveId, long volumeNumber, Long objectCount,
String method, VolumeCipher volumeCipher) {
StringBuilder builder = new StringBuilder(volumeNumber == 1 ? B1_AS : B1_VS)
.append(" v:").append(volumeCipher == null ? NO_ENCRYPTION_SCHEMA_VERSION : ENCRYPTION_SCHEMA_VERSION)
.append(" a:").append(archiveId)
.append(" n:").append(volumeNumber);
if (volumeCipher == null) {
appendPrivateItems(builder, volumeNumber, objectCount, method);
} else {
builder.append(" e:1/").append(volumeCipher.getIterationCount());
String publicItems = builder.toString();
appendPrivateItems(builder, volumeNumber, objectCount, method);
byte[] plaintext = getUtf8Bytes(builder.toString());
builder = new StringBuilder(publicItems).append(" x:").append(encodeBase64(volumeCipher.cipherHead(true, plaintext)));
}
return Bytes.concat(getUtf8Bytes(builder.toString()), SEPARATOR);
}
private static void appendPrivateItems(StringBuilder builder, long volumeNumber, Long objectCount, String method) {
if (volumeNumber == 1) {
if (objectCount != null) {
builder.append(" t:").append(objectCount);
}
if (method != null) {
Preconditions.checkArgument(!method.contains(" "));
builder.append(" m:").append(method);
}
}
}
public static byte[] createVolumeTail(boolean lastVolume, RecordPointer catalogPointer, long minSize, VolumeCipher volumeCipher) {
String signature = lastVolume ? B1_AE : B1_VE;
StringBuilder builder = new StringBuilder();
if (catalogPointer != null) {
builder.append("c:")
.append(catalogPointer.volumeNumber).append('/')
.append(catalogPointer.blockOffset).append('/')
.append(catalogPointer.recordOffset).append(' ');
}
if (volumeCipher != null) {
byte[] plaintext = getUtf8Bytes(builder.append(signature).toString());
builder = new StringBuilder("x:").append(encodeBase64(volumeCipher.cipherTail(true, plaintext))).append(' ');
}
while (builder.length() < minSize - signature.length() - 1) {
builder.append(' ');
}
return Bytes.concat(SEPARATOR, getUtf8Bytes(builder.append(signature).toString()));
}
public static byte[] getUtf8Bytes(String s) {
try {
return s.getBytes(Charsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}