mirror of
https://github.com/AppleCommander/ShrinkItArchive.git
synced 2024-06-02 08:41:28 +00:00
Added ability to get the CRC-16 from a ByteSource and for the MasterHeaderBlock to validate its CRC-16.
This commit is contained in:
parent
6c241ea036
commit
4371c7a730
|
@ -13,6 +13,8 @@ import java.util.GregorianCalendar;
|
||||||
*/
|
*/
|
||||||
public class ByteSource implements ByteConstants {
|
public class ByteSource implements ByteConstants {
|
||||||
private InputStream inputStream;
|
private InputStream inputStream;
|
||||||
|
private long bytesRead = 0;
|
||||||
|
private CRC16 crc = new CRC16();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a ByteSource from an InputStream.
|
* Construct a ByteSource from an InputStream.
|
||||||
|
@ -33,7 +35,12 @@ public class ByteSource implements ByteConstants {
|
||||||
* Note that an unsigned byte needs to be returned in a larger container (ie, a short or int or long).
|
* Note that an unsigned byte needs to be returned in a larger container (ie, a short or int or long).
|
||||||
*/
|
*/
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
return inputStream.read();
|
int b = inputStream.read();
|
||||||
|
if (b != -1) {
|
||||||
|
crc.update(b);
|
||||||
|
bytesRead++;
|
||||||
|
}
|
||||||
|
return b;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the next byte and fail if we are at EOF.
|
* Get the next byte and fail if we are at EOF.
|
||||||
|
@ -51,9 +58,11 @@ public class ByteSource implements ByteConstants {
|
||||||
public byte[] readBytes(int bytes) throws IOException {
|
public byte[] readBytes(int bytes) throws IOException {
|
||||||
byte[] data = new byte[bytes];
|
byte[] data = new byte[bytes];
|
||||||
int read = inputStream.read(data);
|
int read = inputStream.read(data);
|
||||||
|
bytesRead+= read;
|
||||||
if (read < bytes) {
|
if (read < bytes) {
|
||||||
throw new IOException("Requested " + bytes + " bytes, but " + read + " read");
|
throw new IOException("Requested " + bytes + " bytes, but " + read + " read");
|
||||||
}
|
}
|
||||||
|
crc.update(data);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,4 +111,24 @@ public class ByteSource implements ByteConstants {
|
||||||
data[TIMEREC_HOUR], data[TIMEREC_MINUTE], data[TIMEREC_SECOND]);
|
data[TIMEREC_HOUR], data[TIMEREC_MINUTE], data[TIMEREC_SECOND]);
|
||||||
return gc.getTime();
|
return gc.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the CRC-16 to $0000.
|
||||||
|
*/
|
||||||
|
public void resetCrc() {
|
||||||
|
crc.reset();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the current CRC-16 value.
|
||||||
|
*/
|
||||||
|
public long getCrcValue() {
|
||||||
|
return crc.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Answer with the total number of bytes read.
|
||||||
|
*/
|
||||||
|
public long getTotalBytesRead() {
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.util.Date;
|
||||||
public class MasterHeaderBlock {
|
public class MasterHeaderBlock {
|
||||||
private static final int MASTER_HEADER_LENGTH = 48;
|
private static final int MASTER_HEADER_LENGTH = 48;
|
||||||
private int masterCrc;
|
private int masterCrc;
|
||||||
|
private boolean validCrc;
|
||||||
private long totalRecords;
|
private long totalRecords;
|
||||||
private Date archiveCreateWhen;
|
private Date archiveCreateWhen;
|
||||||
private Date archiveModWhen;
|
private Date archiveModWhen;
|
||||||
|
@ -25,15 +26,11 @@ public class MasterHeaderBlock {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the Master Header Block, based on the ByteSource.
|
* Create the Master Header Block, based on the ByteSource.
|
||||||
* To avoid byte counting, we read in the fixed size header
|
|
||||||
* and then work our way through the data. When we are done,
|
|
||||||
* that data is thrown away, and we don't need to ensure
|
|
||||||
* that we've read a consistent number of bytes.
|
|
||||||
*/
|
*/
|
||||||
public MasterHeaderBlock(ByteSource bs) throws IOException {
|
public MasterHeaderBlock(ByteSource bs) throws IOException {
|
||||||
bs = new ByteSource(bs.readBytes(MASTER_HEADER_LENGTH));
|
|
||||||
bs.checkNuFileId();
|
bs.checkNuFileId();
|
||||||
masterCrc = bs.readWord();
|
masterCrc = bs.readWord();
|
||||||
|
bs.resetCrc(); // CRC is computed from this point to the end of the header
|
||||||
totalRecords = bs.readLong();
|
totalRecords = bs.readLong();
|
||||||
archiveCreateWhen = bs.readDate();
|
archiveCreateWhen = bs.readDate();
|
||||||
archiveModWhen = bs.readDate();
|
archiveModWhen = bs.readDate();
|
||||||
|
@ -44,6 +41,11 @@ public class MasterHeaderBlock {
|
||||||
} else {
|
} else {
|
||||||
masterEof = -1;
|
masterEof = -1;
|
||||||
}
|
}
|
||||||
|
// Read whatever remains of the fixed size header
|
||||||
|
while (bs.getTotalBytesRead() < MASTER_HEADER_LENGTH) {
|
||||||
|
bs.readByte();
|
||||||
|
}
|
||||||
|
validCrc = (masterCrc == bs.getCrcValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// GENERATED CODE
|
// GENERATED CODE
|
||||||
|
@ -84,4 +86,7 @@ public class MasterHeaderBlock {
|
||||||
public void setMasterEof(long masterEof) {
|
public void setMasterEof(long masterEof) {
|
||||||
this.masterEof = masterEof;
|
this.masterEof = masterEof;
|
||||||
}
|
}
|
||||||
|
public boolean isValidCrc() {
|
||||||
|
return validCrc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ public class ByteSourceTest extends TestCase {
|
||||||
ByteSource bs = new ByteSource("a".getBytes());
|
ByteSource bs = new ByteSource("a".getBytes());
|
||||||
assertEquals('a', bs.read());
|
assertEquals('a', bs.read());
|
||||||
assertEquals(-1, bs.read());
|
assertEquals(-1, bs.read());
|
||||||
|
assertEquals(1, bs.getTotalBytesRead());
|
||||||
}
|
}
|
||||||
public void testReadB() throws IOException {
|
public void testReadB() throws IOException {
|
||||||
// Just to ensure we can get more than one byte...
|
// Just to ensure we can get more than one byte...
|
||||||
|
@ -27,6 +28,7 @@ public class ByteSourceTest extends TestCase {
|
||||||
assertEquals('l', bs.read());
|
assertEquals('l', bs.read());
|
||||||
assertEquals('o', bs.read());
|
assertEquals('o', bs.read());
|
||||||
assertEquals(-1, bs.read());
|
assertEquals(-1, bs.read());
|
||||||
|
assertEquals(5, bs.getTotalBytesRead());
|
||||||
}
|
}
|
||||||
public void testReadBytesInt() throws IOException {
|
public void testReadBytesInt() throws IOException {
|
||||||
// Ensure we read the requested data.
|
// Ensure we read the requested data.
|
||||||
|
@ -34,6 +36,7 @@ public class ByteSourceTest extends TestCase {
|
||||||
assertEquals("Hello", new String(bs.readBytes(5)));
|
assertEquals("Hello", new String(bs.readBytes(5)));
|
||||||
assertEquals("World", new String(bs.readBytes(5)));
|
assertEquals("World", new String(bs.readBytes(5)));
|
||||||
assertEquals(-1, bs.read());
|
assertEquals(-1, bs.read());
|
||||||
|
assertEquals(10, bs.getTotalBytesRead());
|
||||||
}
|
}
|
||||||
public void textReadBytesIntError() {
|
public void textReadBytesIntError() {
|
||||||
// Ensure that we fail appropriately
|
// Ensure that we fail appropriately
|
||||||
|
@ -43,17 +46,20 @@ public class ByteSourceTest extends TestCase {
|
||||||
fail();
|
fail();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
assertTrue(true); // Expected
|
assertTrue(true); // Expected
|
||||||
|
assertEquals(2, bs.getTotalBytesRead());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void testCheckNuFileId() throws IOException {
|
public void testCheckNuFileId() throws IOException {
|
||||||
ByteSource bs = new ByteSource(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 });
|
ByteSource bs = new ByteSource(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5 });
|
||||||
assertTrue(bs.checkNuFileId());
|
assertTrue(bs.checkNuFileId());
|
||||||
|
assertEquals(6, bs.getTotalBytesRead());
|
||||||
bs = new ByteSource("NotNuFile".getBytes());
|
bs = new ByteSource("NotNuFile".getBytes());
|
||||||
assertFalse(bs.checkNuFileId());
|
assertFalse(bs.checkNuFileId());
|
||||||
}
|
}
|
||||||
public void testCheckNuFxId() throws IOException {
|
public void testCheckNuFxId() throws IOException {
|
||||||
ByteSource bs = new ByteSource(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xd8 });
|
ByteSource bs = new ByteSource(new byte[] { 0x4e, (byte)0xf5, 0x46, (byte)0xd8 });
|
||||||
assertTrue(bs.checkNuFxId());
|
assertTrue(bs.checkNuFxId());
|
||||||
|
assertEquals(4, bs.getTotalBytesRead());
|
||||||
bs = new ByteSource("NotNuFx".getBytes());
|
bs = new ByteSource("NotNuFx".getBytes());
|
||||||
assertFalse(bs.checkNuFxId());
|
assertFalse(bs.checkNuFxId());
|
||||||
}
|
}
|
||||||
|
@ -61,18 +67,22 @@ public class ByteSourceTest extends TestCase {
|
||||||
ByteSource bs = new ByteSource(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
|
ByteSource bs = new ByteSource(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
|
||||||
assertEquals(0x0201, bs.readWord());
|
assertEquals(0x0201, bs.readWord());
|
||||||
assertEquals(0x0403, bs.readWord());
|
assertEquals(0x0403, bs.readWord());
|
||||||
|
assertEquals(4, bs.getTotalBytesRead());
|
||||||
}
|
}
|
||||||
public void testReadWordHighBitSet() throws IOException {
|
public void testReadWordHighBitSet() throws IOException {
|
||||||
ByteSource bs = new ByteSource(new byte[] { (byte)0xff, (byte)0xff });
|
ByteSource bs = new ByteSource(new byte[] { (byte)0xff, (byte)0xff });
|
||||||
assertEquals(0xffff, bs.readWord());
|
assertEquals(0xffff, bs.readWord());
|
||||||
|
assertEquals(2, bs.getTotalBytesRead());
|
||||||
}
|
}
|
||||||
public void testReadLong() throws IOException {
|
public void testReadLong() throws IOException {
|
||||||
ByteSource bs = new ByteSource(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
|
ByteSource bs = new ByteSource(new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 });
|
||||||
assertEquals(0x04030201, bs.readLong());
|
assertEquals(0x04030201, bs.readLong());
|
||||||
|
assertEquals(4, bs.getTotalBytesRead());
|
||||||
}
|
}
|
||||||
public void testReadLongHighBitSet() throws IOException {
|
public void testReadLongHighBitSet() throws IOException {
|
||||||
ByteSource bs = new ByteSource(new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff });
|
ByteSource bs = new ByteSource(new byte[] { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff });
|
||||||
assertEquals(0xffffffffL, bs.readLong());
|
assertEquals(0xffffffffL, bs.readLong());
|
||||||
|
assertEquals(4, bs.getTotalBytesRead());
|
||||||
}
|
}
|
||||||
public void testReadDate() throws IOException {
|
public void testReadDate() throws IOException {
|
||||||
ByteSource bs = new ByteSource(new byte[] {
|
ByteSource bs = new ByteSource(new byte[] {
|
||||||
|
@ -84,11 +94,13 @@ public class ByteSourceTest extends TestCase {
|
||||||
assertEquals(new GregorianCalendar(1988, Calendar.OCTOBER, 22, 1, 10, 0).getTime(), bs.readDate());
|
assertEquals(new GregorianCalendar(1988, Calendar.OCTOBER, 22, 1, 10, 0).getTime(), bs.readDate());
|
||||||
assertEquals(new GregorianCalendar(1988, Calendar.NOVEMBER, 17, 11, 16, 0).getTime(), bs.readDate());
|
assertEquals(new GregorianCalendar(1988, Calendar.NOVEMBER, 17, 11, 16, 0).getTime(), bs.readDate());
|
||||||
assertEquals(new GregorianCalendar(1988, Calendar.OCTOBER, 22, 13, 12, 0).getTime(), bs.readDate());
|
assertEquals(new GregorianCalendar(1988, Calendar.OCTOBER, 22, 13, 12, 0).getTime(), bs.readDate());
|
||||||
|
assertEquals(24, bs.getTotalBytesRead());
|
||||||
}
|
}
|
||||||
public void testReadNullDate() throws IOException {
|
public void testReadNullDate() throws IOException {
|
||||||
ByteSource bs = new ByteSource(new byte[] {
|
ByteSource bs = new ByteSource(new byte[] {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // null date
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // null date
|
||||||
});
|
});
|
||||||
assertNull(bs.readDate());
|
assertNull(bs.readDate());
|
||||||
|
assertEquals(8, bs.getTotalBytesRead());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import junit.framework.TestCase;
|
||||||
* @author robgreene@users.sourceforge.net
|
* @author robgreene@users.sourceforge.net
|
||||||
*/
|
*/
|
||||||
public class MasterHeaderBlockTest extends TestCase {
|
public class MasterHeaderBlockTest extends TestCase {
|
||||||
public void test1() throws IOException {
|
public void testWithValidCrc() throws IOException {
|
||||||
ByteSource bs = new ByteSource(new byte[] {
|
ByteSource bs = new ByteSource(new byte[] {
|
||||||
0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5, (byte)0xdc, 0x1b,
|
0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5, (byte)0xdc, 0x1b,
|
||||||
0x2d, 0x00, 0x00, 0x00, 0x38, 0x0c, 0x14, 0x5f,
|
0x2d, 0x00, 0x00, 0x00, 0x38, 0x0c, 0x14, 0x5f,
|
||||||
|
@ -28,5 +28,19 @@ public class MasterHeaderBlockTest extends TestCase {
|
||||||
assertEquals(new ByteSource(new byte[] {0x29, 0x0d, 0x14, 0x5f, 0x08, 0x07, 0x01, 0x04}).readDate(), b.getArchiveModWhen());
|
assertEquals(new ByteSource(new byte[] {0x29, 0x0d, 0x14, 0x5f, 0x08, 0x07, 0x01, 0x04}).readDate(), b.getArchiveModWhen());
|
||||||
assertEquals(0x01, b.getMasterVersion());
|
assertEquals(0x01, b.getMasterVersion());
|
||||||
assertEquals(0x1acae, b.getMasterEof());
|
assertEquals(0x1acae, b.getMasterEof());
|
||||||
|
assertTrue(b.isValidCrc());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testWithInvalidCrc() throws IOException {
|
||||||
|
ByteSource bs = new ByteSource(new byte[] {
|
||||||
|
0x4e, (byte)0xf5, 0x46, (byte)0xe9, 0x6c, (byte)0xe5, 0x00, 0x00, // <-- Bad CRC!
|
||||||
|
0x2d, 0x00, 0x00, 0x00, 0x38, 0x0c, 0x14, 0x5f,
|
||||||
|
0x08, 0x07, 0x30, 0x04, 0x29, 0x0d, 0x14, 0x5f,
|
||||||
|
0x08, 0x07, 0x01, 0x04, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0xae, (byte)0xac,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||||
|
});
|
||||||
|
MasterHeaderBlock b = new MasterHeaderBlock(bs);
|
||||||
|
assertFalse(b.isValidCrc());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,9 @@ public class NuFileArchiveTest extends TestCase {
|
||||||
MasterHeaderBlock m = a.getMasterHeaderBlock();
|
MasterHeaderBlock m = a.getMasterHeaderBlock();
|
||||||
System.out.printf("Master Header Block\n==================\n"
|
System.out.printf("Master Header Block\n==================\n"
|
||||||
+ "master_crc=$%x\ntotal_records=%d\narchive_create_when=%tc\narchive_mod_when=%tc\n"
|
+ "master_crc=$%x\ntotal_records=%d\narchive_create_when=%tc\narchive_mod_when=%tc\n"
|
||||||
+ "master_version=%d\nmaster_eof=$%x\n\n",
|
+ "master_version=%d\nmaster_eof=$%x\nisValidCrc = %b\n\n",
|
||||||
m.getMasterCrc(), m.getTotalRecords(), m.getArchiveCreateWhen(), m.getArchiveModWhen(),
|
m.getMasterCrc(), m.getTotalRecords(), m.getArchiveCreateWhen(), m.getArchiveModWhen(),
|
||||||
m.getMasterVersion(), m.getMasterEof());
|
m.getMasterVersion(), m.getMasterEof(), m.isValidCrc());
|
||||||
for (HeaderBlock b : a.getHeaderBlocks()) {
|
for (HeaderBlock b : a.getHeaderBlocks()) {
|
||||||
System.out.printf("\tHeader Block\n\t============\n");
|
System.out.printf("\tHeader Block\n\t============\n");
|
||||||
System.out.printf("\theader_crc=$%x\n\tattrib_count=%d\n\tversion_number=%d\n\ttotal_threads=%d\n\t"
|
System.out.printf("\theader_crc=$%x\n\tattrib_count=%d\n\tversion_number=%d\n\ttotal_threads=%d\n\t"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user