index.js

// INDEX.JS
/** Copyright (c) 2018 Craig Yamato */

/**
 * @fileoverview The SyslogPro module for sending syslog messages
 * Most APIs will return a promise. These APIs can be used using
 * `then(...)/catch(...)`
 *
 * Syslog formatting classes can be used as input into a Syslog class to be used
 * simultaneously to the same Syslog server.  The Syslog Class with a configured
 * Syslog server target can also be used as the input into each of the
 * formatting classes so that they may run independently.
 * @author Craig Yamato <craig@kentik.com>
 * @copyright (c) 2018 - Craig Yamato
 * @version 0.1.0
 * @exports Syslog
 * @exports LEEF
 * @exports CEF
 * @module SyslogPro
 */
'use strict';
const moment = require('moment');
const os = require('os');
const dns = require('dns');
let dnsPromises = dns.promises;
const fs = require('fs');

/**
 * Format the ANSI foreground color code from a RGB hex code or ANSI color code
 * @private
 * @param {string} hex - The color hex code in the form of #FFFFFF or Number of
 *     the ANSI color code (30-37 Standard & 0-255 Extended)
 * @returns {Promise} - The formatted ANSI color code
 * @throws {Error} - A Format Error
 */
function rgbToAnsi(hex,
  extendedColor) {
  return new Promise((resolve, reject) => {
    let colorCode = 0; // Var to hold color code
    // Break HEX Code up into RGB
    const hexParts = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    if (hexParts || typeof hex === 'number') {
      if (typeof hex === 'number') {
        if (extendedColor && hex < 256) {
          resolve(hex);
        } else if ((hex > 29 && hex < 38) || (hex > 89 && hex < 98)) {
          resolve(hex);
        } else {
          reject(new Error('FORMAT ERROR: Color code not in range'));
        }
      } else {
        const r = parseInt(hexParts[1], 16);
        const g = parseInt(hexParts[2], 16);
        const b = parseInt(hexParts[3], 16);
        if (extendedColor) {
          if (r === g && g === b) {
            // Gray Scale Color
            if (r < 8) {
              colorCode = 16;
            } else if (r > 248) {
              colorCode = 231;
            } else {
              colorCode = Math.round(((r - 8) / 247) * 24) + 232;
            }
          } else {
            colorCode = 16
                + (36 * Math.round(r / 255 * 5))
                + (6 * Math.round(g / 255 * 5))
                + Math.round(b / 255 * 5);
          }
        } else {
          colorCode = 30;
          const red = r / 255;
          const green = g / 255;
          const blue = b / 255;
          let v = Math.max(red, green, blue) * 100;
          v = Math.round(v / 50);
          if (v === 1) {
            colorCode += ((Math.round(b / 255) << 2)
                | (Math.round(g / 255) << 1)
                | Math.round(r / 255));
          }
          if (v === 2) {
            colorCode += 60;
          }
        }
      }
      resolve(colorCode);
      return;
    } else {
      reject(new Error('TYPE ERROR: Not in RGB color hex or color code'));
      return;
    }
  });
}

/**
 * A class to work with syslog messages using UDP, TCP, or TLS transport.
 * There is support for Syslog message formatting RFC-3164, RFC-5424 including
 * Structured Data, IBM LEEF (Log Event Extended Format), and HP CEF (Common
 * Event Format).
 * Syslog formatting classes can be used as input into a Syslog class to be used
 * simultaneously to the same Syslog server. *
 * @requires moment
 * @version 0.0.0
 * @since 0.0.0
 */
class Syslog {
  /**
   * Construct a new Syslog transport object with user options
   * @public
   * @version 0.0.0
   * @since 0.0.0
   * @this Syslog
   * @param {object} [options] - Options object
   * >>>Transport Configuration
   * @param {string} [options.target='localhost'] - The IP Address|FQDN of the
   *    Syslog Server, this option if set will take presidents over any target
   *    set in a formatting object
   * @param {string} [options.protocol='udp'] - L4 transport protocol
   *    (udp|tcp|tls), this option if set will take presidents over any
   *    transport set in a formatting object
   * @param {number} [options.port=514] - IP port, this option if set will take
   *    presidents over any IP Port set in a formatting object
   * @param {number} [options.tcpTimeout=10000] - Ignored for all other
   *    transports, this option if set will take presidents over any timeout
   *    set in a formatting object
   * @param {string[]} [options.tlsServerCerts] - Array of authorized TLS server
   *    certificates file locations, this option if set will take presidents
   *    over any certificates set in a formatting object
   * @param {string} [options.tlsClientCert] - Client TLS certificate file
   *    location that this client should use, this option if set will take
   *    presidents over any certificates set in a formatting object
   * @param {string} [options.tlsClientKey] - Client TLS key file
   *    location that this client should use, this option if set will take
   *    presidents over any certificates set in a formatting object
   * >>>Syslog Format Settings
   * @param {string} [options.format='none'] - Valid syslog format options for
   *    this module are 'none', 'rfc3164', 'rfc5424', 'leef', 'cef'
   * @param {RFC3164} [options.rfc5424] - {@link module:SyslogPro~RFC5424|
   *    RFC5424 related settings}
   * @param {RFC5424} [options.rfc5424] - {@link module:SyslogPro~RFC5424|
   *    RFC5424 related settings}
   * @param {LEEF} [options.leef] - {@link module:SyslogPro~LEEF|IBM LEEF
   *    (Log Event Extended Format) object}
   * @param {CEF} [options.cef] - {@link module:SyslogPro~CEF|HP CEF
   *    (Common Event Format) formatting object}
   */
  constructor(options) {
    this.constructor__ = true;
    if (!options) {
      options = {};
    }
    // Basic transport setup
    /** @type {string} */
    this.target = options.target || 'localhost';
    /** @type {string} */
    this.protocol = options.protocol || 'udp';
    this.protocol = this.protocol.toLowerCase();
    /** @type {number} */
    this.port = options.port || 514;
    /** @type {number} */
    this.tcpTimeout = options.tcpTimeout || 10000;
    if ((typeof options.tlsServerCerts === 'object'
        && Array.isArray(options.tlsServerCerts))
        || typeof options.tlsServerCerts === 'string') {
      this.addTlsServerCerts(options.tlsServerCerts);
    } else {
      /** @type {string[]} */
      this.tlsServerCerts = [];
    }
    if (options.tlsClientCert) {
      /** @type {string} */
      this.tlsClientCert = options.tlsClientCert;
    }
    if (options.tlsClientKey) {
      /** @type {string} */
      this.tlsClientKey = options.tlsClientKey;
    }
    // Syslog Format
    if (typeof options.format === 'string') {
      /** @type {string} */
      this.format = options.format.toLowerCase();
    } else {
      this.format = options.format || 'none';
    }
    if (options.rfc3164) {
      if (options.rfc3164.constructor__) {
        /** @type {RFC3164} */
        this.rfc3164 = options.rfc3164;
      } else {
        this.rfc3164 = new RFC3164(options);
      }
    }
    if (options.rfc5424) {
      if (options.rfc5424.constructor__) {
        /** @type {RFC5424} */
        this.rfc5424 = options.rfc5424;
      } else {
        this.rfc5424 = new RFC5424(options);
      }
    }
    if (options.leef) {
      if (options.leef.constructor__) {
        /** @type {LEEF} */
        this.leef = options.leef;
      } else {
        this.leef = new LEEF(options);
      }
    }
    if (options.cef) {
      if (options.cef.constructor__) {
        /** @type {CEF} */
        this.cef = options.cef;
      } else {
        this.cef = new CEF(options);
      }
    }
    if (this.format === 'rfc3164' && !this.rfc3164) {
      this.rfc3164 = new RFC3164();
    }
    if (this.format === 'rfc5424' && !this.rfc5424) {
      this.rfc5424 = new RFC5424();
    }
    if (this.format === 'leef' && !this.leef) {
      this.leef = new LEEF();
    }
    if (this.format === 'cef' && !this.cef) {
      this.cef = new CEF();
    }
  }

  /**
   * Add a TLS server certificate which can be used to authenticate the server
   * this syslog client is connecting too.  This function will validate the
   * input as a file location string and add it to an array of certificates
   * @private
   * @version 0.0.0
   * @since 0.0.0
   * @param {string|string[]} certs - File location of the certificate(s)
   * @returns {Promise} - True
   * @throws {Error} - A Type Error
   */
  addTlsServerCerts(certs) {
    return new Promise((resolve, reject) => {
      if (typeof certs === 'object' && Array.isArray(certs)) {
        /** @private @type {string[]} */
        this.tlsServerCerts = certs;
      } else if (typeof certs === 'string') {
        this.tlsServerCerts = [certs];
      } else {
        let errMsg =
            'TYPE ERROR: Server Cert file locations should be a string';
        errMsg += ' or array of strings';
        reject(new Error(errMsg));
      }
      resolve(true);
    });
  }
  /**
   * Send the Syslog message over UDP
   * @private
   * @param {string} msg - The formatted Syslog Message
   * @returns {Promise} - The Syslog formatted string sent
   * @throws {Error} - Network Error
   */
  udpMessage(msg) {
    return new Promise((resolve, reject) => {
      // Test for target DNS and Address Family (IPv4/6) by looking up the DNS
      const dgram = require('dgram');
      const dnsOptions = {
        verbatim: true,
      };
      dnsPromises.lookup(this.target, dnsOptions)
        .then((result) => {
          const udpType = result.family === 4 ? 'udp4' : 'udp6';
          let client = dgram.createSocket(udpType);
          // Turn msg in to a UTF8 buffer
          let msgBuffer = Buffer.from(msg, 'utf8');
          client.send(msgBuffer, this.port, this.target, () => {
            client.close();
            resolve(msg);
          });
        })
        .catch((error) => {
          reject(error); // Reject out of the sendMessage function promise
        });
    });
  }
  /**
   * Send the Syslog message over TCP
   * @private
   * @param {string} msg - The formatted Syslog Message
   * @returns {Promise} - The Syslog formatted string sent
   * @throws {Error} - Timeout error for TCP and TLS connections
   * @throws {Error} - Network Error
   */
  tcpMessage(msg) {
    return new Promise((resolve, reject) => {
      const net = require('net');
      const dnsOptions = {
        verbatim: true,
      };
      dnsPromises.lookup(this.target, dnsOptions)
        .then((result) => {
          const tcpOptions = {
            host: this.target,
            port: this.port,
            family: result.family,
          };
          const client = net.createConnection(tcpOptions, () => {
            // Turn msg in to a UTF8 buffer
            let msgBuffer = Buffer.from(msg, 'utf8');
            client.write(msgBuffer, () => {
              client.end();
            });
          });
          client.setTimeout(this.tcpTimeout);
          client.on('end', () => {
            resolve(msg);
          });
          client.on('timeout', () => {
            client.end();
            reject(new Error('TIMEOUT ERROR: Syslog server TCP timeout'));
          });
          client.on('error', (error) => {
            client.destroy();
            reject(error);
          });
        })
        .catch((error) => {
          reject(error);
        });
    });
  }
  /**
   * Send the Syslog message over TLS
   * @private
   * @param {string} msg - The formatted Syslog Message
   * @returns {Promise} - The Syslog formatted string sent
   * @throws {Error} - Timeout error for TCP and TLS connections
   * @throws {Error} - Network Error
   */
  tlsMessage(msg) {
    return new Promise((resolve, reject) => {
      const tls = require('tls');
      const tlsOptions = {
        host: this.target,
        port: this.port,
      };
      // Load client cert and key if requested
      if (typeof this.tlsClientKey === 'string'
          && typeof this.tlsClientCert === 'string') {
        tlsOptions.key = fs.readFileSync(this.tlsClientKey);
        tlsOptions.cert = fs.readFileSync(this.tlsClientCert);
      } else if (typeof this.tlsClientKey !== 'string'
          && typeof this.tlsClientKey !== 'undefined') {
        let errMsg = 'TYPE ERROR: TLS Client Key is not a file';
        errMsg += 'location string';
        reject(new Error(errMsg));
        return;
      } else if (typeof this.tlsClientCert !== 'string'
          && typeof this.tlsClientCert !== 'undefined') {
        let errMsg = 'TYPE ERROR: TLS Client Cert is not a file';
        errMsg += 'location string';
        reject(new Error(errMsg));
        return;
      }
      // Load any server certs if provided
      let tlsCerts = this.tlsServerCerts.length;
      if (tlsCerts > 0) {
        let tlsOptionsCerts = [];
        for (let certIndex = 0; certIndex < tlsCerts; certIndex++) {
          if (typeof this.tlsServerCerts[certIndex] !== 'string') {
            let errMsg = 'TYPE ERROR: TLS Server Cert is not a file';
            errMsg += 'location string';
            reject(new Error(errMsg));
          }
          let cert = fs.readFileSync(this.tlsServerCerts[certIndex]);
          tlsOptionsCerts.push(cert);
        }
        tlsOptions.ca = tlsOptionsCerts;
        tlsOptions.rejectUnauthorized = true;
      }
      const client = tls.connect(tlsOptions, () => {
        // Turn msg in to a UTF8 buffer
        let msgBuffer = Buffer.from(msg, 'utf8');
        client.write(msgBuffer, () => {
          client.end();
        });
      });
      client.setTimeout(this.tcpTimeout);
      client.on('end', () => {
        resolve(msg);
      });
      client.on('timeout', () => {
        client.end();
        reject(new Error('TIMEOUT ERROR: Syslog server TLS timeout'));
      });
      client.on('error', (error) => {
        client.destroy();
        reject(error);
      });
    });
  }
  /**
   * Send the Syslog message to the selected target Syslog server using the
   * selected transport.
   * @private
   * @param {string} msg - The formatted Syslog Message
   * @returns {Promise} - The Syslog formatted string sent
   * @throws {Error} - Timeout error for TCP and TLS connections
   * @throws {Error} - Network Error
   */
  send(msg) {
    return new Promise((resolve, reject) => {
      if (typeof msg !== 'string') {
        reject(new Error('TYPE ERROR: Syslog message must be a string'));
        return;
      }
      this.protocol = this.protocol.toLowerCase();
      if (this.protocol === 'udp') {
        this.udpMessage(msg)
          .then((result) => {
            resolve(result);
          })
          .catch((reson) => {
            reject(reson);
          });
      } else if (this.protocol === 'tcp') {
        this.tcpMessage(msg)
          .then((result) => {
            resolve(result);
          })
          .catch((reson) => {
            reject(reson);
          });
      } else if (this.protocol === 'tls') {
        this.tlsMessage(msg)
          .then((result) => {
            resolve(result);
          })
          .catch((reson) => {
            reject(reson);
          });
      } else {
        let errorMsg = 'FORMAT ERROR: Protocol not recognized, should be ';
        errorMsg += 'udp|tcp|tls';
        reject(new Error(errorMsg));
      }
    });
  }
}

/**
 * A class to work with RFC3164 formatted syslog messages. The messaging is
 * fully configurable and ANSI foreground colors can be added.  Both ANSI 8 and
 * ANSI 256 color are fully supported. Most APIs will return a promise. These
 * APIs can be used using `then(...)/catch(...)`
 *
 * A Syslog class with a configured
 * Syslog server target can also be used as the input into the formatting
 * classes so that it may run independently.
 *
 * The RFC3164 Syslog logging format is meant to be used as a stream of log data
 * from a service or application. This class is designed to be used in this
 * fashion where new messages are written to the class as needed.
 * @requires moment
 * @version 0.0.0
 * @since 0.0.0
 */
class RFC3164 {
  /**
   * Construct a new RFC3164 formatted Syslog object with user options
   * @public
   * @this RFC3164
   * @param {object} [options] - Options object
   * @param {string} [options.applicationName='NodeJSLogger'] - Application
   * @param {string} [options.hostname=os.hostname] - The name of this server
   * @param {number} [options.facility=23] - Facility code to use sending this
   *    message
   * @param {boolean} [options.color=false] - Apply color coding encoding tag
   *    with syslog message text
   * @param {boolean} [options.extendedColor=false] - Use the extended ANSI
   *    color set encoding tag with syslog message text
   * @param {object} [options.colors] - User defended colors for
   *    severities
   * @param {string} [options.colors.emergencyColor] - A RGB Hex coded color in
   *    the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
   *    & 0-255 Extended)
   * @param {string} [options.colors.alertColor] - A RGB Hex coded color in the
   *    form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
   *    0-255 Extended)
   * @param {string} [options.colors.criticalColor] - A RGB Hex coded color in
   *    the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
   *    & 0-255 Extended)
   * @param {string} [options.colors.errorColor] - A RGB Hex coded color in the
   *    form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
   *    0-255 Extended)
   * @param {string} [options.colors.warningColor] - A RGB Hex coded color in
   *    the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
   *     & 0-255 Extended)
   * @param {string} [options.colors.noticeColor] - A RGB Hex coded color in the
   *     form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
   *     0-255 Extended)
   * @param {string} [options.colors.informationalColor] - A RGB Hex coded color
   *    in the form of #FFFFFF or as or the ANSI color code number (30-37
   *    Standard & 0-255 Extended)
   * @param {string} [options.colors.debugColor] - A RGB Hex coded color in the
   *    form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
   *    0-255 Extended)
   * @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog|
   *    Syslog server connection} that should be used to send messages directly
   *    from this class. @see SyslogPro~Syslog
   */
  constructor(options) {
    /** @private @type {boolean} */
    this.constructor__ = true;
    options = options || {};
    this.hostname = options.hostname || os.hostname();
    this.applicationName = options.applicationName || '';
    this.facility = options.facility || 23;
    if (options.color) {
      /** @type {boolean} */
      this.color = true;
    } else {
      this.color = false;
    }
    if (options.extendedColor) {
      /** @type {boolean} */
      this.extendedColor = true;
    } else {
      this.extendedColor = false;
    }
    if (options.server) {
      if (!options.server.constructor__) {
        /** @private @type {Syslog} */
        this.server = new Syslog(options.server);
      } else {
        this.server = options.server;
      }
    }
    if (this.extendedColor) {
      /** @private @type {number} */
      this.emergencyColor = 1; // Red foreground color
      /** @private @type {number} */
      this.alertColor = 202; // Dark Orange foreground color
      /** @private @type {number} */
      this.criticalColor = 208; // Orange foreground color
      /** @private @type {number} */
      this.errorColor = 178; // Light Orange foreground color
      /** @private @type {number} */
      this.warningColor = 226; // Yellow foreground color
      /** @private @type {number} */
      this.noticeColor = 117; // Light Blue foreground color
      /** @private @type {number} */
      this.informationalColor = 45; // Blue foreground color
      /** @private @type {number} */
      this.debugColor = 27; // Dark Blue foreground color
    } else {
      this.emergencyColor = 31; // Red foreground color
      this.alertColor = 31; // Red foreground color
      this.criticalColor = 31; // Red foreground color
      this.errorColor = 33; // Yellow foreground color
      this.warningColor = 33; // Yellow foreground color
      this.noticeColor = 36; // Blue foreground color
      this.informationalColor = 36; // Blue foreground color
      this.debugColor = 34; // Dark Blue foreground color
    }
    if (typeof options.colors === 'object') {
      this.setColor(options.colors, this.extendedColor);
    }
  }
  /**
   * Sets the color to be used for messages at a set priority
   * @public
   * @param {string} [colors.emergencyColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.alertColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.criticalColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.errorColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.warningColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.noticeColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.informationalColor] - A RGB Hex coded color in the
   *    form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
   *    0-255 Extended)
   * @param {string} [colors.debugColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @throws {Error} A standard error object
   */
  setColor(colors, extendedColor) {
    return new Promise((resolve, reject) => {
      let colorPromises = [];
      if (colors.emergencyColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.emergencyColor, this.extendedColor)
              .then((result) => {
                this.emergencyColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'emergencyColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.alertColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.alertColor, this.extendedColor)
              .then((result) => {
                this.alertColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'alertColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.criticalColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.criticalColor, this.extendedColor)
              .then((result) => {
                this.criticalColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'criticalColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.errorColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.errorColor, this.extendedColor)
              .then((result) => {
                this.errorColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'errorColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.warningColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.warningColor, this.extendedColor)
              .then((result) => {
                this.warningColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'warningColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.noticeColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.noticeColor, this.extendedColor)
              .then((result) => {
                this.noticeColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'noticeColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.informationalColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.informationalColor, this.extendedColor)
              .then((result) => {
                this.informationalColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'informationalColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.debugColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.debugColor, this.extendedColor)
              .then((result) => {
                this.debugColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'debugColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      Promise.all(colorPromises)
        .then((results) => {
          resolve(true);
        })
        .catch((reson) => {
          reject(reson);
        });
    });
  }
  /**
   * Building a formatted message.  Returns a promise with a formatted message
   * @public
   * @param {string} msg - The Syslog Message
   * @param {object} [options] - Options object
   * @param {number} [options.severity=7] - An array of structure
   * @param {number} [options.colorCode=36] - The ANSI color code to use if
   *    message coloration is selected
   * @returns {Promise} A Syslog formatted string according to the selected RFC
   * @throws {Error} A standard error object
   */
  buildMessage(msg, options) {
    return new Promise((resolve, reject) => {
      options = options || {};
      let severity = typeof options.severity === 'number' ?
        options.severity : 6;
      if (typeof msg !== 'string' || options.msgSeverity > 7) {
        let errMsg = 'FORMAT ERROR: Syslog message must be a string';
        errMsg += ' msgSeverity must be a number between 0 and 7';
        reject(new Error(errMsg));
        return;
      }
      let fmtMsg = ''; // Formatted Syslog message string var
      const newLine = '\n';
      const newLineRegEx = /(\r|\n|(\r\n))/;
      const escapeCode = '\u001B';
      const resetColor = '\u001B[0m';
      // The PRI is common to both RFC formats
      const pri = (this.facility * 8) + severity;
      // Remove any newline character
      msg = msg.replace(newLineRegEx, '');
      // Add requested color
      if (this.color) {
        options.msgColor = options.msgColor || 36;
        let colorCode = '[';
        if (this.extendedColor) {
          colorCode += '38;5;'; // Extended 256 Colors ANSI Code
        }
        if (typeof options.msgColor === 'number') {
          colorCode += options.msgColor;
          colorCode += 'm'; // ANSI Color Closer
        } else {
          colorCode = '[39m'; // Use terminal's default color
        }
        msg = escapeCode + colorCode + msg + resetColor;
      }
      // RegEx to find a leading 0 in the day of a DateTime for RFC3164 RFC3164
      // uses BSD timeformat
      const rfc3164DateRegEx =
/((A|D|F|J|M|N|O|S)(a|c|e|p|o|u)(b|c|g|l|n|p|r|t|v|y)\s)0(\d\s\d\d:\d\d:\d\d)/;
      const timestamp = moment()
        .format('MMM DD hh:mm:ss')
        .replace(rfc3164DateRegEx, '$1 $5');
      // Build message
      fmtMsg = '<' + pri + '>';
      fmtMsg += timestamp;
      fmtMsg += ' ' + this.hostname;
      fmtMsg += ' ' + this.applicationName;
      fmtMsg += ' ' + msg;
      fmtMsg += newLine;
      resolve(fmtMsg);
    });
  }
  /**
   * send a RFC5424 formatted message.  Returns a promise with the formatted
   *    message that was sent.  If no server connection was defined when the
   *    class was created a default Syslog connector will be used.
   *    @see SyslogPro~Syslog
   * @public
   * @param {string} msg - The unformatted Syslog message to send
   * @param {object} [options] - Options object
   * @param {number} [options.severity=7] - An array of structure
   * @param {number} [options.colorCode=36] - The ANSI color code to use if
   * @returns {Promise} A Syslog formatted string according to the selected RFC
   * @throws {Error} A standard error object
   */
  send(msg, options) {
    return new Promise((resolve, reject) => {
      if (!this.server) {
        this.server = new Syslog();
      }
      this.buildMessage(msg, options)
        .then((result) => {
          this.server.send(result)
            .then((sendResult) => {
              resolve(sendResult);
            })
            .catch((error) => {
              reject(error);
            });
        })
        .catch((error) => {
          reject(error);
        });
    });
  }
  /**
   * Send a syslog message with a security level of 0 (Emergency)
   * @public
   * @param {string} msg - The emergency message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  emergency(msg) {
    return this.send(msg, {
      severity: 0,
      colorCode: this.emergencyColor,
    });
  }
  /**
   * Send a syslog message with a security level of 0 (Emergency)
   * @public
   * @param {string} msg - The emergency message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  emer(msg) {
    return this.emergency(msg);
  }
  /**
   * Send a syslog message with a severity level of 1 (Alert)
   * @public
   * @param {string} msg - The alert message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  alert(msg) {
    return this.send(msg, {
      severity: 1,
      colorCode: this.alertColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 2 (Critical)
   * @public
   * @param {string} msg - The critical message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  critical(msg) {
    return this.send(msg, {
      severity: 2,
      colorCode: this.criticalColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 2 (Critical)
   * @public
   * @param {string} msg - The critical message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  crit(msg) {
    return this.critical(msg);
  }
  /**
   * Send a syslog message with a severity level of 3 (Error)
   * @public
   * @param {string} msg - The error message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  error(msg) {
    return this.send(msg, {
      severity: 3,
      colorCode: this.errorColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 3 (Error)
   * @public
   * @param {string} msg - The error message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  err(msg) {
    return this.error(msg);
  }
  /**
   * Send a syslog message with a severity level of 4 (Warning)
   * @public
   * @param {string} msg - The warning message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  warning(msg) {
    return this.send(msg, {
      severity: 4,
      colorCode: this.warningColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 4 (Warning)
   * @public
   * @param {string} msg - The warning message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  warn(msg) {
    return this.warning(msg);
  }
  /**
   * Send a syslog message with a severity level of 5 (Notice)
   * @public
   * @param {string} msg - The notice message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  notice(msg) {
    return this.send(msg, {
      severity: 5,
      colorCode: this.noticeColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 5 (Notice)
   * @public
   * @param {string} msg - The notice message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  note(msg) {
    return this.notice(msg);
  }
  /**
   * Send a syslog message with a severity level of 6 (Informational)
   * @public
   * @param {string} msg - The informational message to send to the Syslog
   *    server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  informational(msg) {
    return this.send(msg, {
      severity: 6,
      colorCode: this.informationalColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 6 (Informational)
   * @public
   * @param {string} msg - The informational message to send to the Syslog
   *    server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  info(msg) {
    return this.informational(msg);
  }
  /**
   * Send a syslog message with a severity level of 6 (Informational)
   * @public
   * @param {string} msg - The informational message to send to the Syslog
   *    server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  log(msg) {
    return this.informational(msg);
  }
  /**
   * Send a syslog message with a severity level of 7 (Debug)
   * @public
   * @param {string} msg - The debug message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  debug(msg) {
    return this.send(msg, {
      severity: 7,
      colorCode: this.debugColor,
    });
  }
}

/**
 * A class to work with RFC5424 formatted syslog messages. The messaging is
 * fully configurable and ANSI foreground  * colors can be added.  Both ANSI 8
 * and ANSI 256 color are fully supported.
 *Most APIs will return a promise. These APIs can be used using
 * `then(...)/catch(...)`
 *
 * A Syslog class with a configured
 * Syslog server target can also be used as the input into the formatting
 * classes so that it may run independently.
 *
 * The RFC5424 Syslog logging format is meant to be used as a stream of log data
 * from a service or application. This class is designed to be used in this
 * fashion where new messages are written to the class as needed.
 * @requires moment
 * @version 0.0.0
 * @since 0.0.0
 */
class RFC5424 {
  /**
   * Construct a new RFC5424 formatted Syslog object with user options
   * @public
   * @this RFC5424
   * @param {object} [options] - Options object
   * @param {string} [options.applicationName='NodeJSLogger'] - Application
   * @param {string} [options.hostname=os.hostname] - The name of this server
   * @param {boolean} [options.timestamp=false] - Included a Timestamp
   * @param {boolean} [options.timestampUTC=false] - RFC standard is for
   *    local time
   * @param {boolean} [options.timestampMS=false] - Timestamp with ms
   *    resolution
   * @param {boolean} [options.timestampTZ=true] - Should the timestamp
   *    included time zone
   * @param {boolean} [options.includeStructuredData=false] - Included
   *    any provided structured data
   * @param {boolean} [options.utf8BOM=true] - Included the UTF8
   * @param {boolean} [options.color=false] - Included the UTF8
   * @param {boolean} [options.extendedColor=false] - Included the UTF8
   *    encoding tag with syslog message text
   * @param {object} [options.colors] - User defended colors for
   *    severities
   * @param {string} [options.colors.emergencyColor] - A RGB Hex coded color in
   *    the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
   *    & 0-255 Extended)
   * @param {string} [options.colors.alertColor] - A RGB Hex coded color in the
   *    form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
   *    0-255 Extended)
   * @param {string} [options.colors.criticalColor] - A RGB Hex coded color in
   *    the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
   *    & 0-255 Extended)
   * @param {string} [options.colors.errorColor] - A RGB Hex coded color in the
   *    form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
   *    0-255 Extended)
   * @param {string} [options.colors.warningColor] - A RGB Hex coded color in
   *    the form of #FFFFFF or as or the ANSI color code number (30-37 Standard
   *    & 0-255 Extended)
   * @param {string} [options.colors.noticeColor] - A RGB Hex coded color in the
   *    form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
   *    0-255 Extended)
   * @param {string} [options.colors.informationalColor] - A RGB Hex coded color
   *    in the form of #FFFFFF or as or the ANSI color code number (30-37
   *    Standard & 0-255 Extended)
   * @param {string} [options.colors.debugColor] - A RGB Hex coded color in the
   *    form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
   *    0-255 Extended)
   * @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog|
   *    Syslog server connection} that should be used to send messages directly
   *    from this class. @see SyslogPro~Syslog
   */
  constructor(options) {
    /** @private @type {boolean} */
    this.constructor__ = true;
    options = options || {};
    this.hostname = options.hostname || os.hostname();
    this.applicationName = options.applicationName || '';
    if (typeof options.timestamp === 'undefined' || options.timestamp) {
      /** @type {boolean} */
      this.timestamp = true;
    } else {
      this.timestamp = false;
    }
    if (options.timestampUTC) {
      /** @type {boolean} */
      this.timestampUTC = true;
    } else {
      this.timestampUTC = false;
    }
    if (typeof options.timestampTZ === 'undefined' || options.timestampTZ) {
      /** @type {boolean} */
      this.timestampTZ = true;
    } else {
      this.timestampTZ = false;
    }
    if (options.timestampMS) {
      /** @type {boolean} */
      this.timestampMS = true;
    } else {
      this.timestampMS = false;
    }
    if (options.includeStructuredData) {
      /** @type {boolean} */
      this.includeStructuredData = true;
    } else {
      this.includeStructuredData = false;
    }
    if (typeof options.utf8BOM === 'undefined' || options.utf8BOM) {
      /** @type {boolean} */
      this.utf8BOM = true;
    } else {
      this.utf8BOM = false;
    }
    if (options.color) {
      /** @type {boolean} */
      this.color = true;
    } else {
      this.color = false;
    }
    if (options.extendedColor) {
      /** @type {boolean} */
      this.extendedColor = true;
    } else {
      this.extendedColor = false;
    }
    if (options.server) {
      if (!options.server.constructor__) {
        /** @private @type {Syslog} */
        this.server = new Syslog(options.server);
      } else {
        this.server = options.server;
      }
    }
    if (this.extendedColor) {
      /** @private @type {number} */
      this.emergencyColor = 1; // Red foreground color
      /** @private @type {number} */
      this.alertColor = 202; // Dark Orange foreground color
      /** @private @type {number} */
      this.criticalColor = 208; // Orange foreground color
      /** @private @type {number} */
      this.errorColor = 178; // Light Orange foreground color
      /** @private @type {number} */
      this.warningColor = 226; // Yellow foreground color
      /** @private @type {number} */
      this.noticeColor = 117; // Light Blue foreground color
      /** @private @type {number} */
      this.informationalColor = 45; // Blue foreground color
      /** @private @type {number} */
      this.debugColor = 27; // Dark Blue foreground color
    } else {
      this.emergencyColor = 31; // Red foreground color
      this.alertColor = 31; // Red foreground color
      this.criticalColor = 31; // Red foreground color
      this.errorColor = 33; // Yellow foreground color
      this.warningColor = 33; // Yellow foreground color
      this.noticeColor = 36; // Blue foreground color
      this.informationalColor = 36; // Blue foreground color
      this.debugColor = 34; // Dark Blue foreground color
    }
    if (typeof options.colors === 'object') {
      this.setColor(options.colors, this.extendedColor);
    }
  }
  /**
   * Sets the color to be used for messages at a set priority
   * @public
   * @param {string} [colors.emergencyColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.alertColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.criticalColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.errorColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.warningColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.noticeColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @param {string} [colors.informationalColor] - A RGB Hex coded color in the
   *    form of #FFFFFF or as or the ANSI color code number (30-37 Standard &
   *    0-255 Extended)
   * @param {string} [colors.debugColor] - A RGB Hex coded color in the form
   *    of #FFFFFF or as or the ANSI color code number (30-37 Standard & 0-255
   *    Extended)
   * @throws {Error} A standard error object
   */
  setColor(colors, extendedColor) {
    return new Promise((resolve, reject) => {
      let colorPromises = [];
      if (colors.emergencyColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.emergencyColor, this.extendedColor)
              .then((result) => {
                this.emergencyColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'emergencyColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.alertColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.alertColor, this.extendedColor)
              .then((result) => {
                this.alertColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'alertColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.criticalColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.criticalColor, this.extendedColor)
              .then((result) => {
                this.criticalColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'criticalColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.errorColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.errorColor, this.extendedColor)
              .then((result) => {
                this.errorColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'errorColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.warningColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.warningColor, this.extendedColor)
              .then((result) => {
                this.warningColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'warningColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.noticeColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.noticeColor, this.extendedColor)
              .then((result) => {
                this.noticeColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'noticeColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.informationalColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.informationalColor, this.extendedColor)
              .then((result) => {
                this.informationalColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'informationalColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      if (colors.debugColor) {
        colorPromises.push(
          new Promise((resolve, reject) => {
            rgbToAnsi(colors.debugColor, this.extendedColor)
              .then((result) => {
                this.debugColor = result;
                resolve(true);
              })
              .catch((reson) => {
                reson.message = 'TYPE ERROR: ';
                reson.message += 'debugColor';
                reson.message += ' Not in RGB color hex or color code';
                reject(reson);
              });
          }));
      }
      Promise.all(colorPromises)
        .then((results) => {
          resolve(true);
        })
        .catch((reson) => {
          reject(reson);
        });
    });
  }
  /**
   * Building a formatted message.  Returns a promise with a formatted message
   * @public
   * @param {string} msg - The Syslog Message
   * @param {object} [options] - Options object
   * @param {number} [options.severity=7] - An array of structure
   * @param {number} [options.facility=23] - Facility code to use sending this
   *    message
   * @param {string} [options.pid='-'] - The process id of the service sending
   *    this message
   * @param {string[]} [options.structuredData] - An array of structure
   *    data strings conforming to the IETF/IANA defined SD-IDs or IANA
   *    registered SMI Network Management Private Enterprise Code SD-ID
   *    conforming to the format
   *    [name@<private enterprise number> parameter=value]
   * @param {number} [options.colorCode=36] - The ANSI color code to use if
   *    message coloration is selected
   * @returns {Promise} A Syslog formatted string according to the selected RFC
   * @throws {Error} A standard error object
   */
  buildMessage(msg, options) {
    return new Promise((resolve, reject) => {
      options = options || {};
      let severity = typeof options.severity === 'number' ?
        options.severity : 6;
      if (typeof msg !== 'string' || options.severity > 7) {
        let errMsg = 'FORMAT ERROR: Syslog message must be a string';
        errMsg += ' msgSeverity must be a number between 0 and 7';
        reject(new Error(errMsg));
        return;
      }
      let facility = options.facility || 23;
      let pid = options.pid || '-';
      let id = options.id || '-';
      let msgStructuredData = options.msgStructuredData || [];
      let fmtMsg = ''; // Formated Syslog message string var
      const newLine = '\n';
      const newLineRegEx = /(\r|\n|(\r\n))/;
      const escapeCode = '\u001B';
      const resetColor = '\u001B[0m';
      // The PRI is common to both RFC formats
      const pri = (facility * 8) + severity;
      // Remove any newline character
      msg = msg.replace(newLineRegEx, '');
      // Add requested color
      if (this.color) {
        options.msgColor = options.msgColor || 36;
        let colorCode = '[';
        if (this.extendedColor) {
          colorCode += '38;5;'; // Extended 256 Colors ANSI Code
        }
        if (typeof options.msgColor === 'number') {
          colorCode += options.msgColor;
          colorCode += 'm'; // ANSI Color Closer
        } else {
          colorCode = '[39m'; // Use terminal's default color
        }
        msg = escapeCode + colorCode + msg + resetColor;
      }
      // RFC5424 timestamp formating
      let timestamp = '-';
      if (this.timestamp) {
        let timeQuality = '[timeQuality';
        if (this.timestampUTC) {
          timeQuality += ' tzKnown=1';
          if (this.timestampMS) {
            if (this.timestampTZ) {
              timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ss.SSSSSSZ');
            } else {
              timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ss.SSSSSS');
            }
          } else {
            if (this.timestampTZ) {
              timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ssZ');
            } else {
              timestamp = moment().utc().format('YYYY-MM-DDThh:mm:ss');
            }
          }
        } else {
          if (this.timestampTZ) {
            timeQuality += ' tzKnown=1';
            if (this.timestampMS) {
              timeQuality += ' isSynced=1';
              timeQuality += ' syncAccuracy=0';
              timestamp = moment().format('YYYY-MM-DDThh:mm:ss.SSSSSSZ');
            } else {
              timestamp = moment().format('YYYY-MM-DDThh:mm:ssZ');
            }
          } else {
            timeQuality += ' tzKnown=0';
            if (this.timestampMS) {
              timeQuality += ' isSynced=1';
              timeQuality += ' syncAccuracy=0';
              timestamp = moment().format('YYYY-MM-DDThh:mm:ss.SSSSSS');
            } else {
              timestamp = moment().format('YYYY-MM-DDThh:mm:ss');
            }
          }
        }
        timeQuality += ']';
        msgStructuredData.push(timeQuality);
      }
      // Build Structured Data string
      let structuredData = '-';
      const sdElementCount = msgStructuredData.length;
      if (this.includeStructuredData && sdElementCount > 0) {
        let sdElementNames = [];
        let sdElements = [];
        const sdElementNameRegEx = /(\[)(\S*)(\s|\])/;
        // Loop to drop duplicates of the same SD Element name
        for (let elementIndex = 0;
          elementIndex < sdElementCount;
          elementIndex++) {
          let elementName =
            msgStructuredData[elementIndex]
              .match(sdElementNameRegEx)[2];
          if (!sdElementNames.includes(elementName)) {
            sdElementNames.push(elementName);
            sdElements.push(msgStructuredData[elementIndex]);
          }
        }
        structuredData = sdElements.join('');
      }
      // Build the message
      fmtMsg = '<' + pri + '>';
      fmtMsg += '1'; // Version number
      fmtMsg += ' ' + timestamp;
      fmtMsg += ' ' + this.hostname;
      fmtMsg += ' ' + this.applicationName;
      fmtMsg += ' ' + pid;
      fmtMsg += ' ' + id;
      fmtMsg += ' ' + structuredData;
      if (this.utf8BOM) {
        fmtMsg += ' BOM' + msg;
      } else {
        fmtMsg += ' ' + msg;
      }
      fmtMsg += newLine;
      resolve(fmtMsg);
    });
  }
  /**
   * send a RFC5424 formatted message.  Returns a promise with the formatted
   *    message that was sent.  If no server connection was defined when the
   *    class was created a default Syslog connector will be used.
   *    @see SyslogPro~Syslog
   * @public
   * @param {string} msg - The unformatted Syslog message to send
   * @returns {Promise} A Syslog formatted string according to the selected RFC
   * @throws {Error} A standard error object
   */
  send(msg, options) {
    return new Promise((resolve, reject) => {
      if (!this.server) {
        this.server = new Syslog();
      }
      this.buildMessage(msg, options)
        .then((result) => {
          this.server.send(result)
            .then((sendResult) => {
              resolve(sendResult);
            })
            .catch((error) => {
              reject(error);
            });
        })
        .catch((error) => {
          reject(error);
        });
    });
  }
  /**
   * Send a syslog message with a severity level of 0 (Emergency)
   * @public
   * @param {string} msg - The emergency message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  emergency(msg) {
    return this.send(msg, {
      severity: 0,
      colorCode: this.emergencyColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 0 (Emergency)
   * @public
   * @param {string} msg - The emergency message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  emer(msg) {
    return this.emergency(msg);
  }
  /**
   * Send a syslog message with a severity level of 1 (Alert)
   * @public
   * @param {string} msg - The alert message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  alert(msg) {
    return this.send(msg, {
      severity: 1,
      colorCode: this.alertColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 2 (Critical)
   * @public
   * @param {string} msg - The critical message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  critical(msg) {
    return this.send(msg, {
      severity: 2,
      colorCode: this.criticalColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 2 (Critical)
   * @public
   * @param {string} msg - The critical message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  crit(msg) {
    return this.critical(msg);
  }
  /**
   * Send a syslog message with a severity level of 3 (Error)
   * @public
   * @param {string} msg - The error message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  error(msg) {
    return this.send(msg, {
      severity: 3,
      colorCode: this.errorColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 3 (Error)
   * @public
   * @param {string} msg - The error message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  err(msg) {
    return this.error(msg);
  }
  /**
   * Send a syslog message with a severity level of 4 (Warning)
   * @public
   * @param {string} msg - The warning message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  warning(msg) {
    return this.send(msg, {
      severity: 4,
      colorCode: this.warningColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 4 (Warning)
   * @public
   * @param {string} msg - The warning message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  warn(msg) {
    return this.warning(msg);
  }
  /**
   * Send a syslog message with a severity level of 5 (Notice)
   * @public
   * @param {string} msg - The notice message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  notice(msg) {
    return this.send(msg, {
      severity: 5,
      colorCode: this.noticeColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 5 (Notice)
   * @public
   * @param {string} msg - The notice message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  note(msg) {
    return this.notice(msg);
  }
  /**
   * Send a syslog message with a severity level of 6 (Informational)
   * @public
   * @param {string} msg - The informational message to send to the Syslog
   *    server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  informational(msg) {
    return this.send(msg, {
      severity: 6,
      colorCode: this.informationalColor,
    });
  }
  /**
   * Send a syslog message with a severity level of 6 (Informational)
   * @public
   * @param {string} msg - The informational message to send to the Syslog
   *    server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  info(msg) {
    return this.informational(msg);
  }
  /**
   * Send a syslog message with a severity level of 6 (Informational)
   * @public
   * @param {string} msg - The informational message to send to the Syslog
   *    server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  log(msg) {
    return this.informational(msg);
  }
  /**
   * Send a syslog message with a severity level of 7 (Debug)
   * @public
   * @param {string} msg - The debug message to send to the Syslog server
   * @returns {Promise} - The formatted syslog message sent to the Syslog server
   * @throws {Error} - Any bubbled-up error
   */
  debug(msg) {
    return this.send(msg, {
      severity: 7,
      colorCode: this.debugColor,
    });
  }
}

/**
 * A class to work with IBM LEEF (Log Event Extended Format) messages this form
 * of system messages are designed to work with security systems.  Messages can
 * be saved to file (Saving to file if not part of this module but a LEEF
 * formatted message produced by this module can be saved externally to it) or
 * sent via Syslog.
 * Most APIs will return a promise. These APIs can be used using
 * `then(...)/catch(...)`
 *
 * A Syslog class with a configured Syslog server target can also be used as
 * the input into the formatting classes so that it may run independently. The
 * LEEF format is designed to send event data to a SIEM system and should not
 * be as a logging stream. This class is meant to be used once per message.
 * @requires moment
 * @version 0.0.0
 * @since 0.0.0
 */
class LEEF {
  /**
   * Construct a new LEEF formatting object with user options
   * @public
   * @param {object} [options] - Options object
   * @param {string} [options.vendor='unknown'] - The vendor of the system that
   *    generated the event being reported
   * @param {string} [options.product='unknown'] - The product name of the
   *    system that genrated the event being reported
   * @param {string} [options.version='unknown'] - The version name of the
   *    system that genrated the event being reported
   * @param {string} [options.eventId='unknown'] - The eventId of the
   *    system that genrated the event being reported
   * @param {object} [options.attributes] - LEEF message attributes which
   *    defaults to all base attributes with null values, new attributes should
   *    be added as new elements to this object
   * @param {boolean} [options.syslogHeader='true'] - Should the LEEF message
   *    include a Syslog header with Timestamp and source
   * @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog|
   *    Syslog server connection} that should be used to send messages directly
   *    from this class. @see SyslogPro~Syslog
   */
  constructor(options) {
    /** @private @type {boolean} */
    this.constructor__ = true;
    options = options || {};
    /** @type {string} */
    this.vendor = options.vendor || 'unknown';
    /** @type {string} */
    this.product = options.product || 'unknown';
    /** @type {string} */
    this.version = options.version || 'unknown';
    /** @type {string} */
    this.eventId = options.eventId || 'unknown';
    /** @type {boolean} */
    this.syslogHeader = typeof options.syslogHeader === 'boolean'
      ? options.syslogHeader : true;
    /** @type {object} */
    this.attributes = options.attributes || {
      cat: null,
      devTime: null,
      devTimeFormat: null,
      proto: null,
      sev: null,
      src: null,
      dst: null,
      srcPort: null,
      dstPort: null,
      srcPreNAT: null,
      dstPreNAT: null,
      srcPostNAT: null,
      dstPostNAT: null,
      usrName: null,
      srcMAC: null,
      dstMAC: null,
      srcPreNATPort: null,
      dstPreNATPort: null,
      srcPostNATPort: null,
      dstPostNATPort: null,
      identSrc: null,
      identHostName: null,
      identNetBios: null,
      identGrpName: null,
      identMAC: null,
      vSrc: null,
      vSrcName: null,
      accountName: null,
      srcBytes: null,
      dstBytes: null,
      srcPackets: null,
      dstPackets: null,
      totalPackets: null,
      role: null,
      realm: null,
      policy: null,
      resource: null,
      url: null,
      groupID: null,
      domain: null,
      isLoginEvent: null,
      isLogoutEvent: null,
      identSecondlp: null,
      calLanguage: null,
      AttributeLimits: null,
      calCountryOrRegion: null,
    };
    if (options.server) {
      if (options.server.constructor__) {
        /** @private @type {Syslog} */
        this.server = options.server;
      } else {
        this.server = new Syslog(options.server);
      }
    }
  }
  /**
   *Build a formatted message
   * @public
   * @return {Promise} - string with formatted message
   */
  buildMessage() {
    return new Promise((resolve, reject) => {
      let fmtMsg = 'LEEF:2.0';
      fmtMsg += '|' + this.vendor;
      fmtMsg += '|' + this.product;
      fmtMsg += '|' + this.version;
      fmtMsg += '|' + this.eventId;
      fmtMsg += '|';

      // Build LEEF Attributes
      const Tab = '\x09';
      const leefAttribs = Object.entries(this.attributes);
      const leefAttribsLen = leefAttribs.length;
      for (let attrib = 0; attrib < leefAttribsLen; attrib++) {
        if (leefAttribs[attrib][1] !== null) {
          fmtMsg += leefAttribs[attrib][0] + '=' + leefAttribs[attrib][1] + Tab;
        }
      }
      resolve(fmtMsg);
    });
  }

  /**
   * @public
   * @param {Syslog} [options=false] - A {@link module:SyslogPro~Syslog|
   *    Syslog server connection} that should be used to send messages directly
   *    from this class. @see SyslogPro~Syslog
   */
  send(options) {
    return new Promise((resolve, reject) => {
      this.buildMessage()
        .then((result) => {
          if (!this.server) {
            this.server = new Syslog(options);
          }
          this.server.send(result)
            .then((sendResult) => {
              resolve(sendResult);
            })
            .catch((reson) => {
              reject(reson);
            });
        });
    });
  }
}

/**
 * A class to work with HP CEF (Common Event Format) messages. This form
 * of system messages are designed to work with security systems.  Messages can
 * be saved to file (Saving to file if not part of this module but a CEF
 * formatted message produced by this module can be saved externally to it) or
 * sent via Syslog.
 * Most APIs will return a promise. These APIs can be used using
 * `then(...)/catch(...)`
 *
 * A Syslog class with a configured Syslog server target can also be used as
 * the input into the formatting classes so that it may run independently. The
 * CEF format is designed to send event data to a SIEM system and should not be
 * as a logging stream. This class is meant to be used once per message.
 * @requires moment
 * @version 0.0.0
 * @since 0.0.0
 */
class CEF {
  /**
   * Construct a new CEF formatting object with user options
   * @public
   * @param {object} [options] - Options object
   * @param {string} [options.deviceVendor='unknown'] - The vendor of the system
   *    that generated the event being reported
   * @param {string} [options.deviceProduct='unknown'] - The product name of the
   *    system that genrated the event being reported
   * @param {string} [options.deviceVersion='unknown'] - The version name of the
   *    system that genrated the event being reported
   * @param {string} [options.deviceEventClassId='unknown'] - The eventId of the
   *    system that genrated the event being reported
   * @param {string} [options.name='unknown'] - Name of the service generating
   *    the notice
   * @param {string} [options.severity='unknown'] - Severity of the notification
   * @param {string} [options.extensions={}] - Any CEF Key=Value extensions
   * @param {Syslog} [options.server=false] - A {@link module:SyslogPro~Syslog|
   *    Syslog server connection} that should be used to send messages directly
   *    from this class. @see SyslogPro~Syslog
   */
  constructor(options) {
    /** @private @type {boolean} */
    this.constructor__ = true;
    options = options || {};
    /** @type {string} */
    this.deviceVendor = options.deviceVendor || 'Unknown';
    /** @type {string} */
    this.deviceProduct = options.deviceProduct || 'Unknown';
    /** @type {string} */
    this.deviceVersion = options.deviceVersion || 'Unknown';
    /** @type {string} */
    this.deviceEventClassId = options.deviceEventClassId || 'Unknown';
    /** @type {string} */
    this.name = options.name || 'Unknown';
    /** @type {string} */
    this.severity = options.severity || 'Unknown';
    /** @type {object} */
    this.extensions = options.extensions || {
      deviceAction: null,
      applicationProtocol: null,
      deviceCustomIPv6Address1: null,
      'deviceCustomIPv6 Address1Label': null,
      deviceCustomIPv6Address3: null,
      'deviceCustomIPv6Address3 Label': null,
      'deviceCustomIPv6 Address4': null,
      'deviceCustomIPv6 Address4Label': null,
      deviceEventCategory: null,
      deviceCustomFloatingPoint1: null,
      'deviceCustom FloatingPoint1Label': null,
      deviceCustomFloatingPoint2: null,
      'deviceCustomFloatingPoint2 Label': null,
      deviceCustomFloatingPoint3: null,
      'deviceCustom FloatingPoint3Label': null,
      deviceCustomFloatingPoint4: null,
      'deviceCustom FloatingPoint4Label': null,
      deviceCustomNumber1: null,
      deviceCustomNumber1Label: null,
      DeviceCustomNumber2: null,
      deviceCustomNumber2Label: null,
      deviceCustomNumber3: null,
      deviceCustomNumber3Label: null,
      baseEventCount: null,
      deviceCustomString1: null,
      deviceCustomString1Label: null,
      deviceCustomString2: null,
      deviceCustomString2Label: null,
      deviceCustomString3: null,
      deviceCustomString3Label: null,
      deviceCustomString4: null,
      deviceCustomString4Label: null,
      deviceCustomString5: null,
      deviceCustomString5Label: null,
      deviceCustomString6: null,
      deviceCustomString6Label: null,
      destinationDnsDomain: null,
      destinationServiceName: null,
      'destinationTranslated Address': null,
      destinationTranslatedPort: null,
      deviceCustomDate1: null,
      deviceCustomDate1Label: null,
      deviceCustomDate2: null,
      deviceCustomDate2Label: null,
      deviceDirection: null,
      deviceDnsDomain: null,
      deviceExternalId: null,
      deviceFacility: null,
      deviceInboundInterface: null,
      deviceNtDomain: null,
      deviceOutboundInterface: null,
      devicePayloadId: null,
      deviceProcessName: null,
      deviceTranslatedAddress: null,
      destinationHostName: null,
      destinationMacAddress: null,
      destinationNtDomain: null,
      destinationProcessId: null,
      destinationUserPrivileges: null,
      destinationProcessName: null,
      destinationPort: null,
      destinationAddress: null,
      deviceTimeZone: null,
      destinationUserId: null,
      destinationUserName: null,
      deviceAddress: null,
      deviceHostName: null,
      deviceMacAddress: null,
      deviceProcessId: null,
      endTime: null,
      externalId: null,
      fileCreateTime: null,
      fileHash: null,
      fileId: null,
      fileModificationTime: null,
      filePath: null,
      filePermission: null,
      fileType: null,
      flexDate1: null,
      flexDate1Label: null,
      flexString1: null,
      flexString1Label: null,
      flexString2: null,
      flexString2Label: null,
      filename: null,
      fileSize: null,
      bytesIn: null,
      message: null,
      oldFileCreateTime: null,
      oldFileHash: null,
      oldFileId: null,
      oldFileModificationTime: null,
      oldFileName: null,
      oldFilePath: null,
      oldFileSize: null,
      oldFileType: null,
      bytesOut: null,
      eventOutcome: null,
      transportProtocol: null,
      Reason: null,
      requestUrl: null,
      requestClientApplication: null,
      requestContext: null,
      requestCookies: null,
      requestMethod: null,
      deviceReceiptTime: null,
      sourceHostName: null,
      sourceMacAddress: null,
      sourceNtDomain: null,
      sourceDnsDomain: null,
      sourceServiceName: null,
      sourceTranslatedAddress: null,
      sourceTranslatedPort: null,
      sourceProcessId: null,
      sourceUserPrivileges: null,
      sourceProcessName: null,
      sourcePort: null,
      sourceAddress: null,
      startTime: null,
      sourceUserId: null,
      sourceUserName: null,
      type: null,
      agentDnsDomain: null,
      agentNtDomain: null,
      agentTranslatedAddress: null,
      'agentTranslatedZone ExternalID': null,
      agentTranslatedZoneURI: null,
      agentZoneExternalID: null,
      agentZoneURI: null,
      agentAddress: null,
      agentHostName: null,
      agentId: null,
      agentMacAddress: null,
      agentReceiptTime: null,
      agentType: null,
      agentTimeZone: null,
      agentVersion: null,
      customerExternalID: null,
      customerURI: null,
      'destinationTranslated ZoneExternalID': null,
      'destinationTranslated ZoneURI': null,
      destinationZoneExternalID: null,
      destinationZoneURI: null,
      'deviceTranslatedZone ExternalID': null,
      deviceTranslatedZoneURI: null,
      deviceZoneExternalID: null,
      deviceZoneURI: null,
      destinationGeoLatitude: null,
      destinationGeoLongitude: null,
      eventId: null,
      rawEvent: null,
      sourceGeoLatitude: null,
      sourceGeoLongitude: null,
      'sourceTranslatedZone ExternalID': null,
      sourceTranslatedZoneURI: null,
      sourceZoneExternalID: null,
      sourceZoneURI: null,
    };
    if (options.server) {
      if (options.server.constructor__) {
        /** @private @type {Syslog} */
        this.server = options.server;
      } else {
        this.server = new Syslog(options.server);
      }
    }
  }
  /**
   * Validate this CEF object
   * @public
   * @return {Promise} - True if validated
   * @throws {Error} - First element to fail validation
   */
  validate() {
    return new Promise((resolve, reject) => {
      const Extensions = {
        deviceAction: {
          key: 'act',
          type: 'String',
          len: 63,
          discription: 'Action taken by the device.',
        },
        applicationProtocol: {
          key: 'app',
          type: 'String',
          len: 31,
          discription: 'Application level protocol, example values are HTTP, ' +
              'HTTPS, SSHv2, Telnet, POP, IMPA, IMAPS, and so on.',
        },
        deviceCustomIPv6Address1: {
          key: 'c6a1',
          type: 'String',
          len: null,
          discription: 'One of four IPv6 address fields available to map ' +
              'fields that do not apply to any other in this dictionary. ' +
              'TIP: See the guidelines under “User-Defined Extensions” for ' +
              'tips on using these fields.',
        },
        'deviceCustomIPv6 Address1Label': {
          key: 'c6a1Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomIPv6Address3: {
          key: 'c6a3',
          type: 'String',
          len: null,
          discription: 'One of four IPv6 address fields available to map ' +
              'fields that do not apply to any other in this dictionary. ' +
              'TIP: See the guidelines under “User-Defined Extensions” for ' +
              'tips on using these fields.',
        },
        'deviceCustomIPv6Address3 Label': {
          key: 'c6a3Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        'deviceCustomIPv6 Address4': {
          key: 'c6a4',
          type: 'String',
          len: null,
          discription: 'One of four IPv6 address fields available to map ' +
              'fields that do not apply to any other in this dictionary. ' +
              'TIP: See the guidelines under “User-Defined Extensions” for ' +
              'tips on using these fields.',
        },
        'deviceCustomIPv6 Address4Label': {
          key: 'C6a4Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceEventCategory: {
          key: 'cat',
          type: 'String',
          len: 1023,
          discription: 'Represents the category assigned by the originating ' +
              'device. Devices often use their own categorization schema to ' +
              'classify event. Example: “/Monitor/Disk/Read”',
        },
        deviceCustomFloatingPoint1: {
          key: 'cfp1',
          type: 'Number',
          len: null,
          discription: 'One of four floating point fields available to map ' +
              'fields that do not apply to any other in this dictionary.',
        },
        'deviceCustom FloatingPoint1Label': {
          key: 'cfp1Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomFloatingPoint2: {
          key: 'cfp2',
          type: 'Number',
          len: null,
          discription: 'One of four floating point fields available to map ' +
              'fields that do not apply to any other in this dictionary.',
        },
        'deviceCustomFloatingPoint2 Label': {
          key: 'cfp2Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomFloatingPoint3: {
          key: 'cfp3',
          type: 'Number',
          len: null,
          discription: 'One of four floating point fields available to map ' +
              'fields that do not apply to any other in this dictionary.',
        },
        'deviceCustom FloatingPoint3Label': {
          key: 'cfp3Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomFloatingPoint4: {
          key: 'cfp4',
          type: 'Number',
          len: null,
          discription: 'One of four floating point fields available to map ' +
              'fields that do not apply to any other in this dictionary.',
        },
        'deviceCustom FloatingPoint4Label': {
          key: 'cfp4Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomNumber1: {
          key: 'cn1',
          type: 'Number',
          len: null,
          discription: 'One of three number fields available to map fields ' +
              'that do not apply to any other in this dictionary. Use ' +
              'sparingly and seek a more specific dictionary supplied field ' +
              'when possible.',
        },
        deviceCustomNumber1Label: {
          key: 'cn1Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        DeviceCustomNumber2: {
          key: 'cn2',
          type: 'Number',
          len: null,
          discription: 'One of three number fields available to map fields ' +
              'that do not apply to any other in this dictionary. Use ' +
              'sparingly and seek a more specific, dictionary supplied field ' +
              'when possible.',
        },
        deviceCustomNumber2Label: {
          key: 'cn2Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomNumber3: {
          key: 'cn3',
          type: 'Number',
          len: null,
          discription: 'One of three number fields available to map fields ' +
              'that do not apply to any other in this dictionary. Use ' +
              'sparingly and seek a more specific, dictionary supplied field ' +
              'when possible.',
        },
        deviceCustomNumber3Label: {
          key: 'cn3Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        baseEventCount: {
          key: 'cnt',
          type: 'Number',
          len: null,
          discription: 'A count associated with this event. How many times ' +
              'was this same event observed? Count can be omitted if it is 1.',
        },
        deviceCustomString1: {
          key: 'cs1',
          type: 'String',
          len: 4000,
          discription: 'One of six strings available to map fields that do ' +
              'not apply to any other in this dictionary. Use sparingly and ' +
              'seek a more specific, dictionary supplied field when ' +
              'possible. TIP: See the guidelines under “User-Defined ' +
              'Extensions” for tips on using these fields.',
        },
        deviceCustomString1Label: {
          key: 'cs1Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomString2: {
          key: 'cs2',
          type: 'String',
          len: 4000,
          discription: 'One of six strings available to map fields that do ' +
              'not apply to any other in this dictionary. Use sparingly and ' +
              'seek a more specific, dictionary supplied field when ' +
              'possible. TIP: See the guidelines under “User-Defined ' +
              'Extensions” for tips on using these fields.',
        },
        deviceCustomString2Label: {
          key: 'cs2Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomString3: {
          key: 'cs3',
          type: 'String',
          len: 4000,
          discription: 'One of six strings available to map fields that do ' +
              'not apply to any other in this dictionary. Use sparingly and ' +
              'seek a more specific, dictionary supplied field when ' +
              'possible. TIP: See the guidelines under “User-Defined ' +
              'Extensions” for tips on using these fields.',
        },
        deviceCustomString3Label: {
          key: 'cs3Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomString4: {
          key: 'cs4',
          type: 'String',
          len: 4000,
          discription: 'One of six strings available to map fields that do ' +
              'not apply to any other in this dictionary. Use sparingly and ' +
              'seek a more specific, dictionary supplied field when ' +
              'possible. TIP: See the guidelines under “User-Defined ' +
              'Extensions” for tips on using these fields.',
        },
        deviceCustomString4Label: {
          key: 'cs4Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomString5: {
          key: 'cs5',
          type: 'String',
          len: 4000,
          discription: 'One of six strings available to map fields that do ' +
              'not apply to any other in this dictionary. Use sparingly and ' +
              'seek a more specific, dictionary supplied field when ' +
              'possible. TIP: See the guidelines under “User-Defined ' +
              'Extensions” for tips on using these fields.',
        },
        deviceCustomString5Label: {
          key: 'cs5Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomString6: {
          key: 'cs6',
          type: 'String',
          len: 4000,
          discription: 'One of six strings available to map fields that do ' +
              'not apply to any other in this dictionary. Use sparingly and ' +
              'seek a more specific, dictionary supplied field when ' +
              'possible. TIP: See the guidelines under “User-Defined ' +
              'Extensions” for tips on using these fields.',
        },
        deviceCustomString6Label: {
          key: 'cs6Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        destinationDnsDomain: {
          key: 'destination DnsDomain',
          type: 'String',
          len: 255,
          discription: 'The DNS domain part of the complete fully qualified ' +
              'domain name (FQDN).',
        },
        destinationServiceName: {
          key: 'destination ServiceName',
          type: 'String',
          len: 1023,
          discription: 'The service targeted by this event. Example: “sshd”',
        },
        'destinationTranslated Address': {
          key: 'Destination Translated Address',
          type: 'String',
          len: null,
          discription: 'Identifies the translated destination that the event ' +
              'refers to in an IP network. The format is an IPv4 address. ' +
              'Example: “192.168.10.1”',
        },
        destinationTranslatedPort: {
          key: 'Destination TranslatedPort',
          type: 'Number',
          len: null,
          discription: 'Port after it was translated; for example, a ' +
              'firewall. Valid port numbers are 0 to 65535.',
        },
        deviceCustomDate1: {
          key: 'deviceCustom Date1',
          type: 'String',
          len: null,
          discription: 'One of two timestamp fields available to map fields ' +
              'that do not apply to any other in this dictionary. Use ' +
              'sparingly and seek a more specific, dictionary supplied field ' +
              'when possible. TIP: See the guidelines under “User-Defined ' +
              'Extensions” for tips on using these fields.',
        },
        deviceCustomDate1Label: {
          key: 'deviceCustom Date1Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceCustomDate2: {
          key: 'deviceCustom Date2',
          type: 'String',
          len: null,
          discription: 'One of two timestamp fields available to map fields ' +
              'that do not apply to any other in this dictionary. Use ' +
              'sparingly and seek a more specific, dictionary supplied field ' +
              'when possible. TIP: See the guidelines under “User-Defined ' +
              'Extensions” for tips on using these fields.',
        },
        deviceCustomDate2Label: {
          key: 'deviceCustom Date2Label',
          type: 'String',
          len: 1023,
          discription: 'All custom fields have a corresponding label field. ' +
              'Each of these fields is a string and describes the purpose of ' +
              'the custom field.',
        },
        deviceDirection: {
          key: 'deviceDirection',
          type: 'Number',
          len: null,
          discription: 'Any information about what direction the observed ' +
              'communication has taken. The following values are supported: ' +
              '“0” for inbound or “1” for outbound',
        },
        deviceDnsDomain: {
          key: 'deviceDns Domain',
          type: 'String',
          len: 255,
          discription: 'The DNS domain part of the complete fully qualified ' +
              'domain name (FQDN).',
        },
        deviceExternalId: {
          key: 'device ExternalId',
          type: 'String',
          len: 255,
          discription: 'A name that uniquely identifies the device ' +
              'generating this event.',
        },
        deviceFacility: {
          key: 'deviceFacility',
          type: 'String',
          len: 1023,
          discription: 'The facility generating this event. For example, ' +
              'Syslog has an explicit facility associated with every event.',
        },
        deviceInboundInterface: {
          key: 'deviceInbound Interface',
          type: 'String',
          len: 128,
          discription: 'Interface on which the packet or data entered the ' +
              'device.',
        },
        deviceNtDomain: {
          key: 'deviceNt Domain',
          type: 'String',
          len: 255,
          discription: 'The Windows domain name of the device address.',
        },
        deviceOutboundInterface: {
          key: 'Device Outbound Interface',
          type: 'String',
          len: 128,
          discription: 'Interface on which the packet or data left the ' +
              'device.',
        },
        devicePayloadId: {
          key: 'Device PayloadId',
          type: 'String',
          len: 128,
          discription: 'Unique identifier for the payload associated with ' +
              'the event.',
        },
        deviceProcessName: {
          key: 'deviceProcess Name',
          type: 'String',
          len: 1023,
          discription: 'Process name associated with the event. An example ' +
              'might be the process generating the syslog entry in UNIX.',
        },
        deviceTranslatedAddress: {
          key: 'device Translated Address',
          type: 'String',
          len: null,
          discription: 'Identifies the translated device address that the ' +
              'event refers to in an IP network. The format is an IPv4 ' +
              'address. Example: “192.168.10.1”',
        },
        destinationHostName: {
          key: 'dhost',
          type: 'String',
          len: 1023,
          discription: 'Identifies the destination that an event refers to ' +
              'in an IP network. The format should be a fully qualified ' +
              'domain name (FQDN) associated with the destination node, when ' +
              'a node is available. Examples: “host.domain.com” or “host”.',
        },
        destinationMacAddress: {
          key: 'dmac',
          type: 'String',
          len: null,
          discription: 'Six colon-seperated hexadecimal numbers. Example: ' +
              '“00:0D:60:AF:1B:61”',
        },
        destinationNtDomain: {
          key: 'dntdom',
          type: 'String',
          len: 255,
          discription: 'The Windows domain name of the destination address.',
        },
        destinationProcessId: {
          key: 'dpid',
          type: 'Number',
          len: null,
          discription: 'Provides the ID of the destination process ' +
              'associated with the event. For example, if an event contains ' +
              'process ID 105, 105” is the process ID.',
        },
        destinationUserPrivileges: {
          key: 'dpriv',
          type: 'String',
          len: 1023,
          discription: 'The typical values are “Administrator”, “User”, and ' +
              '“Guest”. This identifies the destination user’s privileges. ' +
              'In UNIX, for example, activity executed on the root user ' +
              'would be identified with destinationUser Privileges of ' +
              '“Administrator”.',
        },
        destinationProcessName: {
          key: 'dproc',
          type: 'String',
          len: 1023,
          discription: 'The name of the event’s destination process. ' +
              'Example: “telnetd” or “sshd”.',
        },
        destinationPort: {
          key: 'dpt',
          type: 'Number',
          len: null,
          discription: 'The valid port numbers are between 0 and 65535.',
        },
        destinationAddress: {
          key: 'dst',
          type: 'String',
          len: null,
          discription: 'Identifies the destination address that the event ' +
              'refers to in an IP network. The format is an IPv4 address. ' +
              'Example: “192.168.10.1”',
        },
        deviceTimeZone: {
          key: 'dtz',
          type: 'String',
          len: 255,
          discription: 'The timezone for the device generating the event.',
        },
        destinationUserId: {
          key: 'duid',
          type: 'String',
          len: 1023,
          discription: 'Identifies the destination user by ID. For example, ' +
              'in UNIX, the root user is generally associated with user ' +
              'ID 0.',
        },
        destinationUserName: {
          key: 'duser',
          type: 'String',
          len: 1023,
          discription: 'Identifies the destination user by name. This is the ' +
              'user associated with the event’s destination. Email addresses ' +
              'are often mapped into the UserName fields. The recipient is a ' +
              'candidate to put into this field.',
        },
        deviceAddress: {
          key: 'dvc',
          type: 'String',
          len: null,
          discription: 'Identifies the device address that an event refers ' +
              'to in an IP network. The format is an IPv4 address. Example: ' +
              '“192.168.10.1”.',
        },
        deviceHostName: {
          key: 'dvchost',
          type: 'String',
          len: 100,
          discription: 'The format should be a fully qualified domain name ' +
              '(FQDN) associated with the device node, when a node is ' +
              'available. Example: “host.domain.com” or “host”.',
        },
        deviceMacAddress: {
          key: 'dvcmac',
          type: 'String',
          len: null,
          discription: 'Six colon-separated hexadecimal numbers. Example: ' +
              '“00:0D:60:AF:1B:61”',
        },
        deviceProcessId: {
          key: 'dvcpid',
          type: 'Number',
          len: null,
          discription: 'Provides the ID of the process on the device ' +
              'generating the event.',
        },
        endTime: {
          key: 'end',
          type: 'String',
          len: null,
          discription: 'The time at which the activity related to the event ' +
              'ended. The format is MMM dd yyyy HH:mm:ss or milliseconds ' +
              'since epoch (Jan 1st1970). An example would be reporting the ' +
              'end of a session.',
        },
        externalId: {
          key: 'externalId',
          type: 'String',
          len: 40,
          discription: 'The ID used by an originating device. They are ' +
              'usually increasing numbers, associated with events.',
        },
        fileCreateTime: {
          key: 'fileCreateTime',
          type: 'String',
          len: null,
          discription: 'Time when the file was created.',
        },
        fileHash: {
          key: 'fileHash',
          type: 'String',
          len: 255,
          discription: 'Hash of a file.',
        },
        fileId: {
          key: 'fileId',
          type: 'String',
          len: 1023,
          discription: 'An ID associated with a file could be the inode.',
        },
        fileModificationTime: {
          key: 'fileModification Time',
          type: 'String',
          len: null,
          discription: 'Time when the file was last modified.',
        },
        filePath: {
          key: 'filePath',
          type: 'String',
          len: 1023,
          discription: 'Full path to the file, including file name itself. ' +
              'Example: C:\Program Files \WindowsNT\Accessories\ wordpad.exe ' +
              'or /usr/bin/zip',
        },
        filePermission: {
          key: 'filePermission',
          type: 'String',
          len: 1023,
          discription: 'Permissions of the file.',
        },
        fileType: {
          key: 'fileType',
          type: 'String',
          len: 1023,
          discription: 'Type of file (pipe, socket, etc.)',
        },
        flexDate1: {
          key: 'flexDate1',
          type: 'String',
          len: null,
          discription: 'A timestamp field available to map a timestamp that ' +
              'does not apply to any other defined timestamp field in this ' +
              'dictionary. Use all flex fields sparingly and seek a more ' +
              'specific, dictionary supplied field when possible. These ' +
              'fields are typically reserved for customer use and should not ' +
              'be set by vendors unless necessary.',
        },
        flexDate1Label: {
          key: 'flexDate1Label',
          type: 'String',
          len: 128,
          discription: 'The label field is a string and describes the ' +
              'purpose of the flex field.',
        },
        flexString1: {
          key: 'flexString1',
          type: 'String',
          len: 1023,
          discription: 'One of four floating point fields available to map ' +
              'fields that do not apply to any other in this dictionary. Use ' +
              'sparingly and seek a more specific, dictionary supplied field ' +
              'when possible. These fields are typically reserved for ' +
              'customer use and should not be set by vendors unless ' +
              'necessary.',
        },
        flexString1Label: {
          key: 'flexString1 Label',
          type: 'String',
          len: 128,
          discription: 'The label field is a string and describes the ' +
              'purpose of the flex field.',
        },
        flexString2: {
          key: 'flexString2',
          type: 'String',
          len: 1023,
          discription: 'One of four floating point fields available to map ' +
              'fields that do not apply to any other in this dictionary. Use ' +
              'sparingly and seek a more specific, dictionary supplied field ' +
              'when possible. These fields are typically reserved for ' +
              'customer use and should not be set by vendors unless ' +
              'necessary.',
        },
        flexString2Label: {
          key: 'flex String2Label',
          type: 'String',
          len: 128,
          discription: 'The label field is a string and describes the ' +
              'purpose of the flex field.',
        },
        filename: {
          key: 'fname',
          type: 'String',
          len: 1023,
          discription: 'Name of the file only (without its path).',
        },
        fileSize: {
          key: 'fsize',
          type: 'Number',
          len: null,
          discription: 'Size of the file.',
        },
        bytesIn: {
          key: 'in',
          type: 'Number',
          len: null,
          discription: 'Number of bytes transferred inbound, relative to the ' +
              'source to destination relationship, meaning that data was ' +
              'flowing from source to destination.',
        },
        message: {
          key: 'msg',
          type: 'String',
          len: 1023,
          discription: 'An arbitrary message giving more details about the ' +
              'event. Multi-line entries can be produced by using \n as the ' +
              'new line separator.',
        },
        oldFileCreateTime: {
          key: 'oldFileCreate Time',
          type: 'String',
          len: null,
          discription: 'Time when old file was created.',
        },
        oldFileHash: {
          key: 'oldFileHash',
          type: 'String',
          len: 255,
          discription: 'Hash of the old file.',
        },
        oldFileId: {
          key: 'oldFileId',
          type: 'String',
          len: 1023,
          discription: 'An ID associated with the old file could be the ' +
              'inode.',
        },
        oldFileModificationTime: {
          key: 'oldFile Modification Time',
          type: 'String',
          len: null,
          discription: 'Time when old file was last modified.',
        },
        oldFileName: {
          key: 'oldFileName',
          type: 'String',
          len: 1023,
          discription: 'Name of the old file.',
        },
        oldFilePath: {
          key: 'oldFilePath',
          type: 'String',
          len: 1023,
          discription: 'Full path to the old fiWindowsNT\\Accessories le, ' +
              'including the file name itself. Examples: c:\\Program ' +
              'Files\\wordpad.exe or /usr/bin/zip',
        },
        oldFileSize: {
          key: 'oldFileSize',
          type: 'Number',
          len: null,
          discription: 'Size of the old file.',
        },
        oldFileType: {
          key: 'oldFileType',
          type: 'String',
          len: 1023,
          discription: 'Type of the old file (pipe, socket, etc.)',
        },
        bytesOut: {
          key: 'out',
          type: 'Number',
          len: null,
          discription: 'Number of bytes transferred outbound relative to the ' +
              'source to destination relationship. For example, the byte ' +
              'number of data flowing from the destination to the source.',
        },
        eventOutcome: {
          key: 'outcome',
          type: 'String',
          len: 63,
          discription: 'Displays the outcome, usually as ‘success’ or ' +
              '‘failure’.',
        },
        transportProtocol: {
          key: 'proto',
          type: 'String',
          len: 31,
          discription: 'Identifies the Layer-4 protocol used. The possible ' +
              'values are protocols such as TCP or UDP.',
        },
        Reason: {
          key: 'reason',
          type: 'String',
          len: 1023,
          discription: 'The reason an audit event was generated. For ' +
              'example “badd password” or “unknown user”. This could also be ' +
              'an error or return code. Example: “0x1234”',
        },
        requestUrl: {
          key: 'request',
          type: 'String',
          len: 1023,
          discription: 'In the case of an HTTP request, this field contains ' +
              'the URL accessed. The URL should contain the protocol as ' +
              'well. Example: “http://www/secure.com”',
        },
        requestClientApplication: {
          key: 'requestClient Application',
          type: 'String',
          len: 1023,
          discription: 'The User-Agent associated with the request.',
        },
        requestContext: {
          key: 'requestContext',
          type: 'String',
          len: 2048,
          discription: 'Description of the content from which the request ' +
              'originated (for example, HTTP Referrer)',
        },
        requestCookies: {
          key: 'requestCookies',
          type: 'String',
          len: 1023,
          discription: 'Cookies associated with the request.',
        },
        requestMethod: {
          key: 'requestMethod',
          type: 'String',
          len: 1023,
          discription: 'The method used to access a URL. Possible values: ' +
              '“POST”, “GET”, etc.',
        },
        deviceReceiptTime: {
          key: 'rt',
          type: 'String',
          len: null,
          discription: 'The time at which the event related to the activity ' +
              'was received. The format is MMM dd yyyy HH:mm:ss or ' +
              'milliseconds since epoch (Jan 1st 1970)',
        },
        sourceHostName: {
          key: 'shost',
          type: 'String',
          len: 1023,
          discription: 'Identifies the source that an event refers to in an ' +
              'IP network. The format should be a fully qualified domain ' +
              'name (DQDN) associated with the source node, when a mode is ' +
              'available. Examples: “host” or “host.domain.com”.',
        },
        sourceMacAddress: {
          key: 'smac',
          type: 'String',
          len: null,
          discription: 'Six colon-separated hexadecimal numbers. Example: ' +
              '“00:0D:60:AF:1B:61”',
        },
        sourceNtDomain: {
          key: 'sntdom',
          type: 'String',
          len: 255,
          discription: 'The Windows domain name for the source address.',
        },
        sourceDnsDomain: {
          key: 'sourceDns Domain',
          type: 'String',
          len: 255,
          discription: 'The DNS domain part of the complete fully qualified ' +
              'domain name (FQDN).',
        },
        sourceServiceName: {
          key: 'source ServiceName',
          type: 'String',
          len: 1023,
          discription: 'The service that is responsible for generating this ' +
              'event.',
        },
        sourceTranslatedAddress: {
          key: 'source Translated Address',
          type: 'String',
          len: null,
          discription: 'Identifies the translated source that the event ' +
              'refers to in an IP network. The format is an IPv4 address. ' +
              'Example: “192.168.10.1”.',
        },
        sourceTranslatedPort: {
          key: 'source TranslatedPort',
          type: 'Number',
          len: null,
          discription: 'A port number after being translated by, for ' +
              'example, a firewall. Valid port numbers are 0 to 65535.',
        },
        sourceProcessId: {
          key: 'spid',
          type: 'Number',
          len: null,
          discription: 'The ID of the source process associated with the ' +
              'event.',
        },
        sourceUserPrivileges: {
          key: 'spriv',
          type: 'String',
          len: 1023,
          discription: 'The typical values are “Administrator”, “User”, and ' +
              '“Guest”. It identifies the source user’s privileges. In UNIX, ' +
              'for example, activity executed by the root user would be ' +
              'identified with “Administrator”.',
        },
        sourceProcessName: {
          key: 'sproc',
          type: 'String',
          len: 1023,
          discription: 'The name of the event’s source process.',
        },
        sourcePort: {
          key: 'spt',
          type: 'Number',
          len: null,
          discription: 'The valid port numbers are 0 to 65535.',
        },
        sourceAddress: {
          key: 'src',
          type: 'String',
          len: null,
          discription: 'Identifies the source that an event refers to in an ' +
              'IP network. The format is an IPv4 address. Example: ' +
              '“192.168.10.1”.',
        },
        startTime: {
          key: 'start',
          type: 'String',
          len: null,
          discription: 'The time when the activity the event referred to ' +
              'started. The format is MMM dd yyyy HH:mm:ss or milliseconds ' +
              'since epoch (Jan 1st 1970)',
        },
        sourceUserId: {
          key: 'suid',
          type: 'String',
          len: 1023,
          discription: 'Identifies the source user by ID. This is the user ' +
              'associated with the source of the event. For example, in ' +
              'UNIX, the root user is generally associated with user ID 0.',
        },
        sourceUserName: {
          key: 'suser',
          type: 'String',
          len: 1023,
          discription: 'Identifies the source user by name. Email addresses ' +
              'are also mapped into the UserName fields. The sender is a ' +
              'candidate to put into this field.',
        },
        type: {
          key: 'type',
          type: 'Number',
          len: null,
          discription: '0 means base event, 1 means aggregated, 2 means ' +
              'correlation, and 3 means action. This field can be omitted ' +
              'for base events (type 0).',
        },
        agentDnsDomain: {
          key: 'agentDns Domain',
          type: 'String',
          len: 255,
          discription: 'The DNS domain name of the ArcSight connector that ' +
              'processed the event.',
        },
        agentNtDomain: {
          key: 'agentNtDomain',
          type: 'String',
          len: 255,
          discription: '',
        },
        agentTranslatedAddress: {
          key: 'agentTranslated Address',
          type: 'String',
          len: null,
          discription: '',
        },
        'agentTranslatedZone ExternalID': {
          key: 'agentTranslated ZoneExternalID',
          type: 'String',
          len: 200,
          discription: '',
        },
        agentTranslatedZoneURI: {
          key: 'agentTranslated Zone URI',
          type: 'String',
          len: 2048,
          discription: '',
        },
        agentZoneExternalID: {
          key: 'agentZone ExternalID',
          type: 'String',
          len: 200,
          discription: '',
        },
        agentZoneURI: {
          key: 'agentZoneURI',
          type: 'String',
          len: 2048,
          discription: '',
        },
        agentAddress: {
          key: 'agt',
          type: 'String',
          len: null,
          discription: 'The IP address of the ArcSight connector that ' +
              'processed the event.',
        },
        agentHostName: {
          key: 'ahost',
          type: 'String',
          len: 1023,
          discription: 'The hostname of the ArcSight connector that ' +
              'processed the event.',
        },
        agentId: {
          key: 'aid',
          type: 'String',
          len: 40,
          discription: 'The agent ID of the ArcSight connector that ' +
              'processed the event.',
        },
        agentMacAddress: {
          key: 'amac',
          type: 'String',
          len: null,
          discription: 'The MAC address of the ArcSight connector that ' +
              'processed the event.',
        },
        agentReceiptTime: {
          key: 'art',
          type: 'String',
          len: null,
          discription: 'The time at which information about the event was ' +
              'received by the ArcSight connector.',
        },
        agentType: {
          key: 'at',
          type: 'String',
          len: 63,
          discription: 'The agent type of the ArcSight connector that ' +
              'processed the event',
        },
        agentTimeZone: {
          key: 'atz',
          type: 'String',
          len: 255,
          discription: 'The agent time zone of the ArcSight connector that ' +
              'processed the event.',
        },
        agentVersion: {
          key: 'av',
          type: 'String',
          len: 31,
          discription: 'The version of the ArcSight connector that processed ' +
              'the event.',
        },
        customerExternalID: {
          key: 'customer ExternalID',
          type: 'String',
          len: 200,
          discription: '',
        },
        customerURI: {
          key: 'customerURI',
          type: 'String',
          len: 2048,
          discription: '',
        },
        'destinationTranslated ZoneExternalID': {
          key: 'destination TranslatedZone ExternalID',
          type: 'String',
          len: 200,
          discription: '',
        },
        'destinationTranslated ZoneURI': {
          key: 'destination Translated ZoneURI',
          type: 'String',
          len: 2048,
          discription: 'The URI for the Translated Zone that the destination ' +
              'asset has been assigned to in ArcSight.',
        },
        destinationZoneExternalID: {
          key: 'destinationZone ExternalID',
          type: 'String',
          len: 200,
          discription: '',
        },
        destinationZoneURI: {
          key: 'destinationZone URI',
          type: 'String',
          len: 2048,
          discription: 'The URI for the Zone that the destination asset has ' +
              'been assigned to in ArcSight.',
        },
        'deviceTranslatedZone ExternalID': {
          key: 'device TranslatedZone ExternalID',
          type: 'String',
          len: 200,
          discription: '',
        },
        deviceTranslatedZoneURI: {
          key: 'device TranslatedZone URI',
          type: 'String',
          len: 2048,
          discription: 'The URI for the Translated Zone that the device ' +
              'asset has been assigned to in ArcSight.',
        },
        deviceZoneExternalID: {
          key: 'deviceZone ExternalID',
          type: 'String',
          len: 200,
          discription: '',
        },
        deviceZoneURI: {
          key: 'deviceZoneURI',
          type: 'String',
          len: 2048,
          discription: 'Thee URI for the Zone that the device asset has been ' +
              'assigned to in ArcSight.',
        },
        destinationGeoLatitude: {
          key: 'dlat',
          type: 'Number',
          len: null,
          discription: 'The latitudinal value from which the ' +
              'destination’s IP address belongs.',
        },
        destinationGeoLongitude: {
          key: 'dlong',
          type: 'Number',
          len: null,
          discription: 'The longitudinal value from which the destination’s ' +
              'IP address belongs.',
        },
        eventId: {
          key: 'eventId',
          type: 'Number',
          len: null,
          discription: 'This is a unique ID that ArcSight assigns to each ' +
              'event.',
        },
        rawEvent: {
          key: 'rawEvent',
          type: 'String',
          len: 4000,
          discription: '',
        },
        sourceGeoLatitude: {
          key: 'slat',
          type: 'Number',
          len: null,
          discription: '',
        },
        sourceGeoLongitude: {
          key: 'slong',
          type: 'Number',
          len: null,
          discription: '',
        },
        'sourceTranslatedZone ExternalID': {
          key: 'source TranslatedZone ExternalID',
          type: 'String',
          len: 200,
          discription: '',
        },
        sourceTranslatedZoneURI: {
          key: 'source TranslatedZone URI',
          type: 'String',
          len: 2048,
          discription: 'The URI for the Translated Zone that the destination ' +
              'asset has been assigned to in ArcSight.',
        },
        sourceZoneExternalID: {
          key: 'sourceZone ExternalID',
          type: 'String',
          len: 200,
          discription: '',
        },
        sourceZoneURI: {
          key: 'sourceZoneURI',
          type: 'String',
          len: 2048,
          discription: 'The URI for the Zone that the source asset has been ' +
              'assigned to in ArcSight.' },
      };
      if (typeof this.deviceVendor !== 'string'
          || typeof this.deviceProduct !== 'string'
          || typeof this.deviceVersion !== 'string'
      ) {
        reject(new Error('TYPE ERROR: CEF Device Info must be a string'));
      }
      if (this.severity
          && (
            (
              typeof this.severity === 'string'
              && (
                this.severity !== 'Unknown'
                && this.severity !== 'Low'
                && this.severity !== 'Medium'
                && this.severity !== 'High'
                && this.severity !== 'Very-High'
              )
            )
            || (
              typeof this.severity === 'number'
              && (
                this.severity < 0
                || this.severity > 10
              )
            )
          )
      ) {
        reject(new Error('TYPE ERROR: CEF Severity not set correctly'));
      }
      const cefExts = Object.entries(this.extensions);
      const cefExtsLen = cefExts.length;
      for (let ext = 0; ext < cefExtsLen; ext++) {
        if (cefExts[ext][1] !== null) {
          if (Extensions[cefExts[ext][0]]) {
            if (typeof cefExts[ext][1] === Extensions[cefExts[ext][0]]
              .type
              .toLowerCase()) {
              if (Extensions[cefExts[ext][0]].len > 0
                  && typeof cefExts[ext][1] === 'string'
                  && cefExts[ext][1].length > Extensions[cefExts[ext][0]].len){
                let errMsg = 'FORMAT ERROR:';
                errMsg += ' CEF Extention Key';
                errMsg += ' ' + cefExts[ext][0];
                errMsg += ' value length is to long;';
                errMsg += ' max length is';
                errMsg += ' ' + Extensions[cefExts[ext][0]].len;
                reject(new Error(errMsg));
              }
            } else {
              let errMsg = 'TYPE ERROR:';
              errMsg += ' CEF Key';
              errMsg += ' ' + cefExts[ext][0];
              errMsg += ' value type was expected to be';
              errMsg += ' ' + Extensions[cefExts[ext][0]].type.toLowerCase();
              reject(new Error(errMsg));
            }
          }
        }
      }
      resolve(true);
    });
  }
  /**
   * Build a CEF formated string
   * @public
   * @return {Promise} - String with formated message
   */
  buildMessage() {
    return new Promise((resolve,
      reject) => {
      let fmtMsg = 'CEF:0';
      fmtMsg += '|' + this.deviceVendor;
      fmtMsg += '|' + this.deviceProduct;
      fmtMsg += '|' + this.deviceVersion;
      fmtMsg += '|' + this.deviceEventClassId;
      fmtMsg += '|' + this.name;
      fmtMsg += '|' + this.severity;
      fmtMsg += '|';

      const cefExts = Object.entries(this.extensions);
      const cefExtsLen = cefExts.length;
      for (let ext = 0; ext < cefExtsLen; ext++) {
        if (cefExts[ext][1] !== null) {
          fmtMsg += cefExts[ext][0] + '=' + cefExts[ext][1] + ' ';
        }
      }
      resolve(fmtMsg);
    });
  }
  /**
   * @public
   * @param {Syslog} [options=false] - A {@link module:SyslogPro~Syslog|
   *    Syslog server connection} that should be used to send messages directly
   *    from this class. @see SyslogPro~Syslog
   */
  send(options) {
    return new Promise((resolve,
      reject) => {
      this.buildMessage()
        .then((result) => {
          if (!this.server) {
            this.server = new Syslog(options);
          }
          this.server.send(result)
            .then((sendResult) => {
              resolve(sendResult);
            })
            .catch((reson) => {
              reject(reson);
            });
        });
    });
  }
}

module.exports = {
  RgbToAnsi: rgbToAnsi,
  RFC3164: RFC3164,
  RFC5424: RFC5424,
  LEEF: LEEF,
  CEF: CEF,
  Syslog: Syslog,
};