Compare commits

7 Commits

11 changed files with 1054 additions and 9 deletions

View File

@@ -1,4 +1,4 @@
Copyright (c) 2017, Mathieu Kerjouan <mk@steepath.eu>
Copyright (c) 2017, Mathieu Kerjouan <mk [at] steepath.eu>
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@@ -1,9 +1,91 @@
rfc3164
=====
# rfc3164
An OTP library
rfc3164 implementation in Erlang. Testing. Don't use it in production!
;)
Build
-----
This module was built with flexibility in mind. So, currently, this
one will return standard and well used erlang datastructure:
* proplist
* map
* record
## Build
$ rebar3 compile
## Usage
By default, this library use proplists (tuple + lists):
RawPacket = <<"<0>1999 Oct 10 11:12:13 myhostname process[123]: message test">>
rfc3164:decode(RawPacket).
% return:
% [{message,<<"message test">>},
% {processid,123},
% {tag,<<"process">>},
% {hostname,<<"myhostname">>},
% {second,13},
% {minute,12},
% {hour,11},
% {day,10},
% {month,10},
% {year,1999},
% {severity,emerg}]
But, you can also use maps:
RawPacket = <<"<0>1999 Oct 10 11:12:13 myhostname process[123]: message test">>
rfc3164:decode(RawPacket, [{export, as_map}]).
% return:
% #{day => 10,
% hostname => <<"myhostname">>,
% hour => 11,
% message => <<"message test">>,
% minute => 12,
% month => 10,
% processid => 123,
% second => 13,
% severity => emerg,
% tag => <<"process">>,
% year => 1999}
And record with `rfc3164` record defined in `rfc3164_lib.hrl`:
RawPacket = <<"<0>1999 Oct 10 11:12:13 myhostname process[123]: message test">>
rfc3164:decode(RawPacket, [{export, as_record}]).
% return:
% #rfc3164{priority = undefined, facility = kern,
% severity = emerg, year = 1999,
% month = 10, day = 10, hour = 11,
% minute = 12, second = 13,
% hostname = <<"myhostname">>,
% tag = <<"process">>, processid = 123,
% message = <<"message test">>}
## Todo list
* Support validation
* Rename interfaces
* Rewrite specifications
* Add more datastructure (record and AST)
* Benchmark
* Unit test
* Documentation
## References
* https://tools.ietf.org/html/rfc3164
* https://svnweb.freebsd.org/base/head/usr.sbin/syslogd/
* https://svnweb.freebsd.org/base/head/lib/libc/gen/syslog.c
* https://svnweb.freebsd.org/base/head/sys/sys/syslog.h
* http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.sbin/syslogd/
* http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/sys/syslog.h
* https://github.com/balabit/syslog-ng
* https://en.wikipedia.org/wiki/Syslog
* https://www.sans.org/reading-room/whitepapers/logging/ins-outs-system-logging-syslog-1168
*

136
notes/project.asciidoc Normal file
View File

@@ -0,0 +1,136 @@
= Project features
This page list all final user case. All these features are currently
not or partialy implemented.
== Coding rules
* Please, respect developpers! Don't make long lines, maximum 80
chars, best to 70 chars.
* Strict rfc3164 implementation, read, and reread this RFC and
current implementation in other language.
* Create specification and unit test
* Don't repeat yourself, create macro.
* Use emacs with emacs-mode
* Try to test with older and current implementation of rsyslog,
syslogng and GNU/BSD implementation
* testing branch is unstable and was created only for test, push in
it when you have done something
* Merge in master branch ONLY when:
** All functions has specification
** All functions has unit test (and pass test)
** All functions has updated documentation
** Benchmark your code based on unit test
== Encoding
* Respect strictly rfc3164 encoding
* Multiple way to encode syslog message
[erlang]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// as map
rfc3164:encode(#{}).
// as proplist
rfc3164:encode([]).
// as record
rfc3164:encode(#rfc3164{}).
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Define good value by default if not defined
[erlang]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Data = #{ priority => <<"13">> },
rfc3164:encode(Data).
// return:
// <<"<13>Jan 1 12:13:14 hostname tag[123]: empty message">>.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Allowing control on output
[erlang]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Data = #{ hostname => "test" },
Options = [{with, [message]}, {without, [year, tag]}]
rfc3164:encode(Data, Options).
// return:
// <<"<13>Jan 1 12:13:14 test: empty message">>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Allow multiple way to set value
[erlang]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Data = #{ facility => 0, severity => emerg, message => "alert!" },
Data2 = #{ facility => kern, severity => 0, message => "alert!" },
rfc3164:encode(Data).
rfc3164:encode(Data2).
// return same value:
// <<"<0>Jan 1 12:13:14 hostname tag[123]: alert!">>.
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
== Decoding
* Multiple way to decode message
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// as proplist
rfc3164:decode(Packet, [{export, as_list}]).
// as map
rfc3164:decode(Packet, [{export, as_map}]).
// as record
rfc3164:decode(Packet, [{export, as_record}).
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Extract only required values on raw packet
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Options = [{extract, [priority, tag]}
,{export, as_map}],
rfc3164:decode(Packet, Options).
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Allow strict and permissive decoding
## Validation
* Validate packet if its could be an rfc3164 implementation message,
returning error/warning.
[erlang]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
rfc3164:check(RawPacket).
// return:
// ok
// or:
// {warning, [{header, unicode}]
// or:
// {error, [priority]}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
## Scrub
* Scrub packets and create good one based on rfc3164.
* Correct header (unicode and more)
* Packet size check
## Portability
* some datastructure (maps) are not supported in all Erlang release,
disable it when Erlang is built for old release.

View File

@@ -9,7 +9,7 @@
{env,[]},
{modules, []},
{maintainers, ["Mathieu Kerjouan"]},
{maintainers, ["Mathieu Kerjouan <mk [at] steepath.eu>"]},
{licenses, ["BSD"]},
{links, [{"Github", "https://github.com/niamtokik/rfc5426"}]}
{links, [{"Github", "https://github.com/niamtokik/rfc3164"}]}
]}.

View File

@@ -1,2 +1,53 @@
%%%-------------------------------------------------------------------
%%% @author Mathieu Kerjouan
%%% @copyright (c) 2017, Mathieu Kerjouan <mk [at] steepath.eu>
%%% @doc
%%% rfc3164 implementation interface.
%%% @end
%%%-------------------------------------------------------------------
-module(rfc3164).
-export([]).
-export([encode/1, encode/2]).
-export([decode/1, decode/2]).
-include_lib("eunit/include/eunit.hrl").
-include("rfc3164_lib.hrl").
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-spec encode(list() | map())
-> bitstring().
encode(_) ->
ok.
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-spec encode(list() | map(), list())
-> bitstring().
encode(_,_) ->
ok.
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-spec decode(bitstring() | list())
-> list() | map().
decode(RawPacket) ->
decode(RawPacket, []).
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-spec decode(bitstring() | list(), list())
-> list() | map() |
{error, bad_args}.
decode(RawPacket, Options)
when is_bitstring(RawPacket) ->
rfc3164_lib:packet_check(RawPacket, Options);
decode(RawPacket, Options)
when is_list(RawPacket) ->
Packet = erlang:list_to_bitstring(RawPacket),
rfc3164_lib:packet_check(Packet, Options);
decode(_, _) ->
{error, bad_args}.

617
src/rfc3164_lib.erl Normal file
View File

@@ -0,0 +1,617 @@
%%%-------------------------------------------------------------------
%%% @author Mathieu Kerjouan
%%% @copyright (c) 2017, Mathieu Kerjouan <mk [at] steepath.eu>
%%% @doc
%%% rfc3164 library.
%%% @end
%%%-------------------------------------------------------------------
-module(rfc3164_lib).
-export([packet_check/1, packet_check/2]).
-include_lib("eunit/include/eunit.hrl").
-include("rfc3164_lib.hrl").
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-spec options() -> list().
options() ->
[struct].
%%--------------------------------------------------------------------
%% datastructure wrapper
%%
%% @doc
%% push function will push value based on exported data type.
%% This function can match multiple defined standard structure
%% as proplist, map and record (rfc3164 defined in
%% rfc3164_lib.hrl header file).
%% @end
%%--------------------------------------------------------------------
-spec push(push(), map()) -> map();
(push(), list()) -> list();
(push(), #rfc3164{}) -> #rfc3164{}.
push({Key, Value}, Prop) ->
push({Key, Value}, Prop, []).
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-spec push(push(), map(), list()) -> map();
(push(), list(), list()) -> list();
(push(), #rfc3164{}, list()) -> #rfc3164{}.
push({Key, Value}, Prop, Options)
when is_map(Prop) ->
maps:put(Key, Value, Prop);
push({Key, Value}, Prop, Options)
when is_list(Prop) ->
[{Key, Value}] ++ Prop;
?PUSH_RECORD(priority);
?PUSH_RECORD(facility);
?PUSH_RECORD(severity);
?PUSH_RECORD(year);
?PUSH_RECORD(month);
?PUSH_RECORD(day);
?PUSH_RECORD(hour);
?PUSH_RECORD(minute);
?PUSH_RECORD(second);
?PUSH_RECORD(hostname);
?PUSH_RECORD(tag);
?PUSH_RECORD(processid);
?PUSH_RECORD(message).
%%--------------------------------------------------------------------
%% @doc
%% pull retrieve value from standard used datastructure. We assume
%% all values was checked. If not, we can add some Options.
%% @end
%%--------------------------------------------------------------------
-spec pull(atom(), #rfc3164{}) -> bitstring();
(term(), list() | map()) -> bitstring().
pull(Key, Prop) ->
pull(Key, Prop, []).
pull_test() ->
?assertEqual( pull(priority, #rfc3164{ priority = <<"123">> })
, <<"123">>),
?assertEqual( pull(facility, push({facility, 0}, #{}))
, <<"0">>).
-spec pull(atom(), #rfc3164{}, list()) -> bitstring();
(term(), list() | map(), list()) -> bitstring().
pull(Key, Prop, Options)
when is_list(Prop) ->
proplists:get_value(Key, Prop, <<>>);
pull(Key, Prop, Options)
when is_map(Prop) ->
maps:get(Key, Prop);
?PULL_RECORD(priority) ->
Prop#rfc3164.priority;
?PULL_RECORD(facility) ->
Prop#rfc3164.facility;
?PULL_RECORD(severity) ->
Prop#rfc3164.severity;
?PULL_RECORD(year) ->
Prop#rfc3164.year;
?PULL_RECORD(month) ->
Prop#rfc3164.month;
?PULL_RECORD(day) ->
Prop#rfc3164.day;
?PULL_RECORD(hour) ->
Prop#rfc3164.hour;
?PULL_RECORD(minute) ->
Prop#rfc3164.minute;
?PULL_RECORD(second) ->
Prop#rfc3164.second;
?PULL_RECORD(hostname) ->
Prop#rfc3164.hostname;
?PULL_RECORD(tag) ->
Prop#rfc3164.tag;
?PULL_RECORD(processid) ->
Prop#rfc3164.processid;
?PULL_RECORD(message) ->
Prop#rfc3164.message;
pull(_, Prop, Options)
when is_record(Prop, rfc3164) ->
<<>>.
%%--------------------------------------------------------------------
%% rfc3164 packet check
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
-spec packet_check(raw_packet())
-> struct().
packet_check(RawPacket) ->
packet_check(RawPacket, []).
-spec packet_check(raw_packet(), list())
-> struct().
packet_check(RawPacket, Options) ->
case proplists:get_value(export, Options, list) of
as_map -> priority(RawPacket, #{}, Options);
as_record -> priority(RawPacket, #rfc3164{}, Options);
_ -> priority(RawPacket, [], Options)
end.
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
-spec priority(bitstring(), map() | list())
-> struct().
priority(RawPacket, PropList) ->
priority(RawPacket, PropList, []).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
-spec priority(bitstring(), map() | list(), list())
-> map() | list().
priority( <<"<", Priority:8/bitstring, ">", Rest/bitstring>>
, PropList
, Options) ->
priority_check(Rest, PropList, Priority, Options);
priority( <<"<0", Priority:8/bitstring, ">", Rest/bitstring>>
, PropList
, Options) ->
Ret = {priority, {deformed, <<"<0", Priority/bitstring>>}},
priority_check(Rest, push(Ret, PropList), Priority, Options);
priority( <<"<00", Priority:8/bitstring, ">", Rest/bitstring>>
, PropList
, Options) ->
Ret = {priority, {deformed, <<"<00", Priority/bitstring>>}},
priority_check(Rest, push(Ret, PropList), Priority, Options);
priority( <<"<000>", Rest/bitstring>>
, PropList
, Options) ->
Ret = {priority, {deformed, <<"<000>">>}},
priority_check(Rest, push(Ret, PropList), 0, Options);
priority( <<"<", Priority:16/bitstring, ">", Rest/bitstring>>
, PropList
, Options) ->
priority_check(Rest, PropList, Priority, Options);
priority( <<"<", Priority:24/bitstring, ">", Rest/bitstring>>
, PropList
, Options) ->
priority_check(Rest, PropList, Priority, Options);
priority( RawPacket
, PropList
, Options)
when is_bitstring(RawPacket) ->
year(RawPacket, push({priority, undefined}, PropList), Options);
% encode
priority( List
, PropList
, Options )
when is_list(List) ->
proplists:get_value(priority, List, <<>>).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
priority_check(RawPacket, PropList, Priority)
when is_bitstring(RawPacket) ->
priority_check(RawPacket, PropList, Priority, []).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
-spec priority_check(raw_packet(), struct(), bitstring(), list())
-> struct().
priority_check( RawPacket
, PropList
, Priority
, Options)
when is_bitstring(RawPacket) ->
case bitstring_to_integer_check(Priority, 0, 191) of
{ok, Integer} ->
facility_and_severity(RawPacket, PropList, Integer, Options);
{error, Reason} ->
year( RawPacket
, push({priority, Reason}, PropList)
, Options)
end.
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
-spec facility_and_severity(raw_packet(), struct(), bitstring(), list())
-> struct().
facility_and_severity(RawPacket, PropList, Priority, Options)
when is_bitstring(RawPacket) ->
Facility = Priority bsr 3,
Severity = Priority - (Facility*8),
ReturnF = push({facility, facility(Facility)}, PropList),
Ret = push({severity, severity(Severity)}, ReturnF),
year(RawPacket, Ret, Options);
%encode
facility_and_severity(List, PropList, Priority, Options)
when is_list(List) ->
% <13> if not defined
Facility = case proplists:get_value(facility, List, user) of
Int when is_integer(Int) -> Int;
At when is_atom(At) -> facility(At)
end,
Severity = case proplists:get_value(severity, List, syslog) of
Integer when is_integer(Integer) -> Integer;
Atom when is_atom(Atom) -> severity(Atom)
end,
Priority = erlang:integer_to_binary(Facility*8+Severity),
<<"<", Priority/bitstring,">">>.
%%--------------------------------------------------------------------
%% facility table, simply hardcoded here.
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
-spec facility(integer()) -> atom();
(atom()) -> integer().
?FACILITY( 0, kern);
?FACILITY( 1, user);
?FACILITY( 2, mail);
?FACILITY( 3, daemon);
?FACILITY( 4, auth);
?FACILITY( 5, syslog);
?FACILITY( 6, lpr);
?FACILITY( 7, news);
?FACILITY( 8, uucp);
?FACILITY( 9, cron);
?FACILITY(10, authpriv);
?FACILITY(11, ftp);
?FACILITY(12, ntp);
?FACILITY(13, security);
?FACILITY(14, console);
?FACILITY(15, reserved);
?FACILITY(16, local0);
?FACILITY(17, local1);
?FACILITY(18, local2);
?FACILITY(19, local3);
?FACILITY(20, local4);
?FACILITY(21, local5);
?FACILITY(22, local6);
?FACILITY(23, local7).
%%--------------------------------------------------------------------
%% severity table, simply hardcoded here.
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
-spec severity(integer()) -> atom();
(atom()) -> integer().
?SEVERITY(0, emerg);
?SEVERITY(1, alert);
?SEVERITY(2, crit);
?SEVERITY(3, err);
?SEVERITY(4, warning);
?SEVERITY(5, notice);
?SEVERITY(6, info);
?SEVERITY(7, debug).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
-spec year(raw_packet(), struct(), list())
-> struct().
year(<<>>, PropList, Options) ->
PropList;
year(<<Year:16/bitstring, " ", Rest/bitstring>>, PropList, Options) ->
case bitstring_to_integer_check(Year, 0, 99) of
{ok, Integer} ->
month(Rest, push({year, Integer}, PropList), Options);
{error, Reason} ->
month(Rest, push({year, Reason}, PropList), Options)
end;
year(<<Year:32/bitstring, " ", Rest/bitstring>>, PropList, Options) ->
case bitstring_to_integer_check(Year, 0, 9999) of
{ok, Integer} ->
month(Rest, push({year, Integer}, PropList), Options);
{error, Reason} ->
month(Rest, push({year, Reason}, PropList), Options)
end;
year(RawPacket, PropList, Options)
when is_bitstring(RawPacket) ->
month(RawPacket, PropList, Options).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
-spec month(raw_packet(), struct(), list())
-> struct().
?MONTH("Jan", 1);
?MONTH("Feb", 2);
?MONTH("Mar", 3);
?MONTH("Apr", 4);
?MONTH("May", 5);
?MONTH("Jun", 6);
?MONTH("Jul", 7);
?MONTH("Aug", 8);
?MONTH("Sep", 9);
?MONTH("Oct", 10);
?MONTH("Nov", 11);
?MONTH("Dec", 12);
month(RawPacket, PropList, Options)
when is_bitstring(RawPacket) ->
day(RawPacket, PropList, Options).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
day(<<Day:8, " ", Rest/bitstring>>, PropList, Options) ->
case bitstring_to_integer_check(<<Day>>, 1, 9) of
{ok, Integer} ->
ttime(Rest, push({day, Integer}, PropList), Options);
{error, Reason} ->
ttime(Rest, push({day, Reason}, PropList), Options)
end;
day(<<" ", Day:8, " ", Rest/bitstring>>, PropList, Options) ->
case bitstring_to_integer_check(<<Day>>, 1, 9) of
{ok, Integer} ->
ttime(Rest, push({day, Integer}, PropList), Options);
{error, Reason} ->
ttime(Rest, push({day, Reason}, PropList), Options)
end;
day(<<DayA:8, DayB:8, " ",Rest/bitstring>>, PropList, Options)
when (DayA >= $1 andalso DayB >= $0 andalso DayB =< $9) orelse
(DayA >= $2 andalso DayB >= $0 andalso DayB =< $9) orelse
(DayA >= $3 andalso DayB >= $0 andalso DayB =< $1) ->
case bitstring_to_integer_check(<<DayA, DayB>>, 10, 31) of
{ok, Integer} ->
ttime(Rest, push({day, Integer}, PropList), Options);
{error, Reason} ->
ttime(Rest, push({day, Reason}, PropList), Options)
end;
day(RawPacket, PropList, Options)
when is_bitstring(RawPacket) ->
ttime(RawPacket, PropList, Options).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
ttime(<<Hour:16/bitstring, ":",
Minute:16/bitstring, ":",
Second:16/bitstring, " ",
Rest/bitstring>>, PropList, Options) ->
hour(Rest, PropList, {Hour, Minute, Second}, Options);
ttime(<<Hour:16/bitstring, ":",
Minute:16/bitstring, ":",
Second:16/bitstring, ".",
MSecond:24/bitstring, " ",
Rest/bitstring>>, PropList, Options) ->
hour(Rest, PropList, {Hour, Minute, Second, MSecond}, Options);
ttime(<<Hour:16/bitstring, ":",
Minute:16/bitstring, ":",
Second:16/bitstring,
Rest/bitstring>>, PropList, Options) ->
hour(Rest, PropList, {Hour, Minute, Second}, Options);
ttime(RawPacket, PropList, Options)
when is_bitstring(RawPacket) ->
timezone(RawPacket, PropList, Options).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
hour(RawPacket, PropList, {Hour, Minute, Second}, Options)
when is_bitstring(RawPacket) ->
case bitstring_to_integer_check(Hour, 0, 23) of
{ok, Integer} ->
Ret = push({hour, Integer}, PropList),
minute(RawPacket, Ret, {Minute, Second}, Options);
{error, Reason} ->
Ret = push({hour, Reason}, PropList),
minute(RawPacket, Ret, {Minute, Second}, Options)
end.
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
minute(RawPacket, PropList, {Minute, Second}, Options)
when is_bitstring(RawPacket) ->
case bitstring_to_integer_check(Minute, 0, 59) of
{ok, Integer} ->
Ret = push({minute, Integer}, PropList),
second(RawPacket, Ret, {Second}, Options);
{error, Reason} ->
Ret = push({minute, Reason}, PropList),
second(RawPacket, Ret, {Second}, Options)
end.
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
second(RawPacket, PropList, {Second}, Options)
when is_bitstring(RawPacket) ->
case bitstring_to_integer_check(Second, 0, 59) of
{ok, Integer} ->
Ret = push({second, Integer}, PropList),
timezone(RawPacket, Ret, Options);
{error, Reason} ->
Ret = push({second, Reason},PropList),
timezone(RawPacket, Ret, Options)
end.
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
timezone(<<"TZ", Rest/bitstring>>, PropList, Options) ->
hostname(Rest, push({timezone, "TZ"}, PropList), Options);
timezone(RawPacket, PropList, Options)
when is_bitstring(RawPacket) ->
hostname(RawPacket, PropList, Options).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
hostname(RawPacket, PropList, Options)
when is_bitstring(RawPacket) ->
hostname(RawPacket, PropList, <<>>, Options).
hostname(<<>>, PropList,
Hostname, Options) ->
message(<<>>, push({hostname, Hostname}, PropList), Options);
hostname(<<":", Rest/bitstring>>, PropList,
Hostname, Options) ->
message(Rest, push({hostname, Hostname}, PropList), Options);
hostname(<<" ", Rest/bitstring>>, PropList,
Hostname, Options) ->
tag(Rest, push({hostname, Hostname}, PropList), Options);
hostname(<<Char:8, Rest/bitstring>>, PropList,
Hostname, Options)
when (Char >= $A andalso Char =< $Z) orelse
(Char >= $a andalso Char =< $z) orelse
Char =:= $. orelse Char =:= $- ->
hostname(Rest, PropList, <<Hostname/bitstring, Char>>, Options);
hostname(<<Char:8, Rest/bitstring>>, PropList,
Hostname, Options) ->
hostname(Rest, PropList, <<Hostname/bitstring, Char>>, not_valid, Options).
hostname(<<":", Rest/bitstring>>, PropList,
Hostname, not_valid, Options) ->
message(Rest, push({hostname, {not_valid, Hostname}}, PropList), Options);
hostname(<<" ", Rest/bitstring>>, PropList,
Hostname, not_valid, Options) ->
tag(Rest, push({hostname, {not_valid, Hostname}}, PropList), Options);
hostname(<<Char:8, Rest/bitstring>>, PropList,
Hostname, not_valid, Options)
when Char >= $A andalso Char =< $Z orelse
Char >= $a andalso Char =< $z orelse
Char =:= $. orelse Char =:= $- ->
hostname(Rest, PropList, <<Hostname/bitstring, Char>>, not_valid, Options).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
tag(RawPacket, PropList, Options)
when is_bitstring(RawPacket) ->
tag(RawPacket, PropList, <<>>, Options).
tag(<<>>, PropList, Tag, Options) ->
PropList;
tag(<<":", Rest/bitstring>>, PropList, Tag, Options) ->
message(Rest, push({tag, Tag}, PropList), Options);
tag(<<"[", Rest/bitstring>>, PropList, Tag, Options) ->
processid(Rest, push({tag, Tag}, PropList), Options);
tag(<<Char:8, Rest/bitstring>>, PropList, Tag, Options)
when (Char >= $0 andalso Char =<$9) orelse
(Char >= $A andalso Char =< $Z) orelse
(Char >= $a andalso Char =< $z) orelse
(Char =:= $.) orelse
(Char =:= $-) ->
tag(Rest, PropList, <<Tag/bitstring, Char>>, Options);
tag(RawPacket, PropList, Tag, Options)
when is_bitstring(RawPacket) ->
message(RawPacket, push({tag, bad_tag}, PropList), Options).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
processid(<<"]", Rest/bitstring>>, PropList, Options) ->
message(Rest, PropList, Options);
processid(RawPacket, PropList, Options)
when is_bitstring(RawPacket) ->
processid(RawPacket, PropList, <<>>, Options).
processid(<<"]: ", Rest/bitstring>>, PropList, ProcessID, Options) ->
try binary_to_integer(ProcessID) of
Integer when Integer >= 0 ->
message(Rest, push({processid, Integer}, PropList), Options);
Integer ->
message(Rest, push({processid, negative_integer}, PropList), Options)
catch
error:Reason ->
message(Rest, push({processid, not_integer}, PropList), Options)
end;
processid(<<Char:8, Rest/bitstring>>, PropList, ProcessID, Options)
when Char >= $0 andalso Char =< $9 ->
processid(Rest, PropList, <<ProcessID/bitstring, Char:8>>, Options);
processid(RawPacket, PropList, ProcessID, Options)
when is_bitstring(RawPacket) ->
message(RawPacket, push({processid, undefined}, PropList), Options).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
message(RawPacket, PropList, Options)
when is_bitstring(RawPacket) ->
push({message, RawPacket}, PropList).
%%--------------------------------------------------------------------
%%
%% @doc
%%
%% @end
%%--------------------------------------------------------------------
-spec bitstring_to_integer_check(bitstring(), integer(), integer())
-> {ok, integer()} |
{error, not_integer}.
bitstring_to_integer_check(Bitstring, Min, Max) ->
try erlang:binary_to_integer(Bitstring) of
Integer when Integer >= Min andalso
Integer =< Max ->
{ok, Integer};
_ -> {error, not_valid}
catch
error:Reason ->
{error, not_integer}
end.

79
src/rfc3164_lib.hrl Normal file
View File

@@ -0,0 +1,79 @@
%%%-------------------------------------------------------------------
%%% @author Mathieu Kerjouan
%%% @copyright (c) 2017, Mathieu Kerjouan <mk [at] steepath.eu>
%%% @doc
%%% rfc3164 headers.
%%% @end
%%%-------------------------------------------------------------------
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-type raw_packet() :: bitstring().
-type options() :: list().
-type struct() :: map() | list().
-type key() :: term().
-type value() :: term().
-type push() :: {key(), value()}.
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-record(rfc3164, { priority = undefined :: undefined | tuple(),
facility = undefined :: atom() | integer(),
severity = undefined :: atom() | integer(),
year = undefined :: undefined | integer(),
month = undefined :: undefined | integer(),
day = undefined :: undefined | integer(),
hour = undefined :: undefined | integer(),
minute = undefined :: undefined | integer(),
second = undefined :: undefined | integer(),
hostname = undefined :: undefined | bitstring(),
tag = undefined :: undefined | bitstring(),
processid = undefined :: undefined | integer(),
message = undefined :: undefined | bitstring()
}
).
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-define( PUSH_RECORD(ATOM),
push({ATOM, Value}, Prop, Options)
when is_record(Prop, rfc3164) ->
Prop#rfc3164{ATOM = Value}
).
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-define(PULL_RECORD(ATOM),
pull(ATOM, Prop, Options)
when is_record(Prop, rfc3164)
).
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-define( FACILITY(INTEGER, ATOM)
, facility(INTEGER) -> ATOM;
facility(ATOM) -> INTEGER
).
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-define( SEVERITY(INTEGER, ATOM)
, severity(INTEGER) -> ATOM;
severity(ATOM) -> INTEGER
).
%%--------------------------------------------------------------------
%%
%%--------------------------------------------------------------------
-define(MONTH(LITERAL, NUMBER),
month(<<LITERAL, " ", Rest/bitstring>>, PropList, Options) ->
day(Rest, push({month, NUMBER}, PropList), Options)
).

20
testing/struct_map.erl Normal file
View File

@@ -0,0 +1,20 @@
%%%-------------------------------------------------------------------
%%% @author Mathieu Kerjouan
%%% @copyright (c) 2017, Mathieu Kerjouan <mk [at] steepath.eu>
%%% @doc documentation about this module
%%% ...
%%% @end
%%%-------------------------------------------------------------------
-module(struct_map).
% -behaviour(gen_server).
% -behaviour(supervisor).
% -behaviour(gen_event).
% -behaviour(gen_fsm).
% -export([]).
% -compile([]).
% -compile([export_all]).
% init() -> ok.
% start() -> ok.
% stop() -> ok.

View File

@@ -0,0 +1,20 @@
%%%-------------------------------------------------------------------
%%% @author Mathieu Kerjouan
%%% @copyright (c) 2017, Mathieu Kerjouan <mk [at] steepath.eu>
%%% @doc documentation about this module
%%% ...
%%% @end
%%%-------------------------------------------------------------------
-module(struct_proplists).
% -behaviour(gen_server).
% -behaviour(supervisor).
% -behaviour(gen_event).
% -behaviour(gen_fsm).
% -export([]).
% -compile([]).
% -compile([export_all]).
% init() -> ok.
% start() -> ok.
% stop() -> ok.

20
testing/struct_record.erl Normal file
View File

@@ -0,0 +1,20 @@
%%%-------------------------------------------------------------------
%%% @author Mathieu Kerjouan
%%% @copyright (c) 2017, Mathieu Kerjouan <mk [at] steepath.eu>
%%% @doc documentation about this module
%%% ...
%%% @end
%%%-------------------------------------------------------------------
-module(struct_record).
% -behaviour(gen_server).
% -behaviour(supervisor).
% -behaviour(gen_event).
% -behaviour(gen_fsm).
% -export([]).
% -compile([]).
% -compile([export_all]).
% init() -> ok.
% start() -> ok.
% stop() -> ok.

20
testing/struct_tuple.erl Normal file
View File

@@ -0,0 +1,20 @@
%%%-------------------------------------------------------------------
%%% @author Mathieu Kerjouan
%%% @copyright (c) 2017, Mathieu Kerjouan <mk [at] steepath.eu>
%%% @doc documentation about this module
%%% ...
%%% @end
%%%-------------------------------------------------------------------
-module(struct_tuple).
% -behaviour(gen_server).
% -behaviour(supervisor).
% -behaviour(gen_event).
% -behaviour(gen_fsm).
% -export([]).
% -compile([]).
% -compile([export_all]).
% init() -> ok.
% start() -> ok.
% stop() -> ok.