fluent-plugin-syslog-gobi-tls 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,189 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016-2023 t.e.morgan.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'socket'
17
+ require 'openssl'
18
+
19
+ module SyslogTls
20
+ # Supports SSL connection to remote host
21
+ class SSLTransport
22
+ CONNECT_TIMEOUT = 10
23
+ # READ_TIMEOUT = 5
24
+ WRITE_TIMEOUT = 5
25
+
26
+ attr_accessor :socket
27
+
28
+ attr_reader :host, :port, :idle_timeout, :ca_cert, :client_cert, :client_key, :verify_cert_name, :ssl_version
29
+
30
+ attr_writer :retries
31
+
32
+ def initialize(host, port, idle_timeout: nil, ca_cert: 'system', client_cert: nil, client_key: nil, verify_cert_name: true, ssl_version: :TLS1_2, max_retries: 1)
33
+ @host = host
34
+ @port = port
35
+ @idle_timeout = idle_timeout
36
+ @ca_cert = ca_cert
37
+ @client_cert = client_cert
38
+ @client_key = client_key
39
+ @verify_cert_name = verify_cert_name
40
+ @ssl_version = ssl_version
41
+ @retries = max_retries
42
+ connect
43
+ end
44
+
45
+ def connect
46
+ @socket = get_ssl_connection
47
+ begin
48
+ begin
49
+ @socket.connect_nonblock
50
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
51
+ select_with_timeout(@socket, :connect_read) && retry
52
+ rescue IO::WaitWritable
53
+ select_with_timeout(@socket, :connect_write) && retry
54
+ end
55
+ rescue Errno::ETIMEDOUT
56
+ raise 'Socket timeout during connect'
57
+ end
58
+ @last_write = Time.now if idle_timeout
59
+ end
60
+
61
+ def get_tcp_connection
62
+ tcp = nil
63
+
64
+ family = Socket::Constants::AF_UNSPEC
65
+ sock_type = Socket::Constants::SOCK_STREAM
66
+ addr_info = Socket.getaddrinfo(host, port, family, sock_type, nil, nil, false).first
67
+ _, port, _, address, family, sock_type = addr_info
68
+
69
+ begin
70
+ sock_addr = Socket.sockaddr_in(port, address)
71
+ tcp = Socket.new(family, sock_type, 0)
72
+ tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEADDR, true)
73
+ tcp.setsockopt(Socket::SOL_SOCKET, Socket::Constants::SO_REUSEPORT, true)
74
+ tcp.connect_nonblock(sock_addr)
75
+ rescue Errno::EINPROGRESS
76
+ select_with_timeout(tcp, :connect_write)
77
+ begin
78
+ tcp.connect_nonblock(sock_addr)
79
+ rescue Errno::EISCONN
80
+ # all good
81
+ rescue SystemCallError
82
+ tcp.close rescue nil
83
+ raise
84
+ end
85
+ rescue SystemCallError
86
+ tcp.close rescue nil
87
+ raise
88
+ end
89
+
90
+ tcp.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
91
+ tcp
92
+ end
93
+
94
+ def get_ssl_connection
95
+ tcp = get_tcp_connection
96
+
97
+ ctx = OpenSSL::SSL::SSLContext.new
98
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
99
+ ctx.min_version = ssl_version
100
+
101
+ ctx.verify_hostname = verify_cert_name != false
102
+
103
+ case ca_cert
104
+ when true, 'true', 'system'
105
+ # use system certs, same as openssl cli
106
+ ctx.cert_store = OpenSSL::X509::Store.new
107
+ ctx.cert_store.set_default_paths
108
+ when false, 'false'
109
+ ctx.verify_hostname = false
110
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
111
+ when %r{/$} # ends in /
112
+ ctx.ca_path = ca_cert
113
+ when String
114
+ ctx.ca_file = ca_cert
115
+ end
116
+
117
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(client_cert)) if client_cert
118
+ ctx.key = OpenSSL::PKey::read(File.read(client_key)) if client_key
119
+ socket = OpenSSL::SSL::SSLSocket.new(tcp, ctx)
120
+ socket.hostname = host
121
+ socket.sync_close = true
122
+ socket
123
+ end
124
+
125
+ # Allow to retry on failed writes
126
+ def write(s)
127
+ if idle_timeout
128
+ if (t=Time.now) > @last_write + idle_timeout
129
+ @socket.close rescue nil
130
+ connect
131
+ else
132
+ @last_write = t
133
+ end
134
+ end
135
+ begin
136
+ retry_id ||= 0
137
+ do_write(s)
138
+ rescue => e
139
+ if (retry_id += 1) < @retries
140
+ @socket.close rescue nil
141
+ connect
142
+ retry
143
+ else
144
+ raise e
145
+ end
146
+ end
147
+ end
148
+
149
+ def do_write(data)
150
+ data.force_encoding('BINARY') # so we can break in the middle of multi-byte characters
151
+ loop do
152
+ sent = 0
153
+ begin
154
+ sent = @socket.write_nonblock(data)
155
+ rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => e
156
+ if e.is_a?(OpenSSL::SSL::SSLError) && e.message !~ /write would block/
157
+ raise e
158
+ else
159
+ select_with_timeout(@socket, :write) && retry
160
+ end
161
+ end
162
+
163
+ break if sent >= data.size
164
+ data = data[sent, data.size]
165
+ end
166
+ end
167
+
168
+ def select_with_timeout(tcp, type)
169
+ case type
170
+ when :connect_read
171
+ args = [[tcp], nil, nil, CONNECT_TIMEOUT]
172
+ when :connect_write
173
+ args = [nil, [tcp], nil, 5]
174
+ # when :read
175
+ # args = [[tcp], nil, nil, READ_TIMEOUT]
176
+ when :write
177
+ args = [nil, [tcp], nil, WRITE_TIMEOUT]
178
+ else
179
+ raise "Unknown select type #{type}"
180
+ end
181
+ IO.select(*args) || raise("Socket timeout during #{type}")
182
+ end
183
+
184
+ # Forward any methods directly to SSLSocket
185
+ def method_missing(method_sym, *arguments, &block)
186
+ @socket.send(method_sym, *arguments, &block)
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,18 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016-2019 t.e.morgan.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ module SyslogTls
17
+ VERSION = '2.1.0'
18
+ end
@@ -0,0 +1,192 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016-2019 t.e.morgan.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'helper'
17
+ require 'ssl'
18
+ require 'date'
19
+ require 'minitest/mock'
20
+ require 'fluent/plugin/out_syslog_tls'
21
+ require 'fluent/test/driver/output'
22
+
23
+ class SyslogTlsOutputTest < Test::Unit::TestCase
24
+ include SSLTestHelper
25
+
26
+ def setup
27
+ Fluent::Test.setup
28
+ @driver = nil
29
+ end
30
+
31
+ def driver(conf='')
32
+ @driver ||= Fluent::Test::Driver::Output.new(Fluent::Plugin::SyslogTlsOutput).configure(conf)
33
+ end
34
+
35
+ def sample_record
36
+ {
37
+ "app_name" => "app",
38
+ "hostname" => "host",
39
+ "procid" => $$,
40
+ "msgid" => 1000,
41
+ "message" => "MESSAGE",
42
+ "severity" => "PANIC",
43
+ }
44
+ end
45
+
46
+ def mock_logger(token='TOKEN')
47
+ io = StringIO.new
48
+ io.set_encoding('utf-8')
49
+ ::SyslogTls::Logger.new(io, token)
50
+ end
51
+
52
+ def test_configure
53
+ config = %{
54
+ host syslog.collection.us1.sumologic.com
55
+ port 6514
56
+ client_cert
57
+ client_key
58
+ verify_cert_name true
59
+ token 1234567890
60
+ }
61
+ instance = driver(config).instance
62
+
63
+ assert_equal 'syslog.collection.us1.sumologic.com', instance.host
64
+ assert_equal '6514', instance.port
65
+ assert_equal '', instance.client_cert
66
+ assert_equal '', instance.client_key
67
+ assert_equal true, instance.verify_cert_name
68
+ assert_equal '1234567890', instance.token
69
+ end
70
+
71
+ def test_default_emit
72
+ config = %{
73
+ host syslog.collection.us1.sumologic.com
74
+ port 6514
75
+ client_cert
76
+ client_key
77
+ }
78
+ instance = driver(config).instance
79
+
80
+ time = Time.now
81
+ record = sample_record
82
+ logger = mock_logger(instance.token)
83
+
84
+ instance.stub(:new_logger, logger) do
85
+ instance.process('test', {time.to_i => record})
86
+ end
87
+
88
+ assert_equal "<134>1 #{time.to_datetime.rfc3339} - - - - - #{record.to_json.to_s}\n\n", logger.transport.string
89
+ end
90
+
91
+ def test_message_headers_mapping
92
+ config = %{
93
+ host syslog.collection.us1.sumologic.com
94
+ port 6514
95
+ client_cert
96
+ client_key
97
+ token 1234567890
98
+ hostname_key hostname
99
+ procid_key procid
100
+ app_name_key app_name
101
+ msgid_key msgid
102
+ }
103
+ instance = driver(config).instance
104
+
105
+ time = Time.now
106
+ record = sample_record
107
+ logger = mock_logger
108
+
109
+ instance.stub(:new_logger, logger) do
110
+ instance.process('test', {time.to_i => record})
111
+ end
112
+
113
+ assert_true logger.transport.string.start_with?("<134>1 #{time.to_datetime.rfc3339} host app #{$$} 1000 [TOKEN]")
114
+ end
115
+
116
+ def test_message_severity_mapping
117
+ config = %{
118
+ host syslog.collection.us1.sumologic.com
119
+ port 6514
120
+ client_cert
121
+ client_key
122
+ token 1234567890
123
+ severity_key severity
124
+ }
125
+ instance = driver(config).instance
126
+
127
+ time = Time.now
128
+ record = sample_record
129
+ logger = mock_logger
130
+
131
+ instance.stub(:new_logger, logger) do
132
+ instance.process('test', {time.to_i => record})
133
+ end
134
+
135
+ assert_true logger.transport.string.start_with?("<128>1")
136
+ end
137
+
138
+ def test_formatter
139
+ config = %{
140
+ host syslog.collection.us1.sumologic.com
141
+ port 6514
142
+ client_cert
143
+ client_key
144
+ token 1234567890
145
+ format out_file
146
+ localtime false
147
+ }
148
+ instance = driver(config).instance
149
+
150
+ time = Time.now
151
+ record = sample_record
152
+ logger = mock_logger
153
+
154
+ instance.stub(:new_logger, logger) do
155
+ instance.process('test', {time.to_i => record})
156
+ end
157
+
158
+ formatted_time = time.dup.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
159
+ assert_equal "<134>1 #{time.to_datetime.rfc3339} - - - - [TOKEN] #{formatted_time}\ttest\t#{record.to_json.to_s}\n\n", logger.transport.string
160
+ end
161
+
162
+ def test_ssl
163
+ time = Time.now
164
+ record = sample_record
165
+
166
+ server = ssl_server
167
+ st = Thread.new {
168
+ client = server.accept
169
+ assert_equal "<134>1 #{time.to_datetime.rfc3339} host app #{$$} 1000 [1234567890] #{record.to_json.to_s}\n", client.gets
170
+ client.close
171
+ }
172
+
173
+ config = %{
174
+ host localhost
175
+ port #{server.addr[1]}
176
+ client_cert
177
+ client_key
178
+ token 1234567890
179
+ hostname_key hostname
180
+ procid_key procid
181
+ app_name_key app_name
182
+ msgid_key msgid
183
+ }
184
+ instance = driver(config).instance
185
+
186
+ SyslogTls::SSLTransport.stub_any_instance(:get_ssl_connection, ssl_client) do
187
+ instance.process('test', {time.to_i => record})
188
+ end
189
+
190
+ st.join
191
+ end
192
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,25 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'simplecov'
16
+
17
+ SimpleCov.start
18
+
19
+ require 'test/unit'
20
+ require 'fluent/test'
21
+ require 'minitest/pride'
22
+ require 'minitest/stub_any_instance'
23
+
24
+ require 'webmock/test_unit'
25
+ WebMock.disable_net_connect!
data/test/ssl.rb ADDED
@@ -0,0 +1,53 @@
1
+ require 'socket'
2
+ require 'openssl'
3
+
4
+ module SSLTestHelper
5
+ def ssl_server(min_version: nil, max_version: nil)
6
+ @ssl_server ||= begin
7
+ tcp_server = TCPServer.new("localhost", 33000 + Random.rand(1000))
8
+ ssl_context = OpenSSL::SSL::SSLContext.new
9
+ ssl_context.cert = certificate
10
+ ssl_context.key = rsa_key
11
+ ssl_context.min_version = min_version if min_version
12
+ ssl_context.max_version = max_version if max_version
13
+ OpenSSL::SSL::SSLServer.new(tcp_server, ssl_context)
14
+ end
15
+ end
16
+
17
+ def ssl_client
18
+ tcp = TCPSocket.new("localhost", ssl_server.addr[1])
19
+ ctx = OpenSSL::SSL::SSLContext.new
20
+ ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_NONE)
21
+ ctx.cert = certificate
22
+ ctx.key = rsa_key
23
+ OpenSSL::SSL::SSLSocket.new(tcp, ctx)
24
+ end
25
+
26
+ def rsa_key
27
+ @rsa_key ||= OpenSSL::PKey::RSA.new(2048)
28
+ end
29
+
30
+ def certificate
31
+ @cert ||= begin
32
+ cert = OpenSSL::X509::Certificate.new
33
+ cert.subject = cert.issuer = OpenSSL::X509::Name.parse("/C=BE/O=Test/OU=Test/CN=Test")
34
+ cert.not_before = Time.now
35
+ cert.not_after = Time.now + 365 * 24 * 60 * 60
36
+ cert.public_key = rsa_key.public_key
37
+ cert.serial = 0x0
38
+ cert.version = 2
39
+
40
+ ef = OpenSSL::X509::ExtensionFactory.new
41
+ ef.subject_certificate = cert
42
+ ef.issuer_certificate = cert
43
+ cert.extensions = [
44
+ ef.create_extension("basicConstraints","CA:TRUE", true),
45
+ ef.create_extension("subjectKeyIdentifier", "hash"),
46
+ # ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
47
+ ]
48
+ cert.add_extension ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
49
+ cert.sign(rsa_key, OpenSSL::Digest::SHA1.new)
50
+ cert
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,48 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016 t.e.morgan.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'helper'
17
+ require 'date'
18
+ require 'syslog_tls/logger'
19
+
20
+ class LoggerTest < Test::Unit::TestCase
21
+ def test_logger_defaults
22
+ io = StringIO.new
23
+ l = SyslogTls::Logger.new(io, "TOKEN")
24
+ time = Time.now
25
+ l.log(:WARN, "MESSAGE", time: time)
26
+ assert_equal "<132>1 #{time.to_datetime.rfc3339} - - - - [TOKEN] MESSAGE\n", io.string
27
+ end
28
+
29
+ def test_logger_default_headers
30
+ io = StringIO.new
31
+ l = SyslogTls::Logger.new(io, "TOKEN")
32
+ l.hostname("hostname")
33
+ l.app_name("appname")
34
+ l.procid($$)
35
+ l.facility("SYSLOG")
36
+ time = Time.now
37
+ l.log(:WARN, "MESSAGE", time: time)
38
+ assert_equal "<44>1 #{time.to_datetime.rfc3339} hostname appname #{$$} - [TOKEN] MESSAGE\n", io.string
39
+ end
40
+
41
+ def test_logger_closed
42
+ io = StringIO.new
43
+ l = SyslogTls::Logger.new(io, "TOKEN")
44
+ assert_false l.closed?
45
+ l.close
46
+ assert_true l.closed?
47
+ end
48
+ end
@@ -0,0 +1,150 @@
1
+ # Copyright 2016 Acquia, Inc.
2
+ # Copyright 2016 t.e.morgan.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'helper'
17
+ require 'date'
18
+ require 'syslog_tls/protocol'
19
+
20
+ class ProtocolTest < Test::Unit::TestCase
21
+ def test_header_defaults
22
+ h = SyslogTls::Header.new
23
+
24
+ # Check defaults
25
+ assert_equal 'INFO', h.severity
26
+ assert_equal 'LOCAL0', h.facility
27
+ assert_equal 1, h.version
28
+ assert_equal SyslogTls::NIL_VALUE, h.hostname
29
+ assert_equal SyslogTls::NIL_VALUE, h.app_name
30
+ assert_equal SyslogTls::NIL_VALUE, h.procid
31
+ assert_equal SyslogTls::NIL_VALUE, h.msgid
32
+
33
+ assert_equal "<#{h.pri}>1 #{h.timestamp.to_datetime.rfc3339} - - - -", h.to_s
34
+ end
35
+
36
+ def test_header_facility_setter
37
+ h = SyslogTls::Header.new
38
+ assert_raise do
39
+ h.facility = "NON_EXISTING"
40
+ end
41
+ SyslogTls::Header::FACILITIES.each do |facility, _|
42
+ assert_nothing_raised do
43
+ h.facility = facility
44
+ end
45
+ end
46
+ end
47
+
48
+ def test_header_severity_setter
49
+ h = SyslogTls::Header.new
50
+ assert_raise do
51
+ h.severity = "NON_EXISTING"
52
+ end
53
+ SyslogTls::Header::SEVERITIES.each do |severity, _|
54
+ assert_nothing_raised do
55
+ h.severity = severity
56
+ end
57
+ end
58
+ end
59
+
60
+ def test_header_timestamp_setter
61
+ h = SyslogTls::Header.new
62
+ assert_raise do
63
+ h.timestamp = Time.now.to_i
64
+ end
65
+ assert_nothing_raised do
66
+ h.timestamp = Time.now
67
+ end
68
+ end
69
+
70
+ def test_header_hostname
71
+ h = SyslogTls::Header.new
72
+ h.hostname = "hostname"
73
+ assert_equal "<#{h.pri}>1 #{h.timestamp.to_datetime.rfc3339} hostname - - -", h.to_s
74
+ end
75
+
76
+ def test_header_appname
77
+ h = SyslogTls::Header.new
78
+ h.app_name = "appname"
79
+ assert_equal "<#{h.pri}>1 #{h.timestamp.to_datetime.rfc3339} - appname - -", h.to_s
80
+ end
81
+
82
+ def test_header_procid
83
+ h = SyslogTls::Header.new
84
+ h.procid = $$
85
+ assert_equal "<#{h.pri}>1 #{h.timestamp.to_datetime.rfc3339} - - #{$$} -", h.to_s
86
+ end
87
+
88
+ def test_header_msgid
89
+ h = SyslogTls::Header.new
90
+ h.msgid = "msgid"
91
+ assert_equal "<#{h.pri}>1 #{h.timestamp.to_datetime.rfc3339} - - - msgid", h.to_s
92
+ end
93
+
94
+ def test_structured_data_defaults
95
+ id = "hash@IANA-ID"
96
+ sd = SyslogTls::StructuredData.new(id)
97
+ assert_equal "[#{id}]", sd.to_s
98
+ end
99
+
100
+ def test_structured_data_key
101
+ id = "hash@IANA-ID"
102
+ sd = SyslogTls::StructuredData.new(id)
103
+ sd.data["key"] = "val"
104
+ assert_equal "[#{id} key=\"val\"]", sd.to_s
105
+ end
106
+
107
+ def test_structured_data_escaping
108
+ id = "hash@IANA-ID"
109
+ sd = SyslogTls::StructuredData.new(id)
110
+ sd.data["key"] = '\]"'
111
+ assert_equal "[#{id} key=\"\\\\\\]\\\"\"]", sd.to_s
112
+ end
113
+
114
+ def test_messsage_defaults
115
+ m = SyslogTls::Message.new
116
+ assert_not_nil m.header
117
+ assert_true m.structured_data.is_a? Array
118
+ assert_equal 0, m.structured_data.length
119
+ assert_equal "", m.msg
120
+
121
+ assert_equal "<134>1 #{m.header.timestamp.to_datetime.rfc3339} - - - - -\n", m.to_s
122
+ end
123
+
124
+ def test_message_msg
125
+ m = SyslogTls::Message.new
126
+ m.msg = "TEST"
127
+ assert_equal "<134>1 #{m.header.timestamp.to_datetime.rfc3339} - - - - - TEST\n", m.to_s
128
+ end
129
+
130
+ def test_message_sd
131
+ m = SyslogTls::Message.new
132
+ m.structured_data << SyslogTls::StructuredData.new("TEST_ID")
133
+ assert_equal "<134>1 #{m.header.timestamp.to_datetime.rfc3339} - - - - [TEST_ID]\n", m.to_s
134
+ end
135
+
136
+ def test_message_multiple_sd
137
+ m = SyslogTls::Message.new
138
+ m.structured_data << SyslogTls::StructuredData.new("TEST_ID")
139
+ m.structured_data << SyslogTls::StructuredData.new("TEST_ID2")
140
+ assert_equal "<134>1 #{m.header.timestamp.to_datetime.rfc3339} - - - - [TEST_ID][TEST_ID2]\n", m.to_s
141
+ end
142
+
143
+ def test_message_multiple_sd_msg
144
+ m = SyslogTls::Message.new
145
+ m.structured_data << SyslogTls::StructuredData.new("TEST_ID")
146
+ m.structured_data << SyslogTls::StructuredData.new("TEST_ID2")
147
+ m.msg = "MSG"
148
+ assert_equal "<134>1 #{m.header.timestamp.to_datetime.rfc3339} - - - - [TEST_ID][TEST_ID2] MSG\n", m.to_s
149
+ end
150
+ end