package org.atomnuke.syslog.parser; import java.text.ParseException; import java.util.Calendar; import org.atomnuke.syslog.StructuredDataBuilder; import org.atomnuke.syslog.SyslogMessage; import org.atomnuke.syslog.SyslogMessageBuilder; import org.atomnuke.syslog.util.RFC3339DateParser; import org.jboss.netty.util.CharsetUtil; /** * http://tools.ietf.org/html/rfc5424 * * @author zinic */ public class FramingSyslogParser { private final Accumulator characterAccumulator; private StructuredDataBuilder structuredDataBuilder; private SyslogMessageBuilder messageBuilder; private boolean countOctets, escaped, skip; private SyslogParserState parserState; private int octetsRemaining; private String errorMessage; private char skipUntil; public FramingSyslogParser() { // Smallest message for syslog is 2K so we'll start there characterAccumulator = new Accumulator(2048); reset(); } public void reset() { messageBuilder = new SyslogMessageBuilder(); parserState = SyslogParserState.START; skipUntil = 0; countOctets = false; escaped = false; skip = false; } public SyslogParserState getState() { return parserState; } public SyslogMessage getResult() { return messageBuilder; } public String getErrorMessage() { return errorMessage; } private void skipUntil(char controlChar) { skip = true; skipUntil = controlChar; } private boolean shouldSkip(byte character) { if (skip && character == skipUntil) { // Done skipping skip = false; return true; } return skip; } public void next(byte nextByte) { if (countOctets) { octetsRemaining--; } try { if (!shouldSkip(nextByte)) { handleByte(nextByte); } } catch (SyslogParserException spe) { updateState(SyslogParserState.ERROR); errorMessage = spe.getMessage(); } } private SyslogParserState currentState() { return parserState; } private void updateState(SyslogParserState state) { parserState = state; } private void handleByte(byte b) throws SyslogParserException { switch (currentState()) { case START: readStart(b); break; case MESSAGE_OCTET_COUNT: readMessageOctetLength(b); break; case PRIORITY: readPriority(b); break; case VERSION: readVersion(b); break; case TIMESTAMP_HEAD: readValueHead(b, SyslogParserState.TIMESTAMP_CONTENT, SyslogParserState.HOSTNAME_HEAD); break; case TIMESTAMP_CONTENT: readTimestampContent(b); break; case HOSTNAME_HEAD: readValueHead(b, SyslogParserState.HOSTNAME_CONTENT, SyslogParserState.APPNAME_HEAD); break; case HOSTNAME_CONTENT: readHostnameContent(b); break; case APPNAME_HEAD: readValueHead(b, SyslogParserState.APPNAME_CONTENT, SyslogParserState.PROCESS_ID_HEAD); break; case APPNAME_CONTENT: readAppNameContent(b); break; case PROCESS_ID_HEAD: readValueHead(b, SyslogParserState.PROCESS_ID_CONTENT, SyslogParserState.MESSAGE_ID_HEAD); break; case PROCESS_ID_CONTENT: readProcessIdContent(b); break; case MESSAGE_ID_HEAD: readValueHead(b, SyslogParserState.MESSAGE_ID_CONTENT, SyslogParserState.STRUCTURED_DATA_HEAD); break; case MESSAGE_ID_CONTENT: readMessageIdContent(b); break; case STRUCTURED_DATA_HEAD: readStructuredDataHead(b); break; case STRUCTURED_DATA_ID: readStructuredDataId(b); break; case STRUCTURED_DATA_PARAM: readStructuredDataParam(b); break; case STRUCTURED_DATA_PARAM_NAME: readStructuredDataParamName(b); break; case STRUCTURED_DATA_PARAM_VALUE: readStructuredDataParamValue(b); break; case MESSAGE_CONTENT: readMessageContent(b); break; case STOP: case ERROR: default: } } private boolean doneAccumulating(byte b, boolean prefixTrim, char... controlCharacters) { boolean done = false; if (!prefixTrim || characterAccumulator.size() > 0 || b != CharConstants.SPACE) { for (char control : controlCharacters) { // Found a control character that matches if (b == control) { if (!escaped) { // If this character isn't escaped then we're done done = true; } else { // If this character is escaped, mark that we aren't escaped anymore and accumulate escaped = false; } break; } } if (!done) { characterAccumulator.add(b); } } return done; } private boolean doneAccumulatingEscaped(byte b, boolean prefixTrim, char... controlCharacters) { if (!escaped && b == CharConstants.ESCAPE) { escaped = true; return false; } return doneAccumulating(b, prefixTrim, controlCharacters); } private void readStart(byte b) { if (b == CharConstants.LEFT_ANGLE_BRACKET) { updateState(SyslogParserState.PRIORITY); } else { // Accumulate this characterAccumulator.add(b); // This has an octet count so let's process it updateState(SyslogParserState.MESSAGE_OCTET_COUNT); } } private Integer readAccumulatedInteger() throws SyslogParserException { final String asciiString = characterAccumulator.getAll(CharsetUtil.US_ASCII); try { // Parse the priority value return Integer.parseInt(asciiString); } catch (NumberFormatException nfe) { throw new SyslogParserException("Unable to read accumulated bytes as a valid integer. Decoded bytes: " + asciiString + " - State: " + currentState() + " - Reason: " + nfe.getMessage(), nfe); } } private void readValueHead(byte b, SyslogParserState contentState, SyslogParserState nilValueState) { if (b == CharConstants.NIL_CHAR) { updateState(nilValueState); } else if (b != CharConstants.SPACE) { characterAccumulator.add(b); updateState(contentState); } } private void readMessageOctetLength(byte b) throws SyslogParserException { if (doneAccumulating(b, false, CharConstants.SPACE)) { countOctets = true; octetsRemaining = readAccumulatedInteger(); // Skip to the left angle bracket skipUntil(CharConstants.LEFT_ANGLE_BRACKET); updateState(SyslogParserState.PRIORITY); } } private void readPriority(byte b) throws SyslogParserException { if (doneAccumulating(b, false, CharConstants.RIGHT_ANGLE_BRACKET)) { // Parse the priority value final Integer priority = readAccumulatedInteger(); messageBuilder.setPriority(priority); // Update the state for reading the version updateState(SyslogParserState.VERSION); } } private void readVersion(byte b) throws SyslogParserException { if (doneAccumulating(b, false, CharConstants.SPACE)) { // Versions aren't specified for older syslog versions if (characterAccumulator.size() == 0) { throw new SyslogParserException("This parser expects messages to conform to rfc5424."); } // Parse the version value final Integer version = readAccumulatedInteger(); messageBuilder.setVersion(version); // Update the state for reading the timestamp updateState(SyslogParserState.TIMESTAMP_HEAD); } } private void readTimestampContent(byte b) throws SyslogParserException { if (doneAccumulating(b, false, CharConstants.SPACE)) { final String timestampValue = characterAccumulator.getAll(CharsetUtil.US_ASCII); try { final Calendar timestamp = RFC3339DateParser.parseRFC3339Date(timestampValue); messageBuilder.setTimestamp(timestamp); updateState(SyslogParserState.HOSTNAME_HEAD); } catch (ParseException parseException) { throw new SyslogParserException("Unable to parse date field: " + timestampValue, parseException); } } } private void readHostnameContent(byte b) { if (doneAccumulating(b, true, CharConstants.SPACE)) { messageBuilder.setOriginHostname(characterAccumulator.getAll(CharsetUtil.US_ASCII)); updateState(SyslogParserState.APPNAME_HEAD); } } private void readAppNameContent(byte b) { if (doneAccumulating(b, true, CharConstants.SPACE)) { messageBuilder.setApplicationName(characterAccumulator.getAll(CharsetUtil.US_ASCII)); updateState(SyslogParserState.PROCESS_ID_HEAD); } } private void readProcessIdContent(byte b) { if (doneAccumulating(b, true, CharConstants.SPACE)) { messageBuilder.setProcessId(characterAccumulator.getAll(CharsetUtil.US_ASCII)); updateState(SyslogParserState.MESSAGE_ID_HEAD); } } private void readMessageIdContent(byte b) { if (doneAccumulating(b, true, CharConstants.SPACE)) { messageBuilder.setMessageId(characterAccumulator.getAll(CharsetUtil.US_ASCII)); updateState(SyslogParserState.STRUCTURED_DATA_HEAD); } } private void readStructuredDataHead(byte b) { if (b == CharConstants.LEFT_SQUARE_BRACKET) { updateState(SyslogParserState.STRUCTURED_DATA_ID); } else if (b != CharConstants.SPACE) { // Not a space or a left square bracket - accumulate this, it's part of the message characterAccumulator.add(b); updateState(SyslogParserState.MESSAGE_CONTENT); } } private void readStructuredDataId(byte b) { if (doneAccumulating(b, true, CharConstants.SPACE)) { structuredDataBuilder = messageBuilder.newStructuredDataBuilder(characterAccumulator.getAll(CharsetUtil.US_ASCII)); updateState(SyslogParserState.STRUCTURED_DATA_PARAM); } } private void readStructuredDataParam(byte b) { if (b != CharConstants.RIGHT_SQUARE_BRACKET) { characterAccumulator.add(b); updateState(SyslogParserState.STRUCTURED_DATA_PARAM_NAME); } else { updateState(SyslogParserState.STRUCTURED_DATA_HEAD); } } private void readStructuredDataParamName(byte b) throws SyslogParserException { if (doneAccumulating(b, false, CharConstants.EQUALS)) { // Switching buffers lets us store the name for now characterAccumulator.switchBuffers(); // Skip the quotation mark, don't care about what's in between skipUntil(CharConstants.QUOTE); // Next is the value itself updateState(SyslogParserState.STRUCTURED_DATA_PARAM_VALUE); } } private void readStructuredDataParamValue(byte b) { if (doneAccumulatingEscaped(b, false, CharConstants.QUOTE)) { // Record our value as a UTF-8 String final String value = characterAccumulator.getAll(CharsetUtil.UTF_8); // Flip buffers to get back to the name characterAccumulator.switchBuffers(); // Read the param name as an ASCII string final String name = characterAccumulator.getAll(CharsetUtil.US_ASCII); // Put the parameter structuredDataBuilder.setParam(name, value); // Read the next parameter pair updateState(SyslogParserState.STRUCTURED_DATA_PARAM); } } private void readMessageContent(byte b) { boolean commitMessageContent = false; // If we're counting octets, this will be fine if (countOctets) { characterAccumulator.add(b); // If this is the last octet then we should commit the message commitMessageContent = octetsRemaining == 0; } else if (doneAccumulatingEscaped(b, false, CharConstants.LINE_FEED)) { commitMessageContent = true; } if (commitMessageContent) { // Record our value as a UTF-8 String final String value = characterAccumulator.getAll(CharsetUtil.UTF_8); // Put the parameter messageBuilder.setContent(value); // Read the next parameter pair updateState(SyslogParserState.STOP); } } }