easyprotocol 0.0.3
A library for quick protocol prototyping and parsing.
License: MIT
Requires Python: >=3.8,<4.0

# EasyProtocol

## Introduction

A library for quick prototyping protocol parsing in python. Not the fastest, not the most efficient, not the coolest, but hopefully the easiest to modify and prototype with.

## Quick Start

### Installing

For now you have to install from source, this will be on pypi soon.

- Current Method

```bash
git clone https://github.com/joeferg425/EasyProtocol.git .
cd EasyProtocol
python -m pip install .
```

- Future, better method

```bash
python -m pip install easyprotocol
```

### Example 1 - Making a Parser From a List of Fields

- Demo Code

    Lets parse something like the following.

    | Name       | Bit Count | Data Type           |
    |:--         |:--        |:--                  |
    | id         | 8         | 8-bit int           |
    | data count | 16        | 16-bit unsigned int |
    | data       | 8         | 8-bit unsigned int  |

    Fixed frame definition. Nothing fancy.

    ```python
    """Define your parser using simple python classes and familiar types."""
    from easyprotocol.base import ParseFieldList, hex
    from easyprotocol.fields import Int8Field, UInt8Field, UInt16Field

    # Make an instance of the modified list type and add your fields as the list items.
    exampleParser = ParseFieldList(
        # give the parser a name
        name="ExampleParser1",
        # define your fields in order
        default=[
            # give each field a name, some standard field types are already defined.
            Int8Field(name="id"),
            UInt16Field(name="count"),
            UInt8Field(name="data"),
        ],
    )

    # Some example data bytes to parse
    data = b"\x01\x00\x01\x80"
    print(f"input bytes:\t{data!r}")
    print(f"input hex:\t{hex(data)}\n")

    # Parse the bytes
    exampleParser.parse(data=data)

    # Print the parsed data to see what we got
    print(f"parsed:\t{exampleParser}")
    print(f"bytes:\t{bytes(exampleParser)!r}")
    print(f"hex:\t{exampleParser.hex_value}\n")

    # Make a new frame from known data to send somewhere (like a socket)
    exampleParser[0].value = 3
    exampleParser.value = [3, 257, 127]
    exampleParser.value = [3, 257, 127]
    print(f"parsed:\t{exampleParser}")
    print(f"bytes:\t{bytes(exampleParser)!r}")
    print(f"hex:\t{exampleParser.hex_value}\n")

    # You can access parsed elements of a ParseList by numeric index.
    for child in exampleParser.children.values():
        print(f'{child.name}:\t{child.value}\t"{child.string_value}"')
    print()

    # Wait, I don't really like hexadecimal
    exampleParser.string_format = "{}"
    for child in exampleParser.children.values():
        child.string_format = "{}"

    # Print the values again
    exampleParser[0].value = 3
    exampleParser.value = [3, 257, 127]
    exampleParser.value = [3, 257, 127]
    print(f"parsed:\t{exampleParser}")
    print(f"bytes:\t{bytes(exampleParser)!r}")
    print(f"hex:\t{exampleParser.hex_value}\n")
    for child in exampleParser.children.values():
        print(f'{child.name}:\t{child.value}\t"{child.string_value}"')
    ```

- Output

    ```bash
    input bytes:    b'\x01\x00\x01\x80'
    input hex:      01 00 01 80

    parsed: ExampleParser1: [id: 1, count: 0001(hex), data: 80(hex)]
    bytes:  b'\x01\x00\x01\x80'
    hex:    01 00 01 80

    parsed: ExampleParser1: [id: 3, count: 0101(hex), data: 7F(hex)]
    bytes:  b'\x03\x01\x01\x7f'
    hex:    03 01 01 7F

    id:     3       "3"
    count:  257     "0101(hex)"
    data:   127     "7F(hex)"

    parsed: ExampleParser1: [id: 3, count: 257, data: 127]
    bytes:  b'\x03\x01\x01\x7f'
    hex:    03 01 01 7F

    id:     3       "3"
    count:  257     "257"
    data:   127     "127"
        ```

### Example 2 - Making a Parser from a Dictionary of Fields

- Demo Code

    Lets parse something like the following.

    | Name       | Bit Count | Data Type              |
    |:--         |:--        |:--                     |
    | id         | 8         | 8-bit unsigned int     |
    | count      | 16        | 16-bit unsigned int    |
    | data array | 8         | 8-bit unsigned int(s)  |

    Variable Frame size, handles a variable length array of uint8 chunks.

    ```python
    """Define your parser using simple python classes and familiar types."""
    from typing import cast

    from easyprotocol.base import ParseFieldDict, ParseFieldList, hex
    from easyprotocol.fields import Int8Field, ParseArrayField, UInt8Field, UInt16Field

    # you can define your field classes before using them in a parser.
    ident = Int8Field(name="id")
    count = UInt16Field(
        name="count",
        # Let's modify the display of the field value
        string_format="{} data items",
    )
    data_array = ParseArrayField(
        name="data",
        count=count,
        array_item_class=UInt8Field,
        array_item_default=0,
    )

    # Make an instance of the modified list type and add your fields as the list items.
    exampleParser = ParseFieldDict(
        # give the parser a name
        name="ExampleParser",
        # define your fields in order
        default=[
            # give each field a name, some standard types are defined for you.
            ident,
            count,
            data_array,
        ],
    )

    # Some example data bytes to parse
    data = b"\x01\x00\x01\x80"
    print(f"input bytes:\t{data!r}\n")
    print(f"input hex:\t{hex(data)}\n")

    # Parse the bytes
    exampleParser.parse(data=data)
    # Print the parsed data to see what we got
    print(f"parsed:\t{exampleParser}")
    print(f"hex:\t{hex(exampleParser)}\n")

    # Make a new frame from known data to send somewhere (like a socket)
    exampleParser["id"].value = 3
    exampleParser["count"].value = 2
    exampleParser["data"].value = [
        127,
        UInt8Field(name="new data", default=15),
    ]

    print(f"parsed:\t{exampleParser}")
    print(f"hex:\t{hex(exampleParser)}\n")

    # You can access parsed elements of a ParseDict by name.
    idField = exampleParser[ident.name]
    dataCountField = exampleParser[count.name]
    dataField = cast(ParseFieldList, exampleParser[data_array.name])

    # The ArrayField is a list type, so children are accessed by numeric index.
    for child in exampleParser.values():
        print(f"{child.name}:\t{child.value}\t\t{child.bits_str}")
    ```

- Output

    ```bash
    input bytes:    b'\x01\x00\x01\x80'

    input hex:      01 00 01 80

    parsed: ExampleParser: {id: 1, count: 1 data items, data: [#0: 80(hex)]}
    hex:    01 00 01 80

    parsed: ExampleParser: {id: 3, count: 2 data items, data: [#0: 7F(hex), #1: 0F(hex)]}
    hex:    03 00 02 7F 0F

    id:     3               00000011:<b
    count:  2               0000000000000010:<b
    data:   [<UInt8Field> #0: 7F(hex), <UInt8Field> #1: 0F(hex)]            0111111100001111:<b
        ```