Bug in NDM writting

Hi!

Context

I’m currently writting NDM files and while doing my unit validation I found an interesting bug.
My unit test is easy and consist in validating that an empty NDM can be generated by Orekit.

Bug detail

While generating my empty NDM I was supprised to see that only the XML header was generated and not the <ndm></ndm> block (even if empty). This block is mandatory according to 8.12.5 of CCSDS blue book.

<?xml version="1.0" encoding="UTF-8"?>

So I tried to parse this file using Orekit and encountered the following exception.

org.orekit.errors.OrekitException: Premature end of file.

	at org.orekit.files.ccsds.utils.lexical.XmlLexicalAnalyzer.accept(XmlLexicalAnalyzer.java:95)
	at org.orekit.files.ccsds.utils.parsing.AbstractMessageParser.parseMessage(AbstractMessageParser.java:154)
	at org.orekit.files.ccsds.ndm.NdmWriterTest.testEmpty(NdmWriterTest.java:61)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.util.ArrayList.forEach(ArrayList.java:1259)
	at java.util.ArrayList.forEach(ArrayList.java:1259)
Caused by: org.xml.sax.SAXParseException; lineNumber: 2; columnNumber: 1; Premature end of file.

How to reproduce?

Please find below a small example to reproduce the issue. The NDM is generated with Orekit and parsed again with Orekit

    @Test
    public void testEmpty() throws IOException {
        StringBuffer stringBuffer = new StringBuffer();
        new WriterBuilder(DataContext.getDefault()).buildNdmWriter()
                                                   .writeMessage(new XmlGenerator(stringBuffer, 0, "empty.xml", 0., false, XmlGenerator.NDM_XML_V3_SCHEMA_LOCATION),
                                                                 new Ndm(new ArrayList<>(), new ArrayList<>()));
        Ndm ndm = new ParserBuilder().buildNdmParser().parseMessage(new DataSource("empty.xml", () -> new StringReader(stringBuffer.toString())));
        Assertions.assertTrue(ndm.getComments().isEmpty());
        Assertions.assertTrue(ndm.getConstituents().isEmpty());
    }

What do you think?

Thanks

Bryan

I found a second bug

Bug detail

I tried to generate a NDM with 1 OPM and 1 APM and found that the </ndm> key is missing at the end of the file. However, it is mandatory.

So, when I try to read this file with Orekit again, I have the following exception (meaning that the key is missing.

org.orekit.errors.OrekitException: XML document structures must start and end within the same entity.

	at org.orekit.files.ccsds.utils.lexical.XmlLexicalAnalyzer.accept(XmlLexicalAnalyzer.java:95)
	at org.orekit.files.ccsds.utils.parsing.AbstractMessageParser.parseMessage(AbstractMessageParser.java:154)
	at org.orekit.files.ccsds.ndm.NdmWriterTest.testBugMissingNdmEndKey(NdmWriterTest.java:88)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at java.util.ArrayList.forEach(ArrayList.java:1259)
	at java.util.ArrayList.forEach(ArrayList.java:1259)
Caused by: org.xml.sax.SAXParseException; lineNumber: 77; columnNumber: 1; XML document structures must start and end within the same entity.

How to reproduce?

Please find below and example explaining how to reproduce.

    @Test
    public void testBugMissingNdmEndKey() throws IOException {
        ParserBuilder parserBuilder = new ParserBuilder(DataContext.getDefault()).withConventions(IERSConventions.IERS_2010)
                                                                                  .withMu(Constants.WGS84_EARTH_MU)
                                                                                  .withDefaultMass(Propagator.DEFAULT_MASS);
        final String opmName = "/ccsds/odm/opm/OPMExample1.txt";
        final Opm opm = parserBuilder.buildOpmParser().parseMessage(new DataSource(opmName, () -> NdmWriterTest.class.getResourceAsStream(opmName)));
        final String apmName = "/ccsds/adm/apm/APMExample01.txt";
        final Apm apm = parserBuilder.buildApmParser().parseMessage(new DataSource(apmName, () -> NdmWriterTest.class.getResourceAsStream(apmName)));
        List<NdmConstituent<?, ?>> constituents = new ArrayList<>();
        constituents.add(opm);
        constituents.add(apm);
        final Ndm initial = new Ndm(new ArrayList<>(), constituents);
        StringBuffer stringBuffer = new StringBuffer();
        new WriterBuilder(DataContext.getDefault()).buildNdmWriter()
                                                   .writeMessage(new XmlGenerator(stringBuffer, 0, "empty.xml", 0., false, XmlGenerator.NDM_XML_V3_SCHEMA_LOCATION),
                                                                 initial);
        Ndm rebuilt = new ParserBuilder().buildNdmParser().parseMessage(new DataSource("empty.xml", () -> new StringReader(stringBuffer.toString())));
        Assertions.assertEquals(2, rebuilt.getConstituents().size());
    }

Do you confirm that these are two bugs and that I’m not just wrongly using the parser/writter :smiley: ?

If yes, I can handle the fixes

Hey Bryan,

From what i remember, i’m not sure if it is explicitely mentioned that the block should be added even for empty/single content. I assumed that it was only required when containing multiple data but i would have to check again.

However even in that case, i guess it should ideally return an Optional (though this would be another feature) that could be empty.

Yeah this one is definitely a bug.

Cheers,
Vincent

Thanks for the answer

For me they are three clues highlighting those tags are required:

  • Orekit is not able to parse a file generated by itself, so definitely there is something wrong somewhere
  • The NdmParserTest#testEmpty() tests with the tags. Without the tags the test do not pass and throws the exception.
  • The standard says: “An NDM combined instantiation shall be delimited with the <ndm></ndm> root element tags instead of one of the individual message tags described in 8.3.2.” My interpretation of this sentence is that they are mandatory. So even if they are no other tags inside.

I’ll fix it.

I’m understanding this otherwise:

“a NDM combined instantiation shall be delimited with <ndm></ndm> instead of one of the individual message tags described in 8.3.2.”

From my point of view:

  • Single OPM (or other file) inside, i expect → <opm></opm>
  • Several OPM (or other files) → <ndm><opm>...</opm><opm>...</opm></ndm>

In that case, how do you know if it’s a simple OPM or a combined NDM with just:

<?xml version="1.0" encoding="UTF-8"?>

In addition, it means that NdmParser#testEmpty do not test properly an empty file and shall be adapted with just the above line (it will throw an exception)

The empty case is not adressed in the CCSDS reference (again, from what i remember :sweat_smile:) so i guess we can use an arbitrary solution or simply not accept it and throw an exception (as it is currently implemented if i understand your post). Writing an empty ndm/other file shall then not be possible following the second option’s logic.

For sure, a single OPM shall be delimited with <opm></opm> and a combined NDM instance with <ndm>some other xml tags</ndm>. I completely agree with you on this point.

If no one objects, I’ll update NdmWriter to output the <ndm></ndm> tags even if the file is empty. This way, it will generate the exact same file used by NdmParserTest#testEmpty()

This is a +1 for me

I agree with the fix proposed by @bcazabonne

Thanks for your replies :slightly_smiling_face:
I created the two issues and will fix them by the end of the week to include them in the 13.1.6 program.