Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CBOR bindings #559

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
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
66 changes: 51 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ jobs:
image:
- x64
- x86
- aarch64
python:
- cp38-cp38
- cp39-cp39
Expand All @@ -65,23 +64,43 @@ jobs:
with:
role-to-assume: ${{ env.CRT_CI_ROLE }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
# Only aarch64 needs this, but it doesn't hurt anything
- name: Install qemu/docker
run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

- name: Build ${{ env.PACKAGE_NAME }}
run: |
aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh
./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-manylinux2014-${{ matrix.image }} build -p ${{ env.PACKAGE_NAME }} --python /opt/python/${{ matrix.python }}/bin/python

manylinux2014-arm64:
runs-on: codebuild-aws-crt-python-arm64-${{ github.run_id }}-${{ github.run_attempt }}-arm-3.0-large
strategy:
fail-fast: false
matrix:
python:
- cp38-cp38
- cp39-cp39
- cp310-cp310
- cp311-cp311
- cp312-cp312
- cp313-cp313
permissions:
id-token: write # This is required for requesting the JWT
steps:
- name: configure AWS credentials (containers)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.CRT_CI_ROLE }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: Build ${{ env.PACKAGE_NAME }}
run: |
aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh
./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-manylinux2014-aarch64 build -p ${{ env.PACKAGE_NAME }} --python /opt/python/${{ matrix.python }}/bin/python

musllinux-1-1:
runs-on: ubuntu-22.04 # latest
strategy:
fail-fast: false
matrix:
image:
- x64
- aarch64
python:
- cp38-cp38
- cp39-cp39
Expand All @@ -97,18 +116,39 @@ jobs:
with:
role-to-assume: ${{ env.CRT_CI_ROLE }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: Build ${{ env.PACKAGE_NAME }}
run: |
aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh
./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-musllinux-1-1-${{ matrix.image }} build -p ${{ env.PACKAGE_NAME }} --python /opt/python/${{ matrix.python }}/bin/python

# Only aarch64 needs this, but it doesn't hurt anything
- name: Install qemu/docker
run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

musllinux-1-1-arm64:
runs-on: codebuild-aws-crt-python-arm64-${{ github.run_id }}-${{ github.run_attempt }}-arm-3.0-large
strategy:
fail-fast: false
matrix:
python:
- cp38-cp38
- cp39-cp39
- cp310-cp310
- cp311-cp311
- cp312-cp312
- cp313-cp313
permissions:
id-token: write # This is required for requesting the JWT
steps:
- name: configure AWS credentials (containers)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.CRT_CI_ROLE }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: Build ${{ env.PACKAGE_NAME }}
run: |
aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh
./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-musllinux-1-1-${{ matrix.image }} build -p ${{ env.PACKAGE_NAME }} --python /opt/python/${{ matrix.python }}/bin/python
./linux-container-ci.sh ${{ env.BUILDER_VERSION }} aws-crt-musllinux-1-1-aarch64 build -p ${{ env.PACKAGE_NAME }} --python /opt/python/${{ matrix.python }}/bin/python

raspberry:
runs-on: ubuntu-20.04 # latest
runs-on: codebuild-aws-crt-python-arm64-${{ github.run_id }}-${{ github.run_attempt }}-arm-3.0-large
strategy:
fail-fast: false
matrix:
Expand All @@ -123,10 +163,6 @@ jobs:
role-to-assume: ${{ env.CRT_CI_ROLE }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}

# set arm arch
- name: Install qemu/docker
run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

- name: Build ${{ env.PACKAGE_NAME }}
run: |
aws s3 cp s3://aws-crt-test-stuff/ci/${{ env.BUILDER_VERSION }}/linux-container-ci.sh ./linux-container-ci.sh && chmod a+x ./linux-container-ci.sh
Expand Down
7 changes: 1 addition & 6 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -386,12 +386,7 @@ poetry.toml
pyrightconfig.json

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
.vscode

# Local History for Visual Studio Code
.history/
Expand Down
226 changes: 226 additions & 0 deletions awscrt/cbor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0.

import _awscrt

from awscrt import NativeResource
from enum import IntEnum
from typing import Union, Any


class AwsCborType(IntEnum):
# Corresponding to `enum aws_cbor_type` in aws/common/cbor.h
Unknown = 0
UnsignedInt = 1
NegativeInt = 2
Float = 3
Bytes = 4
Text = 5
ArrayStart = 6
MapStart = 7
Tag = 8
Bool = 9
Null = 10
Undefined = 11
Break = 12
IndefBytes = 13
IndefStr = 14
IndefArray = 15
IndefMap = 16


class AwsCborEncoder(NativeResource):
""" Encoder for CBOR """

def __init__(self):
super().__init__()
self._binding = _awscrt.cbor_encoder_new(self)

def get_encoded_data(self) -> bytes:
"""Return the current encoded data as bytes

Returns:
bytes: The encoded data currently
"""
return _awscrt.cbor_encoder_get_encoded_data(self._binding)

def write_int(self, val: int):
# TODO: maybe not support bignum for now. Not needed?
"""Write an int as cbor formatted,
val less than -2^64 will be encoded as Negative bignum for CBOR
val between -2^64 to -1, inclusive, will be encode as negative integer for CBOR
val between 0 to 2^64 - 1, inclusive, will be encoded as unsigned integer for CBOR
val greater than 2^64 - 1 will be encoded as Unsigned bignum for CBOR

Args:
val (int): value to be encoded and written to the encoded data.
"""
assert isinstance(val, int)
val_to_encode = val
if val < 0:
# For negative value, the value to encode is -1 - val.
val_to_encode = -1 - val
bit_len = val_to_encode.bit_length()
if bit_len > 64:
# Bignum
bytes_len = bit_len // 8
if bit_len % 8 > 0:
bytes_len += 1
bytes_val = val_to_encode.to_bytes(bytes_len, "big")
if val < 0:
self.write_tag(AwsCborTags.NegativeBigNum) # tag for negative bignum
else:
self.write_tag(AwsCborTags.UnsignedBigNum) # tag for unsigned bignum
return self.write_bytes(bytes_val)

if val >= 0:
return _awscrt.cbor_encoder_write_unsigned_int(self._binding, val_to_encode)
else:
return _awscrt.cbor_encoder_write_negative_int(self._binding, val_to_encode)

def write_float(self, val: float):
"""Write a double as cbor formatted
If the val can be convert the int without loss of precision,
it will be converted to int to be written to as cbor formatted.

Args:
val (float): value to be encoded and written to the encoded data.
"""
assert isinstance(val, float)
# Floating point numbers are usually implemented using double in C
return _awscrt.cbor_encoder_write_float(self._binding, val)

def write_bytes(self, val: bytes):
"""Write bytes as cbor formatted

Args:
val (bytes): value to be encoded and written to the encoded data.
"""
return _awscrt.cbor_encoder_write_bytes(self._binding, val)

def write_text(self, val: str):
"""Write text as cbor formatted

Args:
val (str): value to be encoded and written to the encoded data.
"""
return _awscrt.cbor_encoder_write_text(self._binding, val)

def write_array_start(self, number_entries: int):
"""Add a start of array element.
A legistic with the `number_entries`
for the cbor data items to be included in the array.
`number_entries` should 0 to 2^64 inclusive.
Otherwise, overflow will be raised.

Args:
number_entries (int): number of entries in the array to be written
"""
if number_entries < 0 or number_entries > 2**64:
raise OverflowError()

return _awscrt.cbor_encoder_write_array_start(self._binding, number_entries)

def write_map_start(self, number_entries: int):
"""Add a start of map element, with the `number_entries`
for the number of pair of cbor data items to be included in the map.
`number_entries` should 0 to 2^64 inclusive.
Otherwise, overflow will be raised.

Args:
number_entries (int): number of entries in the map to be written
"""
if number_entries < 0 or number_entries > 2**64:
raise ValueError()

return _awscrt.cbor_encoder_write_map_start(self._binding, number_entries)

def write_tag(self, tag_number: int):
if tag_number < 0 or tag_number > 2**64:
raise ValueError()

return _awscrt.cbor_encoder_write_tag(self._binding, tag_number)

def write_null(self):
return _awscrt.cbor_encoder_write_simple_types(self._binding, AwsCborType.Null)

def write_bool(self, val: bool):
return _awscrt.cbor_encoder_write_bool(self._binding, val)

def write_list(self, val: list):
return _awscrt.cbor_encoder_write_py_list(self._binding, val)

def write_dict(self, val: dict):
return _awscrt.cbor_encoder_write_py_dict(self._binding, val)

def write_data_item(self, data_item: Any):
"""Generic API to write any type of an data_item as cbor formatted.
TODO: timestamp <-> datetime?? Decimal fraction <-> decimal??

Args:
data_item (Any): any type of data_item. If the type is not supported to be converted to cbor format, ValueError will be raised.
"""
return _awscrt.cbor_encoder_write_data_item(self._binding, data_item)


class AwsCborDecoder(NativeResource):
""" Decoder for CBOR """

def __init__(self, src: bytes):
super().__init__()
self._src = src
self._binding = _awscrt.cbor_decoder_new(src)

def peek_next_type(self) -> AwsCborType:
"""Return the AwsCborType of the next data item in the cbor formatted data
"""
return AwsCborType(_awscrt.cbor_decoder_peek_type(self._binding))

def get_remaining_bytes_len(self) -> int:
return _awscrt.cbor_decoder_get_remaining_bytes_len(self._binding)

def consume_next_element(self):
return _awscrt.cbor_decoder_consume_next_element(self._binding)

def consume_next_data_item(self):
return _awscrt.cbor_decoder_consume_next_data_item(self._binding)

def pop_next_unsigned_int(self) -> int:
return _awscrt.cbor_decoder_pop_next_unsigned_int(self._binding)

def pop_next_negative_int(self) -> int:
val = _awscrt.cbor_decoder_pop_next_negative_int(self._binding)
return -1 - val

def pop_next_double(self) -> float:
return _awscrt.cbor_decoder_pop_next_float(self._binding)

def pop_next_bool(self) -> bool:
return _awscrt.cbor_decoder_pop_next_bool(self._binding)

def pop_next_bytes(self) -> bytes:
return _awscrt.cbor_decoder_pop_next_bytes(self._binding)

def pop_next_text(self) -> str:
return _awscrt.cbor_decoder_pop_next_text(self._binding)

def pop_next_array_start(self) -> int:
return _awscrt.cbor_decoder_pop_next_array_start(self._binding)

def pop_next_map_start(self) -> int:
return _awscrt.cbor_decoder_pop_next_map_start(self._binding)

def pop_next_tag_val(self) -> int:
return _awscrt.cbor_decoder_pop_next_tag_val(self._binding)

def pop_next_list(self) -> list:
return _awscrt.cbor_decoder_pop_next_py_list(self._binding)

def pop_next_map(self) -> dict:
return _awscrt.cbor_decoder_pop_next_py_dict(self._binding)

def pop_next_data_item(self) -> Any:
"""Generic API to decode cbor formatted data to a python object.
TODO: tags are NOT supported yet.
"""
return _awscrt.cbor_decoder_pop_next_data_item(self._binding)
Loading
Loading