Skip to content

Commit

Permalink
docs: Add public API and remove autogenerated docs
Browse files Browse the repository at this point in the history
  • Loading branch information
rumpelsepp committed Oct 25, 2022
1 parent 39b482e commit 981f73a
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 10 deletions.
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ fmt:

.PHONY: docs
docs:
$(MAKE) -C docs api-stubs
$(MAKE) -C docs html

.PHONY: test
Expand Down
1 change: 0 additions & 1 deletion docs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@
# SPDX-License-Identifier: CC0-1.0

/_build
/api
6 changes: 0 additions & 6 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ help:

.PHONY: help Makefile

.PHONY: api-stubs
api-stubs:
sphinx-apidoc -o api ../src
sed -i "1 s|.*|Modules|" api/modules.rst
sed -i "2 s|.*|=======|" api/modules.rst

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
Expand Down
11 changes: 11 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Public API

## gallia.transports.base

All available transports are documented in {doc}`../transports`).

```{eval-rst}
.. automodule:: gallia.transports.base
:members:
:show-inheritance:
```
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"myst_parser",
]

myst_heading_anchors = 2
myst_enable_extensions = ["deflist"]

# Add any paths that contain templates here, relative to this directory.
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Several concepts and ideas are implemented in `gallia` in order to provide compr
:maxdepth: 1
:caption: API
api/modules
api
```

`gallia` is designed as a pentesting framework where each test produces a lot of data.
Expand Down
2 changes: 2 additions & 0 deletions docs/transports.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ The relevant transport protocol is specified in the scheme.

### isotp

ISO-TP (ISO 15765-2) as provided by the Linux [socket API](https://www.kernel.org/doc/html/latest/networking/can.html).

The can interface is specified as a host, e.g. `can0`.
The following parameters are available (these are ISOTP transport settings):

Expand Down
67 changes: 66 additions & 1 deletion src/gallia/transports/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@


class TargetURI:
"""TargetURI represents a target to which gallia can connect.
The target string must conform to a URI is specified by RFC3986.
Basically, this is a wrapper around Python's `urlparse()` and
`parse_qs()` methods. TargetURI provides frequently used properties
for a more userfriendly usage. Instances are meant to be passed to
the `connect()` method of transports.
"""
def __init__(self, raw: str) -> None:
self.raw = raw
self.url = urlparse(raw)
Expand All @@ -28,32 +36,47 @@ def from_parts(
port: int | None,
args: dict[str, Any],
) -> TargetURI:
"""Constructs a instance of TargetURI with the given arguments.
The `args` dict is used for the query string.
"""
netloc = host if port is None else join_host_port(host, port)
return TargetURI(urlunparse((scheme, netloc, "", "", urlencode(args), "")))

@property
def scheme(self) -> str:
"""The URI scheme"""
return self.url.scheme

@property
def hostname(self) -> str | None:
"""The hostname (without port)"""
return self.url.hostname

@property
def port(self) -> int | None:
"""The port number"""
return self.url.port

@property
def netloc(self) -> str:
"""The hostname and the portnumber, separated by a colon."""
return self.url.netloc

@property
def location(self) -> str:
assert self.scheme != "", "url scheme is empty"
"""A URI string which only consists of the relevant scheme,
the host and the port.
"""
return f"{self.scheme}://{self.url.netloc}"

@property
def qs_flat(self) -> dict[str, str]:
"""A dict which contains the query string's key/value pairs.
In case a key appears multiple times, this variant only
contains the first found key/value pair. In contrast to
`self.qs`, this variant avoids lists and might be easier
to use for some cases.
"""
d = {}
for k, v in self.qs.items():
d[k] = v[0]
Expand All @@ -68,7 +91,26 @@ def __str__(self) -> str:


class BaseTransport(ABC):
"""BaseTransport is the base class providing the required
interface for all transports used by gallia.
A transport usually is some kind of network protocol which
carries an application level protocol. A good example is
DoIP carrying UDS requests which acts as a minimal middleware
on top of TCP.
This class is to be used as a subclass with all abstractmethods
implemented and the SCHEME property filled.
A few methods provide a `tags` argument. The debug logs of these
calls include these tags in the `tags` property of the relevant
`gallia.log.PenlogRecord`.
"""

#: The scheme for the implemented protocol, e.g. "doip".
SCHEME: str = ""
#: The buffersize of the transport. Might be used in read() calls.
#: Defaults to `io.DEFAULT_BUFFER_SIZE`.
BUFSIZE: int = io.DEFAULT_BUFFER_SIZE

def __init__(self, target: TargetURI) -> None:
Expand All @@ -90,6 +132,7 @@ def __init_subclass__(

@classmethod
def check_scheme(cls, target: TargetURI) -> None:
"""Checks if the provided URI has the correct scheme."""
if target.scheme != cls.SCHEME:
raise ValueError(f"invalid scheme: {target.scheme}; expected: {cls.SCHEME}")

Expand All @@ -100,13 +143,22 @@ async def connect(
target: str | TargetURI,
timeout: float | None = None,
) -> TransportT:
"""Classmethod to connect the transport to a relevant target.
The target argument is a URI, such as `doip://192.0.2.2:13400?src_addr=0xf4&dst_addr=0x1d"`
An instance of the relevant transport class is returned.
"""
...

@abstractmethod
async def close(self) -> None:
"""Terminates the connection and clean up all allocated ressources."""
...

async def reconnect(self: TransportT, timeout: float | None = None) -> TransportT:
"""Closes the connection to the target and reconnects. A new
instance of this class is returned rendering the old one
obsolete. This method is safe for concurrent use.
"""
async with self.mutex:
await self.close()
return await self.connect(self.target)
Expand All @@ -117,6 +169,10 @@ async def read(
timeout: float | None = None,
tags: list[str] | None = None,
) -> bytes:
"""Reads one message and returns its raw byte representation.
An example for one message is 'one line, terminated by \\n' for
a TCP transport yielding lines.
"""
...

@abstractmethod
Expand All @@ -126,6 +182,7 @@ async def write(
timeout: float | None = None,
tags: list[str] | None = None,
) -> int:
"""Writes one message and return the number of written bytes."""
...

async def request(
Expand All @@ -134,6 +191,10 @@ async def request(
timeout: float | None = None,
tags: list[str] | None = None,
) -> bytes:
"""Chains a `self.write()` call with a `self.read()` call.
The call is protected by a mutex and is thus safe for concurrent
use.
"""
async with self.mutex:
return await self.request_unsafe(data, timeout, tags)

Expand All @@ -143,5 +204,9 @@ async def request_unsafe(
timeout: float | None = None,
tags: list[str] | None = None,
) -> bytes:
"""Chains a `self.write()` call with a `self.read()` call.
The call is **not** protected by a mutex. Only use this method
when you know what you are doing.
"""
await self.write(data, timeout, tags)
return await self.read(timeout, tags)

0 comments on commit 981f73a

Please sign in to comment.