From 96fc9fcf00626dd853c50186aca05631fad9bff9 Mon Sep 17 00:00:00 2001 From: Tim Strazzere Date: Tue, 29 Sep 2015 17:59:33 -0700 Subject: [PATCH] Add ability to write to OutputStream. Lots of refactoring involved and adding test cases. Each chunk/section/resouce should now be able to properly use a toByte() function with approapriate getSize() calls. However I do need to double back and check the style pool inside the string section.` --- .../android/content/res/AXMLResource.java | 92 ++++++---- .../java/android/content/res/IntReader.java | 20 +-- .../content/res/chunk/AttributeType.java | 2 +- .../android/content/res/chunk/ChunkType.java | 2 +- .../android/content/res/chunk/ChunkUtil.java | 52 +++--- .../android/content/res/chunk/PoolItem.java | 2 +- .../res/chunk/sections/ChunkSection.java | 8 +- .../chunk/sections/GenericChunkSection.java | 6 +- .../res/chunk/sections/ResourceSection.java | 52 +++++- .../res/chunk/sections/StringSection.java | 163 +++++++++++++++++- .../content/res/chunk/types/AXMLHeader.java | 19 +- .../content/res/chunk/types/Attribute.java | 27 ++- .../content/res/chunk/types/Buffer.java | 22 ++- .../content/res/chunk/types/Chunk.java | 16 +- .../content/res/chunk/types/EndTag.java | 32 +++- .../content/res/chunk/types/GenericChunk.java | 25 ++- .../content/res/chunk/types/NameSpace.java | 32 +++- .../content/res/chunk/types/StartTag.java | 63 ++++++- .../content/res/chunk/types/TextTag.java | 33 +++- src/main/java/diff/rednaga/AXMLPrinter.java | 27 ++- .../chunk/sections/ResourceSectionTest.java | 94 ++++++++++ .../res/chunk/sections/StringSectionTest.java | 90 ++++++++++ .../res/chunk/types/AttributeTest.java | 62 +++++++ .../content/res/chunk/types/EndTagTest.java | 68 ++++++++ .../res/chunk/types/GenericChunkTest.java | 94 ++++++++++ .../res/chunk/types/NameSpaceTest.java | 68 ++++++++ .../content/res/chunk/types/StartTagTest.java | 74 ++++++++ .../content/res/chunk/types/TextTagTest.java | 70 ++++++++ 28 files changed, 1172 insertions(+), 143 deletions(-) create mode 100644 src/test/java/android/content/res/chunk/sections/ResourceSectionTest.java create mode 100644 src/test/java/android/content/res/chunk/sections/StringSectionTest.java create mode 100644 src/test/java/android/content/res/chunk/types/AttributeTest.java create mode 100644 src/test/java/android/content/res/chunk/types/EndTagTest.java create mode 100644 src/test/java/android/content/res/chunk/types/GenericChunkTest.java create mode 100644 src/test/java/android/content/res/chunk/types/NameSpaceTest.java create mode 100644 src/test/java/android/content/res/chunk/types/StartTagTest.java create mode 100644 src/test/java/android/content/res/chunk/types/TextTagTest.java diff --git a/src/main/java/android/content/res/AXMLResource.java b/src/main/java/android/content/res/AXMLResource.java index 3976395..c37bb9a 100644 --- a/src/main/java/android/content/res/AXMLResource.java +++ b/src/main/java/android/content/res/AXMLResource.java @@ -15,11 +15,6 @@ */ package android.content.res; -import java.io.IOException; -import java.io.InputStream; -import java.util.Iterator; -import java.util.LinkedHashSet; - import android.content.res.chunk.ChunkType; import android.content.res.chunk.ChunkUtil; import android.content.res.chunk.sections.ResourceSection; @@ -27,9 +22,17 @@ import android.content.res.chunk.types.AXMLHeader; import android.content.res.chunk.types.Chunk; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Iterator; +import java.util.LinkedHashSet; + /** * Main AXMLResource object - * + * * @author tstrazzere */ public class AXMLResource { @@ -62,29 +65,31 @@ public boolean read(InputStream stream) throws IOException { Chunk chunk = ChunkUtil.createChunk(reader); switch (chunk.getChunkType()) { - case AXML_HEADER: - header = (AXMLHeader) chunk; - size = header.getSize(); - break; - case STRING_SECTION: - stringSection = (StringSection) chunk; - break; - // operational = true; - case RESOURCE_SECTION: - resourceSection = (ResourceSection) chunk; - break; - case START_NAMESPACE: - case END_NAMESPACE: - case START_TAG: - case END_TAG: - case TEXT_TAG: - chunks.add(chunk); - break; - case BUFFER: - // Do nothing right now, not even add it to the chunk stuff - break; - default: - throw new IOException("Hit an unknown chunk type!"); + case AXML_HEADER: + header = (AXMLHeader) chunk; + // TODO : This should warn if true + // This will cause breakages if the header is lying + //size = header.getSize(); + break; + case STRING_SECTION: + stringSection = (StringSection) chunk; + break; + // operational = true; + case RESOURCE_SECTION: + resourceSection = (ResourceSection) chunk; + break; + case START_NAMESPACE: + case END_NAMESPACE: + case START_TAG: + case END_TAG: + case TEXT_TAG: + chunks.add(chunk); + break; + case BUFFER: + // Do nothing right now, not even add it to the chunk stuff + break; + default: + throw new IOException("Hit an unknown chunk type!"); } } @@ -98,6 +103,35 @@ public boolean read(InputStream stream) throws IOException { return false; } + public void write(OutputStream outputStream) throws IOException { + + int chunkSizes = 0; + Iterator iterator = chunks.iterator(); + while (iterator.hasNext()) { + Chunk chunk = iterator.next(); + chunkSizes += chunk.getSize(); + } + + System.out.println("String section size: " + stringSection.getSize()); + System.out.println("Resource section size: " + resourceSection.getSize()); + System.out.println("chunk section size: " + chunkSizes); + System.out.println("Size was " + ((2 * 4) + stringSection.getSize() + resourceSection.getSize() + chunkSizes)); + + outputStream.write(ByteBuffer.allocate(8) + .order(ByteOrder.LITTLE_ENDIAN) + .putInt(ChunkType.AXML_HEADER.getIntType()) + .putInt(((2 * 4) + stringSection.getSize() + resourceSection.getSize() + chunkSizes)) + .array()); + outputStream.write(stringSection.toBytes()); + outputStream.write(resourceSection.toBytes()); + iterator = chunks.iterator(); + while (iterator.hasNext()) { + Chunk chunk = iterator.next(); + outputStream.write(chunk.toBytes()); + } + + } + public void print() { log("%s", header.toXML(stringSection, resourceSection, 0)); diff --git a/src/main/java/android/content/res/IntReader.java b/src/main/java/android/content/res/IntReader.java index fbee83f..d7f7b0b 100644 --- a/src/main/java/android/content/res/IntReader.java +++ b/src/main/java/android/content/res/IntReader.java @@ -20,9 +20,9 @@ /** * Simple helper class that allows reading of integers. - * + *

* TODO: implement buffering - * + * * @author Dmitry Skiba */ public class IntReader { @@ -37,11 +37,9 @@ public IntReader(InputStream stream, boolean bigEndian) { /** * Reset the POJO to use a new stream. - * - * @param newStream - * the {@code InputStream} to use - * @param isBigEndian - * a boolean for whether or not the stream is in Big Endian format + * + * @param newStream the {@code InputStream} to use + * @param isBigEndian a boolean for whether or not the stream is in Big Endian format */ public void reset(InputStream newStream, boolean isBigEndian) { stream = newStream; @@ -77,9 +75,8 @@ public int readInt() throws IOException { /** * Read an integer of a certain length from the current stream. - * - * @param length - * to read + * + * @param length to read * @return * @throws IOException */ @@ -110,13 +107,12 @@ public int readInt(int length) throws IOException { } } - // bytesRead += length; return result; } /** * Skip a specific number of bytes in the stream. - * + * * @param bytes * @throws IOException */ diff --git a/src/main/java/android/content/res/chunk/AttributeType.java b/src/main/java/android/content/res/chunk/AttributeType.java index aaaaeb3..c2d4972 100644 --- a/src/main/java/android/content/res/chunk/AttributeType.java +++ b/src/main/java/android/content/res/chunk/AttributeType.java @@ -17,7 +17,7 @@ /** * Enum for attribute types for ChunkTypes - * + * * @author tstrazzere */ public enum AttributeType { diff --git a/src/main/java/android/content/res/chunk/ChunkType.java b/src/main/java/android/content/res/chunk/ChunkType.java index d983d3d..b692b8c 100644 --- a/src/main/java/android/content/res/chunk/ChunkType.java +++ b/src/main/java/android/content/res/chunk/ChunkType.java @@ -17,7 +17,7 @@ /** * Enum for ChunkTypes - the different types of Chunks available to create - * + * * @author tstrazzere */ public enum ChunkType { diff --git a/src/main/java/android/content/res/chunk/ChunkUtil.java b/src/main/java/android/content/res/chunk/ChunkUtil.java index 394bf2c..7b43f00 100644 --- a/src/main/java/android/content/res/chunk/ChunkUtil.java +++ b/src/main/java/android/content/res/chunk/ChunkUtil.java @@ -15,22 +15,16 @@ */ package android.content.res.chunk; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.sections.ResourceSection; import android.content.res.chunk.sections.StringSection; -import android.content.res.chunk.types.AXMLHeader; -import android.content.res.chunk.types.Buffer; -import android.content.res.chunk.types.Chunk; -import android.content.res.chunk.types.EndTag; -import android.content.res.chunk.types.NameSpace; -import android.content.res.chunk.types.StartTag; -import android.content.res.chunk.types.TextTag; +import android.content.res.chunk.types.*; + +import java.io.IOException; /** * Simple class for reading chunk types. - * + * * @author tstrazzere */ public class ChunkUtil { @@ -52,25 +46,25 @@ public static Chunk createChunk(IntReader reader) throws IOException { ChunkType chunkType = readChunkType(reader); switch (chunkType) { - case AXML_HEADER: - return new AXMLHeader(chunkType, reader); - case STRING_SECTION: - return new StringSection(chunkType, reader); - case RESOURCE_SECTION: - return new ResourceSection(chunkType, reader); - case START_NAMESPACE: - case END_NAMESPACE: - return new NameSpace(chunkType, reader); - case START_TAG: - return new StartTag(chunkType, reader); - case END_TAG: - return new EndTag(chunkType, reader); - case TEXT_TAG: - return new TextTag(chunkType, reader); - case BUFFER: - return new Buffer(chunkType, reader); - default: - throw new IOException("Unexpected tag!"); + case AXML_HEADER: + return new AXMLHeader(chunkType, reader); + case STRING_SECTION: + return new StringSection(chunkType, reader); + case RESOURCE_SECTION: + return new ResourceSection(chunkType, reader); + case START_NAMESPACE: + case END_NAMESPACE: + return new NameSpace(chunkType, reader); + case START_TAG: + return new StartTag(chunkType, reader); + case END_TAG: + return new EndTag(chunkType, reader); + case TEXT_TAG: + return new TextTag(chunkType, reader); + case BUFFER: + return new Buffer(chunkType, reader); + default: + throw new IOException("Unexpected tag!"); } } } diff --git a/src/main/java/android/content/res/chunk/PoolItem.java b/src/main/java/android/content/res/chunk/PoolItem.java index ae06afd..3dc4be6 100644 --- a/src/main/java/android/content/res/chunk/PoolItem.java +++ b/src/main/java/android/content/res/chunk/PoolItem.java @@ -17,7 +17,7 @@ /** * Simple POJO for keeping the offsets and data for items inside of "pools". - * + * * @author tstrazzere */ public class PoolItem { diff --git a/src/main/java/android/content/res/chunk/sections/ChunkSection.java b/src/main/java/android/content/res/chunk/sections/ChunkSection.java index bcd83f2..99b0096 100644 --- a/src/main/java/android/content/res/chunk/sections/ChunkSection.java +++ b/src/main/java/android/content/res/chunk/sections/ChunkSection.java @@ -18,14 +18,14 @@ */ package android.content.res.chunk.sections; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.types.Chunk; +import java.io.IOException; + /** * Interface for Chunk which is a section type - * + * * @author tstrazzere */ public interface ChunkSection extends Chunk { @@ -37,7 +37,7 @@ public interface ChunkSection extends Chunk { /** * Read the - * + * * @param inputReader * @throws IOException */ diff --git a/src/main/java/android/content/res/chunk/sections/GenericChunkSection.java b/src/main/java/android/content/res/chunk/sections/GenericChunkSection.java index 1400ba1..4f8009c 100644 --- a/src/main/java/android/content/res/chunk/sections/GenericChunkSection.java +++ b/src/main/java/android/content/res/chunk/sections/GenericChunkSection.java @@ -15,17 +15,17 @@ */ package android.content.res.chunk.sections; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.types.Chunk; import android.content.res.chunk.types.GenericChunk; +import java.io.IOException; + /** * Generic ChunkSection class for generalizing the reading and minimizing the repetitive code inside of the specific * sections (likely overkill..) - * + * * @author tstrazzere */ public abstract class GenericChunkSection extends GenericChunk implements Chunk, ChunkSection { diff --git a/src/main/java/android/content/res/chunk/sections/ResourceSection.java b/src/main/java/android/content/res/chunk/sections/ResourceSection.java index 71b66d4..1bf1889 100644 --- a/src/main/java/android/content/res/chunk/sections/ResourceSection.java +++ b/src/main/java/android/content/res/chunk/sections/ResourceSection.java @@ -15,20 +15,24 @@ */ package android.content.res.chunk.sections; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.types.Chunk; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; + /** * Concrete class for the section which is specifically for the resource ids. - * + * * @author tstrazzere */ public class ResourceSection extends GenericChunkSection implements Chunk, ChunkSection { - private int[] resourceIDs; + // TODO : Make this an ArrayList so it's easier to add/remove + protected ArrayList resourceIDs; public ResourceSection(ChunkType chunkType, IntReader reader) { super(chunkType, reader); @@ -42,7 +46,7 @@ public ResourceSection(ChunkType chunkType, IntReader reader) { @Override public void readHeader(IntReader inputReader) throws IOException { // Initialize this variable here - resourceIDs = new int[((size / 4) - 2)]; + resourceIDs = new ArrayList<>(); } /* @@ -53,16 +57,26 @@ public void readHeader(IntReader inputReader) throws IOException { @Override public void readSection(IntReader inputReader) throws IOException { for (int i = 0; i < ((size / 4) - 2); i++) { - resourceIDs[i] = inputReader.readInt(); + addResource(inputReader.readInt()); } } + public void addResource(int value) { + resourceIDs.add(value); + } + + @Override + public int getSize() { + // Tag + Size + resourceIds + return 4 + 4 + (resourceIDs.size() * 4); + } + public int getResourceID(int index) { - return resourceIDs[index]; + return resourceIDs.get(index); } public int getResourceCount() { - return resourceIDs.length; + return resourceIDs.size(); } /* @@ -75,4 +89,26 @@ public int getResourceCount() { public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { return null; } + + /* + * (non-Javadoc) + * + * @see android.content.res.chunk.types.Chunk#toBytes() + */ + @Override + public byte[] toBytes() { + byte[] header = super.toBytes(); + + ByteBuffer offsetBuffer = ByteBuffer.allocate(resourceIDs.size() * 4).order(ByteOrder.LITTLE_ENDIAN); + + for (int id : resourceIDs) { + offsetBuffer.putInt(id); + } + byte[] body = offsetBuffer.array(); + + return ByteBuffer.allocate(header.length + body.length) + .put(header) + .put(body) + .array(); + } } diff --git a/src/main/java/android/content/res/chunk/sections/StringSection.java b/src/main/java/android/content/res/chunk/sections/StringSection.java index d97334f..7e971cb 100644 --- a/src/main/java/android/content/res/chunk/sections/StringSection.java +++ b/src/main/java/android/content/res/chunk/sections/StringSection.java @@ -15,14 +15,17 @@ */ package android.content.res.chunk.sections; -import java.io.IOException; -import java.util.ArrayList; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.PoolItem; import android.content.res.chunk.types.Chunk; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; + public class StringSection extends GenericChunkSection implements Chunk, ChunkSection { // This specific tag appears unused but might need to be implemented? or used as an unknown? @@ -65,7 +68,7 @@ public void readSection(IntReader inputReader) throws IOException { } if (!stringChunkPool.isEmpty()) { - readPool(stringChunkPool, stringChunkCount, stringChunkFlags, inputReader); + readPool(stringChunkPool, stringChunkFlags, inputReader); } // TODO : Does this need the flags? @@ -75,15 +78,15 @@ public void readSection(IntReader inputReader) throws IOException { } if (!styleChunkPool.isEmpty()) { - readPool(styleChunkPool, styleChunkCount, stringChunkFlags, inputReader); + readPool(styleChunkPool, stringChunkFlags, inputReader); } } // TODO : Ensure we goto the proper offset in the case it isn't in proper order - private void readPool(ArrayList pool, int poolSize, int flags, IntReader inputReader) throws IOException { + private void readPool(ArrayList pool, int flags, IntReader inputReader) throws IOException { int offset = 0; for (PoolItem item : pool) { - // XXX: This assumes that the pool is ordered... + // TODO: This assumes that the pool is ordered... inputReader.skip(item.getOffset() - offset); offset = item.getOffset(); @@ -112,7 +115,6 @@ private void readPool(ArrayList pool, int poolSize, int flags, IntRead } public String getString(int index) { - // index--; if ((index > -1) && (index < stringChunkCount)) { return stringChunkPool.get(index).getString(); } @@ -128,4 +130,149 @@ public String getStyle(int index) { public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { return null; } + + @Override + public int getSize() { + int stringDataSize = 0; + int previousSize; + for (PoolItem item : stringChunkPool) { + previousSize = stringDataSize; + // TODO: This is potentially wrong + // length identifier + stringDataSize += ((stringChunkFlags & UTF8_FLAG) == 0) ? 2 : 1; + // actual string data + stringDataSize += item.getString().length() * (((stringChunkFlags & UTF8_FLAG) == 0) ? 2 : 1); + // buffer + int bufferSize = 4 - (stringDataSize - previousSize) % 4; + if (bufferSize > 0 && bufferSize < 4) { + stringDataSize += bufferSize; + } + } + + int styleDataSize = 0; + for (PoolItem item : styleChunkPool) { + stringDataSize += item.getString().length() * (((stringChunkFlags & UTF8_FLAG) == 0) ? 2 : 1); + } + + return (2 * 4) + // Header + (5 * 4) + // static sections + (stringChunkCount * 4) + // string table offset size + stringDataSize + + (styleChunkCount * 4) + // style table offset size + styleDataSize; + } + + /* + * (non-Javadoc) + * + * @see android.content.res.chunk.types.Chunk#toBytes() + */ + @Override + public byte[] toBytes() { + byte[] header = super.toBytes(); + + // TODO : We need to ensure these are already "sorted" + ByteBuffer offsetBuffer = ByteBuffer.allocate(stringChunkPool.size() * 4) + .order(ByteOrder.LITTLE_ENDIAN); + int offset = 0; + int previousOffset; + ArrayList stringData = new ArrayList<>(); + for (PoolItem item : stringChunkPool) { + offsetBuffer.putInt(offset); + previousOffset = offset; + + // TODO : Ensure this is properly handled, potentially a ULEB128? + // Add string length bytes + if (item.getString().length() > 255) { + System.err.println("Error, string length is greater than the current expected lengths!"); + } + offset += ((stringChunkFlags & UTF8_FLAG) == 0) ? 2 : 1; + + // Add length of string based on if UTF-8 flag is enabled + offset += item.getString().length() * (((stringChunkFlags & UTF8_FLAG) == 0) ? 2 : 1); + + // Add buffer + int bufferSize = 4 - ((offset - previousOffset) % 4); + if (bufferSize > 0 && bufferSize < 4) { + offset += bufferSize; + } + + // Append actual length + data + ByteBuffer length; + if ((stringChunkFlags & UTF8_FLAG) == 0) { + length = ByteBuffer.allocate(2) + .order(ByteOrder.LITTLE_ENDIAN) + .putShort((short) item.getString().length()); + } else { + length = ByteBuffer.allocate(1) + .put((byte) item.getString().length()); + } + + ByteBuffer string = ByteBuffer.allocate(item.getString().length() * (((stringChunkFlags & UTF8_FLAG) == 0) ? 2 : 1)) + .order(ByteOrder.LITTLE_ENDIAN); + for (byte character : item.getString().getBytes()) { + if ((stringChunkFlags & UTF8_FLAG) == 0) { + string.putShort(character); + } else { + string.put(character); + } + } + + ByteBuffer stringDataBuffer = ByteBuffer.allocate(offset - previousOffset) + .order(ByteOrder.LITTLE_ENDIAN) + .put(length.array()) + .put(string.array()); + + if (bufferSize > 0 && bufferSize < 4) { + // TODO : fix this + byte[] buffer = new byte[bufferSize]; + Arrays.fill(buffer, (byte) 0x00); + stringDataBuffer.put(buffer); + } + + stringData.add(stringDataBuffer.array()); + } + + // Combine strings into one buffer + ByteBuffer stringsBuffer = ByteBuffer.allocate(offsetBuffer.capacity() + offset) + .order(ByteOrder.LITTLE_ENDIAN) + .put(offsetBuffer.array()); + for (byte[] data : stringData) { + stringsBuffer.put(data); + } + byte[] strings = stringsBuffer.array(); + +// byte[] styles = new byte[]{0x00}; + + int newStringChunkOffset = 0; + if (!stringChunkPool.isEmpty()) { + newStringChunkOffset = (5 * 4) /* header + 3 other ints above it */ + + stringChunkCount * 4 /* index table size */ + + 8 /* (this space and the style chunk offset */; + } + + int newStyleChunkOffset = 0; + if (!styleChunkPool.isEmpty()) { + newStyleChunkOffset = (6 * 4) /* header + 4 other ints above it */ + + styleChunkCount * 4 /* index table size */ + + 8 /* (this space and the style chunk offset */; + } + + byte[] body = ByteBuffer.allocate(5 * 4) + .order(ByteOrder.LITTLE_ENDIAN) + .putInt(stringChunkCount) + .putInt(styleChunkCount) + .putInt(stringChunkFlags) + .putInt(newStringChunkOffset) + .putInt(newStyleChunkOffset) + .array(); + + return ByteBuffer.allocate(header.length + body.length + strings.length /*+ styles.length*/) + .order(ByteOrder.LITTLE_ENDIAN) + .put(header) + .put(body) + .put(strings) +// .put(styles) + .array(); + } } diff --git a/src/main/java/android/content/res/chunk/types/AXMLHeader.java b/src/main/java/android/content/res/chunk/types/AXMLHeader.java index fef2c50..1c8c578 100644 --- a/src/main/java/android/content/res/chunk/types/AXMLHeader.java +++ b/src/main/java/android/content/res/chunk/types/AXMLHeader.java @@ -15,18 +15,19 @@ */ package android.content.res.chunk.types; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.sections.ResourceSection; import android.content.res.chunk.sections.StringSection; +import java.io.IOException; + /** * ChunkType which is for the AXMLHeader, should be at the beginning and only the beginning of the files. - * + *

* TODO : Check and warn if not at the beginning - * + * TODO : toBytes() needs to understand the correct size of the entire file + * * @author tstrazzere */ public class AXMLHeader extends GenericChunk { @@ -55,4 +56,14 @@ public void readHeader(IntReader inputReader) throws IOException { public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { return indent(indent) + ""; } + + /* + * (non-Javadoc) + * + * @see android.content.res.chunk.types.Chunk#toBytes() + */ + @Override + public byte[] toBytes() { + return super.toBytes(); + } } diff --git a/src/main/java/android/content/res/chunk/types/Attribute.java b/src/main/java/android/content/res/chunk/types/Attribute.java index c92cfdc..8beb4b9 100644 --- a/src/main/java/android/content/res/chunk/types/Attribute.java +++ b/src/main/java/android/content/res/chunk/types/Attribute.java @@ -15,17 +15,19 @@ */ package android.content.res.chunk.types; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.AttributeType; import android.content.res.chunk.ChunkType; import android.content.res.chunk.sections.ResourceSection; import android.content.res.chunk.sections.StringSection; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + /** * Specific type of Chunk which contains the metadata - * + * * @author tstrazzere */ public class Attribute implements Chunk { @@ -77,7 +79,7 @@ public ChunkType getChunkType() { */ @Override public int getSize() { - return 4; + return 4 * 5; } /* @@ -121,4 +123,21 @@ public String toXML(StringSection stringSection, ResourceSection resourceSection return buffer.toString(); } + + /* + * (non-Javadoc) + * + * @see android.content.res.chunk.types.Chunk#toBytes() + */ + @Override + public byte[] toBytes() { + return ByteBuffer.allocate(getSize()) + .order(ByteOrder.LITTLE_ENDIAN) + .putInt(uri) + .putInt(name) + .putInt(stringData) + .putInt(attributeType) + .putInt(data) + .array(); + } } diff --git a/src/main/java/android/content/res/chunk/types/Buffer.java b/src/main/java/android/content/res/chunk/types/Buffer.java index 167fdce..8e9430d 100644 --- a/src/main/java/android/content/res/chunk/types/Buffer.java +++ b/src/main/java/android/content/res/chunk/types/Buffer.java @@ -15,20 +15,20 @@ */ package android.content.res.chunk.types; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.sections.ResourceSection; import android.content.res.chunk.sections.StringSection; +import java.io.IOException; + /** * This "buffer" chunk is currently being used for empty space, though it might not be needed - * + *

* TODO: Verify this is needed - * + *

* TODO: If kept, should potentially alert/warn if it happens - * + * * @author tstrazzere */ public class Buffer implements Chunk { @@ -77,4 +77,16 @@ public int getSize() { public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { return null; } + + /* + * (non-Javadoc) + * + * @see android.content.res.chunk.types.Chunk#toBytes() + */ + @Override + public byte[] toBytes() { + return new byte[]{ + 0x00, 0x00, 0x00, 0x00 + }; + } } diff --git a/src/main/java/android/content/res/chunk/types/Chunk.java b/src/main/java/android/content/res/chunk/types/Chunk.java index fa3a6af..a190aa9 100644 --- a/src/main/java/android/content/res/chunk/types/Chunk.java +++ b/src/main/java/android/content/res/chunk/types/Chunk.java @@ -15,23 +15,23 @@ */ package android.content.res.chunk.types; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.sections.ResourceSection; import android.content.res.chunk.sections.StringSection; +import java.io.IOException; + /** * Generic interface for everything that is at minimum a "chunk" - * + * * @author tstrazzere */ public interface Chunk { /** * Read the header section of the chunk - * + * * @param reader * @throws IOException */ @@ -48,6 +48,7 @@ public interface Chunk { public int getSize(); // XXX: Not sure this needs to exist + /** * @return a String representation of the Chunk */ @@ -61,4 +62,11 @@ public interface Chunk { */ public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent); + /** + * Get the a byte[] for the chunk + * + * @return + */ + public byte[] toBytes(); + } diff --git a/src/main/java/android/content/res/chunk/types/EndTag.java b/src/main/java/android/content/res/chunk/types/EndTag.java index ff3451c..51a8646 100644 --- a/src/main/java/android/content/res/chunk/types/EndTag.java +++ b/src/main/java/android/content/res/chunk/types/EndTag.java @@ -15,16 +15,18 @@ */ package android.content.res.chunk.types; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.sections.ResourceSection; import android.content.res.chunk.sections.StringSection; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + /** * Specific chunk for ending sections and/or namespaces - * + * * @author tstrazzere */ public class EndTag extends GenericChunk implements Chunk { @@ -61,4 +63,28 @@ public void readHeader(IntReader inputReader) throws IOException { public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { return indent(indent) + ""; } + + /* + * (non-Javadoc) + * + * @see android.content.res.chunk.types.Chunk#toBytes() + */ + @Override + public byte[] toBytes() { + byte[] header = super.toBytes(); + + byte[] body = ByteBuffer.allocate(4 * 4) + .order(ByteOrder.LITTLE_ENDIAN) + .putInt(lineNumber) + .putInt(unknown) + .putInt(namespaceUri) + .putInt(name) + .array(); + + return ByteBuffer.allocate(header.length + body.length) + .order(ByteOrder.LITTLE_ENDIAN) + .put(header) + .put(body) + .array(); + } } diff --git a/src/main/java/android/content/res/chunk/types/GenericChunk.java b/src/main/java/android/content/res/chunk/types/GenericChunk.java index e5c5170..9a6553e 100644 --- a/src/main/java/android/content/res/chunk/types/GenericChunk.java +++ b/src/main/java/android/content/res/chunk/types/GenericChunk.java @@ -15,14 +15,16 @@ */ package android.content.res.chunk.types; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + /** * Abstract class for the generic lifting required by all Chunks - * + * * @author tstrazzere */ public abstract class GenericChunk implements Chunk { @@ -54,7 +56,7 @@ public ChunkType getChunkType() { } /* - * (non-Javadoc) + *` (non-Javadoc) * * @see android.content.res.chunk.types.Chunk#getSize() */ @@ -71,7 +73,7 @@ public int getStartPosition() { /** * @param indents - * @return a number of indents needed for properly formating XML + * @return a number of indents needed for properly formatting XML */ protected String indent(int indents) { StringBuffer buffer = new StringBuffer(); @@ -80,4 +82,17 @@ protected String indent(int indents) { } return buffer.toString(); } + + /* + * (non-Javadoc) + * + * @see android.content.res.chunk.types.Chunk#toBytes() + */ + @Override + public byte[] toBytes() { + return ByteBuffer.allocate(8) + .order(ByteOrder.LITTLE_ENDIAN) + .putInt(type.getIntType()) + .putInt(getSize()).array(); + } } diff --git a/src/main/java/android/content/res/chunk/types/NameSpace.java b/src/main/java/android/content/res/chunk/types/NameSpace.java index 15d2d68..6ca5e48 100644 --- a/src/main/java/android/content/res/chunk/types/NameSpace.java +++ b/src/main/java/android/content/res/chunk/types/NameSpace.java @@ -15,16 +15,18 @@ */ package android.content.res.chunk.types; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.sections.ResourceSection; import android.content.res.chunk.sections.StringSection; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + /** * Namespace Chunk - used for denoting the borders of the XML boundries - * + * * @author tstrazzere */ public class NameSpace extends GenericChunk implements Chunk { @@ -88,4 +90,28 @@ public String toXML(StringSection stringSection, ResourceSection resourceSection return ""; } } + + /* + * (non-Javadoc) + * + * @see android.content.res.chunk.types.Chunk#toBytes() + */ + @Override + public byte[] toBytes() { + byte[] header = super.toBytes(); + + byte[] body = ByteBuffer.allocate(4 * 4) + .order(ByteOrder.LITTLE_ENDIAN) + .putInt(lineNumber) + .putInt(unknown) + .putInt(prefix) + .putInt(uri) + .array(); + + return ByteBuffer.allocate(header.length + body.length) + .order(ByteOrder.LITTLE_ENDIAN) + .put(header) + .put(body) + .array(); + } } \ No newline at end of file diff --git a/src/main/java/android/content/res/chunk/types/StartTag.java b/src/main/java/android/content/res/chunk/types/StartTag.java index d9c15ae..b35f866 100644 --- a/src/main/java/android/content/res/chunk/types/StartTag.java +++ b/src/main/java/android/content/res/chunk/types/StartTag.java @@ -15,16 +15,19 @@ */ package android.content.res.chunk.types; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.sections.ResourceSection; import android.content.res.chunk.sections.StringSection; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; + /** * StartTag type of Chunk, differs from a Namespace as there will be specific metadata inside of it - * + * * @author tstrazzere */ public class StartTag extends GenericChunk implements Chunk { @@ -36,7 +39,7 @@ public class StartTag extends GenericChunk implements Chunk { private int flags; private int attributeCount; private int classAttribute; - private Attribute[] attributes; + private ArrayList attributes; public StartTag(ChunkType chunkType, IntReader inputReader) { super(chunkType, inputReader); @@ -57,9 +60,9 @@ public void readHeader(IntReader inputReader) throws IOException { attributeCount = inputReader.readInt(); classAttribute = inputReader.readInt(); if (attributeCount > 0) { - attributes = new Attribute[attributeCount]; + attributes = new ArrayList<>(); for (int i = 0; i < attributeCount; i++) { - attributes[i] = new Attribute(inputReader); + attributes.add(new Attribute(inputReader)); } } } @@ -68,6 +71,11 @@ public int getLineNumber() { return lineNumber; } + @Override + public int getSize() { + return (9 * 4) + (attributeCount * 20); + } + /* * (non-Javadoc) * @@ -84,7 +92,7 @@ public String toXML(StringSection stringSection, ResourceSection resourceSection for (int i = 0; i < attributeCount; i++) { buffer.append(indent(indent + 1)); - buffer.append(attributes[i].toXML(stringSection, resourceSection, indent)); + buffer.append(attributes.get(i).toXML(stringSection, resourceSection, indent)); buffer.append("\n"); } @@ -93,4 +101,45 @@ public String toXML(StringSection stringSection, ResourceSection resourceSection return buffer.toString(); } + + /* + * (non-Javadoc) + * + * @see android.content.res.chunk.types.Chunk#toBytes() + */ + @Override + public byte[] toBytes() { + byte[] header = super.toBytes(); + + byte[] staticBody = ByteBuffer.allocate(7 * 4) + .order(ByteOrder.LITTLE_ENDIAN) + .putInt(lineNumber) + .putInt(unknown) + .putInt(namespaceUri) + .putInt(name) + .putInt(flags) + .putInt(attributeCount) + .putInt(classAttribute) + .array(); + + byte[] dynamicBody; + if (attributeCount > 0) { + ByteBuffer attributeData = ByteBuffer.allocate(attributeCount * 20) + .order(ByteOrder.LITTLE_ENDIAN); + for (Attribute attribute : attributes) { + attributeData.put(attribute.toBytes()); + } + + dynamicBody = attributeData.array(); + } else { + dynamicBody = new byte[]{}; + } + + return ByteBuffer.allocate(header.length + staticBody.length + dynamicBody.length) + .order(ByteOrder.LITTLE_ENDIAN) + .put(header) + .put(staticBody) + .put(dynamicBody) + .array(); + } } diff --git a/src/main/java/android/content/res/chunk/types/TextTag.java b/src/main/java/android/content/res/chunk/types/TextTag.java index 3008990..9715386 100644 --- a/src/main/java/android/content/res/chunk/types/TextTag.java +++ b/src/main/java/android/content/res/chunk/types/TextTag.java @@ -15,16 +15,18 @@ */ package android.content.res.chunk.types; -import java.io.IOException; - import android.content.res.IntReader; import android.content.res.chunk.ChunkType; import android.content.res.chunk.sections.ResourceSection; import android.content.res.chunk.sections.StringSection; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + /** * Specific Chunk which contains a text key and value - * + * * @author tstrazzere */ public class TextTag extends GenericChunk implements Chunk { @@ -69,4 +71,29 @@ public String toXML(StringSection stringSection, ResourceSection resourceSection return buffer.toString(); } + + /* + * (non-Javadoc) + * + * @see android.content.res.chunk.types.Chunk#toBytes() + */ + @Override + public byte[] toBytes() { + byte[] header = super.toBytes(); + + byte[] body = ByteBuffer.allocate(5 * 4) + .order(ByteOrder.LITTLE_ENDIAN) + .putInt(lineNumber) + .putInt(unknown) + .putInt(name) + .putInt(unknown2) + .putInt(unknown3) + .array(); + + return ByteBuffer.allocate(header.length + body.length) + .order(ByteOrder.LITTLE_ENDIAN) + .put(header) + .put(body) + .array(); + } } diff --git a/src/main/java/diff/rednaga/AXMLPrinter.java b/src/main/java/diff/rednaga/AXMLPrinter.java index 4ec375a..eeb020d 100644 --- a/src/main/java/diff/rednaga/AXMLPrinter.java +++ b/src/main/java/diff/rednaga/AXMLPrinter.java @@ -15,18 +15,16 @@ */ package diff.rednaga; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - import android.content.res.AXMLResource; +import java.io.*; +import java.util.Properties; + /** * A slimmed down version of the original AXMLPrinter from Dmitry Skiba. - * + *

* Prints xml document from Android's binary xml file. - * + * * @author Tim Strazzere */ public class AXMLPrinter { @@ -51,7 +49,7 @@ public class AXMLPrinter { } - public static void main(String[] arguments) { + public static void main(String[] arguments) throws IOException { if (arguments.length < 1) { System.out.println("Usage: AXMLPrinter "); return; @@ -63,14 +61,25 @@ public static void main(String[] arguments) { return; } + FileInputStream fileInputStream = null; + FileOutputStream fileOutputStream = null; try { AXMLResource axmlResource = new AXMLResource(); - axmlResource.read(new FileInputStream(arguments[0])); + fileInputStream = new FileInputStream(arguments[0]); + axmlResource.read(fileInputStream); axmlResource.print(); + if (arguments.length > 1) { + File file = new File(arguments[1]); + fileOutputStream = new FileOutputStream(file); + axmlResource.write(fileOutputStream); + } } catch (Exception e) { e.printStackTrace(); + } finally { + fileInputStream.close(); + fileOutputStream.close(); } } diff --git a/src/test/java/android/content/res/chunk/sections/ResourceSectionTest.java b/src/test/java/android/content/res/chunk/sections/ResourceSectionTest.java new file mode 100644 index 0000000..978b81c --- /dev/null +++ b/src/test/java/android/content/res/chunk/sections/ResourceSectionTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015 Red Naga + * + * 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 android.content.res.chunk.sections; + +import android.content.res.IntReader; +import android.content.res.chunk.ChunkType; +import android.content.res.chunk.types.GenericChunk; +import junit.framework.TestCase; +import org.junit.Assert; + +import java.io.IOException; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author tstrazzere + */ +public class ResourceSectionTest extends TestCase { + // Implement a new type of Generic chunk just to test specific functionality + protected class TestResourceSection extends ResourceSection { + public TestResourceSection(ChunkType chunkType, IntReader inputReader) { + super(chunkType, inputReader); + } + + @Override + public void readSection(IntReader inputReader) throws IOException { + addResource(0); + addResource(1); + addResource(2); + addResource(3); + addResource(4); + } + } + + private GenericChunk underTest; + + private IntReader mockReader; + private ChunkType mockChunkType; + + + @Override + public void setUp() throws Exception { + super.setUp(); + mockReader = mock(IntReader.class); + // Mock the size read -- however this should /not/ be the output! + // We expect that the section truncates the to proper length since it's assumed to have been + // modified since the size differs + when(mockReader.readInt()).thenReturn(0xBB60); + + mockChunkType = mock(ChunkType.class); + + underTest = new TestResourceSection(mockChunkType, mockReader); + } + + public void testToBytes() throws Exception { + // (chunk type) RESOURCE_SECTION + (size) 0xBB60 + (int[]) resourceIDs + byte[] expected = { + // RESOURCE_SECTION + (byte) 0x80, (byte) 0x01, (byte) 0x08, (byte) 0x00, + // (size) 4 (tag) + 4 (size) + 5 * 4 (resource ids) + (byte) 0x1C, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // (int) 0 + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // (int) 1 + (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // (int) 2 + (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // (int) 3 + (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // (int) 4 + (byte) 0x04, (byte) 0x00, (byte) 0x00, (byte) 0x00 + }; + + when(mockChunkType.getIntType()).thenReturn(ChunkType.RESOURCE_SECTION.getIntType()); + + byte[] actual = underTest.toBytes(); + + Assert.assertArrayEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/android/content/res/chunk/sections/StringSectionTest.java b/src/test/java/android/content/res/chunk/sections/StringSectionTest.java new file mode 100644 index 0000000..b8c5595 --- /dev/null +++ b/src/test/java/android/content/res/chunk/sections/StringSectionTest.java @@ -0,0 +1,90 @@ +package android.content.res.chunk.sections; + +import android.content.res.IntReader; +import android.content.res.chunk.ChunkType; +import junit.framework.TestCase; +import org.junit.Assert; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by diff on 9/29/15. + */ +public class StringSectionTest extends TestCase { + + private StringSection underTest; + + private IntReader mockReader; + private ChunkType mockChunkType; + + @Override + public void setUp() throws Exception { + super.setUp(); + mockReader = mock(IntReader.class); + // Mock the string section data + when(mockReader.readInt()).thenReturn( + (12 * 4), // size + 0x02, // string count + 0x00, // style count + 0x100, // string chunk flags + 0x24, // string pool offset + 0x00, // style pool offset + // string pool + 0x00, // item 1 offset + 0x08); // item 2 offset + when(mockReader.readByte()).thenReturn( + 0x04, // item 1 length + 0x64, // d + 0x69, // i + 0x66, // f + 0x66, // f + // 0x00, // buffer + // 0x00, // buffer + // 0x00, // buffer + 0x03, // item 2 length + 0x74, // t + 0x69, // i + 0x6D); // m + + mockChunkType = mock(ChunkType.class); + when(mockChunkType.getIntType()).thenReturn(ChunkType.STRING_SECTION.getIntType()); + + underTest = new StringSection(mockChunkType, mockReader); + } + + public void testToBytes() throws Exception { + byte[] expected = { + // STRING_SECTION + (byte) 0x01, (byte) 0x00, (byte) 0x1C, (byte) 0x00, + // size + (byte) (12 * 4), (byte) 0x00, (byte) 0x00, (byte) 0x00, + // string count + (byte) 0x02, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // TODO : Really should test this when I get a good sample + // style count + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // string chunk flags + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, + // string pool offset + (byte) 0x24, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // style pool offset + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // string pool offsets - item 1 + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // string pool offsets - item 2 + (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // string data - item 1 + // len(diff) d i f + (byte) 0x04, (byte) 0x64, (byte) 0x69, (byte) 0x66, + // f (buffer ----------------------------) + (byte) 0x66, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // string data - item 2 + // len(tim) t i m + (byte) 0x03, (byte) 0x74, (byte) 0x69, (byte) 0x6d + }; + + byte[] actual = underTest.toBytes(); + Assert.assertArrayEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/android/content/res/chunk/types/AttributeTest.java b/src/test/java/android/content/res/chunk/types/AttributeTest.java new file mode 100644 index 0000000..ea529d2 --- /dev/null +++ b/src/test/java/android/content/res/chunk/types/AttributeTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015 Red Naga + * + * 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 android.content.res.chunk.types; + +import android.content.res.IntReader; +import junit.framework.TestCase; +import org.junit.Assert; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author tstrazzere + */ +public class AttributeTest extends TestCase { + + private Attribute underTest; + + private IntReader mockReader; + + + @Override + public void setUp() throws Exception { + super.setUp(); + mockReader = mock(IntReader.class); + // Mock the attribute data + when(mockReader.readInt()).thenReturn(0x35, 0x60, 0x10, 0x3000008, 0x2C); + + underTest = new Attribute(mockReader); + } + + public void testToBytes() throws Exception { + byte[] expected = { + // uri + (byte) 0x35, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // name + (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // string data + (byte) 0x10, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // type + (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0x03, + // data + (byte) 0x2C, (byte) 0x00, (byte) 0x00, (byte) 0x00 + }; + + byte[] actual = underTest.toBytes(); + Assert.assertArrayEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/android/content/res/chunk/types/EndTagTest.java b/src/test/java/android/content/res/chunk/types/EndTagTest.java new file mode 100644 index 0000000..26c3190 --- /dev/null +++ b/src/test/java/android/content/res/chunk/types/EndTagTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Red Naga + * + * 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 android.content.res.chunk.types; + +import android.content.res.IntReader; +import android.content.res.chunk.ChunkType; +import junit.framework.TestCase; +import org.junit.Assert; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author tstrazzere + */ +public class EndTagTest extends TestCase { + + private EndTag underTest; + + private IntReader mockReader; + private ChunkType mockChunkType; + + @Override + public void setUp() throws Exception { + super.setUp(); + mockReader = mock(IntReader.class); + // Mock the end tag data + when(mockReader.readInt()).thenReturn(0x18, 0x19, 0xFFFFFFFF, 0xFFFFFFFF, 0x46); + + mockChunkType = mock(ChunkType.class); + when(mockChunkType.getIntType()).thenReturn(ChunkType.END_TAG.getIntType()); + + underTest = new EndTag(mockChunkType, mockReader); + } + + public void testToBytes() throws Exception { + byte[] expected = { + // END_TAG + (byte) 0x03, (byte) 0x01, (byte) 0x10, (byte) 0x00, + // size + (byte) 0x18, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // line number + (byte) 0x19, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // unknown + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + // namespace + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + // name + (byte) 0x46, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + byte[] actual = underTest.toBytes(); + Assert.assertArrayEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/android/content/res/chunk/types/GenericChunkTest.java b/src/test/java/android/content/res/chunk/types/GenericChunkTest.java new file mode 100644 index 0000000..1338730 --- /dev/null +++ b/src/test/java/android/content/res/chunk/types/GenericChunkTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2015 Red Naga + * + * 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 android.content.res.chunk.types; + +import android.content.res.IntReader; +import android.content.res.chunk.ChunkType; +import android.content.res.chunk.sections.ResourceSection; +import android.content.res.chunk.sections.StringSection; +import junit.framework.TestCase; +import org.junit.Assert; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author tstrazzere + */ +public class GenericChunkTest extends TestCase { + + // Implement a new type of Generic chunk just to test specific functionality + protected class TestChunk extends GenericChunk { + + public TestChunk(ChunkType chunkType, IntReader inputReader) { + super(chunkType, inputReader); + } + + @Override + public void readHeader(IntReader reader) throws IOException { + + } + + @Override + public String toXML(StringSection stringSection, ResourceSection resourceSection, int indent) { + return null; + } + + @Override + public byte[] toBytes() { + byte[] chunks = super.toBytes(); + return ByteBuffer.allocate(chunks.length + 1).put(chunks).put((byte) 0xFF).array(); + } + } + + private GenericChunk underTest; + + private IntReader mockReader; + private ChunkType mockChunkType; + + + @Override + public void setUp() throws Exception { + super.setUp(); + mockReader = mock(IntReader.class); + // Mock the size read + when(mockReader.readInt()).thenReturn(0xBB60); + + mockChunkType = mock(ChunkType.class); + when(mockChunkType.getIntType()).thenReturn(ChunkType.AXML_HEADER.getIntType()); + + underTest = new TestChunk(mockChunkType, mockReader); + } + + public void testToBytes() throws Exception { + // (chunk type) AXML_HEADER + (size) 0xBB60 + (TestChunk.toBytes() addition) 0xFF + byte[] expected = { + // AXML_HEADER + (byte) 0x03, (byte) 0x00, (byte) 0x08, (byte) 0x00, + // (size) 0xBB60 + (byte) 0x60, (byte) 0xBB, (byte) 0x00, (byte) 0x00, + // (TestChunk.toBytes() addition) 0xFF + (byte) 0xFF + }; + + byte[] actual = underTest.toBytes(); + + Assert.assertArrayEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/android/content/res/chunk/types/NameSpaceTest.java b/src/test/java/android/content/res/chunk/types/NameSpaceTest.java new file mode 100644 index 0000000..43856be --- /dev/null +++ b/src/test/java/android/content/res/chunk/types/NameSpaceTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Red Naga + * + * 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 android.content.res.chunk.types; + +import android.content.res.IntReader; +import android.content.res.chunk.ChunkType; +import junit.framework.TestCase; +import org.junit.Assert; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author tstrazzere + */ +public class NameSpaceTest extends TestCase { + + private NameSpace underTest; + + private IntReader mockReader; + private ChunkType mockChunkType; + + @Override + public void setUp() throws Exception { + super.setUp(); + mockReader = mock(IntReader.class); + // Mock the namespace data + when(mockReader.readInt()).thenReturn(0x18, 0x19, 0xFFFFFFFF, 0x5C, 0x12); + + mockChunkType = mock(ChunkType.class); + when(mockChunkType.getIntType()).thenReturn(ChunkType.START_NAMESPACE.getIntType()); + + underTest = new NameSpace(mockChunkType, mockReader); + } + + public void testToBytes() throws Exception { + byte[] expected = { + // END_TAG + (byte) 0x00, (byte) 0x01, (byte) 0x10, (byte) 0x00, + // size + (byte) 0x18, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // line number + (byte) 0x19, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // unknown + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + // prefix + (byte) 0x5C, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // uri + (byte) 0x12, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + byte[] actual = underTest.toBytes(); + Assert.assertArrayEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/android/content/res/chunk/types/StartTagTest.java b/src/test/java/android/content/res/chunk/types/StartTagTest.java new file mode 100644 index 0000000..68871ce --- /dev/null +++ b/src/test/java/android/content/res/chunk/types/StartTagTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 2015 Red Naga + * + * 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 android.content.res.chunk.types; + +import android.content.res.IntReader; +import android.content.res.chunk.ChunkType; +import junit.framework.TestCase; +import org.junit.Assert; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author tstrazzere + */ +public class StartTagTest extends TestCase { + + private StartTag underTest; + + private IntReader mockReader; + private ChunkType mockChunkType; + + @Override + public void setUp() throws Exception { + super.setUp(); + mockReader = mock(IntReader.class); + // Mock the namespace data + when(mockReader.readInt()).thenReturn(9 * 4, 0x17, 0xFFFFFFFF, 0xFFFFFFFF, 0x41, 0x140014, 0, 0); + + mockChunkType = mock(ChunkType.class); + when(mockChunkType.getIntType()).thenReturn(ChunkType.START_TAG.getIntType()); + + underTest = new StartTag(mockChunkType, mockReader); + } + + public void testToBytes() throws Exception { + byte[] expected = { + // START_TAG + (byte) 0x02, (byte) 0x01, (byte) 0x10, (byte) 0x00, + // size + (byte) (9 * 4), (byte) 0x00, (byte) 0x00, (byte) 0x00, + // line number + (byte) 0x17, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // unknown + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + // namespace uri + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + // name + (byte) 0x41, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // flags + (byte) 0x14, (byte) 0x00, (byte) 0x14, (byte) 0x00, + // attribute count + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // attribute class + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + }; + + byte[] actual = underTest.toBytes(); + Assert.assertArrayEquals(expected, actual); + } +} \ No newline at end of file diff --git a/src/test/java/android/content/res/chunk/types/TextTagTest.java b/src/test/java/android/content/res/chunk/types/TextTagTest.java new file mode 100644 index 0000000..ee6f3e5 --- /dev/null +++ b/src/test/java/android/content/res/chunk/types/TextTagTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 Red Naga + * + * 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 android.content.res.chunk.types; + +import android.content.res.IntReader; +import android.content.res.chunk.ChunkType; +import junit.framework.TestCase; +import org.junit.Assert; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author tstrazzere + */ +public class TextTagTest extends TestCase { + + private TextTag underTest; + + private IntReader mockReader; + private ChunkType mockChunkType; + + @Override + public void setUp() throws Exception { + super.setUp(); + mockReader = mock(IntReader.class); + // Mock the text tag data + when(mockReader.readInt()).thenReturn(7 * 4, 0x17, 0xFFFFFFFF, 0x1C, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); + + mockChunkType = mock(ChunkType.class); + when(mockChunkType.getIntType()).thenReturn(ChunkType.TEXT_TAG.getIntType()); + + underTest = new TextTag(mockChunkType, mockReader); + } + + public void testToBytes() throws Exception { + byte[] expected = { + // TEXT_TAG + (byte) 0x04, (byte) 0x01, (byte) 0x10, (byte) 0x00, + // size + (byte) (7 * 4), (byte) 0x00, (byte) 0x00, (byte) 0x00, + // line number + (byte) 0x17, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // unknown + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + // name + (byte) 0x1C, (byte) 0x00, (byte) 0x00, (byte) 0x00, + // unknown2 + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + // unknown3 + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF + }; + + byte[] actual = underTest.toBytes(); + Assert.assertArrayEquals(expected, actual); + } +} \ No newline at end of file