package com.kryptnostic.kodex.v1.serialization.crypto; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.Map; import org.apache.commons.codec.binary.StringUtils; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.kryptnostic.kodex.v1.models.blocks.ChunkingStrategy; /** * @author sinaiman * */ public class DefaultChunkingStrategy implements ChunkingStrategy { @JsonCreator public DefaultChunkingStrategy() { } /** * This strategy will subdivide byte data into blocks of this length */ public static final int BLOCK_LENGTH_IN_BYTES = 1_048_576; // 1 MB @Override @JsonIgnore public <T> Iterable<byte[]> split( T object ) throws IOException { ObjectMapper mapper = Encryptable.getMapper(); ByteBuffer plaintext = null; try { byte[] bytes = null; if ( object instanceof String ) { bytes = StringUtils.getBytesUtf8( (String) object ); } else { bytes = mapper.writeValueAsBytes( object ); } plaintext = ByteBuffer.wrap( bytes ); } catch ( JsonProcessingException e ) { throw new IOException( e ); } int remaining = plaintext.remaining(); int remainingBlocks = (int) Math.ceil( (double) remaining / (double) getLength() ); List<byte[]> byteBlocks = Lists.newArrayListWithCapacity( remainingBlocks ); // divide plain bytes into chunks of BLOCK_LENGTH_IN_BYTES, add these blocks to byteBlocks while ( remaining > 0 ) { byte[] block; // Re-allocate byte block each time as it will be handed off to list if ( remaining >= getLength() ) { block = new byte[ getLength() ]; } else { block = new byte[ remaining ]; } // move bytes and adjust cursors for buffer plaintext.get( block ); byteBlocks.add( block ); remaining = plaintext.remaining(); } return byteBlocks; } @Override @JsonIgnore public <T> T join( Iterable<byte[]> blocks, Class<T> klass ) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); for ( byte[] b : blocks ) { baos.write( b ); } ObjectMapper mapper = Encryptable.getMapper(); T plainData = null; if ( klass.isAssignableFrom( String.class ) ) { plainData = (T) StringUtils.newStringUtf8( baos.toByteArray() ); } else { plainData = mapper.<T> readValue( baos.toByteArray(), klass ); } return plainData; } @Override @JsonIgnore public int getLength() { return BLOCK_LENGTH_IN_BYTES; } public static Map<Integer, Integer> getBlockIndexForByteOffset( List<Integer> offsets ) { Map<Integer, Integer> blockIndices = Maps.newHashMap(); for ( Integer offset : offsets ) { blockIndices.put( offset, (int) Math.floor( offset / (double) BLOCK_LENGTH_IN_BYTES ) ); } return blockIndices; } }