From adb2972d812f4c98b9e94826a7a5f2c06b3e02b1 Mon Sep 17 00:00:00 2001
From: smittythekid <57874528+smittythekid@users.noreply.github.com>
Date: Tue, 7 Apr 2026 21:17:47 +0200
Subject: [PATCH] Ordnerstruktur + .gitignore angepasst
---
.gitignore | 3 +
src/main/java/vs/AsciiChars.java | 87 ++++++++++
src/main/java/vs/StructuredData.java | 180 ++++++++++++++++++++
src/main/java/vs/SyslogMessage.java | 213 ++++++++++++++++++++++++
src/test/java/vs/AsciiChars.java | 87 ++++++++++
src/test/java/vs/AsciiCharsTest.java | 56 +++++++
src/test/java/vs/SyslogMessageTest.java | 93 +++++++++++
7 files changed, 719 insertions(+)
create mode 100644 src/main/java/vs/AsciiChars.java
create mode 100644 src/main/java/vs/StructuredData.java
create mode 100644 src/main/java/vs/SyslogMessage.java
create mode 100644 src/test/java/vs/AsciiChars.java
create mode 100644 src/test/java/vs/AsciiCharsTest.java
create mode 100644 src/test/java/vs/SyslogMessageTest.java
diff --git a/.gitignore b/.gitignore
index 9154f4c..bde3447 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,6 @@
hs_err_pid*
replay_pid*
+# IDE files
+justfile
+pom.xml
diff --git a/src/main/java/vs/AsciiChars.java b/src/main/java/vs/AsciiChars.java
new file mode 100644
index 0000000..bdd005b
--- /dev/null
+++ b/src/main/java/vs/AsciiChars.java
@@ -0,0 +1,87 @@
+package vs;
+
+/**
+ * helper class for RFC 5424 (https://datatracker.ietf.org/doc/html/rfc5424)
+ * compliant log messages as immutable Java objects - representation of a subset
+ * of printable strings of specific length
+ *
+ * @author Sandro Leuchter
+ *
+ */
+public abstract class AsciiChars {
+
+ private final String value;
+
+ public String value() {
+ return this.value;
+ }
+
+ protected AsciiChars(int length, String value) {
+ if (value != null) {
+ if (value.length() > length) {
+ throw new IllegalArgumentException(
+ "Stringlänge = " + value.length() + " > " + length
+ );
+ }
+ for (int c : value.getBytes()) {
+ if (c < 33 || c > 126) {
+ throw new IllegalArgumentException(
+ "Stringinhalt nicht printable US-ASCII ohne Space"
+ );
+ }
+ }
+ }
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ if (value() == null || value().length() == 0) {
+ return "-";
+ } else {
+ return value();
+ }
+ }
+
+ public static final class L004 extends AsciiChars {
+
+ public L004(String value) {
+ super(4, value);
+ }
+ }
+
+ public static final class L012 extends AsciiChars {
+
+ public L012(String value) {
+ super(12, value);
+ }
+ }
+
+ public static final class L032 extends AsciiChars {
+
+ public L032(String value) {
+ super(32, value);
+ }
+ }
+
+ public static final class L048 extends AsciiChars {
+
+ public L048(String value) {
+ super(48, value);
+ }
+ }
+
+ public static final class L128 extends AsciiChars {
+
+ public L128(String value) {
+ super(128, value);
+ }
+ }
+
+ public static final class L255 extends AsciiChars {
+
+ public L255(String value) {
+ super(255, value);
+ }
+ }
+}
diff --git a/src/main/java/vs/StructuredData.java b/src/main/java/vs/StructuredData.java
new file mode 100644
index 0000000..2c54682
--- /dev/null
+++ b/src/main/java/vs/StructuredData.java
@@ -0,0 +1,180 @@
+package vs;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * helper class for RFC 5424 (https://datatracker.ietf.org/doc/html/rfc5424)
+ * compliant log messages as immutable Java objects - structured data (set of
+ * key/value-pairs) with some predefined sets according to the standard
+ *
+ * @author Sandro Leuchter
+ *
+ */
+public class StructuredData {
+
+ public static class Element {
+
+ private final String name;
+ private List parameters;
+
+ public static Element newTimeQuality(
+ boolean tzKnown,
+ boolean isSynced
+ ) {
+ return newTimeQuality(tzKnown, isSynced, null);
+ }
+
+ public static Element newTimeQuality(
+ boolean tzKnown,
+ boolean isSynced,
+ Integer syncAccuracy
+ ) {
+ var e = new Element("timeQuality");
+ e.add(new Param("tzKnown", (tzKnown) ? "1" : "0"));
+ e.add(new Param("isSynced", (isSynced) ? "1" : "0"));
+ if (syncAccuracy != null && !isSynced) {
+ e.add(new Param("syncAccuracy", String.valueOf(syncAccuracy)));
+ }
+ return e;
+ }
+
+ public static Element newOrigin(
+ String enterpriseId,
+ String software,
+ String swVersion
+ ) {
+ return newOrigin(
+ new String[] {},
+ enterpriseId,
+ software,
+ swVersion
+ );
+ }
+
+ public static Element newOrigin(
+ String ip,
+ String enterpriseId,
+ String software,
+ String swVersion
+ ) {
+ return newOrigin(
+ new String[] { ip },
+ enterpriseId,
+ software,
+ swVersion
+ );
+ }
+
+ public static Element newOrigin(
+ String[] ip,
+ String enterpriseId,
+ String software,
+ String swVersion
+ ) {
+ var e = new Element("origin");
+ for (var p : ip) {
+ e = e.add(new Param("ip", p));
+ }
+ if (enterpriseId != null && !enterpriseId.equals("")) {
+ e = e.add(new Param("enterpriseId", enterpriseId));
+ }
+ if (software != null && !software.equals("")) {
+ e = e.add(new Param("software", software));
+ }
+ if (swVersion != null && !swVersion.equals("")) {
+ e = e.add(new Param("swVersion", swVersion));
+ }
+ return e;
+ }
+
+ public static Element newMeta(
+ Integer sequenceId,
+ Integer sysUpTime,
+ String language
+ ) {
+ var e = new Element("meta");
+ if (sequenceId != null && sequenceId > 0) {
+ e = e.add(
+ new Param(
+ "sequenceId",
+ String.valueOf(sequenceId % 2147483647)
+ )
+ );
+ }
+ if (sysUpTime != null && sysUpTime >= 0) {
+ e = e.add(new Param("sysUpTime", String.valueOf(sysUpTime)));
+ }
+ if (language != null && !language.equals("")) {
+ e = e.add(new Param("language", language));
+ }
+ return e;
+ }
+
+ public Element(String name) {
+ this.name = name;
+ this.parameters = new ArrayList<>();
+ }
+
+ public Element add(Param parameter) {
+ var e = new Element(this.name);
+ e.parameters = this.parameters;
+ e.parameters.add(parameter);
+ return e;
+ }
+
+ @Override
+ public String toString() {
+ var str = "[" + this.name;
+ for (var p : this.parameters) {
+ str = str + " " + p.toString();
+ }
+ return str + "]";
+ }
+ }
+
+ public static class Param {
+
+ private final String name;
+ // name: printable US-ASCII string ^[@=\]\"\s]+
+ // "@" + private enterpise number "@\d+(\.\d+)*"
+ private final String value;
+
+ public Param(String name, String value) {
+ this.name = name; // 7-Bit ASCII
+ this.value = value; // UTF-8
+ }
+
+ @Override
+ public String toString() {
+ return this.name + "=\"" + this.value + "\"";
+ }
+ }
+
+ private List params;
+
+ public StructuredData() {
+ this.params = new ArrayList<>();
+ }
+
+ public StructuredData(List params) {
+ this.params = params;
+ }
+
+ public String toString() {
+ if (this.params.size() == 0) {
+ return "-";
+ }
+ var str = "";
+ for (var p : this.params) {
+ str = str + p.toString();
+ }
+ return str;
+ }
+
+ public StructuredData add(Element e) {
+ var p = this.params;
+ p.add(e);
+ return new StructuredData(p);
+ }
+}
diff --git a/src/main/java/vs/SyslogMessage.java b/src/main/java/vs/SyslogMessage.java
new file mode 100644
index 0000000..156810c
--- /dev/null
+++ b/src/main/java/vs/SyslogMessage.java
@@ -0,0 +1,213 @@
+package vs;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * RFC 5424 (https://datatracker.ietf.org/doc/html/rfc5424) compliant log
+ * messages as immutable Java objects
+ *
+ * @author Sandro Leuchter
+ *
+ */
+public class SyslogMessage implements Serializable {
+
+ private static final long serialVersionUID = -5895573029109990861L;
+ private final Facility fac;
+ private final Severity sev;
+ private final AsciiChars.L255 host;
+ private final AsciiChars.L048 appName;
+ private final AsciiChars.L128 procId;
+ private final AsciiChars.L032 msgId;
+ private final StructuredData data;
+ private final Message message;
+
+ public SyslogMessage(
+ Facility fac,
+ Severity sev,
+ AsciiChars.L255 host,
+ AsciiChars.L048 appName,
+ AsciiChars.L128 procId,
+ AsciiChars.L032 msgId,
+ StructuredData data,
+ Message message
+ ) {
+ this.fac = fac;
+ this.sev = sev;
+ this.host = host;
+ this.appName = appName;
+ this.procId = procId;
+ this.msgId = msgId;
+ this.data = data;
+ this.message = message;
+ }
+
+ public Facility fac() {
+ return this.fac;
+ }
+
+ public Severity sev() {
+ return sev;
+ }
+
+ public AsciiChars.L255 host() {
+ return host;
+ }
+
+ public AsciiChars.L048 appName() {
+ return appName;
+ }
+
+ public AsciiChars.L128 procId() {
+ return procId;
+ }
+
+ public AsciiChars.L032 msgId() {
+ return msgId;
+ }
+
+ public StructuredData data() {
+ return data;
+ }
+
+ public Message message() {
+ return message;
+ }
+
+ public static int version() {
+ return VERSION;
+ }
+
+ public static enum Facility {
+ KERNEL,
+ USER,
+ MAIL_SYSTEM,
+ SYS_DAEMON,
+ SECURITY1,
+ INTERNAL,
+ PRINTER,
+ NEWS,
+ UUCP,
+ CLOCK1,
+ SECURITY2,
+ FTP,
+ NTP,
+ AUDIT,
+ ALERT,
+ CLOCK2,
+ LOCAL0,
+ LOCAL1,
+ LOCAL2,
+ LOCAL3,
+ LOCAL4,
+ LOCAL5,
+ LOCAL6,
+ LOCAL7,
+ }
+
+ public static enum Severity {
+ EMERGENCY,
+ ALERT,
+ CRITICAL,
+ ERROR,
+ WARNING,
+ NOTICE,
+ INFORMATIONAL,
+ DEBUG,
+ }
+
+ public static interface Message {
+ public Object message();
+
+ public int length();
+ }
+
+ public static class BinaryMessage implements Message {
+
+ private Byte[] message;
+
+ public BinaryMessage(Byte[] message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return message.toString();
+ }
+
+ @Override
+ public Object message() {
+ return this.message;
+ }
+
+ @Override
+ public int length() {
+ return this.message.length;
+ }
+ }
+
+ public static class TextMessage implements Message {
+
+ private String message; // UTF8
+
+ public TextMessage(String message) {
+ this.message = message;
+ }
+
+ @Override
+ public String toString() {
+ return "\u00EF\u00BB\u00BF" + message.toString();
+ }
+
+ @Override
+ public Object message() {
+ return this.message;
+ }
+
+ @Override
+ public int length() {
+ return this.message.length();
+ }
+ }
+
+ static final int VERSION = 1; // RFC 5424, Mar 2009
+
+ @Override
+ public String toString() {
+ var prival = String.valueOf(fac().ordinal() * 8 + sev().ordinal());
+ var d = "";
+ if (data() != null) {
+ d = " " + data();
+ }
+ var m = "";
+ if (
+ message() != null &&
+ message().message() != null &&
+ message().length() > 0
+ ) {
+ m = " " + message();
+ }
+ var timestamp = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(
+ new Date()
+ );
+ return (
+ "<" +
+ prival +
+ ">" +
+ VERSION +
+ " " +
+ timestamp +
+ " " +
+ host().toString() +
+ " " +
+ appName().toString() +
+ " " +
+ procId().toString() +
+ " " +
+ msgId().toString() +
+ d +
+ m
+ );
+ }
+}
diff --git a/src/test/java/vs/AsciiChars.java b/src/test/java/vs/AsciiChars.java
new file mode 100644
index 0000000..bdd005b
--- /dev/null
+++ b/src/test/java/vs/AsciiChars.java
@@ -0,0 +1,87 @@
+package vs;
+
+/**
+ * helper class for RFC 5424 (https://datatracker.ietf.org/doc/html/rfc5424)
+ * compliant log messages as immutable Java objects - representation of a subset
+ * of printable strings of specific length
+ *
+ * @author Sandro Leuchter
+ *
+ */
+public abstract class AsciiChars {
+
+ private final String value;
+
+ public String value() {
+ return this.value;
+ }
+
+ protected AsciiChars(int length, String value) {
+ if (value != null) {
+ if (value.length() > length) {
+ throw new IllegalArgumentException(
+ "Stringlänge = " + value.length() + " > " + length
+ );
+ }
+ for (int c : value.getBytes()) {
+ if (c < 33 || c > 126) {
+ throw new IllegalArgumentException(
+ "Stringinhalt nicht printable US-ASCII ohne Space"
+ );
+ }
+ }
+ }
+ this.value = value;
+ }
+
+ @Override
+ public String toString() {
+ if (value() == null || value().length() == 0) {
+ return "-";
+ } else {
+ return value();
+ }
+ }
+
+ public static final class L004 extends AsciiChars {
+
+ public L004(String value) {
+ super(4, value);
+ }
+ }
+
+ public static final class L012 extends AsciiChars {
+
+ public L012(String value) {
+ super(12, value);
+ }
+ }
+
+ public static final class L032 extends AsciiChars {
+
+ public L032(String value) {
+ super(32, value);
+ }
+ }
+
+ public static final class L048 extends AsciiChars {
+
+ public L048(String value) {
+ super(48, value);
+ }
+ }
+
+ public static final class L128 extends AsciiChars {
+
+ public L128(String value) {
+ super(128, value);
+ }
+ }
+
+ public static final class L255 extends AsciiChars {
+
+ public L255(String value) {
+ super(255, value);
+ }
+ }
+}
diff --git a/src/test/java/vs/AsciiCharsTest.java b/src/test/java/vs/AsciiCharsTest.java
new file mode 100644
index 0000000..8aab640
--- /dev/null
+++ b/src/test/java/vs/AsciiCharsTest.java
@@ -0,0 +1,56 @@
+package vs;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+
+class AsciiCharsTest {
+
+ @Test
+ void nullValue() {
+ var ac = new AsciiChars.L004(null);
+ assertEquals(ac.toString(), "-");
+ }
+
+ @Test
+ void emptyValue() {
+ var ac = new AsciiChars.L004("");
+ assertEquals(ac.toString(), "-");
+ }
+
+ @Test
+ void longValue() {
+ var ac = new AsciiChars.L004("1234");
+ assertEquals(ac.toString(), "1234");
+ }
+
+ @Test
+ void longerValue() {
+ var thrown = assertThrows(IllegalArgumentException.class, () -> {
+ new AsciiChars.L004("12345");
+ });
+ assertEquals("Stringlänge = 5 > 4", thrown.getMessage());
+ }
+
+ @Test
+ void space() {
+ var thrown = assertThrows(IllegalArgumentException.class, () -> {
+ new AsciiChars.L004("1 1");
+ });
+ assertEquals(
+ "Stringinhalt nicht printable US-ASCII ohne Space",
+ thrown.getMessage()
+ );
+ }
+
+ @Test
+ void special() {
+ var thrown = assertThrows(IllegalArgumentException.class, () -> {
+ new AsciiChars.L004("ä");
+ });
+ assertEquals(
+ "Stringinhalt nicht printable US-ASCII ohne Space",
+ thrown.getMessage()
+ );
+ }
+}
diff --git a/src/test/java/vs/SyslogMessageTest.java b/src/test/java/vs/SyslogMessageTest.java
new file mode 100644
index 0000000..b60ff0c
--- /dev/null
+++ b/src/test/java/vs/SyslogMessageTest.java
@@ -0,0 +1,93 @@
+package vs;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import vs.StructuredData.*;
+import vs.SyslogMessage.*;
+
+class SyslogMessageTest {
+
+ @Test
+ void testToString() {
+ var m1 = new SyslogMessage(
+ //
+ Facility.SECURITY1, //
+ Severity.CRITICAL, //
+ new AsciiChars.L255("mymachine.example.com"), //
+ new AsciiChars.L048("su"), //
+ new AsciiChars.L128(""), //
+ new AsciiChars.L032("ID47"), //
+ new StructuredData() //
+ .add(Element.newTimeQuality(true, true))
+ .add(
+ new Element("exampleSDID@32473")
+ .add(new Param("iut", "3"))
+ .add(new Param("eventSource", "Application"))
+ .add(new Param("eventID", "1011"))
+ )
+ .add(
+ new Element("examplePriority@32473").add(
+ new Param("class", "high")
+ )
+ ),
+ new TextMessage("'su root' failed for lonvick on /dev/pts/8")
+ );
+ var s = m1.toString();
+ assertEquals(s.substring(0, 6), "<34>1 ");
+ assertEquals(
+ s.substring(s.length() - 221, s.length()),
+ " mymachine.example.com su - ID47 [timeQuality tzKnown=\"1\" isSynced=\"1\"][exampleSDID@32473 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"][examplePriority@32473 class=\"high\"] 'su root' failed for lonvick on /dev/pts/8"
+ );
+ }
+
+ @Test
+ void test2() {
+ var m1 = new SyslogMessage(
+ //
+ Facility.SECURITY1, //
+ Severity.CRITICAL, //
+ new AsciiChars.L255("mymachine.example.com"), //
+ new AsciiChars.L048("su"), //
+ new AsciiChars.L128(""), //
+ new AsciiChars.L032("ID47"), //
+ null, //
+ new TextMessage("'su root' failed for lonvick on /dev/pts/8")
+ );
+ var s = m1.toString();
+ assertEquals(s.substring(0, 6), "<34>1 ");
+ assertEquals(
+ s.substring(s.length() - 78, s.length()),
+ " mymachine.example.com su - ID47 'su root' failed for lonvick on /dev/pts/8"
+ );
+ }
+
+ @Test
+ void test3() {
+ var m1 = new SyslogMessage(
+ //
+ Facility.SECURITY1, //
+ Severity.CRITICAL, //
+ new AsciiChars.L255("mymachine.example.com"), //
+ new AsciiChars.L048("su"), //
+ new AsciiChars.L128(""), //
+ new AsciiChars.L032("ID47"), //
+ new StructuredData() //
+ .add(Element.newTimeQuality(true, true))
+ .add(
+ Element.newOrigin(
+ new String[] { "0.0.8.8", "8.8.8.8" },
+ null,
+ null,
+ null
+ )
+ )
+ .add(Element.newMeta(null, 32, "de")),
+ new BinaryMessage(null)
+ );
+ assertEquals(
+ m1.data().toString(),
+ "[timeQuality tzKnown=\"1\" isSynced=\"1\"][origin ip=\"0.0.8.8\" ip=\"8.8.8.8\"][meta sysUpTime=\"32\" language=\"de\"]"
+ );
+ }
+}