Skip to content

Commit

Permalink
add length attribute to XML for SBC and byte[] IBDO fields. (#724)
Browse files Browse the repository at this point in the history
  • Loading branch information
jdcove2 committed Mar 28, 2024
1 parent 117affd commit f7de34e
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 64 deletions.
36 changes: 29 additions & 7 deletions src/main/java/emissary/core/IBaseDataObjectXmlCodecs.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ public final class IBaseDataObjectXmlCodecs {
* The XML attribute name for Encoding.
*/
public static final String ENCODING_ATTRIBUTE_NAME = "encoding";
/**
* The XML attribute name for Length.
*/
public static final String LENGTH_ATTRIBUTE_NAME = "length";
/**
* A map of element names of IBaseDataObject methods that get/set primitives and their default values.
*/
Expand Down Expand Up @@ -489,8 +493,11 @@ public void encode(final List<SeekableByteChannelFactory> values, final Element
try {
final byte[] bytes = SeekableByteChannelHelper.getByteArrayFromChannel(value,
BaseDataObject.MAX_BYTE_ARRAY_SIZE);
final Element childElement = preserve(protectedElementBase64(childElementName, bytes));

parentElement.addContent(preserve(protectedElementBase64(childElementName, bytes)));
childElement.setAttribute(LENGTH_ATTRIBUTE_NAME, Integer.toString(bytes.length));

parentElement.addContent(childElement);
} catch (final IOException e) {
LOGGER.error("Could not get bytes from SeekableByteChannel!", e);
}
Expand All @@ -513,8 +520,11 @@ public void encode(final List<SeekableByteChannelFactory> values, final Element
try {
final byte[] bytes = SeekableByteChannelHelper.getByteArrayFromChannel(value,
BaseDataObject.MAX_BYTE_ARRAY_SIZE);
final Element childElement = preserve(protectedElementHash(childElementName, bytes));

childElement.setAttribute(LENGTH_ATTRIBUTE_NAME, Integer.toString(bytes.length));

parentElement.addContent(preserve(protectedElementHash(childElementName, bytes)));
parentElement.addContent(childElement);
} catch (final IOException e) {
LOGGER.error("Could not get bytes from SeekableByteChannel!", e);
}
Expand Down Expand Up @@ -584,7 +594,11 @@ private static class ByteArrayEncoder implements ElementEncoder<byte[]> {
public void encode(final List<byte[]> values, final Element parentElement, final String childElementName) {
for (final byte[] value : values) {
if (value != null) {
parentElement.addContent(preserve(protectedElementBase64(childElementName, value)));
final Element childElement = preserve(protectedElementBase64(childElementName, value));

childElement.setAttribute(LENGTH_ATTRIBUTE_NAME, Integer.toString(value.length));

parentElement.addContent(childElement);
}
}
}
Expand Down Expand Up @@ -639,10 +653,14 @@ public void encode(final List<Map<String, byte[]>> values, final Element parentE
for (final Map<String, byte[]> value : values) {
for (final Entry<String, byte[]> view : value.entrySet()) {
final Element metaElement = new Element(VIEW);
final Element nameElement = preserve(protectedElement(NAME, view.getKey()));
final Element valueElement = preserve(protectedElementBase64(VALUE, view.getValue()));

valueElement.setAttribute(LENGTH_ATTRIBUTE_NAME, Integer.toString(view.getValue().length));

parentElement.addContent(metaElement);
metaElement.addContent(preserve(protectedElement(NAME, view.getKey())));
metaElement.addContent(preserve(protectedElementBase64(VALUE, view.getValue())));
metaElement.addContent(nameElement);
metaElement.addContent(valueElement);
}
}
}
Expand All @@ -656,10 +674,14 @@ public void encode(final List<Map<String, byte[]>> values, final Element parentE
for (final Map<String, byte[]> value : values) {
for (final Entry<String, byte[]> view : value.entrySet()) {
final Element metaElement = new Element(IbdoXmlElementNames.VIEW);
final Element nameElement = preserve(protectedElement(IbdoXmlElementNames.NAME, view.getKey()));
final Element valueElement = preserve(protectedElementSha256(IbdoXmlElementNames.VALUE, view.getValue()));

valueElement.setAttribute(LENGTH_ATTRIBUTE_NAME, Integer.toString(view.getValue().length));

parentElement.addContent(metaElement);
metaElement.addContent(preserve(protectedElement(IbdoXmlElementNames.NAME, view.getKey())));
metaElement.addContent(preserve(protectedElementSha256(IbdoXmlElementNames.VALUE, view.getValue())));
metaElement.addContent(nameElement);
metaElement.addContent(valueElement);
}
}
}
Expand Down
244 changes: 189 additions & 55 deletions src/test/java/emissary/core/IBaseDataObjectXmlHelperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import emissary.core.IBaseDataObjectXmlCodecs.ElementEncoders;
import emissary.core.channels.InMemoryChannelFactory;
import emissary.core.channels.SeekableByteChannelFactory;
import emissary.core.constants.IbdoXmlElementNames;
import emissary.kff.KffDataObjectHandler;
import emissary.test.core.junit5.UnitTest;
import emissary.util.ByteUtil;
import emissary.util.PlaceComparisonHelper;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.XMLReaders;
import org.junit.jupiter.api.Test;
Expand All @@ -28,6 +30,7 @@
import static emissary.core.IBaseDataObjectXmlCodecs.DEFAULT_ELEMENT_DECODERS;
import static emissary.core.IBaseDataObjectXmlCodecs.DEFAULT_ELEMENT_ENCODERS;
import static emissary.core.IBaseDataObjectXmlCodecs.SHA256_ELEMENT_ENCODERS;
import static emissary.core.IBaseDataObjectXmlCodecs.extractBytes;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

Expand All @@ -54,40 +57,76 @@ void testParentIbdoNoFieldsChanged() throws Exception {
assertNull(sha256Diff);
}

private static void setAllFieldsPrintable(final IBaseDataObject ibdo, final byte[] bytes) {
ibdo.setData(bytes);
ibdo.setBirthOrder(5);
ibdo.setBroken("Broken1");
ibdo.setBroken("Broken2");
ibdo.setClassification("Classification");
ibdo.pushCurrentForm("Form1");
ibdo.pushCurrentForm("Form2");
ibdo.setFilename("Filename");
ibdo.setFileType("FileType");
ibdo.setFontEncoding("FontEncoding");
ibdo.setFooter("Footer".getBytes(StandardCharsets.ISO_8859_1));
ibdo.setHeader("Header".getBytes(StandardCharsets.ISO_8859_1));
ibdo.setHeaderEncoding("HeaderEncoding");
ibdo.setId("Id");
ibdo.setNumChildren(9);
ibdo.setNumSiblings(10);
ibdo.setOutputable(false);
ibdo.setPriority(1);
ibdo.addProcessingError("ProcessingError1");
ibdo.addProcessingError("ProcessingError2");
ibdo.setTransactionId("TransactionId");
ibdo.setWorkBundleId("WorkBundleId");
ibdo.putParameter("Parameter1Key", "Parameter1Value");
ibdo.putParameter("Parameter2Key", Arrays.asList("Parameter2Value1", "Parameter2Value2"));
ibdo.putParameter("Parameter3Key", Arrays.asList(10L, 20L));
ibdo.addAlternateView("AlternateView1Key", "AlternateView1Value".getBytes(StandardCharsets.ISO_8859_1));
ibdo.addAlternateView("AlternateView11Key", "AlternateView11Value".getBytes(StandardCharsets.ISO_8859_1));
}

private static void setAllFieldsNonPrintable(final IBaseDataObject ibdo, final byte[] bytes) {
ibdo.setData(bytes);
ibdo.setBirthOrder(5);
ibdo.setBroken("\001Broken1");
ibdo.setBroken("\001Broken2");
ibdo.setClassification("\001Classification");
ibdo.pushCurrentForm("Form1");
ibdo.pushCurrentForm("Form2");
ibdo.setFilename("\001Filename");
ibdo.setFileType("\001FileType");
ibdo.setFontEncoding("\001FontEncoding");
ibdo.setFooter("\001Footer".getBytes(StandardCharsets.ISO_8859_1));
ibdo.setHeader("\001Header".getBytes(StandardCharsets.ISO_8859_1));
ibdo.setHeaderEncoding("\001HeaderEncoding");
ibdo.setId("\001Id");
ibdo.setNumChildren(9);
ibdo.setNumSiblings(10);
ibdo.setOutputable(false);
ibdo.setPriority(1);
ibdo.addProcessingError("\001ProcessingError1");
ibdo.addProcessingError("\001ProcessingError2");
ibdo.setTransactionId("\001TransactionId");
ibdo.setWorkBundleId("\001WorkBundleId");
ibdo.putParameter("\020Parameter1Key", "\020Parameter1Value");
ibdo.putParameter("\020Parameter2Key", "\020Parameter2Value");
ibdo.addAlternateView("\200AlternateView1Key",
"\200AlternateView1Value".getBytes(StandardCharsets.ISO_8859_1));
ibdo.addAlternateView("\200AlternateView11Key",
"\200AlternateView11Value".getBytes(StandardCharsets.ISO_8859_1));
}

@Test
void testParentIbdoAllFieldsChanged() throws Exception {
final IBaseDataObject initialIbdo = new BaseDataObject();
final IBaseDataObject expectedIbdo = new BaseDataObject();
final List<IBaseDataObject> expectedChildren = new ArrayList<>();
final List<IBaseDataObject> actualChildren = new ArrayList<>();
final byte[] bytes = "Data".getBytes(StandardCharsets.ISO_8859_1);

expectedIbdo.setData("Data".getBytes(StandardCharsets.ISO_8859_1));
expectedIbdo.setBirthOrder(5);
expectedIbdo.setBroken("Broken1");
expectedIbdo.setBroken("Broken2");
expectedIbdo.setClassification("Classification");
expectedIbdo.pushCurrentForm("Form1");
expectedIbdo.pushCurrentForm("Form2");
expectedIbdo.setFilename("Filename");
expectedIbdo.setFileType("FileType");
expectedIbdo.setFontEncoding("FontEncoding");
expectedIbdo.setFooter("Footer".getBytes(StandardCharsets.ISO_8859_1));
expectedIbdo.setHeader("Header".getBytes(StandardCharsets.ISO_8859_1));
expectedIbdo.setHeaderEncoding("HeaderEncoding");
expectedIbdo.setId("Id");
expectedIbdo.setNumChildren(9);
expectedIbdo.setNumSiblings(10);
expectedIbdo.setOutputable(false);
expectedIbdo.setPriority(1);
expectedIbdo.addProcessingError("ProcessingError1");
expectedIbdo.addProcessingError("ProcessingError2");
expectedIbdo.setTransactionId("TransactionId");
expectedIbdo.setWorkBundleId("WorkBundleId");
expectedIbdo.putParameter("Parameter1Key", "Parameter1Value");
expectedIbdo.putParameter("Parameter2Key", Arrays.asList("Parameter2Value1", "Parameter2Value2"));
expectedIbdo.putParameter("Parameter3Key", Arrays.asList(10L, 20L));
expectedIbdo.addAlternateView("AlternateView1Key", "AlternateView1Value".getBytes(StandardCharsets.ISO_8859_1));
expectedIbdo.addAlternateView("AlternateView2Key", "AlternateView2Value".getBytes(StandardCharsets.ISO_8859_1));
setAllFieldsPrintable(expectedIbdo, bytes);

final IBaseDataObject actualIbdo = ibdoFromXmlFromIbdo(expectedIbdo, expectedChildren, initialIbdo,
actualChildren, DEFAULT_ELEMENT_ENCODERS);
Expand All @@ -112,34 +151,7 @@ void testBase64Conversion() throws Exception {
final List<IBaseDataObject> actualChildren = new ArrayList<>();
final byte[] bytes = "\001Data".getBytes(StandardCharsets.ISO_8859_1);

expectedIbdo.setData(bytes);
expectedIbdo.setBirthOrder(5);
expectedIbdo.setBroken("\001Broken1");
expectedIbdo.setBroken("\001Broken2");
expectedIbdo.setClassification("\001Classification");
expectedIbdo.pushCurrentForm("Form1");
expectedIbdo.pushCurrentForm("Form2");
expectedIbdo.setFilename("\001Filename");
expectedIbdo.setFileType("\001FileType");
expectedIbdo.setFontEncoding("\001FontEncoding");
expectedIbdo.setFooter("\001Footer".getBytes(StandardCharsets.ISO_8859_1));
expectedIbdo.setHeader("\001Header".getBytes(StandardCharsets.ISO_8859_1));
expectedIbdo.setHeaderEncoding("\001HeaderEncoding");
expectedIbdo.setId("\001Id");
expectedIbdo.setNumChildren(9);
expectedIbdo.setNumSiblings(10);
expectedIbdo.setOutputable(false);
expectedIbdo.setPriority(1);
expectedIbdo.addProcessingError("\001ProcessingError1");
expectedIbdo.addProcessingError("\001ProcessingError2");
expectedIbdo.setTransactionId("\001TransactionId");
expectedIbdo.setWorkBundleId("\001WorkBundleId");
expectedIbdo.putParameter("\020Parameter1Key", "\020Parameter1Value");
expectedIbdo.putParameter("\020Parameter2Key", "\020Parameter2Value");
expectedIbdo.addAlternateView("\200AlternateView1Key",
"\200AlternateView1Value".getBytes(StandardCharsets.ISO_8859_1));
expectedIbdo.addAlternateView("\200AlternateView2Key",
"\200AlternateView2Value".getBytes(StandardCharsets.ISO_8859_1));
setAllFieldsNonPrintable(expectedIbdo, bytes);

final IBaseDataObject actualIbdo = ibdoFromXmlFromIbdo(expectedIbdo, expectedChildren, initialIbdo,
actualChildren, DEFAULT_ELEMENT_ENCODERS);
Expand All @@ -163,6 +175,128 @@ void testBase64Conversion() throws Exception {
assertNull(sha256Diff);
}

@Test
void testLengthAttributeDefault() throws Exception {
final IBaseDataObject ibdo = new BaseDataObject();
final List<IBaseDataObject> children = new ArrayList<>();
final byte[] bytes = "Data".getBytes(StandardCharsets.ISO_8859_1);

setAllFieldsPrintable(ibdo, bytes);

final String xmlString = IBaseDataObjectXmlHelper.xmlFromIbdo(ibdo, children, ibdo, DEFAULT_ELEMENT_ENCODERS);

final SAXBuilder builder = new SAXBuilder(XMLReaders.NONVALIDATING);
final Document document = builder.build(new StringReader(xmlString));
final Element root = document.getRootElement();

assertNull(checkLengthElement(root, (int) ibdo.getChannelSize(), "answers", "data"));
assertNull(checkLengthElement(root, ibdo.footer().length, "answers", "footer"));
assertNull(checkLengthElement(root, ibdo.header().length, "answers", "header"));
assertNull(checkLengthKeyValue(root, ibdo.getAlternateView("AlternateView1Key").length, "AlternateView1Key", "answers", "view"));
assertNull(checkLengthKeyValue(root, ibdo.getAlternateView("AlternateView11Key").length, "AlternateView11Key", "answers", "view"));
}

@Test
void testLengthAttributeHash() throws Exception {
final IBaseDataObject ibdo = new BaseDataObject();
final List<IBaseDataObject> children = new ArrayList<>();
final byte[] bytes = "Data".getBytes(StandardCharsets.ISO_8859_1);

setAllFieldsNonPrintable(ibdo, bytes);

final String xmlString = IBaseDataObjectXmlHelper.xmlFromIbdo(ibdo, children, ibdo, DEFAULT_ELEMENT_ENCODERS);

final SAXBuilder builder = new SAXBuilder(XMLReaders.NONVALIDATING);
final Document document = builder.build(new StringReader(xmlString));
final Element root = document.getRootElement();

assertNull(checkLengthElement(root, (int) ibdo.getChannelSize(), "answers", "data"));
assertNull(checkLengthElement(root, ibdo.footer().length, "answers", "footer"));
assertNull(checkLengthElement(root, ibdo.header().length, "answers", "header"));
assertNull(checkLengthKeyValue(root, ibdo.getAlternateView("\200AlternateView1Key").length, "\200AlternateView1Key", "answers", "view"));
assertNull(checkLengthKeyValue(root, ibdo.getAlternateView("\200AlternateView11Key").length, "\200AlternateView11Key", "answers", "view"));
}

private static String checkLengthElement(Element rootElement, int lengthToCheck, String... xmlPathElementNames) {
Element element = rootElement;

for (int i = 0; i < xmlPathElementNames.length; i++) {
final String xmlPathElementName = xmlPathElementNames[i];
final List<Element> elements = element.getChildren(xmlPathElementName);

if (elements == null || elements.size() != 1) {
return "Cound not find element " + xmlPathElementName;
}

element = elements.get(0);
}

String lengthElement = element.getAttributeValue(IBaseDataObjectXmlCodecs.LENGTH_ATTRIBUTE_NAME);

if (lengthElement == null) {
return "Could not find length element";
}

try {
long length = Long.parseLong(lengthElement);

if (length == lengthToCheck) {
return null;
} else {
return "Looking for length " + lengthToCheck + ", but found " + length;
}
} catch (NumberFormatException e) {
return "NumberFormatException: " + lengthElement;
}
}

private static String checkLengthKeyValue(Element rootElement, int lengthToCheck, String key, String... xmlPathElementNames) {
Element element = rootElement;

for (int i = 0; i < xmlPathElementNames.length - 1; i++) {
final String xmlPathElementName = xmlPathElementNames[i];
final List<Element> elements = element.getChildren(xmlPathElementName);

if (elements == null || elements.isEmpty()) {
return "Cound not find element " + xmlPathElementName;
}

element = elements.get(0);
}

List<Element> keyValueElements = element.getChildren(xmlPathElementNames[xmlPathElementNames.length - 1]);

for (int j = 0; j < keyValueElements.size(); j++) {
final Element e = keyValueElements.get(j);
final Element nameElement = e.getChild(IbdoXmlElementNames.NAME);
final String name = nameElement.getValue();
final String nameEncoding = nameElement.getAttributeValue(IBaseDataObjectXmlCodecs.ENCODING_ATTRIBUTE_NAME);
final String nameDecoded = new String(extractBytes(nameEncoding, name), StandardCharsets.UTF_8);

if (nameDecoded.equals(key)) {
element = e.getChild(IbdoXmlElementNames.VALUE);
}
}

String lengthElement = element.getAttributeValue(IBaseDataObjectXmlCodecs.LENGTH_ATTRIBUTE_NAME);

if (lengthElement == null) {
return "Could not find length element";
}

try {
long length = Long.parseLong(lengthElement);

if (length == lengthToCheck) {
return null;
} else {
return "Looking for length " + lengthToCheck + ", but found " + length;
}
} catch (NumberFormatException e) {
return "NumberFormatException: " + lengthElement;
}
}

@Test
void testAttachmentsAndExtractedRecords() throws Exception {
final IBaseDataObject initialIbdo = new BaseDataObject();
Expand Down
Loading

0 comments on commit f7de34e

Please sign in to comment.