Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ pip install marine
`output` is a dict between field name to its parsed value.
If the packet didn't pass the filter or fields weren't passed to the function, it will be `None`.


## Running Tests

## Contributing
### Guidelines
Syntax formatting is done using [Black](https://github.com/psf/black)

### Running Tests
The tests are written using pytest. To run the tests, you need to provide the library file (`libmarine.so`),
and place it next to the file `tests/fixtures/marine/marine_fixtures.py`.

Expand Down
67 changes: 47 additions & 20 deletions marine/marine.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


class MarineResult(Structure):
_fields_ = [('output', c_char_p), ('result', c_int)]
_fields_ = [("output", c_char_p), ("result", c_int)]


MARINE_RESULT_POINTER = POINTER(MarineResult)
Expand All @@ -15,52 +15,73 @@ class MarineResult(Structure):
class Marine:
def __init__(self, lib_path):
if not os.path.exists(lib_path):
raise ValueError(f'Marine could not be located at {lib_path}')
raise ValueError(f"Marine could not be located at {lib_path}")

try:
cdll.LoadLibrary(lib_path)
except Exception:
raise OSError('Could not load Marine')
raise OSError("Could not load Marine")

self._filters_cache = dict()
self._marine = CDLL(lib_path)
self._marine.marine_dissect_packet.restype = MARINE_RESULT_POINTER
self._marine.marine_free.argtypes = [MARINE_RESULT_POINTER]
return_code = self._marine.init_marine()
if return_code < 0:
raise RuntimeError('Could not initialize Marine')

def filter_and_parse(self, packet: bytes, bpf: Optional[str] = None, display_filter: Optional[str] = None,
fields: Optional[list] = None) -> (bool, Dict[str, str]):
raise RuntimeError("Could not initialize Marine")

def filter_and_parse(
self,
packet: bytes,
bpf: Optional[str] = None,
display_filter: Optional[str] = None,
fields: Optional[list] = None,
) -> (bool, Dict[str, str]):
if bpf is None and display_filter is None and fields is None:
raise ValueError('At least one form of dissection must be passed to the function')
raise ValueError(
"At least one form of dissection must be passed to the function"
)

if isinstance(bpf, str):
bpf = bpf.encode('utf-8')
bpf = bpf.encode("utf-8")
if isinstance(display_filter, str):
display_filter = display_filter.encode('utf-8')
display_filter = display_filter.encode("utf-8")

if fields is not None:
encoded_fields = [f.encode('utf-8') if isinstance(f, str) else f for f in fields]
encoded_fields = [
f.encode("utf-8") if isinstance(f, str) else f for f in fields
]
else:
encoded_fields = None

filter_key = (bpf, display_filter, tuple(encoded_fields) if fields is not None else None)
filter_key = (
bpf,
display_filter,
tuple(encoded_fields) if fields is not None else None,
)
if filter_key in self._filters_cache:
filter_id = self._filters_cache[filter_key]
else:
filter_id, err = self._add_or_get_filter(bpf, display_filter, encoded_fields)
filter_id, err = self._add_or_get_filter(
bpf, display_filter, encoded_fields
)
if filter_id < 0:
raise ValueError(err) # TODO: create custom exception for every error type
raise ValueError(
err
) # TODO: create custom exception for every error type
self._filters_cache[filter_key] = filter_id

packet_data = self._prepare_packet_data(packet)
marine_result = self._marine.marine_dissect_packet(filter_id, packet_data, len(packet_data))
marine_result = self._marine.marine_dissect_packet(
filter_id, packet_data, len(packet_data)
)
success, result = False, None
if marine_result.contents.result == 1:
success = True
if fields is not None:
parsed_output = self._parse_output(marine_result.contents.output.decode('utf-8'))
parsed_output = self._parse_output(
marine_result.contents.output.decode("utf-8")
)
result = dict(zip(fields, parsed_output))
self._marine.marine_free(marine_result)
return success, result
Expand All @@ -69,23 +90,29 @@ def filter_and_parse(self, packet: bytes, bpf: Optional[str] = None, display_fil
def _parse_output(output: str) -> List[str]:
# TODO: this is a bottleneck. Find a better way to provide output from the c code
f = StringIO(output)
csv_parsed_output = next(csv.reader(f, delimiter='\t', quotechar='"'))
csv_parsed_output = next(csv.reader(f, delimiter="\t", quotechar='"'))
return csv_parsed_output

@staticmethod
def _prepare_packet_data(packet: bytes):
return (c_ubyte * len(packet)).from_buffer_copy(packet)

def _add_or_get_filter(self, bpf: Optional[bytes] = None, display_filter: Optional[bytes] = None,
fields: Optional[List[bytes]] = None) -> (int, bytes):
def _add_or_get_filter(
self,
bpf: Optional[bytes] = None,
display_filter: Optional[bytes] = None,
fields: Optional[List[bytes]] = None,
) -> (int, bytes):
if fields is not None:
fields_len = len(fields)
fields_c_arr = (c_char_p * fields_len)(*fields)
else:
fields_len = 0
fields_c_arr = None
err_msg = (c_char * 512)()
filter_id = self._marine.marine_add_filter(bpf, display_filter, fields_c_arr, fields_len, err_msg)
filter_id = self._marine.marine_add_filter(
bpf, display_filter, fields_c_arr, fields_len, err_msg
)
return filter_id, err_msg.value

def __del__(self):
Expand Down
16 changes: 9 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from setuptools import setup

setup(name='marine',
version='0.1',
description='Python client for Marine',
url='https://github.com/tomlegkov/marine-python',
author='Tom Legkov',
author_email='tom.legkov@outlook.com',
packages=['marine'])
setup(
name="marine",
version="0.1",
description="Python client for Marine",
url="https://github.com/tomlegkov/marine-python",
author="Tom Legkov",
author_email="tom.legkov@outlook.com",
packages=["marine"],
)