class RrdpParser {

    public void parseSnapshot(InputStream inputStream, Consumer<SnapshotHeader> processSnapshotHeader, Consumer<SnapshotObject> processSnapshotObject) {
        try {
            final XMLInputFactory factory = XMLInputFactory.newInstance();
            final XMLEventReader eventReader = factory.createXMLEventReader(inputStream);

            String sessionId = null;
            BigInteger serial = null;
            String uri = null;
            StringBuilder base64 = new StringBuilder();
            boolean inPublishElement = false;
            boolean snapshotHeaderProcessed = false;

            final Base64.Decoder decoder = Base64.getDecoder();

            while (eventReader.hasNext()) {
                final XMLEvent event = eventReader.nextEvent();

                switch (event.getEventType()) {
                    case XMLStreamConstants.START_ELEMENT:
                        final StartElement startElement = event.asStartElement();
                        final String qName = startElement.getName().getLocalPart().toLowerCase(Locale.ROOT);

                        switch (qName) {
                            case "publish":
                                if (!snapshotHeaderProcessed) {
                                    throw new RrdpException(ErrorCodes.RRDP_PARSE_ERROR, "snapshot header not present before published objects");
                                }
                                uri = getAttr(startElement, "uri", "Uri is not present in 'publish' element");
                                inPublishElement = true;
                                break;
                            case "snapshot":
                                serial = new BigInteger(getAttr(startElement, "serial", "Notification serial is not present"));
                                sessionId = getAttr(startElement, "session_id", "Session id is not present");
                                processSnapshotHeader.accept(new SnapshotHeader(sessionId, serial));
                                snapshotHeaderProcessed = true;
                                break;
                        }
                        break;

                    case XMLStreamConstants.CHARACTERS:
                        final Characters characters = event.asCharacters();
                        if (inPublishElement) {
                            final String thisBase64 = characters.getData();
                            base64.append(thisBase64.replaceAll("\\s", ""));
                        }
                        break;

                    case XMLStreamConstants.END_ELEMENT:
                        final EndElement endElement = event.asEndElement();
                        final String qqName = endElement.getName().getLocalPart().toLowerCase(Locale.ROOT);
                        if ("publish".equals(qqName)) {
                            final byte[] decoded = decoder.decode(base64.toString());
                            processSnapshotObject.accept(new SnapshotObject(decoded, uri));
                            inPublishElement = false;
                            base64 = new StringBuilder();
                        }
                        break;
                }
            }
        } catch (XMLStreamException e) {
            throw new RrdpException("Couldn't parse snapshot: ", e);
        }
    }

}