Initial commit: syslog server and test client
commit
b7f5dbc7fb
|
|
@ -0,0 +1,2 @@
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Test client for the RFC 5424 syslog server (UDP only).
|
||||||
|
|
||||||
|
Service discovery: broadcast an empty datagram to 255.255.255.255:8888,
|
||||||
|
the server's reply source address is the syslog server IP.
|
||||||
|
Then log messages go to <server-ip>:514 (RFC 5424 format, no structured data).
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
python syslog_client.py "hello world"
|
||||||
|
python syslog_client.py -s 4 -f 1 --app myapp "disk almost full"
|
||||||
|
python syslog_client.py --server 192.168.0.10 "skip discovery"
|
||||||
|
python syslog_client.py --oversize 2000 "too long on purpose"
|
||||||
|
python syslog_client.py --raw "garbage, not RFC 5424"
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
DISCOVERY_PORT = 8888
|
||||||
|
SYSLOG_PORT = 514
|
||||||
|
BROADCAST = "255.255.255.255"
|
||||||
|
|
||||||
|
|
||||||
|
def discover(timeout, retries, discovery_port, broadcast=BROADCAST):
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
try:
|
||||||
|
for attempt in range(1, retries + 1):
|
||||||
|
print(f"[discovery] broadcast {attempt}/{retries} -> {broadcast}:{discovery_port}")
|
||||||
|
sock.sendto(b"", (broadcast, discovery_port))
|
||||||
|
try:
|
||||||
|
_, (ip, port) = sock.recvfrom(1024)
|
||||||
|
print(f"[discovery] reply from {ip}:{port}")
|
||||||
|
return ip
|
||||||
|
except socket.timeout:
|
||||||
|
continue
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def build_message(facility, severity, app, procid, msgid, text, hostname):
|
||||||
|
pri = facility * 8 + severity
|
||||||
|
# RFC 5424 timestamp: full-date "T" full-time, UTC with Z
|
||||||
|
ts = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||||
|
# HEADER SP STRUCTURED-DATA SP MSG ; SD is NILVALUE "-"
|
||||||
|
return f"<{pri}>1 {ts} {hostname} {app} {procid} {msgid} - {text}"
|
||||||
|
|
||||||
|
|
||||||
|
def run_demo(server, port, hostname, procid):
|
||||||
|
bm = lambda fac, sev, app, msgid, text, host=hostname: build_message(
|
||||||
|
fac, sev, app, procid, msgid, text, host).encode("utf-8")
|
||||||
|
ts = datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||||
|
cases = [
|
||||||
|
("valid: user.info", bm(1, 6, "demo", "ID1", "hello from demo client")),
|
||||||
|
("valid: kernel.emerg", bm(0, 0, "kernel", "BOOT", "system on fire")),
|
||||||
|
("valid: local7.debug", bm(23, 7, "myapp", "-", "debug details")),
|
||||||
|
("valid: nil fields", f"<14>1 {ts} - - - - - only nil header".encode()),
|
||||||
|
("valid: UTF-8 text with BOM", f"<14>1 {ts} {hostname} demo {procid} U1 - ".encode()
|
||||||
|
+ b"\xef\xbb\xbf" + "Grüße, 世界".encode("utf-8")),
|
||||||
|
("valid: no message body", f"<14>1 {ts} {hostname} demo {procid} E1 -".encode()),
|
||||||
|
("valid: ~480 bytes", bm(1, 6, "demo", "LEN", "A" * 400)),
|
||||||
|
("INVALID: too long (3000 B)", bm(1, 6, "demo", "LEN", "B" * 3000)),
|
||||||
|
("INVALID: APP-NAME 49 chars", bm(1, 6, "a" * 49, "ID1", "app name too long")),
|
||||||
|
("INVALID: MSGID 33 chars", bm(1, 6, "demo", "m" * 33, "msgid too long")),
|
||||||
|
("INVALID: no PRI", f"1 {ts} {hostname} demo {procid} ID1 - no pri".encode()),
|
||||||
|
("INVALID: PRI out of range <192>", f"<192>1 {ts} {hostname} demo {procid} ID1 - bad pri".encode()),
|
||||||
|
("INVALID: garbage", b"this is not syslog"),
|
||||||
|
("INVALID: empty datagram", b""),
|
||||||
|
]
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
for label, data in cases:
|
||||||
|
sock.sendto(data, (server, port))
|
||||||
|
print(f"[demo] {label:34} {len(data):5} B {data[:60]!r}")
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
p = argparse.ArgumentParser(description="Syslog (RFC 5424, UDP) test client")
|
||||||
|
p.add_argument("message", nargs="?", help="log message text (not needed with --demo)")
|
||||||
|
p.add_argument("--demo", action="store_true",
|
||||||
|
help="discover server, then send a set of example packets (valid and invalid)")
|
||||||
|
p.add_argument("-s", "--severity", type=int, default=6, choices=range(8),
|
||||||
|
help="0=emerg .. 7=debug (default 6=info)")
|
||||||
|
p.add_argument("-f", "--facility", type=int, default=1, choices=range(24),
|
||||||
|
help="0..23 (default 1=user)")
|
||||||
|
p.add_argument("--app", default="testclient", help="APP-NAME")
|
||||||
|
p.add_argument("--msgid", default="-", help="MSGID")
|
||||||
|
p.add_argument("--hostname", default=socket.gethostname().replace(" ", "_") or "-")
|
||||||
|
p.add_argument("-n", "--count", type=int, default=1, help="send message N times")
|
||||||
|
p.add_argument("--server", help="server IP, skips auto-discovery")
|
||||||
|
p.add_argument("--port", type=int, default=SYSLOG_PORT, help="syslog port (default 514)")
|
||||||
|
p.add_argument("--discovery-port", type=int, default=DISCOVERY_PORT)
|
||||||
|
p.add_argument("--broadcast", default=BROADCAST, metavar="ADDR",
|
||||||
|
help="discovery target (default 255.255.255.255; use 127.255.255.255 "
|
||||||
|
"or 127.0.0.1 to stay on localhost)")
|
||||||
|
p.add_argument("--timeout", type=float, default=2.0, help="discovery wait per try (s)")
|
||||||
|
p.add_argument("--retries", type=int, default=3)
|
||||||
|
p.add_argument("--oversize", type=int, metavar="BYTES",
|
||||||
|
help="pad message to this many bytes total (test server length check)")
|
||||||
|
p.add_argument("--raw", action="store_true",
|
||||||
|
help="send MESSAGE verbatim, no RFC 5424 header (test server validation)")
|
||||||
|
args = p.parse_args()
|
||||||
|
|
||||||
|
server = args.server or discover(args.timeout, args.retries, args.discovery_port, args.broadcast)
|
||||||
|
if not server:
|
||||||
|
print("[error] no server found", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
procid = str(os.getpid())
|
||||||
|
if args.demo:
|
||||||
|
return run_demo(server, args.port, args.hostname, procid)
|
||||||
|
if not args.message:
|
||||||
|
p.error("message required unless --demo")
|
||||||
|
if args.raw:
|
||||||
|
data = args.message.encode("utf-8")
|
||||||
|
else:
|
||||||
|
data = build_message(args.facility, args.severity, args.app, procid,
|
||||||
|
args.msgid, args.message, args.hostname).encode("utf-8")
|
||||||
|
if args.oversize and len(data) < args.oversize:
|
||||||
|
data += b"X" * (args.oversize - len(data))
|
||||||
|
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
for i in range(args.count):
|
||||||
|
try:
|
||||||
|
sock.sendto(data, (server, args.port))
|
||||||
|
except OSError as e:
|
||||||
|
print(f"[error] send failed ({len(data)} bytes): {e}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
print(f"[send] {i + 1}/{args.count} {len(data)} bytes -> {server}:{args.port}")
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
**/.DS_Store
|
||||||
|
**/*.class
|
||||||
|
/bin/
|
||||||
|
/.project
|
||||||
|
/.classpath
|
||||||
|
/.factorypath
|
||||||
|
/.settings/
|
||||||
|
/target/
|
||||||
|
/.apt_generated/
|
||||||
|
/.apt_generated_tests/
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<annotationProcessing>
|
||||||
|
<profile name="Maven default annotation processors profile" enabled="true">
|
||||||
|
<sourceOutputDir name="target/generated-sources/annotations" />
|
||||||
|
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
|
||||||
|
<outputRelativeToContentRoot value="true" />
|
||||||
|
<module name="vs.pu01" />
|
||||||
|
</profile>
|
||||||
|
</annotationProcessing>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding">
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
|
||||||
|
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Central Repository" />
|
||||||
|
<option name="url" value="https://repo.maven.apache.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
|
<component name="MavenProjectsManager">
|
||||||
|
<option name="originalFiles">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/pom.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="openjdk-26" project-jdk-type="JavaSDK" />
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
default:
|
||||||
|
mvn clean compile test
|
||||||
|
exec class args: compile
|
||||||
|
mvn exec:java -Dexec.mainClass={{class}} -Dexec.args={{args}}
|
||||||
|
clean:
|
||||||
|
mvn clean
|
||||||
|
compile:
|
||||||
|
mvn compile
|
||||||
|
test: compile
|
||||||
|
mvn test
|
||||||
|
javadoc:
|
||||||
|
mvn javadoc:javadoc
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>vs</groupId>
|
||||||
|
<artifactId>vs.pu01</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.release>10</maven.compiler.release>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
<dependencies>
|
||||||
|
<dependency><!-- für Unit-Tests -->
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>5.10.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency><!-- für Lombok -->
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.48</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency><!-- für net.jcip Annotationen -->
|
||||||
|
<groupId>net.jcip</groupId>
|
||||||
|
<artifactId>jcip-annotations</artifactId>
|
||||||
|
<version>1.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin><!-- für Unit-Tests [mvn test] -->
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin><!-- [mvn compile] -->
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.9.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin><!-- [mvn exec:java] -->
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>exec-maven-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin><!-- [mvn javadoc:javadoc] -->
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>3.5.0</version>
|
||||||
|
<configuration>
|
||||||
|
<show>private</show>
|
||||||
|
<locale>en_US</locale>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
package vs;
|
||||||
|
|
||||||
|
import com.sun.jdi.InternalException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.DatagramSocket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
|
||||||
|
static DatagramSocket syslogSocket;
|
||||||
|
static DatagramSocket broadcastSocket;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args){
|
||||||
|
|
||||||
|
try {
|
||||||
|
syslogSocket = new DatagramSocket(514);
|
||||||
|
} catch (SocketException e) {
|
||||||
|
System.err.println("Couldnt bind to port 514, shutting down");
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
broadcastSocket = new DatagramSocket(8888);
|
||||||
|
} catch (SocketException e) {
|
||||||
|
System.err.println("Couldnt bind to port 8888, shutting down");
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
broadcastSocket.setSoTimeout(200);
|
||||||
|
syslogSocket.setSoTimeout(200);
|
||||||
|
} catch (SocketException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
while (true){
|
||||||
|
|
||||||
|
byte[] buffer = new byte[2048];
|
||||||
|
|
||||||
|
DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length);
|
||||||
|
|
||||||
|
//broadcast read and reply
|
||||||
|
try {
|
||||||
|
broadcastSocket.receive(packet);
|
||||||
|
|
||||||
|
//we received braodcast message, answer from syslog socket so they know where we are
|
||||||
|
DatagramPacket answer = new DatagramPacket(new byte[1],0,1,packet.getSocketAddress());
|
||||||
|
syslogSocket.send(answer);
|
||||||
|
}catch (SocketTimeoutException e){
|
||||||
|
//nop, just empty
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
//syslog logic
|
||||||
|
|
||||||
|
packet = new DatagramPacket(buffer,0,buffer.length);
|
||||||
|
|
||||||
|
try {
|
||||||
|
syslogSocket.receive(packet);
|
||||||
|
}catch (SocketTimeoutException e){
|
||||||
|
//nop, just empty
|
||||||
|
continue;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SyslogMessage msg = parseSysMessage(packet.getData());
|
||||||
|
System.out.println("Log received from "+packet.getAddress().getHostName() +":"+ packet.getPort()+ " : "+msg.toString());
|
||||||
|
}catch (Exception e){
|
||||||
|
System.err.println("msg couldnt be parsed, "+e.getMessage());
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SyslogMessage parseSysMessage(byte[] buff) throws InternalException{
|
||||||
|
if (buff[0] != 60){
|
||||||
|
throw new InternalException("msg not well formed");
|
||||||
|
}
|
||||||
|
SyslogMessage.Facility fac;
|
||||||
|
SyslogMessage.Severity sev;
|
||||||
|
|
||||||
|
int step = 1;
|
||||||
|
|
||||||
|
String prival_str = "";
|
||||||
|
for(;step < 4;step++){
|
||||||
|
if (buff[step] == 62) break;
|
||||||
|
prival_str += (char) buff[step];
|
||||||
|
}
|
||||||
|
if (buff[step] != 62) throw new InternalException("PRIVAL malformed");
|
||||||
|
if (prival_str.isEmpty()) throw new InternalException("PRIVAL not there");
|
||||||
|
|
||||||
|
int primval = Integer.parseInt(prival_str);
|
||||||
|
fac = SyslogMessage.Facility.values()[primval/8];
|
||||||
|
sev = SyslogMessage.Severity.values()[primval%8];
|
||||||
|
|
||||||
|
//version is always 1
|
||||||
|
if (buff[++step] != 49) throw new InternalException("VERSION NOT 1");
|
||||||
|
|
||||||
|
//skip whitespace and verion
|
||||||
|
++step;
|
||||||
|
++step;
|
||||||
|
|
||||||
|
//TIMESTAMP (apparently we skip this)
|
||||||
|
while(buff[step] != 32)
|
||||||
|
step++;
|
||||||
|
|
||||||
|
//skip whitespace
|
||||||
|
step++;
|
||||||
|
|
||||||
|
//HOSTNAME
|
||||||
|
StringBuilder hostnameBuilder = new StringBuilder();
|
||||||
|
while(buff[step] != 32){
|
||||||
|
hostnameBuilder.append((char) buff[step]);
|
||||||
|
step++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AsciiChars.L255 hostname = new AsciiChars.L255(hostnameBuilder.toString());
|
||||||
|
|
||||||
|
//skip whitespace
|
||||||
|
step++;
|
||||||
|
|
||||||
|
//APP-NAME
|
||||||
|
StringBuilder appNameBuilder = new StringBuilder();
|
||||||
|
while (buff[step] != 32){
|
||||||
|
appNameBuilder.append((char) buff[step]);
|
||||||
|
step++;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsciiChars.L048 appName = new AsciiChars.L048(appNameBuilder.toString());
|
||||||
|
|
||||||
|
//skip whitespace
|
||||||
|
step++;
|
||||||
|
|
||||||
|
//PROCID
|
||||||
|
StringBuilder procIDBuilder = new StringBuilder();
|
||||||
|
while (buff[step] != 32){
|
||||||
|
procIDBuilder.append((char) buff[step]);
|
||||||
|
step++;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsciiChars.L128 procID = new AsciiChars.L128(procIDBuilder.toString());
|
||||||
|
|
||||||
|
//skip whitespace
|
||||||
|
step++;
|
||||||
|
|
||||||
|
//MSGID
|
||||||
|
StringBuilder msgIDBuilder = new StringBuilder();
|
||||||
|
while (buff[step] != 32){
|
||||||
|
msgIDBuilder.append((char) buff[step]);
|
||||||
|
step++;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsciiChars.L032 msgID = new AsciiChars.L032(msgIDBuilder.toString());
|
||||||
|
|
||||||
|
//skip whitespace
|
||||||
|
step++;
|
||||||
|
|
||||||
|
|
||||||
|
//skip structured data
|
||||||
|
while (buff[step] != 32){
|
||||||
|
step++;
|
||||||
|
}
|
||||||
|
|
||||||
|
//MESSAGE
|
||||||
|
|
||||||
|
|
||||||
|
String end = new String(buff,step,buff.length-step-1);
|
||||||
|
SyslogMessage.Message msg;
|
||||||
|
|
||||||
|
if (end.startsWith("\u00EF\u00BB\u00BF")){
|
||||||
|
msg = new SyslogMessage.TextMessage(end);
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Byte[] msgBuff = new Byte[buff.length-step];
|
||||||
|
for (int i = 0;i < msgBuff.length;i++){
|
||||||
|
msgBuff[i] = buff[i+step];
|
||||||
|
}
|
||||||
|
msg = new SyslogMessage.BinaryMessage(msgBuff);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SyslogMessage(fac,sev,hostname,appName,procID,msgID,null,msg);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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<Param> 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<Element> params;
|
||||||
|
|
||||||
|
public StructuredData() {
|
||||||
|
this.params = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StructuredData(List<Element> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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\"]"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue