--- Special thanks to:
--- Bernd Schoeller (bernd dot schoeller from inf dot ethz dot ch)
---
--- file: application.e
---
indexing
description : "Reads all standard input and tries to handle messages in each line."
class
APPLICATION
create
make
feature -- Initialization.
make is
-- Run application.
local
options_parsing_result: OPTIONS_PARSING_RESULT
do
options_parsing_result := (create {OPTION_PARSER}).parse
if options_parsing_result.is_ok then
options := options_parsing_result.options
read_lines
else
io.put_string ("Invalid command line arguments: " +
options_parsing_result.reason)
io.put_new_line
end
end
feature {NONE} -- Implementation.
options: OPTIONS
read_lines is
-- Read lines from standard input and parses each.
local
input: KL_STDIN_FILE
do
create input.make
from
input.read_line
until
input.end_of_file
loop
parse_line( input.last_string )
input.read_line
end
end
parse_line( line: STRING ) is
-- Parse and process one line.
require
line /= Void
local
line_parser: LINE_PARSER
analyzer: HEADERS_AND_BODY_ANALYZER
analyzing_result: ANALYZING_RESULT
do
create line_parser.make (line)
if line_parser.is_message_extracted then
create analyzer.make (line_parser.message)
analyzing_result := analyzer.analyze
if analyzing_result.is_good_message then
show_good_message_description (analyzing_result)
else
show_problem_description (line, analyzing_result)
end
end
end
show_good_message_description (analyzing_result: ANALYZING_RESULT) is
-- Shows description of good message if this is enabled.
do
if options.show_good_messages then
io.put_string ("OK: ")
io.put_string (analyzing_result.description)
io.put_new_line
end
end
show_problem_description (
line: STRING;
analyzing_result: ANALYZING_RESULT) is
-- Shows description of problematic message with
-- additional information.
require
line /= Void
analyzing_result /= Void
local
additional_info: ADDITIONAL_INFO_EXTRACTOR
do
create additional_info.make (line)
io.put_string ("ERROR: at [" + additional_info.timestamp +
"]; from: '" + additional_info.source_addr +
"'; to: '" + additional_info.dest_addr +
"'; " + analyzing_result.description)
io.put_new_line
end
end -- class APPLICATION
---
--- file: message_body.e
---
indexing
description: "Message body container"
class
MESSAGE_BODY
create
make
feature -- Initialization
make (raw_data: ARRAY [NATURAL_8]) is
-- Creates objects with the data specified.
do
data := raw_data.twin
end
feature -- Access
data: ARRAY [NATURAL_8]
-- Message body raw data.
infix "@" (zero_based_index: INTEGER): NATURAL_8 is
-- Returns Nth byte from body.
require
valid_index: zero_based_index >= 0 and zero_based_index < length
do
Result := data @ (data.lower + zero_based_index)
end
fragment (zero_based_start: INTEGER; size: INTEGER): ARRAY [NATURAL_8] is
-- Returns body fragment.
-- If size = 0 then returns empty array.
require
-- Note: in case when headers occupes all body index
-- can be equal to body length.
valid_start: zero_based_start >= 0 and zero_based_start <= length
valid_length: zero_based_start + size <= length
size >= 0
do
if 0 /= size then
Result := data.subarray (data.lower + zero_based_start,
data.lower + zero_based_start + size - 1)
else
create Result.make (1, 0)
end
ensure
Result /= Void
Result.count = size
end
length: INTEGER is
-- Message body length.
do
Result := data.count
end
end
---
--- file: analyzing_result.e
---
indexing
description: "Base class for results of message body analyzing"
deferred class
ANALYZING_RESULT
feature
is_good_message: BOOLEAN is
-- Returns false if there is some problem with the message.
deferred
end
description: STRING is
-- Returns description for analyzing result.
deferred
ensure
Result /= Void
end
end
---
--- file: bad_result.e
---
indexing
description: "Information about unsuccessful analyzing result."
class
BAD_RESULT
inherit
ANALYZING_RESULT
HEX_TEXT_REPRESENTATION
create
make
feature
make (failure_reason: STRING; msg_full_body: ARRAY [NATURAL_8]) is
-- Creates object with reason description and full
-- message body.
do
reason := failure_reason.twin
full_body := msg_full_body.twin
end
is_good_message: BOOLEAN is
-- Always returns false.
do
Result := false
end
description: STRING is
-- Returns reason and hex dump of message body.
do
Result := "What: " + reason + "; Full body: " +
binary_to_hex_text (full_body)
end
feature {NONE}
reason: STRING
full_body: ARRAY [NATURAL_8]
end
---
--- file: header_element.e
---
indexing
description: "GSM 03.40 Header Element"
class
HEADER_ELEMENT
create
make
feature
make (element_id: NATURAL_8; element_data: ARRAY [NATURAL_8]) is
-- Create header element with ID and DATA.
require
element_data /= Void
do
id := element_id
data := element_data.twin
end
id: NATURAL_8
data: ARRAY [NATURAL_8]
end
---
--- file: successful_result.e
---
indexing
description: "Information about successful checking result"
class
SUCCESSFUL_RESULT
inherit
ANALYZING_RESULT
HEX_TEXT_REPRESENTATION
create
make_empty, make_with_header_elements_and_message
feature -- Creation
make_empty is
-- Creates result without header elements and message.
do
create {LINKED_LIST [HEADER_ELEMENT]}header_elements.make
create message.make (1, 0)
end
make_with_header_elements_and_message (
msg_header_elements: LIST [HEADER_ELEMENT];
message_body: ARRAY [NATURAL_8]) is
-- Creates result with header elements and message body.
require
msg_header_elements /= Void
message_body /= Void
do
header_elements := msg_header_elements
message := message_body
end
feature -- Queries
is_good_message: BOOLEAN is
-- Always returns true.
do
Result := true
end
description: STRING is
-- Returns text representation of header elements and body.
do
create Result.make_empty
from
header_elements.start
until
header_elements.after
loop
Result.append ("Element: Id: " +
(header_elements.item).id.to_hex_string +
", Data: " +
binary_to_hex_text ((header_elements.item).data) +
"; ")
header_elements.forth
end
Result.append ("Message: " + binary_to_hex_text (message))
end
feature {NONE} -- Implementation
header_elements: LIST [HEADER_ELEMENT]
message: ARRAY [NATURAL_8]
end
---
--- file: headers_and_body_analyzer.e
---
indexing
description: "Analyzer of message headers and body"
class
HEADERS_AND_BODY_ANALYZER
create
make
feature -- Initialization
make (message_body: MESSAGE_BODY) is
-- Initializes analyzer.
do
body := message_body
end
feature -- Main functionality
analyze: ANALYZING_RESULT is
-- Analyzes binary image for headers and body errors.
do
if 0 = body.length then
-- Empty body is correct.
create {SUCCESSFUL_RESULT}Result.make_empty
else
Result := check_header_and_body
end
ensure
Result /= Void
end
feature {NONE} -- Implementation
body: MESSAGE_BODY
check_header_and_body: ANALYZING_RESULT is
-- Checks headers and body on non-empty message.
require
non_empty_message: body.length > 0
local
header_length: INTEGER
do
header_length := (body @ 0).to_integer_32
if 0 = header_length then
create {BAD_RESULT}Result.make ("Zero header length!", body.data)
elseif header_length >= body.length then
Result := result_header_length_too_big (header_length)
else
Result := check_header_elements_and_body (header_length)
end
end
check_header_elements_and_body (
header_length: INTEGER): ANALYZING_RESULT is
-- Checks header elements and body.
require
body.length > 0
header_length > 0
body.length > header_length
local
elements: LINKED_LIST [HEADER_ELEMENT]
i: INTEGER
element_id, element_size: NATURAL_8
bad_element: BOOLEAN
next_element: HEADER_ELEMENT
do
create elements.make
from
i := 0
bad_element := false
variant
header_length - i
until
i + 2 >= header_length or bad_element
loop
element_id := body @ (1 + i); i := i + 1
element_size := body @ (1 + i); i := i + 1
if element_size < header_length + 1 - i then
create next_element.make (
element_id,
body.fragment (1 + i, element_size))
check next_element.data.count = element_size end
elements.put_right (next_element)
elements.forth
i := i + element_size
else
bad_element := true
Result := result_element_size_too_big (
1 + i,
header_length,
element_size)
end
end
if not bad_element then
Result := check_message_body_and_make_final_result (
header_length,
elements)
end
end
check_message_body_and_make_final_result (
header_length: INTEGER;
elements: LIST [HEADER_ELEMENT]): ANALYZING_RESULT is
-- Check correcteness of message and make the final analyzing result.
local
message: ARRAY [NATURAL_8]
i: INTEGER
bad_byte: BOOLEAN
do
message := body.fragment(
header_length + 1,
body.length - header_length - 1)
from
i := message.lower
bad_byte := false
variant
message.upper + 1 - i
until
i > message.upper or bad_byte
loop
if 0 /= (message @ i).bit_and (0x80) then
bad_byte := true
create {BAD_RESULT}Result.make (
"Byte with non-zero highest bit in message! Byte: " +
(message @ i).to_hex_string,
body.data)
end
i := i + 1
end
if not bad_byte then
create {SUCCESSFUL_RESULT}Result.make_with_header_elements_and_message (
elements, message)
end
end
result_header_length_too_big (
header_length: INTEGER): BAD_RESULT is
-- Creates BAD_RESULT object with appropriate message.
do
create Result.make (
"Header length too big! Header length: " +
formatter.formatted (header_length) +
", Body length: " +
formatter.formatted (body.length),
body.data)
end
result_element_size_too_big (
position: INTEGER;
header_length: INTEGER;
element_size: INTEGER): BAD_RESULT is
-- Creates BAD_RESULT object with appropriate message.
do
create Result.make (
"Invalid element size! Position: " +
formatter.formatted (position) +
", HeaderLength: " +
formatter.formatted (header_length) +
", ElementSize: " +
formatter.formatted (element_size),
body.data)
end
formatter: FORMAT_INTEGER is
-- Returns formatter for INTEGER objects.
once
create Result.make (1)
end
end
---
--- file: line_parser.e
---
indexing
description: "Input line parser"
class
LINE_PARSER
create
make
feature -- Initialization
make (line: STRING) is
-- Tries to parse line and extract message body.
local
body_image: ARRAY [NATURAL_8]
do
if line.count > 2 then
body_image := extract_short_message_body (line)
if body_image /= Void then
create extracted_message.make (body_image)
end
end
end
feature -- Main functionality
is_message_extracted: BOOLEAN is
-- Returns true if line could be parsed.
do
Result := extracted_message /= Void
end
message: MESSAGE_BODY is
-- Accessor for extracted message body.
require
is_message_extracted
do
Result := extracted_message
ensure
Result /= Void
end
feature {NONE} -- Implementation
extracted_message: MESSAGE_BODY
-- Body of extracted message.
-- Equal to Void if message doesn't extracted.
extract_short_message_body (line: STRING): ARRAY [NATURAL_8] is
-- Try to extract message body from the current line.
require
line /= Void
do
short_message_body_regex.match (line)
if short_message_body_regex.has_matched then
Result := string_image_to_binary (
short_message_body_regex.captured_substring (1))
else
tlv_message_payload_regex.match (line)
if tlv_message_payload_regex.has_matched then
Result := hex_image_to_binary (
tlv_message_payload_regex.captured_substring (1))
end
end
end
short_message_body_regex: RX_PCRE_REGULAR_EXPRESSION is
-- Returns regular expression for matching short_message body.
once
create Result.make
Result.compile ("\{short_message %"(.+)%" }")
check
Result.is_compiled
end
ensure
Result.is_compiled
end
tlv_message_payload_regex: RX_PCRE_REGULAR_EXPRESSION is
-- Returns regular expression for matching 'message payload TLV'.
once
create Result.make
Result.compile ("\{tlv \{t 0x424} \{l 0x[0-9A-Fa-f]+} \{v ([^}]+)}")
ensure
Result.is_compiled
end
string_image_to_binary (string_image: STRING): ARRAY [NATURAL_8] is
-- Converts string image representation to binary image.
require
string_image /= Void
local
string_without_escapes: STRING
i: INTEGER
do
string_without_escapes := unescape_string_content (
unescape_string_content (string_image))
create Result.make (1, string_without_escapes.count)
from
i := 1
variant
string_without_escapes.count + 1 - i
until
i > string_without_escapes.count
loop
Result.put (string_without_escapes.item_code (i).to_natural_8, i)
i := i + 1
end
ensure
Result /= Void
end
hex_image_to_binary (string_image: STRING): ARRAY [NATURAL_8] is
-- Converts image in the form 'xx xx xx xx...' to binary.
require
string_image /= Void
local
hex_images: LIST [STRING]
i: INTEGER
do
hex_images := string_image.split (' ')
create Result.make (1, hex_images.count)
from
hex_images.start
i := 1
until
hex_images.after
loop
Result.put (byte_from_hex (hex_images.item), i)
i := i + 1
hex_images.forth
end
ensure
Result /= Void
end
unescape_string_content (string_image: STRING): STRING is
-- Part of conversion implementation process.
-- Changes escape sequence into characters.
local
ch: CHARACTER_8
i: INTEGER
do
create Result.make_empty
from
i := 1
variant
string_image.count + 1 - i
until
i > string_image.count
loop
ch := string_image @ i
if '\' = ch then
i := i + 1
ch := string_image @ i
inspect ch
when 'n' then ch := '%N'
when 'r' then ch := '%R'
when 't' then ch := '%T'
when 'x' then
ch := byte_from_hex (string_image.substring (i + 1, i + 2)).to_character_8
i := i + 2
else
-- Remain character unchanged.
end
end
Result.append_character( ch )
i := i + 1
end
ensure
Result /= Void
end
byte_from_hex (hex: STRING_8): NATURAL_8 is
-- Convert 'xx' string to int8.
require
hex.count = 2 or hex.count = 1
local
i: INTEGER
do
Result := 0
from
i := 1
variant
hex.count + 1 - i
until
i > hex.count
loop
Result := (Result * 16 + hexadecimal_characters.index_of ((hex @ i).upper, 1) - 1).to_natural_8
i := i + 1
end
end
hexadecimal_characters: STRING_8 is "0123456789ABCDEF"
end
---
--- file: additional_info_extractor.e
---
indexing
description: "Helper object for extraction timestamp, source and dest addresses"
class
ADDITIONAL_INFO_EXTRACTOR
create
make
feature -- Initialization
make (line: STRING) is
-- Initializes objects by parsing the line specified.
require
line /= Void
do
additional_info_regex.match (line)
if additional_info_regex.has_matched then
timestamp := additional_info_regex.captured_substring (1)
source_addr := additional_info_regex.captured_substring (2)
dest_addr := additional_info_regex.captured_substring (3)
else
(create {EXCEPTIONS}).raise ("Unable to parse line for additional info")
end
end
timestamp: STRING
source_addr: STRING
dest_addr: STRING
feature {NONE} -- Implementation
additional_info_regex: RX_PCRE_REGULAR_EXPRESSION is
-- Returns regex for additional info extraction.
once
create Result.make
Result.compile(
"^LOG \[([^]]+)\].*\{source_addr %"([^%"]+)%" }.*\{dest_addr %"([^%"]+)%" }" )
ensure
Result /= Void
Result.is_compiled
end
end
---
--- file: hex_text_representation.e
---
indexing
description: "Impementation of binary data to text transformation."
class
HEX_TEXT_REPRESENTATION
feature
binary_to_hex_text (bin_image: ARRAY [NATURAL_8] ): STRING is
-- Converts binary data to string in format 'xx xx xx...'.
require
bin_image /= Void
local
i: INTEGER
do
create Result.make (bin_image.upper * 3)
from
i := bin_image.lower
variant
bin_image.upper + 1 - i
until
i > bin_image.upper
loop
if i > bin_image.lower then
Result.append (" ")
end
Result.append ((bin_image @ i).to_hex_string)
i := i + 1
end
end
end
---
--- file: options.e
---
indexing
description: "Program options container"
class
OPTIONS
feature -- Access
show_good_messages: BOOLEAN
set_show_good_messages( value: BOOLEAN ) is
do
show_good_messages := value
end
end
---
--- file: options_parsing_result.e
---
indexing
description: "Command line options parsing result"
class
OPTIONS_PARSING_RESULT
create
make_ok,
make_failed
feature -- Initialization
make_ok (parsed_options: OPTIONS) is
-- Creation when parsing successful.
do
is_ok := true
options := parsed_options
end
make_failed( failure_reason: STRING ) is
-- Creation when parsing failed.
require
failure_reason /= Void
do
is_ok := false
reason := failure_reason.twin
end
feature -- Access
is_ok: BOOLEAN
options: OPTIONS
reason: STRING
invariant
options_iff_ok: is_ok implies (options /= Void)
reason_iff_failure: (not is_ok) implies ((reason /= Void) and (options = Void))
end
---
--- file: option_parser.e
---
indexing
description: "Command line option parser"
class
OPTION_PARSER
inherit
ARGUMENTS
feature -- Commands
parse: OPTIONS_PARSING_RESULT is
-- Parse command line options.
local
options: OPTIONS
reason: STRING
i: INTEGER
show_good_messages: BOOLEAN
do
from
i := 1
until
(i > argument_count) or (reason /= Void)
loop
if ("--show-good").is_equal (argument (i)) then
show_good_messages := true
else
reason := "Unsupported argument: " + argument (i)
end
i := i + 1
end
if reason /= Void then
create Result.make_failed (reason)
else
create options
options.set_show_good_messages (show_good_messages)
create Result.make_ok (options)
end
ensure
Result /= Void
end
end
-- vim:ts=4:sts=4:sw=4: