package com.eucalyptus.objectstorage.pipeline.handlers; import com.eucalyptus.auth.login.Hmacv4LoginModule; import com.eucalyptus.objectstorage.pipeline.handlers.AwsChunkStream.AwsChunk; import com.eucalyptus.objectstorage.pipeline.handlers.AwsChunkStream.StreamingHttpRequest; import com.google.common.base.Charsets; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.handler.codec.http.DefaultHttpChunk; import org.jboss.netty.handler.codec.http.HttpChunk; import org.junit.Before; import org.junit.Test; import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.*; public class AwsChunkStreamTest { private static final String LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tortor metus, sagittis eget " + "augue ut,\n" + "feugiat vehicula risus. Integer tortor mauris, vehicula nec mollis et, consectetur eget tortor. In ut\n" + "elit" + " " + "sagittis, ultrices est ut, iaculis turpis. In hac habitasse platea dictumst. Donec laoreet tellus\n" + "at auctor " + "tempus. " + "Praesent" + " nec diam sed urna sollicitudin vehicula eget id est. Vivamus sed laoreet\n" + "lectus. Aliquam " + "convallis condimentum" + " risus, vitae" + " porta justo venenatis vitae. Phasellus vitae nunc\n" + "varius, volutpat quam nec, " + "mollis urna. Donec tempus, " + "nisi vitae gravida " + "facilisis, sapien sem malesuada\n" + "purus, id semper libero ipsum " + "condimentum nulla. Suspendisse vel mi " + "leo. Morbi pellentesque " + "placerat congue.\n" + "Nunc sollicitudin nunc diam, nec " + "hendrerit dui commodo sed. Duis dapibus " + "commodo elit, id commodo erat\n" + "congue id. Aliquam erat volutpat.\n"; private static final byte[] FINAL_CHUNK = new byte[0]; private static final String CRLF = "\r\n"; private static final String CHUNK_SIGNATURE_HEADER = ";chunk-signature="; private static final int END_CHUNK_OFFSET = 8370; private static final int MID_CHUNK_PAYLOAD_OFFSET = 9000; private static final int MID_CHUNK_LEN_DECLARATION_OFFSET = 8372; private static final int MID_CHUNK_SIG_DECLARATION_OFFSET = 8384; private static final int MID_CHUNK_SIG_OFFSET = 8411; private static final String TEST_STRING = buildString(4096 * 6 + 1000); private static final String CHUNKED_STRING = chunkString(TEST_STRING, 4096); AwsChunkStream stream; @Before public void beforeMethod() { stream = new AwsChunkStream(); } @Test public void testAppend() throws Exception { ChannelBuffer content = ChannelBuffers.copiedBuffer(CHUNKED_STRING, Charsets.UTF_8); StreamingHttpRequest request = stream.append(new DefaultHttpChunk(content)); assertEquals(request.awsChunks.size(), 8); } @Test public void testConvertAwsChunkToHttpChunk() throws Exception { // Given ChannelBuffer content = ChannelBuffers.copiedBuffer(CHUNKED_STRING, Charsets.UTF_8); StreamingHttpRequest request = stream.append(new DefaultHttpChunk(content)); AwsChunk chunk = request.awsChunks.get(0); // When HttpChunk httpChunk = chunk.toHttpChunk(); // Then assert http chunk = aws chunk - (header + CRLF) assertEquals(httpChunk.getContent().readableBytes(), chunk.getContents().readableBytes() - 87); } @Test public void testAppendChunksCleanlySplit() throws Exception { assertPayloadSplitOn(END_CHUNK_OFFSET); } @Test public void testAppendChunksSplitOnPayload() throws Exception { assertPayloadSplitOn(MID_CHUNK_PAYLOAD_OFFSET); } @Test public void testAppendChunksSplitOnLenDeclaration() throws Exception { assertPayloadSplitOn(MID_CHUNK_LEN_DECLARATION_OFFSET); } @Test public void testAppendChunksSplitOnSigDeclaration() throws Exception { assertPayloadSplitOn(MID_CHUNK_SIG_DECLARATION_OFFSET); } @Test public void testAppendChunksSplitOnSig() throws Exception { assertPayloadSplitOn(MID_CHUNK_SIG_OFFSET); } private List<AwsChunk> assertPayloadSplitOn(int index) { // Given String str1 = CHUNKED_STRING.substring(0, index); String str2 = CHUNKED_STRING.substring(index); ChannelBuffer part1Content = ChannelBuffers.copiedBuffer(str1, Charsets.UTF_8); ChannelBuffer part2Content = ChannelBuffers.copiedBuffer(str2, Charsets.UTF_8); // When StreamingHttpRequest part1Chunks = stream.append(new DefaultHttpChunk(part1Content)); StreamingHttpRequest part2Chunks = stream.append(new DefaultHttpChunk(part2Content)); // Then assertEquals(2, part1Chunks.awsChunks.size()); assertEquals(6, part2Chunks.awsChunks.size()); List<AwsChunk> allChunks = new ArrayList<>(); allChunks.addAll(part1Chunks.awsChunks); allChunks.addAll(part2Chunks.awsChunks); assertChunks(allChunks); return part2Chunks.awsChunks; } private void assertChunks(List<AwsChunk> chunks) { assertChunk(chunks.get(0), 4096, "9f63a819511c7ee3d0dd464715825e117b6556475e5b4217e203328b3c8523eb"); assertChunk(chunks.get(1), 4096, "ddccac04688b78d3581fb4832043760709ae73f9bf58b825fd5bfcda0cd429f3"); assertChunk(chunks.get(2), 4096, "515052f47f69235db5da3a12f108b13d09ba5668c2c0f0b0be0d9cd0979b69a8"); assertChunk(chunks.get(3), 4096, "e2b18125a38f5f67e9c00f08cecf9e74d316e816af035b6dff411acd5af346cd"); assertChunk(chunks.get(4), 4096, "32cb3ade0e4dff0f83c5e5ff6762feb3c96363676d227307e4214c90339e37dc"); assertChunk(chunks.get(5), 4096, "42a60ca16f1b4b7b9f77c847fc73defcd616542064e86e4ecbf0c8aa5e741a58"); assertChunk(chunks.get(6), 1000, "f9a09d1d93ea40d3863de5337a1c417cea4ba6918c4ddd303cb625330a8d1450"); assertChunk(chunks.get(7), 0, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); } private void assertChunk(AwsChunk chunk, int chunkSize, String signature) { assertEquals(chunkSize, chunk.chunkSize); assertEquals(signature, chunk.chunkSignature); } /** * Builds a chunked string. */ private static String chunkString(String payload, int chunkSize) { try { StringBuilder result = new StringBuilder(); ByteArrayInputStream inputStream = new ByteArrayInputStream(payload.getBytes("UTF-8")); int bytesRead; byte[] chunkedPayload = new byte[chunkSize]; while ((bytesRead = inputStream.read(chunkedPayload, 0, chunkedPayload.length)) != -1) { String chunk = buildSignedChunk(bytesRead, chunkedPayload); result.append(chunk); } result.append(buildSignedChunk(0, chunkedPayload)); return result.toString(); } catch (Exception ignore) { return null; } } /** * Builds a signed chunk. */ private static String buildSignedChunk(int userDataLen, byte[] userData) throws Exception { if (userDataLen == 0) { userData = FINAL_CHUNK; } else if (userDataLen < userData.length) { byte[] userDataNew = new byte[userDataLen]; System.arraycopy(userData, 0, userDataNew, 0, userDataLen); userData = userDataNew; } String payload = new String(userData); String chunkSize = Integer.toHexString(userData.length); String chunkSignature = Hmacv4LoginModule.digestUTF8(payload); return chunkSize + CHUNK_SIGNATURE_HEADER + chunkSignature + CRLF + payload + CRLF; } /** * Builds a Lorem Ipsum string of the {@code length}. */ private static String buildString(int length) { StringBuilder sb = new StringBuilder(length); for (int i = 0, j = 0; i < length; i++, j++) { if (j == LOREM_IPSUM.length()) j = 0; sb.append(LOREM_IPSUM.charAt(j)); } return sb.toString(); } }