-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from marc-christian-schulze/optimizations
Optimizations
- Loading branch information
Showing
18 changed files
with
593 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<groupId>com.github.marc-christian-schulze.structs4java</groupId> | ||
<artifactId>examples</artifactId> | ||
<version>1.0.54-SNAPSHOT</version> | ||
<packaging>pom</packaging> | ||
|
||
<modules> | ||
<module>zip-file-format</module> | ||
</modules> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
<groupId>com.github.marc-christian-schulze.structs4java</groupId> | ||
<artifactId>examples-zip-file-format</artifactId> | ||
<version>1.0.54-SNAPSHOT</version> | ||
|
||
<properties> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<maven.compiler.source>17</maven.compiler.source> | ||
<maven.compiler.target>17</maven.compiler.target> | ||
</properties> | ||
|
||
<description>Example project showing how to define file structures using the example of ZIP files.</description> | ||
|
||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>com.github.marc-christian-schulze.structs4java</groupId> | ||
<artifactId>structs4java-maven-plugin</artifactId> | ||
<version>1.0.54-SNAPSHOT</version> | ||
<executions> | ||
<execution> | ||
<id>compile-structs</id> | ||
<goals> | ||
<goal>compile</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>junit</groupId> | ||
<artifactId>junit</artifactId> | ||
<version>4.13.2</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package examples.zip; | ||
|
||
struct LocalFileHeader { | ||
uint8_t signature[4]; | ||
uint16_t version; | ||
bitfield uint16_t { | ||
uint16_t generalPurposeFlags : 16; | ||
} | ||
uint16_t compressionMethod; | ||
uint16_t lastModificationTime; | ||
uint16_t lastModificationDate; | ||
uint32_t crc32OfUncompressedData; | ||
uint32_t compressedSizeInBytes; | ||
uint32_t uncompressedSizeInBytes; | ||
uint16_t fileNameLengthInBytes sizeof(fileName); | ||
uint16_t extraFieldLengthInBytes sizeof(extraFields); | ||
char fileName[]; | ||
ExtraFieldRecord extraFields[]; | ||
} | ||
|
||
struct ExtraFieldRecord { | ||
uint8_t signature[2]; | ||
uint16_t length sizeof(data); | ||
uint8_t data[]; | ||
} | ||
|
||
struct CentralDirectoryFileHeader { | ||
uint8_t signature[4]; | ||
uint16_t versionMadeBy; | ||
uint16_t versionNeededToExtract; | ||
bitfield uint16_t { | ||
uint16_t generalPurposeFlags : 16; | ||
} | ||
uint16_t compressionMethod; | ||
uint16_t fileLastModificationTime; | ||
uint16_t fileLastModificationDate; | ||
uint32_t crc32OfUncompressedData; | ||
uint32_t compressedSizeInBytes; | ||
uint32_t uncompressedSizeInBytes; | ||
uint16_t fileNameLengthInBytes sizeof(fileName); | ||
uint16_t extraFieldLengthInBytes sizeof(extraFields); | ||
uint16_t fileCommentLengthInBytes sizeof(fileComment); | ||
uint16_t diskNumber; | ||
uint16_t internalAttributes; | ||
uint32_t externalAttributes; | ||
uint32_t relativeOffsetOfLocalFileHeader; | ||
char fileName[]; | ||
uint8_t extraFields[]; | ||
uint8_t fileComment[]; | ||
} | ||
|
||
struct EndOfCentralDirectoryRecord { | ||
uint8_t signature[4]; | ||
uint16_t diskNumber; | ||
uint16_t diskWhereCentralDirectoryStarts; | ||
uint16_t numberOfCentralDirectoryRecordsOnThisDisk; | ||
uint16_t totalNumberOfCentralDirectoryRecords; | ||
uint32_t sizeOfCentralDirectoryInBytes; | ||
uint32_t offsetOfStartOfCentralDirectory; | ||
uint16_t commentLength sizeof(comment); | ||
uint8_t comment[]; | ||
} |
116 changes: 116 additions & 0 deletions
116
examples/zip-file-format/src/test/java/examples/zip/ZipFileReadingTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package examples.zip; | ||
|
||
import org.junit.Test; | ||
|
||
import java.io.IOException; | ||
import java.io.RandomAccessFile; | ||
import java.nio.ByteBuffer; | ||
import java.nio.ByteOrder; | ||
import java.nio.channels.FileChannel; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertTrue; | ||
|
||
public class ZipFileReadingTest { | ||
|
||
@Test | ||
public void testUsingExampleZipFile() throws IOException { | ||
try(RandomAccessFile raf = new RandomAccessFile("src/test/resources/example.zip", "r")) { | ||
ByteBuffer buffer = raf.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, raf.length()); | ||
|
||
// ZIP file specification requires little endian encoding | ||
buffer.order(ByteOrder.LITTLE_ENDIAN); | ||
|
||
// first, we need to search backwards in the ZIP file for the end of central directory | ||
assertTrue("No valid central directory found", seekToEndOfCentralDirectory(buffer)); | ||
|
||
// read from the buffer using the generated Java classes from the zip.structs file | ||
EndOfCentralDirectoryRecord eocd = EndOfCentralDirectoryRecord.read(buffer); | ||
|
||
assertEquals("We expect 3 entries in the ZIP file", 3, eocd.getNumberOfCentralDirectoryRecordsOnThisDisk()); | ||
assertEquals("We expect 3 entries in the ZIP file", 3, eocd.getTotalNumberOfCentralDirectoryRecords()); | ||
|
||
// seek to first entry of the central directory | ||
buffer.position((int) eocd.getOffsetOfStartOfCentralDirectory()); | ||
|
||
// let's retrieve the file names from the directory | ||
CentralDirectoryFileHeader cdfhDirectory = CentralDirectoryFileHeader.read(buffer); | ||
assertEquals(cdfhDirectory.getFileName(), "zip-content/"); | ||
|
||
CentralDirectoryFileHeader cdfhFile2 = CentralDirectoryFileHeader.read(buffer); | ||
assertEquals(cdfhFile2.getFileName(), "zip-content/file2.txt"); | ||
|
||
CentralDirectoryFileHeader cdfhFile1 = CentralDirectoryFileHeader.read(buffer); | ||
assertEquals(cdfhFile1.getFileName(), "zip-content/file1.txt"); | ||
|
||
// finally, let's read the file contents | ||
// first, we need to seek to the location of the local file header | ||
buffer.position((int) cdfhFile1.getRelativeOffsetOfLocalFileHeader()); | ||
// next, we read the local file header using our generated Java class | ||
LocalFileHeader fhFile1 = LocalFileHeader.read(buffer); | ||
// right after the header, the actual file content is placed | ||
byte[] contentFile1 = new byte[(int) fhFile1.getCompressedSizeInBytes()]; | ||
buffer.get(contentFile1); | ||
assertEquals("content file 1\n", new String(contentFile1)); | ||
|
||
// same for file 2 | ||
buffer.position((int) cdfhFile2.getRelativeOffsetOfLocalFileHeader()); | ||
LocalFileHeader fhFile2 = LocalFileHeader.read(buffer); | ||
byte[] contentFile2 = new byte[(int) fhFile2.getCompressedSizeInBytes()]; | ||
buffer.get(contentFile2); | ||
assertEquals("content file 2\n", new String(contentFile2)); | ||
} | ||
} | ||
|
||
private static boolean seekToEndOfCentralDirectory(ByteBuffer buffer) throws IOException { | ||
int value; | ||
|
||
// we start scanning from the end of the file | ||
buffer.position(buffer.limit()); | ||
|
||
// first search for 0x06 | ||
value = readPreviousByteFromBuffer(buffer); | ||
while(value != 0x06) { | ||
if(buffer.position() < 4) { | ||
return false; | ||
} | ||
value = readPreviousByteFromBuffer(buffer); | ||
} | ||
|
||
// now we search for 0x05 | ||
value = readPreviousByteFromBuffer(buffer); | ||
while(value != 0x05) { | ||
if(buffer.position() < 3) { | ||
return false; | ||
} | ||
value = readPreviousByteFromBuffer(buffer); | ||
} | ||
|
||
// now we search for 0x4b | ||
value = readPreviousByteFromBuffer(buffer); | ||
while(value != 0x4b) { | ||
if(buffer.position() < 2) { | ||
return false; | ||
} | ||
value = readPreviousByteFromBuffer(buffer); | ||
} | ||
|
||
// finally we search for 0x50 | ||
value = readPreviousByteFromBuffer(buffer); | ||
while(value != 0x50) { | ||
if(buffer.position() < 1) { | ||
return false; | ||
} | ||
value = readPreviousByteFromBuffer(buffer); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private static int readPreviousByteFromBuffer(ByteBuffer buffer) throws IOException { | ||
buffer.position(buffer.position() - 1); | ||
int value = buffer.get() & 0xFF; | ||
buffer.position(buffer.position() - 1); | ||
return value; | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
examples/zip-file-format/src/test/java/examples/zip/ZipFileWritingTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package examples.zip; | ||
|
||
import org.junit.Test; | ||
import static org.junit.Assert.assertEquals; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.IOException; | ||
import java.nio.ByteBuffer; | ||
import java.nio.ByteOrder; | ||
import java.util.zip.CRC32; | ||
import java.util.zip.Checksum; | ||
import java.util.zip.ZipEntry; | ||
import java.util.zip.ZipInputStream; | ||
|
||
public class ZipFileWritingTest { | ||
|
||
@Test | ||
public void testWritingZipFile() throws IOException { | ||
ByteBuffer buffer = ByteBuffer.allocate(4096); | ||
buffer.order(ByteOrder.LITTLE_ENDIAN); | ||
|
||
// our file content to store | ||
byte[] fileData = "example file content".getBytes("UTF-8"); | ||
|
||
// compute the CRC32 checksum of the file content | ||
Checksum checksum = new CRC32(); | ||
checksum.update(fileData, 0, fileData.length); | ||
long crc32 = checksum.getValue(); | ||
|
||
// first we create and write the local file header | ||
long positionOfLocalFileHeader = buffer.position(); | ||
LocalFileHeader fhFile = new LocalFileHeader(); | ||
fhFile.setSignature(ByteBuffer.wrap(new byte[]{ 0x50, 0x4b, 0x03, 0x04 })); | ||
fhFile.setFileName("example.txt"); | ||
fhFile.setCompressedSizeInBytes(fileData.length); | ||
fhFile.setUncompressedSizeInBytes(fileData.length); | ||
fhFile.setCrc32OfUncompressedData(crc32); | ||
fhFile.write(buffer); | ||
// right after the local file header, the actual file content needs to be stored | ||
buffer.put(fileData); | ||
|
||
// now we need to create the central directory header | ||
long positionOfFirstCentralDirectoryHeader = buffer.position(); | ||
CentralDirectoryFileHeader cdfh = new CentralDirectoryFileHeader(); | ||
cdfh.setSignature(ByteBuffer.wrap(new byte[]{ 0x50, 0x4b, 0x01, 0x02 })); | ||
cdfh.setFileName("example.txt"); | ||
cdfh.setCrc32OfUncompressedData(crc32); | ||
cdfh.setCompressedSizeInBytes(fileData.length); | ||
cdfh.setUncompressedSizeInBytes(fileData.length); | ||
cdfh.setRelativeOffsetOfLocalFileHeader(positionOfLocalFileHeader); | ||
cdfh.write(buffer); | ||
|
||
// finally, we need to write the closing end of central directory header | ||
EndOfCentralDirectoryRecord eocdr = new EndOfCentralDirectoryRecord(); | ||
eocdr.setSignature(ByteBuffer.wrap(new byte[]{ 0x50, 0x4b, 0x05, 0x06 })); | ||
eocdr.setOffsetOfStartOfCentralDirectory(positionOfFirstCentralDirectoryHeader); | ||
eocdr.setNumberOfCentralDirectoryRecordsOnThisDisk(1); | ||
eocdr.setTotalNumberOfCentralDirectoryRecords(1); | ||
eocdr.write(buffer); | ||
|
||
// now we read the ZIP file using Java on-board classes and verify | ||
ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(buffer.array())); | ||
ZipEntry fileEntry = zis.getNextEntry(); | ||
assertEquals("example.txt", fileEntry.getName()); | ||
byte[] fileContentRead = zis.readAllBytes(); | ||
assertEquals("example file content", new String(fileContentRead, "UTF-8")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
The `example.zip` file was created without compression using | ||
``` | ||
zip -0 -r example.zip zip-content/ | ||
``` |
Binary file not shown.
1 change: 1 addition & 0 deletions
1
examples/zip-file-format/src/test/resources/zip-content/file1.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
content file 1 |
1 change: 1 addition & 0 deletions
1
examples/zip-file-format/src/test/resources/zip-content/file2.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
content file 2 |
Oops, something went wrong.