Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 690fa4ec93 | |||
| 02d907b440 | |||
| c02a6fc3ab | |||
| 05945892e3 | |||
| ffc5ae4c1c | |||
| c8d37043ef | |||
| f67fb957d7 |
2
LICENSE
2
LICENSE
@@ -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
|
||||
|
||||
92
README.md
92
README.md
@@ -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
136
notes/project.asciidoc
Normal 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.
|
||||
@@ -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"}]}
|
||||
]}.
|
||||
|
||||
@@ -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
617
src/rfc3164_lib.erl
Normal 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
79
src/rfc3164_lib.hrl
Normal 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
20
testing/struct_map.erl
Normal 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.
|
||||
20
testing/struct_proplists.erl
Normal file
20
testing/struct_proplists.erl
Normal 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
20
testing/struct_record.erl
Normal 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
20
testing/struct_tuple.erl
Normal 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.
|
||||
Reference in New Issue
Block a user