Browse Source

Fixed io exception in MODCAParser caused by the improper use of mark() and reset()

on the MODCA data input stream.  See bugzilla 50909.


git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1104135 13f79535-47bb-0310-9956-ffa450edef68
pull/21/head
Peter Hancock 13 years ago
parent
commit
fac959a376

+ 3
- 2
src/java/org/apache/fop/afp/apps/FontPatternExtractor.java View File

@@ -55,9 +55,10 @@ public class FontPatternExtractor {
UnparsedStructuredField strucField;
while ((strucField = parser.readNextStructuredField()) != null) {
if (strucField.getSfTypeID() == 0xD3EE89) {
byte[] sfData = strucField.getData();
println(strucField.toString());
HexDump.dump(strucField.getData(), 0, printStream, 0);
baout.write(strucField.getData());
HexDump.dump(sfData, 0, printStream, 0);
baout.write(sfData);
}
}


+ 48
- 24
src/java/org/apache/fop/afp/parser/MODCAParser.java View File

@@ -20,15 +20,23 @@
package org.apache.fop.afp.parser;

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.afp.parser.UnparsedStructuredField.Introducer;

/**
* An simple MO:DCA/AFP parser.
*/
public class MODCAParser {

private static final Log LOG = LogFactory.getLog(MODCAParser.class);

private static final int INTRODUCER_LENGTH = 8;

/** The carriage control character (0x5A) used to indicate the start of a structured field. */
public static final byte CARRIAGE_CONTROL_CHAR = (byte)(0x5A & 0xFF);

@@ -39,19 +47,9 @@ public class MODCAParser {
* @param in the {@link InputStream} to read the AFP file from.
*/
public MODCAParser(InputStream in) {
if (!in.markSupported()) {
in = new java.io.BufferedInputStream(in);
}
this.din = new DataInputStream(in);
}

/**
* Returns the {@link DataInputStream} used for parsing structured fields.
* @return the data input stream
*/
public DataInputStream getDataInputStream() {
return this.din;
}

/**
* Reads the next structured field from the input stream.
@@ -61,20 +59,46 @@ public class MODCAParser {
* @throws IOException if an I/O error occurs
*/
public UnparsedStructuredField readNextStructuredField() throws IOException {
try {
while (true) {
byte b = din.readByte(); //Skip 0x5A character if necessary (ex. AFP)
if (b == 0x0D || b == 0x0A) {
//CR and LF may be used as field delimiters
continue;
} else if (b == CARRIAGE_CONTROL_CHAR) {
break; //Signals the start of a new structured field
}

//Find the SF delimiter
do {
//Exhausted streams and so no next SF
// - null return represents this case
// TODO should this happen?
if (din.available() == 0) {
return null;
}
} while (din.readByte() != CARRIAGE_CONTROL_CHAR);

//Read introducer as byte array to preserve any data not parsed below
byte[] introducerData = new byte[INTRODUCER_LENGTH]; //Length of introducer
din.readFully(introducerData);

Introducer introducer = new Introducer(introducerData);

int dataLength = introducer.getLength() - INTRODUCER_LENGTH;

//Handle optional extension
byte[] extData = null;
if (introducer.isExtensionPresent()) {
short extLength = 0;
extLength = (short)((din.readByte()) & 0xFF);
if (extLength > 0) {
extData = new byte[extLength - 1];
din.readFully(extData);
dataLength -= extLength;
}
} catch (EOFException eof) {
return null;
}
return UnparsedStructuredField.readStructuredField(getDataInputStream());
}
//Read payload
byte[] data = new byte[dataLength];
din.readFully(data);

UnparsedStructuredField sf = new UnparsedStructuredField(introducer, data, extData);

if (LOG.isTraceEnabled()) {
LOG.trace(sf);
}

return sf;
}
}

+ 321
- 327
src/java/org/apache/fop/afp/parser/UnparsedStructuredField.java View File

@@ -19,337 +19,331 @@

package org.apache.fop.afp.parser;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.text.DecimalFormat;

import org.apache.commons.io.HexDump;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* Represents an unparsed (generic) AFP structured field.
*/
public class UnparsedStructuredField {

private static final Log LOG = LogFactory.getLog(UnparsedStructuredField.class);

private static final int INTRODUCER_LENGTH = 8;

private short sfLength;
private byte sfClassCode;
private byte sfTypeCode;
private byte sfCategoryCode;
private boolean sfiExtensionPresent;
private boolean sfiSegmentedData;
private boolean sfiPaddingPresent;
private short extLength;
private byte[] introducerData;
private byte[] extData;
private byte[] data;

/**
* Default constructor.
*/
public UnparsedStructuredField() {
//nop
}

/**
* Reads a structured field from a {@link DataInputStream}. The resulting object can be
* further interpreted be follow-up code.
* @param din the stream to read from
* @return the generic structured field
* @throws IOException if an I/O error occurs
*/
public static UnparsedStructuredField readStructuredField(DataInputStream din)
throws IOException {
UnparsedStructuredField sf = new UnparsedStructuredField();

//Read introducer as byte array to preserve any data not parsed below
din.mark(INTRODUCER_LENGTH);
sf.introducerData = new byte[INTRODUCER_LENGTH]; //Length of introducer
din.readFully(sf.introducerData);
din.reset();

//Parse the introducer
short len;
try {
len = din.readShort();
} catch (EOFException eof) {
return null;
}
sf.sfLength = len;
sf.sfClassCode = din.readByte();
sf.sfTypeCode = din.readByte();
sf.sfCategoryCode = din.readByte();

//Flags
byte f = din.readByte();
sf.sfiExtensionPresent = (f & 0x01) != 0;
sf.sfiSegmentedData = (f & 0x04) != 0;
sf.sfiPaddingPresent = (f & 0x10) != 0;
din.skip(2); //Reserved

int dataLength = sf.sfLength - INTRODUCER_LENGTH;

//Handle optional extension
if (sf.sfiExtensionPresent) {
sf.extLength = (short)(((short)din.readByte()) & 0xFF);
if (sf.extLength > 0) {
sf.extData = new byte[sf.extLength - 1];
din.readFully(sf.extData);
dataLength -= sf.extLength;
}
}

//Read payload
sf.data = new byte[dataLength];
din.readFully(sf.data);

if (LOG.isTraceEnabled()) {
LOG.trace(sf);
}

return sf;
}

/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer("Structured Field: ");
sb.append(Integer.toHexString(getSfTypeID()).toUpperCase());
sb.append(", len=");
sb.append(new DecimalFormat("00000").format(getSfLength()));
sb.append(" ").append(getTypeCodeAsString());
sb.append(" ").append(getCategoryCodeAsString());
if (isSfiExtensionPresent()) {
sb.append(", SFI extension present");
}
if (isSfiSegmentedData()) {
sb.append(", segmented data");
}
if (isSfiPaddingPresent()) {
sb.append(", with padding");
}
return sb.toString();
}

/**
* Dump the structured field as hex data to the given {@link PrintStream}.
* @param out the {@link PrintStream} to dump to
* @throws IOException if an I/O error occurs
*/
public void dump(PrintStream out) throws IOException {
out.println(toString());
HexDump.dump(getData(), 0, out, 0);
}

/**
* Dump the structured field as hex data to <code>System.out</code>.
* @throws IOException if an I/O error occurs
*/
public void dump() throws IOException {
dump(System.out);
}

/**
* Returns type code function name for this field.
* @return the type code function name
*/
public String getTypeCodeAsString() {
switch ((int)getSfTypeCode() & 0xFF) {
case 0xA0: return "Attribute";
case 0xA2: return "CopyCount";
case 0xA6: return "Descriptor";
case 0xA7: return "Control";
case 0xA8: return "Begin";
case 0xA9: return "End";
case 0xAB: return "Map";
case 0xAC: return "Position";
case 0xAD: return "Process";
case 0xAF: return "Include";
case 0xB0: return "Table";
case 0xB1: return "Migration";
case 0xB2: return "Variable";
case 0xB4: return "Link";
case 0xEE: return "Data";
default: return "Unknown:" + Integer.toHexString((int)getSfTypeCode()).toUpperCase();
}
}

/**
* Returns category code function name for this field.
* @return the category code function name
*/
public String getCategoryCodeAsString() {
switch ((int)getSfCategoryCode() & 0xFF) {
case 0x5F: return "Page Segment";
case 0x6B: return "Object Area";
case 0x77: return "Color Attribute Table";
case 0x7B: return "IM Image";
case 0x88: return "Medium";
case 0x89: return "Font";
case 0x8A: return "Coded Font";
case 0x90: return "Process Element";
case 0x92: return "Object Container";
case 0x9B: return "Presentation Text";
case 0xA7: return "Index";
case 0xA8: return "Document";
case 0xAD: return "Page Group";
case 0xAF: return "Page";
case 0xBB: return "Graphics";
case 0xC3: return "Data Resource";
case 0xC4: return "Document Environment Group (DEG)";
case 0xC6: return "Resource Group";
case 0xC7: return "Object Environment Group (OEG)";
case 0xC9: return "Active Environment Group (AEG)";
case 0xCC: return "Medium Map";
case 0xCD: return "Form Map";
case 0xCE: return "Name Resource";
case 0xD8: return "Page Overlay";
case 0xD9: return "Resource Environment Group (REG)";
case 0xDF: return "Overlay";
case 0xEA: return "Data Supression";
case 0xEB: return "Bar Code";
case 0xEE: return "No Operation";
case 0xFB: return "Image";
default: return "Unknown:" + Integer.toHexString((int)getSfTypeCode()).toUpperCase();
}
}

/**
* Returns the structured field's length.
* @return the field length
*/
public short getSfLength() {
return this.sfLength;
}

/**
* Returns the structured field's identifier.
* @return the field identifier
*/
public int getSfTypeID() {
return ((getSfClassCode() & 0xFF) << 16)
| ((getSfTypeCode() & 0xFF) << 8)
| (getSfCategoryCode() & 0xFF);
}

/**
* Returns the structured field's class code.
* @return the field class code
*/
public byte getSfClassCode() {
return this.sfClassCode;
}

/**
* Returns the structured field's type code.
* @return the type code
*/
public byte getSfTypeCode() {
return this.sfTypeCode;
}

/**
* Returns the structured field's category code.
* @return the sfCategoryCode
*/
public byte getSfCategoryCode() {
return this.sfCategoryCode;
}

/**
* Indicates whether an field introducer extension is present.
* @return true if an field introducer extension is present
*/
public boolean isSfiExtensionPresent() {
return this.sfiExtensionPresent && (this.extData != null);
}

/**
* Indicates whether segmented data is present.
* @return true if the data is segmented
*/
public boolean isSfiSegmentedData() {
return this.sfiSegmentedData;
}

/**
* Indicates whether the data is padded.
* @return true if the data is padded
*/
public boolean isSfiPaddingPresent() {
return this.sfiPaddingPresent;
}

/**
* Returns the length of the extension if present.
* @return the length of the extension (or 0 if no extension is present)
*/
public short getExtLength() {
return this.extLength;
}

/**
* Returns the extension data if present.
* @return the extension data (or null if no extension is present)
*/
public byte[] getExtData() {
return this.extData;
}

/**
* Returns the structured field's payload.
* @return the field's data
*/
public byte[] getData() {
return this.data;
}

/**
* Returns the structured field's introducer data.
* @return the introducer data
*/
public byte[] getIntroducerData() {
return this.introducerData;
}

/**
* Returns the complete structured field as a byte array.
* @return the complete field data
*/
public byte[] getCompleteFieldAsBytes() {
int len = INTRODUCER_LENGTH;
if (isSfiExtensionPresent()) {
len += getExtLength();
}
len += getData().length;
byte[] bytes = new byte[len];
int pos = 0;
System.arraycopy(getIntroducerData(), 0, bytes, pos, INTRODUCER_LENGTH);
pos += INTRODUCER_LENGTH;
if (isSfiExtensionPresent()) {
System.arraycopy(getExtData(), 0, bytes, pos, getExtLength());
pos += getExtLength();
}
System.arraycopy(getData(), 0, bytes, pos, getData().length);
return bytes;
}

/**
* Writes this structured field to the given {@link OutputStream}.
* @param out the output stream
* @throws IOException if an I/O error occurs
*/
public void writeTo(OutputStream out) throws IOException {
out.write(this.introducerData);
if (isSfiExtensionPresent()) {
out.write(this.extData);
}
out.write(this.data);
}
* Represents an unparsed (generic) AFP structured field.
*/
public final class UnparsedStructuredField {

private final Introducer introducer;
private final byte[] extData;
private final byte[] data;

/**
*
* @param Structured field introducer
* @param data Structured field data
* @param extData Structured field extension data
*/
UnparsedStructuredField(Introducer introducer,
byte[] data, byte[] extData) {
this.introducer = introducer;
this.data = data;
if (extData != null) {
this.extData = extData;
} else {
this.extData = null;
}
}

@Override
public String toString() {
StringBuffer sb = new StringBuffer("Structured Field: ");
sb.append(Integer.toHexString(getSfTypeID()).toUpperCase());
sb.append(", len=");
sb.append(new DecimalFormat("00000").format(getSfLength()));
sb.append(" ").append(getTypeCodeAsString());
sb.append(" ").append(getCategoryCodeAsString());
if (isSfiExtensionPresent()) {
sb.append(", SFI extension present");
}
if (isSfiSegmentedData()) {
sb.append(", segmented data");
}
if (isSfiPaddingPresent()) {
sb.append(", with padding");
}
return sb.toString();
}


/**
* Returns type code function name for this field.
* @return the type code function name
*/
private String getTypeCodeAsString() {
switch (getSfTypeCode() & 0xFF) {
case 0xA0: return "Attribute";
case 0xA2: return "CopyCount";
case 0xA6: return "Descriptor";
case 0xA7: return "Control";
case 0xA8: return "Begin";
case 0xA9: return "End";
case 0xAB: return "Map";
case 0xAC: return "Position";
case 0xAD: return "Process";
case 0xAF: return "Include";
case 0xB0: return "Table";
case 0xB1: return "Migration";
case 0xB2: return "Variable";
case 0xB4: return "Link";
case 0xEE: return "Data";
default: return "Unknown:" + Integer.toHexString(getSfTypeCode()).toUpperCase();
}
}

/**
* Returns category code function name for this field.
* @return the category code function name
*/
private String getCategoryCodeAsString() {
switch (getSfCategoryCode() & 0xFF) {
case 0x5F: return "Page Segment";
case 0x6B: return "Object Area";
case 0x77: return "Color Attribute Table";
case 0x7B: return "IM Image";
case 0x88: return "Medium";
case 0x89: return "Font";
case 0x8A: return "Coded Font";
case 0x90: return "Process Element";
case 0x92: return "Object Container";
case 0x9B: return "Presentation Text";
case 0xA7: return "Index";
case 0xA8: return "Document";
case 0xAD: return "Page Group";
case 0xAF: return "Page";
case 0xBB: return "Graphics";
case 0xC3: return "Data Resource";
case 0xC4: return "Document Environment Group (DEG)";
case 0xC6: return "Resource Group";
case 0xC7: return "Object Environment Group (OEG)";
case 0xC9: return "Active Environment Group (AEG)";
case 0xCC: return "Medium Map";
case 0xCD: return "Form Map";
case 0xCE: return "Name Resource";
case 0xD8: return "Page Overlay";
case 0xD9: return "Resource Environment Group (REG)";
case 0xDF: return "Overlay";
case 0xEA: return "Data Supression";
case 0xEB: return "Bar Code";
case 0xEE: return "No Operation";
case 0xFB: return "Image";
default: return "Unknown:" + Integer.toHexString(getSfTypeCode()).toUpperCase();
}
}

/**
* Returns the structured field's length.
* @return the field length
*/
public short getSfLength() {
return introducer.length;
}

/**
* Returns the structured field's identifier.
* @return the field identifier
*/
public int getSfTypeID() {
return ((getSfClassCode() & 0xFF) << 16)
| ((getSfTypeCode() & 0xFF) << 8)
| (getSfCategoryCode() & 0xFF);
}

/**
* Returns the structured field's class code.
* @return the field class code
*/
public byte getSfClassCode() {
return introducer.classCode;
}

/**
* Returns the structured field's type code.
* @return the type code
*/
public byte getSfTypeCode() {
return introducer.typeCode;
}

/**
* Returns the structured field's category code.
* @return the sfCategoryCode
*/
public byte getSfCategoryCode() {
return introducer.categoryCode;
}

/**
* Indicates whether an field introducer extension is present.
* @return true if an field introducer extension is present
*/
public boolean isSfiExtensionPresent() {
return introducer.extensionPresent && (this.extData != null);
}

/**
* Indicates whether segmented data is present.
* @return true if the data is segmented
*/
public boolean isSfiSegmentedData() {
return introducer.segmentedData;
}

/**
* Indicates whether the data is padded.
* @return true if the data is padded
*/
public boolean isSfiPaddingPresent() {
return introducer.paddingPresent;
}

/**
* Returns the length of the extension if present.
* @return the length of the extension (or 0 if no extension is present)
*/
public short getExtLength() {
return (extData != null) ? (short)(extData.length + 1) : 0;

}

/**
* Returns the extension data if present.
* @return the extension data (or null if no extension is present)
*/
byte[] getExtData() {
if (this.extData == null) {
return null;
}
byte[] rtn = new byte[this.extData.length];
System.arraycopy(this.extData, 0, rtn, 0, rtn.length);
return rtn;
}

/**
* Returns the structured field's payload.
* @return the field's data
*/
public byte[] getData() {
if (this.data == null) {
return null;
}
byte[] rtn = new byte[this.data.length];
System.arraycopy(this.data, 0, rtn, 0, rtn.length);
return rtn;
}

//For unit testing
byte[] getIntroducerData() {
return introducer.getIntroducerData();
}

/**
* Returns the complete structured field as a byte array.
* @return the complete field data
*/
public byte[] getCompleteFieldAsBytes() {

ByteArrayOutputStream baos = new ByteArrayOutputStream(getSfLength());
try {
writeTo(baos);
} catch (IOException ioe) {
//nop
}
return baos.toByteArray();

}

/**
* Writes this structured field to the given {@link OutputStream}.
* @param out the output stream
* @throws IOException if an I/O error occurs
*/
public void writeTo(OutputStream out) throws IOException {
out.write(introducer.introducerData);
if (isSfiExtensionPresent()) {
out.write(this.extData.length + 1);
out.write(this.extData);
}
out.write(this.data);
}

static final class Introducer {

private final short length;
private final byte classCode;
private final byte typeCode;
private final byte categoryCode;
private final boolean extensionPresent;
private final boolean segmentedData;
private final boolean paddingPresent;
private final byte[] introducerData;

Introducer(byte[] introducerData) throws IOException {

this.introducerData = introducerData;

// Parse the introducer; the 8 bytes have already been read from the stream just
// before, so we parse the introducer from the byte array
DataInputStream iis = new DataInputStream(
new ByteArrayInputStream(introducerData));

length = iis.readShort();

classCode = iis.readByte();
typeCode = iis.readByte();
categoryCode = iis.readByte();

//Flags
byte f = iis.readByte();

extensionPresent = (f & 0x01) != 0;
segmentedData = (f & 0x04) != 0;
paddingPresent = (f & 0x10) != 0;

}


public short getLength() {
return length;
}

public byte getClassCode() {
return classCode;
}

public byte getTypeCode() {
return typeCode;
}

public byte getCategoryCode() {
return categoryCode;
}

public boolean isExtensionPresent() {
return extensionPresent;
}


public boolean isSegmentedData() {
return segmentedData;
}

public boolean isPaddingPresent() {
return paddingPresent;
}

public byte[] getIntroducerData() {
byte[] rtn = new byte[introducerData.length];
System.arraycopy(introducerData, 0, rtn, 0, rtn.length);
return rtn;
}
}

}

+ 1
- 1
src/java/org/apache/fop/afp/util/AFPResourceUtil.java View File

@@ -29,8 +29,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.afp.AFPConstants;
import org.apache.fop.afp.modca.ResourceObject;
import org.apache.fop.afp.modca.AbstractAFPObject.Category;
import org.apache.fop.afp.modca.ResourceObject;
import org.apache.fop.afp.parser.MODCAParser;
import org.apache.fop.afp.parser.UnparsedStructuredField;


+ 3
- 0
status.xml View File

@@ -59,6 +59,9 @@
documents. Example: the fix of marks layering will be such a case when it's done.
-->
<release version="FOP Trunk" date="TBD">
<action context="Renderers" dev="PH" type="fix" fixes-bug="50909">
Fixed io exception in MODCAParser caused by the improper use of mark() and reset() on the
MODCA data input stream. Added unit test. </action>
<action context="Fonts" dev="JM" type="fix" fixes-bug="51144" due-to="Mehdi Houshmand">
Removed invalid entries in ToUnicode table of CID subset fonts.
</action>

+ 2
- 0
test/java/org/apache/fop/StandardTestSuite.java View File

@@ -23,6 +23,7 @@ import junit.framework.Test;
import junit.framework.TestSuite;

import org.apache.fop.area.ViewportTestSuite;
import org.apache.fop.afp.parser.MODCAParserTestCase;
import org.apache.fop.fonts.DejaVuLGCSerifTest;
import org.apache.fop.image.loader.batik.ImageLoaderTestCase;
import org.apache.fop.image.loader.batik.ImagePreloaderTestCase;
@@ -57,6 +58,7 @@ public class StandardTestSuite {
suite.addTest(new TestSuite(PDFCMapTestCase.class));
suite.addTest(new TestSuite(PDFsRGBSettingsTestCase.class));
suite.addTest(new TestSuite(DejaVuLGCSerifTest.class));
suite.addTest(new TestSuite(MODCAParserTestCase.class));
suite.addTest(AFPTestSuite.suite());
suite.addTest(PSTestSuite.suite());
suite.addTest(RichTextFormatTestSuite.suite());

+ 238
- 0
test/java/org/apache/fop/afp/parser/MODCAParserTestCase.java View File

@@ -0,0 +1,238 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/

/* $Id$ */

package org.apache.fop.afp.parser;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.InputStream;
import java.util.Arrays;

import junit.framework.TestCase;

/**
* MODCAParser and MODCAParser.UnparsedStructuredField Unit tests
*/
public class MODCAParserTestCase extends TestCase {

/** The carriage control character (0x5A) used to indicate the start of a structured field. */
public static final byte CARRIAGE_CONTROL_CHAR = (byte)0x5A;
/**ASCII carriage return control character*/
public static final byte CARRIAGE_RETURN = (byte)0x0A;
/**ASCII line feed control character */
public static final byte LINE_FEED = (byte)0x0D;
/** 8 byte introducer describe the SF */
private static final int INTRODUCER_LENGTH = 8;

/**
* Test that the MODCA parser recognises carriage control (0x5A) as the Structured Field
* delimeter
*
* @throws Exception *
*/
public void testReadNextStructuredField1() throws Exception {

// carriage control (0x5A) delimits structured fields,
// and control is handed to readStructuredField(DataInputStream)
byte[][] goodInputStream = new byte[][]{
new byte[]{CARRIAGE_CONTROL_CHAR}
};

for (byte[] b : goodInputStream) {
try {
new MODCAParser(new ByteArrayInputStream(b))
.readNextStructuredField();
fail("BAD SF should throw EOF: " + byteArrayToString(b));
} catch (EOFException eof) {
//passed
}
}

// EOFException thrown when reading the input stream are caught and
// a null value is returned
byte[][] badInputStream = new byte[][]{
new byte[]{},
new byte[]{CARRIAGE_RETURN},
new byte[]{LINE_FEED}
};

for (byte[] b : badInputStream) {
UnparsedStructuredField usf = new MODCAParser(new ByteArrayInputStream(b))
.readNextStructuredField();
assertNull(usf);
}
}


/**
* Test that the MODCA parser correctly constructs an UnparsedStructuredField
* from a well formed structured field
*
* @throws Exception *
*/
public void testReadNextStructuredField2() throws Exception {

// no extension data
testSF((byte)0xd3, (byte)0xa8, (byte)0x89, //SFTypeID
(byte)0, //flags excluding the bits for
//extension present, segmented data and padding present
false, false,
new byte[]{0, 0},
new byte[]{1}, null);

// with extension data
testSF((byte)0xd3, (byte)0xa8, (byte)0x89, //SFTypeID
(byte)0, //flags excluding the bits for
//extension present, segmented data and padding present
false, false,
new byte[]{0, 0},
new byte[]{1}, new byte[]{10});

// with ignored reserved bits
testSF((byte)0xd3, (byte)0xa8, (byte)0x89, //SFTypeID
(byte)0, //flags excluding the bits for
//extension present, segmented data and padding present
false, false,
new byte[]{1, 2},
new byte[]{1}, null);

// with padding present and segmented data
testSF((byte)0xd3, (byte)0xa8, (byte)0x89, //SFTypeID
(byte)(1 << 3), //flags excluding the bits for
//extension present, segmented data and padding present
true, true,
new byte[]{0, 0},
new byte[]{1}, null);

// with flags non zero
testSF((byte)0xd3, (byte)0xa8, (byte)0x89, //SFTypeID
(byte)(1 << 3), //flags excluding the bits for
//extension present, segmented data and padding present
false, false,
new byte[]{0, 0},
new byte[]{1}, null);
}


private void testSF(byte classCode, byte typeCode, byte categoryCode,
byte flags, boolean segmentedData, boolean paddingPresent, byte[] reserved,
byte[] data, byte[] extData) throws Exception {

byte extDataLength = 0;
boolean extensionPresent = (extData != null);

if (extensionPresent) {
flags = (byte)(flags | 0x01);
extDataLength = (byte)(extData.length + 1); //length includes length byte
}

if (segmentedData) {
flags = (byte)(flags | 0x04);
}

if (paddingPresent) {
flags = (byte)(flags | 0x10);
}

short length = (short)(INTRODUCER_LENGTH + data.length + extDataLength);
byte[] lengthBytes = new byte[]{(byte)(length >> 8), (byte)(length & 0xFF)};

byte[] sfBytes = new byte[length];

//introducer bytes
sfBytes[0] = lengthBytes[0];
sfBytes[1] = lengthBytes[1];
sfBytes[2] = classCode;
sfBytes[3] = typeCode;
sfBytes[4] = categoryCode;
sfBytes[5] = flags;
sfBytes[6] = reserved[0];
sfBytes[7] = reserved[1];

if (extDataLength > 0) {
sfBytes[8] = (byte)(extData.length + 1);
System.arraycopy(extData, 0, sfBytes, 9, extData.length);
}

System.arraycopy(data, 0, sfBytes, length - data.length, data.length);


byte[] delimiteredSF = new byte[length + 1];

delimiteredSF[0] = (byte)0x5A;

System.arraycopy(sfBytes, 0, delimiteredSF, 1, length);

InputStream bis = new ByteArrayInputStream(delimiteredSF);

UnparsedStructuredField actual = new MODCAParser(bis)
.readNextStructuredField();

//check introducer
assertEquals(length, actual.getSfLength());
assertEquals(classCode, actual.getSfClassCode());
assertEquals(typeCode, actual.getSfTypeCode());
assertEquals(categoryCode, actual.getSfCategoryCode());
assertEquals(extensionPresent, actual.isSfiExtensionPresent());
assertEquals(segmentedData, actual.isSfiSegmentedData());
assertEquals(paddingPresent, actual.isSfiPaddingPresent());

byte[] introducerData = new byte[]{(byte)(length >> 8), (byte)(length & 0xFF),
classCode, typeCode, categoryCode, flags, reserved[0], reserved[1]};

assertTrue(Arrays.equals(introducerData, actual.getIntroducerData()));

//check data
assertTrue(Arrays.equals(data, actual.getData()));

//check extension data
if (extData != null) {
assertTrue(Arrays.equals(extData, actual.getExtData()));
}
assertEquals(
(extData == null) ? 0 : extData.length + 1, // 1 byte for length byte
actual.getExtLength());

assertTrue(Arrays.equals(data, actual.getData()));

int expectedSfTypeID = ((classCode & 0xFF) << 16)
| ((typeCode & 0xFF) << 8)
| (categoryCode & 0xFF);

assertEquals(expectedSfTypeID, actual.getSfTypeID());

assertTrue(Arrays.equals(sfBytes, actual.getCompleteFieldAsBytes()));

ByteArrayOutputStream baos = new ByteArrayOutputStream();
actual.writeTo(baos);
assertTrue(Arrays.equals(sfBytes, baos.toByteArray()));

}


private static String byteArrayToString(byte[] byteArray) {
StringBuilder sb = new StringBuilder();
for (byte b : byteArray) {
sb.append(Integer.toHexString(b & 0xFF)).append(" ");
}
return sb.toString();
}

}

Loading…
Cancel
Save