From b6858c993fe0b2153a972a0af9ed8168fcb28d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Mon, 24 Jul 2023 09:54:14 +0200 Subject: [PATCH 01/44] TMP --- docs/api.md | 5 - docs/api/chainpack.md | 18 + docs/api/index.md | 8 + docs/api/rpcurl.md | 5 + docs/index.md | 2 +- flake.lock | 100 +- flake.nix | 26 +- include/foo.h | 17 - include/meson.build | 13 +- include/shv/chainpack.h | 96 ++ include/shv/cpcp.h | 27 + include/shv/cpcp_convert.h | 9 + include/shv/cpcp_pack.h | 39 + include/shv/cpcp_unpack.h | 158 +++ include/shv/cpon.h | 79 ++ include/shv/rpcclient.h | 39 + include/shv/rpcreceiver.h | 10 + include/shv/rpcurl.h | 75 ++ libfoo/count.c | 29 - libfoo/libfoo.version | 8 - libfoo/match.gperf | 32 - libfoo/meson.build | 29 - libshvchainpack/README.md | 6 + libshvchainpack/chainpack.c | 738 ++++++++++++ libshvchainpack/cpcp.c | 409 +++++++ libshvchainpack/cpcp_convert.c | 282 +++++ libshvchainpack/cpon.c | 1433 +++++++++++++++++++++++ libshvchainpack/libshvchainpack.version | 94 ++ libshvchainpack/meson.build | 31 + libshvrpc/libshvrpc.version | 18 + libshvrpc/meson.build | 36 + libshvrpc/rpcclient.c | 43 + libshvrpc/rpcclient.h | 13 + libshvrpc/rpcclient_datagram.c | 11 + libshvrpc/rpcclient_serial.c | 12 + libshvrpc/rpcclient_stream.c | 115 ++ libshvrpc/rpcurl.c | 162 +++ libshvrpc/rpcurl_query.gperf | 37 + libshvrpc/rpcurl_scheme.gperf | 32 + meson.build | 34 +- meson_options.txt | 16 +- src/config.c | 47 - src/config.h | 20 - src/main.c | 35 - src/meson.build | 22 - tests/run/foo.bats | 66 -- tests/run/meson.build | 6 +- tests/unit/config.c | 46 - tests/unit/count.c | 30 - tests/unit/libshvchainpack/chainpack.c | 361 ++++++ tests/unit/libshvchainpack/cpon.c | 342 ++++++ tests/unit/libshvchainpack/meson.build | 18 + tests/unit/libshvchainpack/packunpack.c | 28 + tests/unit/libshvchainpack/packunpack.h | 47 + tests/unit/libshvrpc/meson.build | 16 + tests/unit/libshvrpc/rpcurl.c | 153 +++ tests/unit/meson.build | 35 +- 57 files changed, 5153 insertions(+), 465 deletions(-) delete mode 100644 docs/api.md create mode 100644 docs/api/chainpack.md create mode 100644 docs/api/index.md create mode 100644 docs/api/rpcurl.md delete mode 100644 include/foo.h create mode 100644 include/shv/chainpack.h create mode 100644 include/shv/cpcp.h create mode 100644 include/shv/cpcp_convert.h create mode 100644 include/shv/cpcp_pack.h create mode 100644 include/shv/cpcp_unpack.h create mode 100644 include/shv/cpon.h create mode 100644 include/shv/rpcclient.h create mode 100644 include/shv/rpcreceiver.h create mode 100644 include/shv/rpcurl.h delete mode 100644 libfoo/count.c delete mode 100644 libfoo/libfoo.version delete mode 100644 libfoo/match.gperf delete mode 100644 libfoo/meson.build create mode 100644 libshvchainpack/README.md create mode 100644 libshvchainpack/chainpack.c create mode 100644 libshvchainpack/cpcp.c create mode 100644 libshvchainpack/cpcp_convert.c create mode 100644 libshvchainpack/cpon.c create mode 100644 libshvchainpack/libshvchainpack.version create mode 100644 libshvchainpack/meson.build create mode 100644 libshvrpc/libshvrpc.version create mode 100644 libshvrpc/meson.build create mode 100644 libshvrpc/rpcclient.c create mode 100644 libshvrpc/rpcclient.h create mode 100644 libshvrpc/rpcclient_datagram.c create mode 100644 libshvrpc/rpcclient_serial.c create mode 100644 libshvrpc/rpcclient_stream.c create mode 100644 libshvrpc/rpcurl.c create mode 100644 libshvrpc/rpcurl_query.gperf create mode 100644 libshvrpc/rpcurl_scheme.gperf delete mode 100644 src/config.c delete mode 100644 src/config.h delete mode 100644 src/main.c delete mode 100644 src/meson.build delete mode 100755 tests/run/foo.bats delete mode 100644 tests/unit/config.c delete mode 100644 tests/unit/count.c create mode 100644 tests/unit/libshvchainpack/chainpack.c create mode 100644 tests/unit/libshvchainpack/cpon.c create mode 100644 tests/unit/libshvchainpack/meson.build create mode 100644 tests/unit/libshvchainpack/packunpack.c create mode 100644 tests/unit/libshvchainpack/packunpack.h create mode 100644 tests/unit/libshvrpc/meson.build create mode 100644 tests/unit/libshvrpc/rpcurl.c diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index 549dc7b..0000000 --- a/docs/api.md +++ /dev/null @@ -1,5 +0,0 @@ -API reference -============= - -```{autodoxygenfile} foo.h -``` diff --git a/docs/api/chainpack.md b/docs/api/chainpack.md new file mode 100644 index 0000000..b80afc0 --- /dev/null +++ b/docs/api/chainpack.md @@ -0,0 +1,18 @@ +# Chainpack and CPON + +The serialization and deserialization of Chainpack and CPON data formats. + +## Common pack and unpack + +```{autodoxygenfile} cpcp.h +``` + +## Chainpack pack and unpack + +```{autodoxygenfile} chainpack.h +``` + +## CPON pack and unpack + +```{autodoxygenfile} cpon.h +``` diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 0000000..f80f3a2 --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,8 @@ +# API documentation + +```{toctree} +:maxdepth: 3 + +chainpack +rpcurl +``` diff --git a/docs/api/rpcurl.md b/docs/api/rpcurl.md new file mode 100644 index 0000000..edf6f7a --- /dev/null +++ b/docs/api/rpcurl.md @@ -0,0 +1,5 @@ +RPC URL +======= + +```{autodoxygenfile} rpcurl.h +``` diff --git a/docs/index.md b/docs/index.md index 8915a48..51f8b8b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,5 +7,5 @@ Table of contents ```{toctree} :maxdepth: 2 -api +api/index ``` diff --git a/flake.lock b/flake.lock index fd8b239..c9c2eee 100644 --- a/flake.lock +++ b/flake.lock @@ -6,11 +6,11 @@ "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1682322459, - "narHash": "sha256-TVdk4BfW5zBKKlovUUjn+4jz/BvHHz137hr6WmcPs4c=", + "lastModified": 1689863573, + "narHash": "sha256-OqVGz3TxlzpMQQnoPU7WoEM1zG2Nc5DSRrgCVbL9BjA=", "owner": "cynerd", "repo": "check-suite", - "rev": "aee611fe5d9707acd7da899fe7f8fdc4ba1c2872", + "rev": "cdfe694ed10f9a12b81796a8f7feaf3967fe1025", "type": "github" }, "original": { @@ -24,11 +24,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", "type": "github" }, "original": { @@ -41,11 +41,28 @@ "systems": "systems_2" }, "locked": { - "lastModified": 1687709756, - "narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=", + "lastModified": 1689068808, + "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, + "locked": { + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", "owner": "numtide", "repo": "flake-utils", - "rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", "type": "github" }, "original": { @@ -55,11 +72,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1682109806, - "narHash": "sha256-d9g7RKNShMLboTWwukM+RObDWWpHKaqTYXB48clBWXI=", + "lastModified": 1689752456, + "narHash": "sha256-VOChdECcEI8ixz8QY+YC4JaNEFwQd1V8bA0G4B28Ki0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2362848adf8def2866fabbffc50462e929d7fffb", + "rev": "7f256d7da238cb627ef189d56ed590739f42f13b", "type": "github" }, "original": { @@ -69,11 +86,25 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1687886075, - "narHash": "sha256-PeayJDDDy+uw1Ats4moZnRdL1OFuZm1Tj+KiHlD67+o=", + "lastModified": 1689752456, + "narHash": "sha256-VOChdECcEI8ixz8QY+YC4JaNEFwQd1V8bA0G4B28Ki0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a565059a348422af5af9026b5174dc5c0dcefdae", + "rev": "7f256d7da238cb627ef189d56ed590739f42f13b", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_3": { + "locked": { + "lastModified": 1682109806, + "narHash": "sha256-d9g7RKNShMLboTWwukM+RObDWWpHKaqTYXB48clBWXI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2362848adf8def2866fabbffc50462e929d7fffb", "type": "github" }, "original": { @@ -85,7 +116,29 @@ "inputs": { "check-suite": "check-suite", "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs_2", + "shvapp": "shvapp" + } + }, + "shvapp": { + "inputs": { + "flake-utils": "flake-utils_3", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1682590530, + "narHash": "sha256-Agpswh52I3qOZeaAmw3V/Kzw1zb6yyQ5mqaZOBzCE5k=", + "ref": "refs/heads/master", + "rev": "fefca3fc2ef80ae8486ba892edf79fbc5ce4d7e8", + "revCount": 2063, + "submodules": true, + "type": "git", + "url": "https://github.com/silicon-heaven/shvapp.git" + }, + "original": { + "submodules": true, + "type": "git", + "url": "https://github.com/silicon-heaven/shvapp.git" } }, "systems": { @@ -117,6 +170,21 @@ "repo": "default", "type": "github" } + }, + "systems_3": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index fca45ba..4c36e38 100644 --- a/flake.nix +++ b/flake.nix @@ -3,6 +3,7 @@ inputs = { check-suite.url = "github:cynerd/check-suite"; + shvapp.url = "git+https://github.com/silicon-heaven/shvapp.git?submodules=1"; }; outputs = { @@ -10,6 +11,7 @@ flake-utils, nixpkgs, check-suite, + shvapp, }: with builtins; with flake-utils.lib; @@ -24,17 +26,17 @@ filter = path: type: ! hasSuffix ".nix" path; }; outputs = ["out" "doc"]; + sphinxRoot = "../docs"; buildInputs = [ - check - pkgs.check-suite + uriparser ]; nativeBuildInputs = [ - bash - bats - gperf + # Build tools meson ninja + gperf pkg-config + # Documentation doxygen (sphinxHook.overrideAttrs (oldAttrs: { propagatedBuildInputs = with python3Packages; [ @@ -44,7 +46,18 @@ ]; })) ]; - sphinxRoot = "../docs"; + doCheck = true; + checkInputs = [ + # Unit tests + check + pkgs.check-suite + ]; + nativeCheckInputs = [ + pkgs.shvapp + # Run tests + bash + bats + ]; }; }; in @@ -53,6 +66,7 @@ shvc = final: prev: packages (id prev); default = composeManyExtensions [ check-suite.overlays.default + shvapp.overlays.default self.overlays.shvc ]; }; diff --git a/include/foo.h b/include/foo.h deleted file mode 100644 index c775009..0000000 --- a/include/foo.h +++ /dev/null @@ -1,17 +0,0 @@ -/***************************************************************************** - * Copyright (C) 2022 Elektroline Inc. All rights reserved. - * K Ladvi 1805/20 - * Prague, 184 00 - * info@elektroline.cz (+420 284 021 111) - *****************************************************************************/ -#ifndef _FOO_H_ -#define _FOO_H_ -#include - -/*! Count number of foo lines in data read from provided file. - * @param f: file object open for reading - * @returns number of "foo:" prefix lines found in file. - */ -unsigned count_foo(FILE *f) __attribute__((nonnull)); - -#endif diff --git a/include/meson.build b/include/meson.build index 68734d2..38dd93d 100644 --- a/include/meson.build +++ b/include/meson.build @@ -1,3 +1,14 @@ includes = include_directories('.') -libfoo_headers = files('foo.h') +libshvchainpack_headers = files( + 'shv/chainpack.h', + 'shv/cpcp.h', + 'shv/cpcp_convert.h', + 'shv/cpcp_pack.h', + 'shv/cpcp_unpack.h', + 'shv/cpon.h', +) +libshvrpc_headers = files( + 'shv/rpcurl.h', + 'shv/rpcclient.h', +) diff --git a/include/shv/chainpack.h b/include/shv/chainpack.h new file mode 100644 index 0000000..1d38eec --- /dev/null +++ b/include/shv/chainpack.h @@ -0,0 +1,96 @@ +#ifndef SHV_CHAINPACK_H +#define SHV_CHAINPACK_H + +#include + +#include +#include +#include +#include + +typedef enum { + CP_INVALID = -1, + + CP_Null = 128, + CP_UInt, + CP_Int, + CP_Double, + CP_Bool, + CP_Blob, + CP_String, // UTF8 encoded string + CP_DateTimeEpoch_depr, // deprecated + CP_List, + CP_Map, + CP_IMap, + CP_MetaMap, + CP_Decimal, + CP_DateTime, + CP_CString, + + CP_FALSE = 253, + CP_TRUE = 254, + CP_TERM = 255, +} chainpack_pack_packing_schema; + +const char *chainpack_packing_schema_name(int sch); + +void chainpack_pack_uint_data(cpcp_pack_context *pack_context, uint64_t num); + +void chainpack_pack_null(cpcp_pack_context *pack_context) + __attribute__((nonnull)); + +void chainpack_pack_boolean(cpcp_pack_context *pack_context, bool b) + __attribute__((nonnull)); + +void chainpack_pack_int(cpcp_pack_context *pack_context, int64_t i) + __attribute__((nonnull)); + +void chainpack_pack_uint(cpcp_pack_context *pack_context, uint64_t i) + __attribute__((nonnull)); + +void chainpack_pack_double(cpcp_pack_context *pack_context, double d) + __attribute__((nonnull)); + +void chainpack_pack_decimal(cpcp_pack_context *pack_context, const cpcp_decimal *v) + __attribute__((nonnull)); + +void chainpack_pack_date_time(cpcp_pack_context *pack_context, const cpcp_date_time *v) + __attribute__((nonnull)); + +void chainpack_pack_blob( + cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len); +void chainpack_pack_blob_start(cpcp_pack_context *pack_context, + size_t string_len, const uint8_t *buff, size_t buff_len); +void chainpack_pack_blob_cont( + cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len); + +void chainpack_pack_string( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len); +void chainpack_pack_string_start(cpcp_pack_context *pack_context, + size_t string_len, const char *buff, size_t buff_len); +void chainpack_pack_string_cont( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len); + +void chainpack_pack_cstring( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len); +void chainpack_pack_cstring_terminated( + cpcp_pack_context *pack_context, const char *str); +void chainpack_pack_cstring_start( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len); +void chainpack_pack_cstring_cont( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len); +void chainpack_pack_cstring_finish(cpcp_pack_context *pack_context); + +void chainpack_pack_list_begin(cpcp_pack_context *pack_context); +void chainpack_pack_map_begin(cpcp_pack_context *pack_context); +void chainpack_pack_imap_begin(cpcp_pack_context *pack_context); +void chainpack_pack_meta_begin(cpcp_pack_context *pack_context); + +void chainpack_pack_container_end(cpcp_pack_context *pack_context); + +uint64_t chainpack_unpack_uint_data(cpcp_unpack_context *unpack_context, bool *ok); +uint64_t chainpack_unpack_uint_data2( + cpcp_unpack_context *unpack_context, int *err_code); +void chainpack_unpack_next(cpcp_unpack_context *unpack_context); + +#endif diff --git a/include/shv/cpcp.h b/include/shv/cpcp.h new file mode 100644 index 0000000..920f532 --- /dev/null +++ b/include/shv/cpcp.h @@ -0,0 +1,27 @@ +#ifndef SHV_CPCP_H +#define SHV_CPCP_H + +#include +#include + + +typedef enum { + CPCP_ChainPack = 1, + CPCP_Cpon, +} cpcp_pack_format; + +typedef enum { + CPCP_RC_OK = 0, + CPCP_RC_MALLOC_ERROR, + CPCP_RC_BUFFER_OVERFLOW, + CPCP_RC_BUFFER_UNDERFLOW, + CPCP_RC_MALFORMED_INPUT, + CPCP_RC_LOGICAL_ERROR, + CPCP_RC_CONTAINER_STACK_OVERFLOW, + CPCP_RC_CONTAINER_STACK_UNDERFLOW, +} cpcp_error_codes; + +const char *cpcp_error_string(int err_no); + + +#endif diff --git a/include/shv/cpcp_convert.h b/include/shv/cpcp_convert.h new file mode 100644 index 0000000..b8fc846 --- /dev/null +++ b/include/shv/cpcp_convert.h @@ -0,0 +1,9 @@ +#ifndef SHV_CPCP_CONVERT_H +#define SHV_CPCP_CONVERT_H + +#include + +void ccpcp_convert(cpcp_unpack_context *in_ctx, cpcp_pack_format in_format, + cpcp_pack_context *out_ctx, cpcp_pack_format out_format); + +#endif diff --git a/include/shv/cpcp_pack.h b/include/shv/cpcp_pack.h new file mode 100644 index 0000000..356ae63 --- /dev/null +++ b/include/shv/cpcp_pack.h @@ -0,0 +1,39 @@ +#ifndef SHV_CPCP_PACK_H +#define SHV_CPCP_PACK_H + +#include +#include + + +typedef struct { + const char *indent; + uint8_t json_output:1; +} cpcp_cpon_pack_options; + +struct cpcp_pack_context; + +typedef void (*cpcp_pack_overflow_handler)( + struct cpcp_pack_context *, size_t size_hint); + +typedef struct cpcp_pack_context { + char *start; + char *current; + char *end; + int nest_count; + int err_no; /* handlers can save error here */ + cpcp_pack_overflow_handler handle_pack_overflow; + void *custom_context; + cpcp_cpon_pack_options cpon_options; + size_t bytes_written; +} cpcp_pack_context; + +void cpcp_pack_context_init(cpcp_pack_context *pack_context, void *data, + size_t length, cpcp_pack_overflow_handler poh); +void cpcp_pack_context_dry_run_init(cpcp_pack_context *pack_context); + +size_t cpcp_pack_copy_byte(cpcp_pack_context *pack_context, uint8_t b); +size_t cpcp_pack_copy_bytes( + cpcp_pack_context *pack_context, const void *str, size_t len); + + +#endif diff --git a/include/shv/cpcp_unpack.h b/include/shv/cpcp_unpack.h new file mode 100644 index 0000000..6ed0b05 --- /dev/null +++ b/include/shv/cpcp_unpack.h @@ -0,0 +1,158 @@ +#ifndef SHV_CPCP_UNPACK_H +#define SHV_CPCP_UNPACK_H + +#include +#include +#include + + +struct cpcp_unpack_context; + +typedef enum { + CPCP_ITEM_INVALID = 0, + CPCP_ITEM_NULL, + CPCP_ITEM_BOOLEAN, + CPCP_ITEM_INT, + CPCP_ITEM_UINT, + CPCP_ITEM_DOUBLE, + CPCP_ITEM_DECIMAL, + CPCP_ITEM_BLOB, + CPCP_ITEM_STRING, + CPCP_ITEM_DATE_TIME, + + CPCP_ITEM_LIST, + CPCP_ITEM_MAP, + CPCP_ITEM_IMAP, + CPCP_ITEM_META, + CPCP_ITEM_CONTAINER_END, +} cpcp_item_types; + +const char *cpcp_item_type_to_string(cpcp_item_types t); + +#ifndef CPCP_MAX_STRING_KEY_LEN +#define CPCP_STRING_CHUNK_BUFF_LEN 256 +#endif + +typedef struct { + long string_size; + long size_to_load; + char *chunk_start; + size_t chunk_size; + size_t chunk_buff_len; + unsigned chunk_cnt; + uint8_t last_chunk:1, blob_hex:1; +} cpcp_string; + +void cpcp_string_init( + cpcp_string *str_it, struct cpcp_unpack_context *unpack_context); + +typedef struct { + int64_t msecs_since_epoch; + int minutes_from_utc; +} cpcp_date_time; + +typedef struct { + int64_t mantisa; + int exponent; +} cpcp_decimal; + +double cpcp_exponentional_to_double( + const int64_t mantisa, const int exponent, const int base); +double cpcp_decimal_to_double(const int64_t mantisa, const int exponent); +size_t cpcp_decimal_to_string( + char *buff, size_t buff_len, int64_t mantisa, int exponent); + +typedef struct { + union { + cpcp_string String; + cpcp_date_time DateTime; + cpcp_decimal Decimal; + uint64_t UInt; + int64_t Int; + double Double; + bool Bool; + } as; + cpcp_item_types type; +} cpcp_item; + +typedef struct { + cpcp_item_types container_type; + size_t item_count; + cpcp_item_types current_item_type; +} cpcp_container_state; + +void cpcp_container_state_init( + cpcp_container_state *self, cpcp_item_types cont_type); + +struct cpcp_container_stack; + +typedef int (*cpcp_container_stack_overflow_handler)(struct cpcp_container_stack *); + +typedef struct cpcp_container_stack { + cpcp_container_state *container_states; + size_t length; + size_t capacity; + cpcp_container_stack_overflow_handler overflow_handler; +} cpcp_container_stack; + +void cpcp_container_stack_init(cpcp_container_stack *self, + cpcp_container_state *states, size_t capacity, + cpcp_container_stack_overflow_handler hnd); + +typedef size_t (*cpcp_unpack_underflow_handler)(struct cpcp_unpack_context *); + +typedef struct cpcp_unpack_context { + cpcp_item item; + const char *start; + const char *current; + const char *end; /* logical end of buffer */ + int err_no; /* handlers can save error here */ + int parser_line_no; /* helps to find parser error line */ + const char *err_msg; + cpcp_container_stack *container_stack; + cpcp_unpack_underflow_handler handle_unpack_underflow; + void *custom_context; + char default_string_chunk_buff[CPCP_STRING_CHUNK_BUFF_LEN]; + char *string_chunk_buff; + size_t string_chunk_buff_len; +} cpcp_unpack_context; + +void cpcp_unpack_context_init(cpcp_unpack_context *self, const void *data, + size_t length, cpcp_unpack_underflow_handler huu, cpcp_container_stack *stack); + +cpcp_container_state *cpcp_unpack_context_push_container_state( + cpcp_unpack_context *self, cpcp_item_types container_type); +cpcp_container_state *cpcp_unpack_context_top_container_state( + cpcp_unpack_context *self); +cpcp_container_state *cpcp_unpack_context_parent_container_state( + cpcp_unpack_context *self); +cpcp_container_state *cpcp_unpack_context_closed_container_state( + cpcp_unpack_context *self); +void cpcp_unpack_context_pop_container_state(cpcp_unpack_context *self); + +const char *cpcp_unpack_take_byte(cpcp_unpack_context *unpack_context); +const char *cpcp_unpack_peek_byte(cpcp_unpack_context *unpack_context); +#define UNPACK_ERROR(error_code, error_msg) \ + { \ + unpack_context->item.type = CPCP_ITEM_INVALID; \ + unpack_context->err_no = error_code; \ + unpack_context->err_msg = error_msg; \ + return; \ + } + +#define UNPACK_TAKE_BYTE(p) \ + { \ + p = cpcp_unpack_take_byte(unpack_context); \ + if (!p) \ + return; \ + } + +#define UNPACK_PEEK_BYTE(p) \ + { \ + p = cpcp_unpack_peek_byte(unpack_context); \ + if (!p) \ + return; \ + } + + +#endif diff --git a/include/shv/cpon.h b/include/shv/cpon.h new file mode 100644 index 0000000..37cd939 --- /dev/null +++ b/include/shv/cpon.h @@ -0,0 +1,79 @@ +#ifndef SHV_CPON_H +#define SHV_CPON_H + +#include + +#include + +int64_t cpon_timegm(struct tm *tm); +void cpon_gmtime(int64_t epoch_sec, struct tm *tm); + +/******************************* P A C K **********************************/ + +void cpon_pack_null(cpcp_pack_context *pack_context) + __attribute__((nonnull)); + +void cpon_pack_boolean(cpcp_pack_context *pack_context, bool b) + __attribute__((nonnull)); + +void cpon_pack_int(cpcp_pack_context *pack_context, int64_t i) + __attribute__((nonnull)); + +void cpon_pack_uint(cpcp_pack_context *pack_context, uint64_t i) + __attribute__((nonnull)); + +void cpon_pack_double(cpcp_pack_context *pack_context, double d) + __attribute__((nonnull)); + +void cpon_pack_decimal(cpcp_pack_context *pack_context, const cpcp_decimal *v) + __attribute__((nonnull)); + +void cpon_pack_date_time(cpcp_pack_context *pack_context, const cpcp_date_time *v) + __attribute__((nonnull)); + +typedef enum { CCPON_Auto = 0, CCPON_Always, CCPON_Never } cpon_msec_policy; +void cpon_pack_date_time_str(cpcp_pack_context *pack_context, int64_t epoch_msecs, + int min_from_utc, cpon_msec_policy msec_policy, bool with_tz); + +void cpon_pack_blob( + cpcp_pack_context *pack_context, const uint8_t *s, size_t buff_len); +void cpon_pack_blob_start( + cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len); +void cpon_pack_blob_cont( + cpcp_pack_context *pack_context, const uint8_t *buff, unsigned buff_len); +void cpon_pack_blob_finish(cpcp_pack_context *pack_context); + +void cpon_pack_string(cpcp_pack_context *pack_context, const char *s, size_t l); +void cpon_pack_string_terminated(cpcp_pack_context *pack_context, const char *s); +void cpon_pack_string_start( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len); +void cpon_pack_string_cont( + cpcp_pack_context *pack_context, const char *buff, unsigned buff_len); +void cpon_pack_string_finish(cpcp_pack_context *pack_context); + +void cpon_pack_list_begin(cpcp_pack_context *pack_context); +void cpon_pack_list_end(cpcp_pack_context *pack_context, bool is_oneliner); + +void cpon_pack_map_begin(cpcp_pack_context *pack_context); +void cpon_pack_map_end(cpcp_pack_context *pack_context, bool is_oneliner); + +void cpon_pack_imap_begin(cpcp_pack_context *pack_context); +void cpon_pack_imap_end(cpcp_pack_context *pack_context, bool is_oneliner); + +void cpon_pack_meta_begin(cpcp_pack_context *pack_context); +void cpon_pack_meta_end(cpcp_pack_context *pack_context, bool is_oneliner); + +void cpon_pack_copy_str(cpcp_pack_context *pack_context, const char *str); + +void cpon_pack_field_delim( + cpcp_pack_context *pack_context, bool is_first_field, bool is_oneliner); +void cpon_pack_key_val_delim(cpcp_pack_context *pack_context); + +/***************************** U N P A C K ********************************/ +const char *cpon_unpack_skip_insignificant(cpcp_unpack_context *unpack_context); +void cpon_unpack_next(cpcp_unpack_context *unpack_context); +void cpon_unpack_date_time(cpcp_unpack_context *unpack_context, struct tm *tm, + int *msec, int *utc_offset); + + +#endif diff --git a/include/shv/rpcclient.h b/include/shv/rpcclient.h new file mode 100644 index 0000000..6f5e521 --- /dev/null +++ b/include/shv/rpcclient.h @@ -0,0 +1,39 @@ +#ifndef SHV_RPCCLIENT_H +#define SHV_RPCCLIENT_H + +#include +#include +#include + +struct rpcclient; +typedef struct rpcclient *rpcclient_t; + + +rpcclient_t rpcclient_connect(const struct rpcurl *url); + +rpcclient_t rpcclient_stream_new(int readfd, int writefd); +rpcclient_t rpcclient_stream_tcp_connect(const char *location, int port); +rpcclient_t rpcclient_stream_unix_connect(const char *location); + +rpcclient_t rpcclient_datagram_neq(void); +rpcclient_t rpcclient_datagram_udp_connect(const char *location, int port); + +rpcclient_t rpcclient_serial_new(int fd); +rpcclient_t rpcclient_serial_connect(const char *path); + +bool rpcclient_login(rpcclient_t, const char *username, const char *password, + enum rpc_login_type type); + +void rpcclient_disconnect(rpcclient_t client); + + + +bool rpcclient_next_message(rpcclient_t client); + +struct cpcp_item *rpcclient_next_item(rpcclient_t client); + + +struct cpcp_pack_context *rpcclient_pack_context(rpcclient_t client); + + +#endif diff --git a/include/shv/rpcreceiver.h b/include/shv/rpcreceiver.h new file mode 100644 index 0000000..75725c1 --- /dev/null +++ b/include/shv/rpcreceiver.h @@ -0,0 +1,10 @@ +#ifndef SHV_RPCRECEIVER_H +#define SHV_RPCRECEIVER_H + +#include +#include +#include + + + +#endif diff --git a/include/shv/rpcurl.h b/include/shv/rpcurl.h new file mode 100644 index 0000000..053d5c0 --- /dev/null +++ b/include/shv/rpcurl.h @@ -0,0 +1,75 @@ +#ifndef _SHV_RPCURL_H +#define _SHV_RPCURL_H + +/*! Protocol used to connect client to the server or to listen for connection on */ +enum rpc_protocol { + /*! TCP/IP protocol with SHV RPC Stream link layer used to transport + * messages. + */ + RPC_PROTOCOL_TCP, + /*! UDP/IP protocol with SHV RPC Datagram link layer used to transport + * messages. + */ + RPC_PROTOCOL_UDP, + /*! Unix domain name socket with SHV RPC Stream link layer used to transport + * messages. + */ + RPC_PROTOCOL_LOCAL_SOCKET, + /*! Serial port with SHV RPC Stream link layer used to transport messages. */ + RPC_PROTOCOL_SERIAL_PORT, +}; + +/*! The format of the password passed to the login process. */ +enum rpc_login_type { + /*! The password is in plain text */ + RPC_LOGIN_PLAIN, + /*! Password is hashed with SHA1 password */ + RPC_LOGIN_SHA1, +}; + +/*! SHV RPC URL representation */ +struct rpcurl { + /*! Protocol used to connect to the server */ + enum rpc_protocol protocol; + /*! Location where client should connect to or server listen on */ + const char *location; + /*! Port number used for TCP and UDP connections */ + int port; + /*! User name used to login client connection to the server */ + const char *username; + /*! Password used to login client connection to the server */ + const char *password; + /*! The format of the password */ + enum rpc_login_type login_type; + /*! Device ID sent as part of login information to the server */ + const char *device_id; + /*! Device's requested mount point on server */ + const char *device_mountpoint; +}; + + +/*! Parse string URL. + * + * @param url: String representation of URL + * @param error_pos: Pointer to the position of error in the URL string. + * `NULL` can be passed if you are not interested in the error location. + * @returns `struct rpcurl` allocated using `malloc`. or `NULL` in case of URL + * parse error. Do not free returned memory using `free`, you need to use + * `rpcurl_free` instead. + */ +struct rpcurl *rpcurl_parse(const char *url, const char **error_pos) __attribute__((nonnull(1))); + +/*! Free RPC URL representation previously parsed using by `rpcurl_parse`. + * + * @param rpc_url: pointer returned by `rpcurl_parse` or `NULL`. + */ +void rpcurl_free(struct rpcurl *rpc_url); + +/*! Convert RPC URL to the string representation. + * + * @param rpc_url: Pointer to the RPC URL to be converted to the string. + * @returns `malloc` allocated string with URL. + */ +char *rpcurl_str(struct rpcurl *rpc_url) __attribute__((nonnull)); + +#endif diff --git a/libfoo/count.c b/libfoo/count.c deleted file mode 100644 index 115f43b..0000000 --- a/libfoo/count.c +++ /dev/null @@ -1,29 +0,0 @@ -/***************************************************************************** - * Copyright (C) 2022 Elektroline Inc. All rights reserved. - * K Ladvi 1805/20 - * Prague, 184 00 - * info@elektroline.cz (+420 284 021 111) - *****************************************************************************/ -#include -#include -#include -#include - -#include "match.gperf.h" - -unsigned count_foo(FILE *f) { - unsigned res = 0; - char *line = NULL; - size_t len = 0; - ssize_t nread; - while ((nread = getline(&line, &len, f)) != -1) { - char *colon = strchr(line, ':'); - if (colon == NULL) - continue; // ignore lines without colon - const struct gperf_match *gm = gperf_match(line, colon - line); - if (gm != NULL && gm->matches) - res++; - } - free(line); - return res; -} diff --git a/libfoo/libfoo.version b/libfoo/libfoo.version deleted file mode 100644 index d799189..0000000 --- a/libfoo/libfoo.version +++ /dev/null @@ -1,8 +0,0 @@ -V0.0 { - global: - log_foo; - - count_foo; - - local: *; -}; diff --git a/libfoo/match.gperf b/libfoo/match.gperf deleted file mode 100644 index 4f5ca39..0000000 --- a/libfoo/match.gperf +++ /dev/null @@ -1,32 +0,0 @@ -%compare-lengths -%compare-strncmp -%ignore-case - -%pic -%null-strings -%readonly-tables -%switch=1 -%language=ANSI-C - -%define hash-function-name gperf_match_hash -%define lookup-function-name gperf_match -%define string-pool-name gperf_match_string -%define slot-name id -%define constants-prefix MATCH - -%delimiters=, - -%struct-type -%{ -struct gperf_match { - int id; - bool matches; -}; -%} - -struct gperf_match; - -%% -foo, true -fee, false -%% diff --git a/libfoo/meson.build b/libfoo/meson.build deleted file mode 100644 index 6f87555..0000000 --- a/libfoo/meson.build +++ /dev/null @@ -1,29 +0,0 @@ -libfoo_sources = [ - files('count.c'), - gperf.process('match.gperf'), -] -libfoo_dependencies = [] - -libfoo = library( - 'foo', - libfoo_sources, - version: '0.0.0', - dependencies: libfoo_dependencies, - include_directories: includes, - link_args: '-Wl,--version-script=' + join_paths( - meson.current_source_dir(), - 'libfoo.version', - ), - install: not meson.is_subproject(), -) -install_headers(libfoo_headers) - -libfoo_dep = declare_dependency( - dependencies: libfoo_dependencies, - include_directories: includes, - link_with: libfoo, -) - - -pkg_mod = import('pkgconfig') -pkg_mod.generate(libfoo, description: 'Library counting foos..') diff --git a/libshvchainpack/README.md b/libshvchainpack/README.md new file mode 100644 index 0000000..86442f6 --- /dev/null +++ b/libshvchainpack/README.md @@ -0,0 +1,6 @@ +Files in this library are fork of chainpack implementation in +[libshv](https://github.com/silicon-heaven/libshv/tree/master/libshvchainpack/c). +They were forked primarily to provide documentation in headers. It should be +possible to copy over source files but you need to merge header changes. + +The libshv commit of latest merge: 6ea68d7b37b633e0cc574af706a5477b5dca0f37 diff --git a/libshvchainpack/chainpack.c b/libshvchainpack/chainpack.c new file mode 100644 index 0000000..87f183b --- /dev/null +++ b/libshvchainpack/chainpack.c @@ -0,0 +1,738 @@ +#include + +#include +#include + + +// UTC msec since 2.2. 2018 folowed by signed UTC offset in 1/4 hour +// Fri Feb 02 2018 00:00:00 == 1517529600 EPOCH +static const int64_t SHV_EPOCH_MSEC = 1517529600000; + +const char *chainpack_packing_schema_name(int sch) { + switch (sch) { + case CP_INVALID: + return "INVALID"; + case CP_FALSE: + return "FALSE"; + case CP_TRUE: + return "TRUE"; + + case CP_Null: + return "Null"; + case CP_UInt: + return "UInt"; + case CP_Int: + return "Int"; + case CP_Double: + return "Double"; + case CP_Bool: + return "Bool"; + case CP_Blob: + return "Blob"; + case CP_String: + return "String"; + case CP_CString: + return "CString"; + case CP_List: + return "List"; + case CP_Map: + return "Map"; + case CP_IMap: + return "IMap"; + case CP_DateTimeEpoch_depr: + return "DateTimeEpoch_depr"; + case CP_DateTime: + return "DateTime"; + case CP_MetaMap: + return "MetaMap"; + case CP_Decimal: + return "Decimal"; + + case CP_TERM: + return "TERM"; + } + return ""; +} + +static void copy_bytes_cstring( + cpcp_pack_context *pack_context, const void *str, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + if (pack_context->err_no != CPCP_RC_OK) + return; + uint8_t ch = ((const uint8_t *)str)[i]; + switch (ch) { + case '\0': + case '\\': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, ch); + break; + default: + cpcp_pack_copy_byte(pack_context, ch); + } + } +} + +#if defined(__GNUC__) && __GNUC__ >= 4 + +static int significant_bits_part_length(uint64_t n) { + int len = 0; + int llbits = sizeof(long long) * CHAR_BIT; + + if (n == 0) + return 0; + + if ((llbits < 64) && (n & 0xFFFFFFFF00000000)) { + len += 32; + n >>= 32; + } + + len += llbits - __builtin_clzll(n); + + return len; +} + +#else /* Fallback for generic compiler */ + +// see https://en.wikipedia.org/wiki/Find_first_set#CLZ +static const uint8_t sig_table_4bit[16] = { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4}; + +static int significant_bits_part_length(uint64_t n) { + int len = 0; + + if (n & 0xFFFFFFFF00000000) { + len += 32; + n >>= 32; + } + if (n & 0xFFFF0000) { + len += 16; + n >>= 16; + } + if (n & 0xFF00) { + len += 8; + n >>= 8; + } + if (n & 0xF0) { + len += 4; + n >>= 4; + } + len += sig_table_4bit[n]; + return len; +} + +#endif /* end of significant_bits_part_length function */ + +// number of bytes needed to encode bit_len +static int bytes_needed(int bit_len) { + int cnt; + if (bit_len <= 28) + cnt = (bit_len - 1) / 7 + 1; + else + cnt = (bit_len - 1) / 8 + 2; + return cnt; +} + +/* UInt + 0 ... 7 bits 1 byte |0|x|x|x|x|x|x|x|<-- LSB + 8 ... 14 bits 2 bytes |1|0|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB +15 ... 21 bits 3 bytes |1|1|0|x|x|x|x|x| |x|x|x|x|x|x|x|x| +|x|x|x|x|x|x|x|x|<-- LSB 22 ... 28 bits 4 bytes |1|1|1|0|x|x|x|x| +|x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB 29+ bits 5+ +bytes |1|1|1|1|n|n|n|n| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| +... <-- LSB n == 0 -> 4 bytes number (32 bit number) n == 1 -> 5 bytes +number n == 14 -> 18 bytes number n == 15 -> for future (number of bytes will be +specified in next byte) +*/ + +static void pack_uint_data_helper( + cpcp_pack_context *pack_context, uint64_t num, int bit_len) { + int byte_cnt = bytes_needed(bit_len); + uint8_t bytes[byte_cnt]; + int i; + for (i = byte_cnt - 1; i >= 0; --i) { + uint8_t r = num & 255; + bytes[i] = r; + num = num >> 8; + } + + uint8_t *head = bytes; + if (bit_len <= 28) { + uint8_t mask = (uint8_t)(0xf0 << (4 - byte_cnt)); + *head = (uint8_t)(*head & ~mask); + mask = (uint8_t)(mask << 1); + *head = *head | mask; + } else { + *head = (uint8_t)(0xf0 | (byte_cnt - 5)); + } + + for (i = 0; i < byte_cnt; ++i) { + uint8_t r = bytes[i]; + cpcp_pack_copy_byte(pack_context, r); + } +} + +void chainpack_pack_uint_data(cpcp_pack_context *pack_context, uint64_t num) { + const size_t UINT_BYTES_MAX = 18; + if (sizeof(num) > UINT_BYTES_MAX) { + pack_context->err_no = CPCP_RC_LOGICAL_ERROR; //("writeData_UInt: value + // too big to pack!"); + return; + } + + int bitlen = significant_bits_part_length(num); + pack_uint_data_helper(pack_context, num, bitlen); +} + +/* + 0 ... 7 bits 1 byte |0|s|x|x|x|x|x|x|<-- LSB + 8 ... 14 bits 2 bytes |1|0|s|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB +15 ... 21 bits 3 bytes |1|1|0|s|x|x|x|x| |x|x|x|x|x|x|x|x| +|x|x|x|x|x|x|x|x|<-- LSB 22 ... 28 bits 4 bytes |1|1|1|0|s|x|x|x| +|x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB 29+ bits 5+ +bytes |1|1|1|1|n|n|n|n| |s|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| +... <-- LSB n == 0 -> 4 bytes number (32 bit number) n == 1 -> 5 bytes +number n == 14 -> 18 bytes number n == 15 -> for future (number of bytes will be +specified in next byte) +*/ + +// return max bit length >= bit_len, which can be encoded by same number of bytes +static int expand_bit_len(int bit_len) { + int ret; + int byte_cnt = bytes_needed(bit_len); + if (bit_len <= 28) { + ret = byte_cnt * (8 - 1) - 1; + } else { + ret = (byte_cnt - 1) * 8 - 1; + } + return ret; +} + +static void chainpack_pack_int_data(cpcp_pack_context *pack_context, int64_t snum) { + uint64_t num = (uint64_t)(snum < 0 ? -snum : snum); + bool neg = (snum < 0); + + int bitlen = significant_bits_part_length(num); + bitlen++; // add sign bit + if (neg) { + int sign_pos = expand_bit_len(bitlen); + uint64_t sign_bit_mask = (uint64_t)1 << sign_pos; + num |= sign_bit_mask; + } + pack_uint_data_helper(pack_context, num, bitlen); +} + +void chainpack_pack_uint(cpcp_pack_context *pack_context, uint64_t i) { + if (pack_context->err_no) + return; + if (i < 64) { + cpcp_pack_copy_byte(pack_context, i % 64); + } else { + cpcp_pack_copy_byte(pack_context, CP_UInt); + chainpack_pack_uint_data(pack_context, i); + } +} + +void chainpack_pack_int(cpcp_pack_context *pack_context, int64_t i) { + if (pack_context->err_no) + return; + if (i >= 0 && i < 64) { + cpcp_pack_copy_byte(pack_context, (uint8_t)((i % 64) + 64)); + } else { + cpcp_pack_copy_byte(pack_context, CP_Int); + chainpack_pack_int_data(pack_context, i); + } +} + +void chainpack_pack_decimal(cpcp_pack_context *pack_context, const cpcp_decimal *v) { + if (pack_context->err_no) + return; + cpcp_pack_copy_byte(pack_context, CP_Decimal); + chainpack_pack_int_data(pack_context, v->mantisa); + chainpack_pack_int_data(pack_context, v->exponent); +} + +void chainpack_pack_double(cpcp_pack_context *pack_context, double d) { + if (pack_context->err_no) + return; + + cpcp_pack_copy_byte(pack_context, CP_Double); + + const uint8_t *bytes = (const uint8_t *)&d; + int len = sizeof(double); + + int n = 1; + int i; + if (*(char *)&n == 1) { + // little endian if true + for (i = 0; i < len; i++) + cpcp_pack_copy_byte(pack_context, bytes[i]); + } else { + for (i = len - 1; i >= 0; i--) + cpcp_pack_copy_byte(pack_context, bytes[i]); + } +} + +void chainpack_pack_date_time(cpcp_pack_context *pack_context, const cpcp_date_time *v) { + if (pack_context->err_no) + return; + + cpcp_pack_copy_byte(pack_context, CP_DateTime); + + // some arguable optimizations when msec == 0 or TZ_offset == 0 + // this can save byte in packed date-time, but packing scheme is more + // complicated + int64_t msecs = v->msecs_since_epoch - SHV_EPOCH_MSEC; + int offset = (v->minutes_from_utc / 15) & 0x7F; + int ms = (int)(msecs % 1000); + if (ms == 0) + msecs /= 1000; + if (offset != 0) { + msecs *= 128; + msecs |= offset; + } + msecs *= 4; + if (offset != 0) + msecs |= 1; + if (ms == 0) + msecs |= 2; + chainpack_pack_int_data(pack_context, msecs); +} + +void chainpack_pack_null(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + + cpcp_pack_copy_byte(pack_context, CP_Null); +} + +static void chainpack_pack_true(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + cpcp_pack_copy_byte(pack_context, CP_TRUE); +} + +static void chainpack_pack_false(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + cpcp_pack_copy_byte(pack_context, CP_FALSE); +} + +void chainpack_pack_boolean(cpcp_pack_context *pack_context, bool b) { + if (pack_context->err_no) + return; + if (b) + chainpack_pack_true(pack_context); + else + chainpack_pack_false(pack_context); +} + +void chainpack_pack_list_begin(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + cpcp_pack_copy_byte(pack_context, CP_List); +} + +void chainpack_pack_map_begin(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + cpcp_pack_copy_byte(pack_context, CP_Map); +} + +void chainpack_pack_imap_begin(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + cpcp_pack_copy_byte(pack_context, CP_IMap); +} + +void chainpack_pack_meta_begin(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + cpcp_pack_copy_byte(pack_context, CP_MetaMap); +} + +void chainpack_pack_container_end(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + cpcp_pack_copy_byte(pack_context, CP_TERM); +} + +void chainpack_pack_blob( + cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len) { + chainpack_pack_blob_start(pack_context, buff_len, buff, buff_len); +} + +void chainpack_pack_blob_start(cpcp_pack_context *pack_context, + size_t string_len, const uint8_t *buff, size_t buff_len) { + cpcp_pack_copy_byte(pack_context, CP_Blob); + chainpack_pack_uint_data(pack_context, string_len); + cpcp_pack_copy_bytes(pack_context, buff, buff_len); +} + +void chainpack_pack_blob_cont( + cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len) { + cpcp_pack_copy_bytes(pack_context, buff, buff_len); +} + +void chainpack_pack_string( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { + chainpack_pack_string_start(pack_context, buff_len, buff, buff_len); +} + +void chainpack_pack_string_start(cpcp_pack_context *pack_context, + size_t string_len, const char *buff, size_t buff_len) { + cpcp_pack_copy_byte(pack_context, CP_String); + chainpack_pack_uint_data(pack_context, string_len); + cpcp_pack_copy_bytes(pack_context, buff, buff_len); +} + +void chainpack_pack_string_cont( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { + cpcp_pack_copy_bytes(pack_context, buff, buff_len); +} + +void chainpack_pack_cstring( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { + chainpack_pack_cstring_start(pack_context, buff, buff_len); + chainpack_pack_cstring_finish(pack_context); +} + +void chainpack_pack_cstring_terminated( + cpcp_pack_context *pack_context, const char *str) { + size_t len = strlen(str); + chainpack_pack_cstring(pack_context, str, len); +} + +void chainpack_pack_cstring_start( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { + cpcp_pack_copy_byte(pack_context, CP_CString); + copy_bytes_cstring(pack_context, buff, buff_len); +} + +void chainpack_pack_cstring_cont( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { + copy_bytes_cstring(pack_context, buff, buff_len); +} + +void chainpack_pack_cstring_finish(cpcp_pack_context *pack_context) { + cpcp_pack_copy_byte(pack_context, '\0'); +} + +//============================ U N P A C K ================================= + +/// @pbitlen is used to enable same function usage for signed int unpacking +static void unpack_uint(cpcp_unpack_context *unpack_context, int *pbitlen) { + if (pbitlen) + *pbitlen = 0; + uint64_t num = 0; + int bitlen = 0; + + const char *p; + UNPACK_TAKE_BYTE(p); + uint8_t head = (uint8_t)(*p); + + int bytes_to_read_cnt; + if ((head & 128) == 0) { + bytes_to_read_cnt = 0; + num = head & 127; + bitlen = 7; + } else if ((head & 64) == 0) { + bytes_to_read_cnt = 1; + num = head & 63; + bitlen = 6 + 8; + } else if ((head & 32) == 0) { + bytes_to_read_cnt = 2; + num = head & 31; + bitlen = 5 + 2 * 8; + } else if ((head & 16) == 0) { + bytes_to_read_cnt = 3; + num = head & 15; + bitlen = 4 + 3 * 8; + } else { + bytes_to_read_cnt = (head & 0xf) + 4; + bitlen = bytes_to_read_cnt * 8; + } + int i; + for (i = 0; i < bytes_to_read_cnt; ++i) { + UNPACK_TAKE_BYTE(p); + uint8_t r = (uint8_t)(*p); + num = (num << 8) + r; + }; + + unpack_context->item.as.UInt = num; + unpack_context->item.type = CPCP_ITEM_UINT; + if (pbitlen) + *pbitlen = bitlen; +} + +static void unpack_int(cpcp_unpack_context *unpack_context, int64_t *pval) { + int64_t snum = 0; + int bitlen; + unpack_uint(unpack_context, &bitlen); + if (unpack_context->err_no == CPCP_RC_OK) { + const uint64_t sign_bit_mask = (uint64_t)1 << (bitlen - 1); + uint64_t num = unpack_context->item.as.UInt; + snum = (int64_t)num; + + // Note: masked value assignment to bool variable would be undefined on + // some platforms. + + if (num & sign_bit_mask) { + snum &= ~(int64_t)sign_bit_mask; + snum = -snum; + } + } + if (pval) + *pval = snum; +} + +void unpack_string(cpcp_unpack_context *unpack_context) { + const char *p; + cpcp_string *it = &unpack_context->item.as.String; + + bool is_cstr = it->string_size < 0; + if (is_cstr) { + for (it->chunk_size = 0; it->chunk_size < it->chunk_buff_len;) { + UNPACK_TAKE_BYTE(p); + if (*p == '\\') { + UNPACK_TAKE_BYTE(p); + if (!p) + return; + switch (*p) { + case '\\': + (it->chunk_start)[it->chunk_size++] = '\\'; + break; + case '0': + (it->chunk_start)[it->chunk_size++] = '\0'; + break; + default: + (it->chunk_start)[it->chunk_size++] = *p; + break; + } + break; + } + if (*p == '\0') { + // end of string + it->last_chunk = 1; + break; + } + + (it->chunk_start)[it->chunk_size++] = *p; + } + } else { + it->chunk_size = 0; + while (it->size_to_load > 0 && it->chunk_size < it->chunk_buff_len) { + UNPACK_TAKE_BYTE(p); + (it->chunk_start)[it->chunk_size++] = *p; + it->size_to_load--; + } + it->last_chunk = (it->size_to_load == 0); + } + it->chunk_cnt++; + unpack_context->item.type = CPCP_ITEM_STRING; +} + +void unpack_blob(cpcp_unpack_context *unpack_context) { + unpack_string(unpack_context); + unpack_context->item.type = CPCP_ITEM_BLOB; +} + +void chainpack_unpack_next(cpcp_unpack_context *unpack_context) { + if (unpack_context->err_no) + return; + + if (unpack_context->item.type == CPCP_ITEM_STRING || + unpack_context->item.type == CPCP_ITEM_BLOB) { + cpcp_string *str_it = &unpack_context->item.as.String; + if (!str_it->last_chunk) { + if (unpack_context->item.type == CPCP_ITEM_STRING) + unpack_string(unpack_context); + else + unpack_blob(unpack_context); + return; + } + } + + const char *p; + UNPACK_TAKE_BYTE(p); + + uint8_t packing_schema = (uint8_t)(*p); + + cpcp_container_state *top_cont_state = + cpcp_unpack_context_top_container_state(unpack_context); + if (top_cont_state && packing_schema != CP_TERM) { + top_cont_state->item_count++; + } + + unpack_context->item.type = CPCP_ITEM_INVALID; + if (packing_schema < 128) { + if (packing_schema & 64) { + // tiny Int + unpack_context->item.type = CPCP_ITEM_INT; + unpack_context->item.as.Int = packing_schema & 63; + } else { + // tiny UInt + unpack_context->item.type = CPCP_ITEM_UINT; + unpack_context->item.as.UInt = packing_schema & 63; + } + } else { + switch (packing_schema) { + case CP_Null: { + unpack_context->item.type = CPCP_ITEM_NULL; + break; + } + case CP_TRUE: { + unpack_context->item.type = CPCP_ITEM_BOOLEAN; + unpack_context->item.as.Bool = 1; + break; + } + case CP_FALSE: { + unpack_context->item.type = CPCP_ITEM_BOOLEAN; + unpack_context->item.as.Bool = 0; + break; + } + case CP_Int: { + int64_t n; + unpack_int(unpack_context, &n); + unpack_context->item.type = CPCP_ITEM_INT; + unpack_context->item.as.Int = n; + break; + } + case CP_UInt: { + unpack_uint(unpack_context, NULL); + break; + } + case CP_Double: { + unpack_context->item.type = CPCP_ITEM_DOUBLE; + uint8_t *bytes = (uint8_t *)&(unpack_context->item.as.Double); + int len = sizeof(double); + + int n = 1; + int i; + if (*(char *)&n == 1) { + // little endian if true + for (i = 0; i < len; i++) { + UNPACK_TAKE_BYTE(p); + bytes[i] = (uint8_t)(*p); + } + } else { + for (i = len - 1; i >= 0; i--) { + UNPACK_TAKE_BYTE(p); + bytes[i] = (uint8_t)(*p); + } + } + break; + } + case CP_Decimal: { + int64_t mant; + unpack_int(unpack_context, &mant); + int64_t exp; + unpack_int(unpack_context, &exp); + unpack_context->item.type = CPCP_ITEM_DECIMAL; + unpack_context->item.as.Decimal.mantisa = mant; + unpack_context->item.as.Decimal.exponent = (int)exp; + break; + } + case CP_DateTime: { + int64_t d; + unpack_int(unpack_context, &d); + int8_t offset = 0; + bool has_tz_offset = d & 1; + bool has_not_msec = d & 2; + d >>= 2; + if (has_tz_offset) { + offset = d & 0x7F; + offset = (int8_t)(offset << 1); + offset >>= 1; // sign extension + d >>= 7; + } + if (has_not_msec) + d *= 1000; + d += SHV_EPOCH_MSEC; + + unpack_context->item.type = CPCP_ITEM_DATE_TIME; + cpcp_date_time *it = &unpack_context->item.as.DateTime; + it->msecs_since_epoch = d; + it->minutes_from_utc = offset * (int)15; + break; + } + case CP_MetaMap: { + unpack_context->item.type = CPCP_ITEM_META; + cpcp_unpack_context_push_container_state( + unpack_context, unpack_context->item.type); + break; + } + case CP_Map: { + unpack_context->item.type = CPCP_ITEM_MAP; + cpcp_unpack_context_push_container_state( + unpack_context, unpack_context->item.type); + break; + } + case CP_IMap: { + unpack_context->item.type = CPCP_ITEM_IMAP; + cpcp_unpack_context_push_container_state( + unpack_context, unpack_context->item.type); + break; + } + case CP_List: { + unpack_context->item.type = CPCP_ITEM_LIST; + cpcp_unpack_context_push_container_state( + unpack_context, unpack_context->item.type); + break; + } + case CP_TERM: { + unpack_context->item.type = CPCP_ITEM_CONTAINER_END; + cpcp_unpack_context_pop_container_state(unpack_context); + break; + } + case CP_Blob: { + cpcp_string *it = &unpack_context->item.as.String; + cpcp_string_init(it, unpack_context); + unpack_uint(unpack_context, NULL); + if (unpack_context->err_no == CPCP_RC_OK) { + it->string_size = (long)(unpack_context->item.as.UInt); + it->size_to_load = it->string_size; + unpack_blob(unpack_context); + } + break; + } + case CP_String: { + cpcp_string *it = &unpack_context->item.as.String; + cpcp_string_init(it, unpack_context); + unpack_uint(unpack_context, NULL); + if (unpack_context->err_no == CPCP_RC_OK) { + it->string_size = (long)(unpack_context->item.as.UInt); + it->size_to_load = it->string_size; + unpack_string(unpack_context); + } + break; + } + case CP_CString: { + cpcp_string *it = &unpack_context->item.as.String; + cpcp_string_init(it, unpack_context); + it->string_size = -1; + it->size_to_load = it->string_size; + unpack_string(unpack_context); + break; + } + default: + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Invalid type info."); + } + } +} + +uint64_t chainpack_unpack_uint_data(cpcp_unpack_context *unpack_context, bool *ok) { + int err_code; + uint64_t n = chainpack_unpack_uint_data2(unpack_context, &err_code); + if (ok) + *ok = (err_code == CPCP_RC_OK); + return n; +} + +uint64_t chainpack_unpack_uint_data2( + cpcp_unpack_context *unpack_context, int *err_code) { + unpack_uint(unpack_context, NULL); + if (err_code) + *err_code = unpack_context->err_no; + return unpack_context->item.as.UInt; +} diff --git a/libshvchainpack/cpcp.c b/libshvchainpack/cpcp.c new file mode 100644 index 0000000..99a773f --- /dev/null +++ b/libshvchainpack/cpcp.c @@ -0,0 +1,409 @@ +#include + +#include + +const char *cpcp_error_string(int err_no) { + switch (err_no) { + case CPCP_RC_OK: + return ""; + case CPCP_RC_MALLOC_ERROR: + return "MALLOC_ERROR"; + case CPCP_RC_BUFFER_OVERFLOW: + return "BUFFER_OVERFLOW"; + case CPCP_RC_BUFFER_UNDERFLOW: + return "BUFFER_UNDERFLOW"; + case CPCP_RC_MALFORMED_INPUT: + return "MALFORMED_INPUT"; + case CPCP_RC_LOGICAL_ERROR: + return "LOGICAL_ERROR"; + case CPCP_RC_CONTAINER_STACK_OVERFLOW: + return "CONTAINER_STACK_OVERFLOW"; + case CPCP_RC_CONTAINER_STACK_UNDERFLOW: + return "CONTAINER_STACK_UNDERFLOW"; + default: + return "UNKNOWN"; + } +} + +//=========================== PACK ============================ +void cpcp_pack_context_init(cpcp_pack_context *pack_context, void *data, + size_t length, cpcp_pack_overflow_handler poh) { + pack_context->start = pack_context->current = (char *)data; + pack_context->end = pack_context->start; + if (length) { + pack_context->end += length; + } + pack_context->err_no = 0; + pack_context->handle_pack_overflow = poh; + pack_context->err_no = CPCP_RC_OK; + pack_context->cpon_options.indent = NULL; + pack_context->cpon_options.json_output = 0; + pack_context->nest_count = 0; + pack_context->custom_context = NULL; + pack_context->bytes_written = 0; +} + +void cpcp_pack_context_dry_run_init(cpcp_pack_context *pack_context) { + cpcp_pack_context_init(pack_context, NULL, 0, NULL); +} + +static bool is_dry_run(cpcp_pack_context *pack_context) { + return pack_context->handle_pack_overflow == NULL && + pack_context->start == NULL && pack_context->end == NULL && + pack_context->current == NULL; +} + +// try to make size_hint bytes space in pack_context +// returns number of bytes available in pack_context buffer, can be < size_hint, +// but always > 0 returns 0 if fails +static size_t cpcp_pack_make_space( + cpcp_pack_context *pack_context, size_t size_hint) { + if (pack_context->err_no != CPCP_RC_OK) + return 0; + size_t free_space = (size_t)(pack_context->end - pack_context->current); + if (free_space < size_hint) { + if (!pack_context->handle_pack_overflow) { + pack_context->err_no = CPCP_RC_BUFFER_OVERFLOW; + return 0; + } + pack_context->handle_pack_overflow(pack_context, size_hint); + free_space = (size_t)(pack_context->end - pack_context->current); + if (free_space < 1) { + pack_context->err_no = CPCP_RC_BUFFER_OVERFLOW; + return 0; + } + } + return free_space; +} + +// alocate more bytes, move current after allocated bytec +static char *cpcp_pack_reserve_space(cpcp_pack_context *pack_context, size_t more) { + if (pack_context->err_no != CPCP_RC_OK) + return NULL; + size_t free_space = cpcp_pack_make_space(pack_context, more); + if (free_space < more) { + pack_context->err_no = CPCP_RC_BUFFER_OVERFLOW; + return NULL; + } + char *p = pack_context->current; + pack_context->current = p + more; + return p; +} + +size_t cpcp_pack_copy_byte(cpcp_pack_context *pack_context, uint8_t b) { + pack_context->bytes_written += 1; + if (is_dry_run(pack_context)) + return 1; + + char *p = cpcp_pack_reserve_space(pack_context, 1); + if (!p) + return 0; + *p = (char)b; + return 1; +} + +size_t cpcp_pack_copy_bytes( + cpcp_pack_context *pack_context, const void *str, size_t len) { + pack_context->bytes_written += len; + if (is_dry_run(pack_context)) + return len; + + size_t copied = 0; + while (pack_context->err_no == CPCP_RC_OK && copied < len) { + size_t buff_size = cpcp_pack_make_space(pack_context, len); + if (buff_size == 0) { + pack_context->err_no = CPCP_RC_BUFFER_OVERFLOW; + return 0; + } + size_t rest = len - copied; + if (rest > buff_size) + rest = buff_size; + memcpy(pack_context->current, ((const char *)str) + copied, rest); + copied += rest; + pack_context->current += rest; + } + return copied; +} + +//================================ UNPACK ================================ +const char *cpcp_item_type_to_string(cpcp_item_types t) { + switch (t) { + case CPCP_ITEM_INVALID: + break; + case CPCP_ITEM_NULL: + return "NULL"; + case CPCP_ITEM_BOOLEAN: + return "BOOLEAN"; + case CPCP_ITEM_INT: + return "INT"; + case CPCP_ITEM_UINT: + return "UINT"; + case CPCP_ITEM_DOUBLE: + return "DOUBLE"; + case CPCP_ITEM_DECIMAL: + return "DECIMAL"; + case CPCP_ITEM_BLOB: + return "BLOB"; + case CPCP_ITEM_STRING: + return "STRING"; + case CPCP_ITEM_DATE_TIME: + return "DATE_TIME"; + case CPCP_ITEM_LIST: + return "LIST"; + case CPCP_ITEM_MAP: + return "MAP"; + case CPCP_ITEM_IMAP: + return "IMAP"; + case CPCP_ITEM_META: + return "META"; + case CPCP_ITEM_CONTAINER_END: + return "CONTAINER_END"; + } + return "INVALID"; +} + +void cpcp_container_state_init( + cpcp_container_state *self, cpcp_item_types cont_type) { + self->container_type = cont_type; + self->current_item_type = CPCP_ITEM_INVALID; + self->item_count = 0; +} + +void cpcp_container_stack_init(cpcp_container_stack *self, + cpcp_container_state *states, size_t capacity, + cpcp_container_stack_overflow_handler hnd) { + self->container_states = states; + self->capacity = capacity; + self->length = 0; + self->overflow_handler = hnd; +} + +void cpcp_unpack_context_init(cpcp_unpack_context *self, const void *data, + size_t length, cpcp_unpack_underflow_handler huu, cpcp_container_stack *stack) { + self->item.type = CPCP_ITEM_INVALID; + self->start = self->current = (const char *)data; + self->end = self->start + length; + self->err_no = CPCP_RC_OK; + self->parser_line_no = 1; + self->err_msg = ""; + self->handle_unpack_underflow = huu; + self->custom_context = NULL; + self->container_stack = stack; + self->string_chunk_buff = self->default_string_chunk_buff; + self->string_chunk_buff_len = sizeof(self->default_string_chunk_buff); +} + +cpcp_container_state *cpcp_unpack_context_push_container_state( + cpcp_unpack_context *self, cpcp_item_types container_type) { + if (!self->container_stack) { + // C++ implementation does not require container states stack + return NULL; + } + if (self->container_stack->length == self->container_stack->capacity) { + if (!self->container_stack->overflow_handler) { + self->err_no = CPCP_RC_CONTAINER_STACK_OVERFLOW; + return NULL; + } + int rc = self->container_stack->overflow_handler(self->container_stack); + if (rc < 0) { + self->err_no = CPCP_RC_CONTAINER_STACK_OVERFLOW; + return NULL; + } + } + if (self->container_stack->length < self->container_stack->capacity - 1) { + cpcp_container_state *state = self->container_stack->container_states + + self->container_stack->length; + cpcp_container_state_init(state, container_type); + self->container_stack->length++; + return state; + } + self->err_no = CPCP_RC_CONTAINER_STACK_OVERFLOW; + return NULL; +} + +cpcp_container_state *cpcp_unpack_context_top_container_state( + cpcp_unpack_context *self) { + if (self->container_stack && self->container_stack->length > 0) { + return self->container_stack->container_states + + self->container_stack->length - 1; + } + return NULL; +} + +cpcp_container_state *cpcp_unpack_context_parent_container_state( + cpcp_unpack_context *self) { + if (self->container_stack && self->container_stack->length > 0) { + cpcp_container_state *top_st = self->container_stack->container_states + + self->container_stack->length - 1; + if (top_st && top_st->item_count == 0) { + if (self->container_stack->length > 1) + return self->container_stack->container_states + + self->container_stack->length - 2; + + return NULL; + } + return top_st; + } + return NULL; +} + +cpcp_container_state *cpcp_unpack_context_closed_container_state( + cpcp_unpack_context *self) { + if (self->container_stack && self->item.type == CPCP_ITEM_CONTAINER_END) { + cpcp_container_state *st = self->container_stack->container_states + + self->container_stack->length; + return st; + } + return NULL; +} + +void cpcp_unpack_context_pop_container_state(cpcp_unpack_context *self) { + if (self->container_stack && self->container_stack->length > 0) { + self->container_stack->length--; + } +} + +void cpcp_string_init(cpcp_string *self, cpcp_unpack_context *unpack_context) { + self->chunk_size = 0; + self->chunk_cnt = 0; + self->last_chunk = 0; + self->string_size = -1; + self->size_to_load = -1; + self->chunk_start = unpack_context->string_chunk_buff; + self->chunk_buff_len = unpack_context->string_chunk_buff_len; + self->blob_hex = 0; +} + +const char *cpcp_unpack_take_byte(cpcp_unpack_context *unpack_context) { + const char *p = cpcp_unpack_peek_byte(unpack_context); + if (p) + unpack_context->current++; + return p; +} + +const char *cpcp_unpack_peek_byte(cpcp_unpack_context *unpack_context) { + static const size_t more = 1; + if (unpack_context->current >= unpack_context->end) { + if (!unpack_context->handle_unpack_underflow) { + unpack_context->err_no = CPCP_RC_BUFFER_UNDERFLOW; + return NULL; + } + size_t sz = unpack_context->handle_unpack_underflow(unpack_context); + if (sz < more) { + unpack_context->err_no = CPCP_RC_BUFFER_UNDERFLOW; + return NULL; + } + unpack_context->current = unpack_context->start; + } + const char *p = unpack_context->current; + return p; +} + +double cpcp_exponentional_to_double( + int64_t const mantisa, const int exponent, const int base) { + double d = (double)mantisa; + int i; + for (i = 0; i < exponent; ++i) + d *= base; + for (i = exponent; i < 0; ++i) + d /= base; + return d; +} + +double cpcp_decimal_to_double(const int64_t mantisa, const int exponent) { + return cpcp_exponentional_to_double(mantisa, exponent, 10); +} + +static size_t int_to_str(char *buff, size_t buff_len, int64_t val) { + size_t n = 0; + bool neg = false; + char *str = buff; + size_t i; + if (val < 0) { + neg = true; + val = -val; + str = buff + 1; + buff_len--; + } + if (val == 0) { + if (n == buff_len) + return 0; + str[n++] = '0'; + } else + while (val != 0) { + int d = (int)(val % 10); + val /= 10; + if (n == buff_len) + return 0; + str[n++] = '0' + (char)d; + } + for (i = 0; i < n / 2; ++i) { + char c = str[i]; + str[i] = str[n - i - 1]; + str[n - i - 1] = c; + } + if (neg) { + buff[0] = '-'; + n++; + } + return n; +} + +size_t cpcp_decimal_to_string( + char *buff, size_t buff_len, int64_t mantisa, int exponent) { + bool neg = false; + if (mantisa < 0) { + mantisa = -mantisa; + neg = true; + } + + // at least 21 characters for 64-bit types. + char *str = buff; + if (neg) { + str++; + buff_len--; + } + size_t mantisa_str_len = int_to_str(str, buff_len, mantisa); + if (mantisa_str_len == 0) { + return mantisa_str_len; + } + + size_t dec_places = (exponent < 0) ? (size_t)(-exponent) : 0; + if (dec_places > 0 && dec_places < mantisa_str_len) { + size_t dot_ix = mantisa_str_len - dec_places; + for (size_t i = dot_ix; i < mantisa_str_len; ++i) + str[mantisa_str_len + dot_ix - i] = + str[mantisa_str_len + dot_ix - i - 1]; + str[dot_ix] = '.'; + mantisa_str_len++; + } else if (dec_places > 0 && dec_places <= 3) { + size_t extra_0_cnt = dec_places - mantisa_str_len; + for (size_t i = 0; i < mantisa_str_len; ++i) + str[mantisa_str_len - i - 1 + extra_0_cnt + 2] = + str[mantisa_str_len - i - 1]; + str[0] = '0'; + str[1] = '.'; + for (size_t i = 0; i < extra_0_cnt; ++i) + str[2 + i] = '0'; + mantisa_str_len += extra_0_cnt + 2; + } else if (exponent > 0 && mantisa_str_len + (unsigned)exponent <= 9) { + for (size_t i = 0; i < (unsigned)exponent; ++i) + str[mantisa_str_len++] = '0'; + str[mantisa_str_len++] = '.'; + } else if (exponent == 0) { + str[mantisa_str_len++] = '.'; + } else { + str[mantisa_str_len++] = 'e'; + size_t n2 = int_to_str( + str + mantisa_str_len, buff_len - mantisa_str_len, exponent); + if (n2 == 0) { + return n2; + } + mantisa_str_len += n2; + } + if (neg) { + buff[0] = '-'; + mantisa_str_len++; + } + return mantisa_str_len; +} diff --git a/libshvchainpack/cpcp_convert.c b/libshvchainpack/cpcp_convert.c new file mode 100644 index 0000000..b2238e0 --- /dev/null +++ b/libshvchainpack/cpcp_convert.c @@ -0,0 +1,282 @@ +#include +#include + +#include + +void cpcp_convert(cpcp_unpack_context *in_ctx, cpcp_pack_format in_format, + cpcp_pack_context *out_ctx, cpcp_pack_format out_format) { + if (!in_ctx->container_stack) { + // cpcp_convert() cannot worj without input context container state set + in_ctx->err_no = CPCP_RC_LOGICAL_ERROR; + return; + } + bool o_cpon_input = (in_format == CPCP_Cpon); + bool o_chainpack_output = (out_format == CPCP_ChainPack); + bool meta_just_closed = false; + do { + if (o_cpon_input) + cpon_unpack_next(in_ctx); + else + chainpack_unpack_next(in_ctx); + if (in_ctx->err_no != CPCP_RC_OK) + break; + + cpcp_container_state *parent_state = + cpcp_unpack_context_parent_container_state(in_ctx); + if (o_chainpack_output) { + } else { + if (parent_state != NULL) { + bool is_string_concat = 0; + if (in_ctx->item.type == CPCP_ITEM_STRING) { + cpcp_string *it = &(in_ctx->item.as.String); + if (it->chunk_cnt > 1) { + // multichunk string + // this can happen, when parsed string is greater than + // unpack_context buffer or escape sequence is + // encountered concatenate it with previous chunk + is_string_concat = 1; + } + } + if (!is_string_concat && + in_ctx->item.type != CPCP_ITEM_CONTAINER_END) { + switch (parent_state->container_type) { + case CPCP_ITEM_LIST: + if (!meta_just_closed) + cpon_pack_field_delim(out_ctx, + parent_state->item_count == 1, false); + break; + case CPCP_ITEM_MAP: + case CPCP_ITEM_IMAP: + case CPCP_ITEM_META: { + bool is_key = (parent_state->item_count % 2); + if (is_key) { + if (!meta_just_closed) + cpon_pack_field_delim(out_ctx, + parent_state->item_count == 1, false); + } else { + // delimite value + if (!meta_just_closed) + cpon_pack_key_val_delim(out_ctx); + } + break; + } + default: + break; + } + } + } + } + meta_just_closed = false; + switch (in_ctx->item.type) { + case CPCP_ITEM_INVALID: { + // end of input + break; + } + case CPCP_ITEM_NULL: { + if (o_chainpack_output) + chainpack_pack_null(out_ctx); + else + cpon_pack_null(out_ctx); + break; + } + case CPCP_ITEM_LIST: { + if (o_chainpack_output) + chainpack_pack_list_begin(out_ctx); + else + cpon_pack_list_begin(out_ctx); + break; + } + case CPCP_ITEM_MAP: { + if (o_chainpack_output) + chainpack_pack_map_begin(out_ctx); + else + cpon_pack_map_begin(out_ctx); + break; + } + case CPCP_ITEM_IMAP: { + if (o_chainpack_output) + chainpack_pack_imap_begin(out_ctx); + else + cpon_pack_imap_begin(out_ctx); + break; + } + case CPCP_ITEM_META: { + if (o_chainpack_output) + chainpack_pack_meta_begin(out_ctx); + else + cpon_pack_meta_begin(out_ctx); + break; + } + case CPCP_ITEM_CONTAINER_END: { + cpcp_container_state *st = + cpcp_unpack_context_closed_container_state(in_ctx); + if (!st) { + in_ctx->err_no = CPCP_RC_CONTAINER_STACK_UNDERFLOW; + return; + } + meta_just_closed = (st->container_type == CPCP_ITEM_META); + + if (o_chainpack_output) { + chainpack_pack_container_end(out_ctx); + } else { + switch (st->container_type) { + case CPCP_ITEM_LIST: + cpon_pack_list_end(out_ctx, false); + break; + case CPCP_ITEM_MAP: + cpon_pack_map_end(out_ctx, false); + break; + case CPCP_ITEM_IMAP: + cpon_pack_imap_end(out_ctx, false); + break; + case CPCP_ITEM_META: + cpon_pack_meta_end(out_ctx, false); + break; + default: + // cannot finish Cpon container without container + // type info + in_ctx->err_no = CPCP_RC_LOGICAL_ERROR; + return; + } + } + break; + } + case CPCP_ITEM_BLOB: { + cpcp_string *it = &in_ctx->item.as.String; + if (o_chainpack_output) { + if (it->chunk_cnt == 1 && it->last_chunk) { + // one chunk string with known length is always packed + // as RAW + chainpack_pack_blob(out_ctx, (uint8_t *)it->chunk_start, + it->chunk_size); + } else if (it->string_size >= 0) { + if (it->chunk_cnt == 1) + chainpack_pack_blob_start(out_ctx, + (size_t)(it->string_size), + (uint8_t *)it->chunk_start, + (size_t)(it->string_size)); + else + chainpack_pack_blob_cont(out_ctx, + (uint8_t *)it->chunk_start, it->chunk_size); + } else { + // cstring + // not supported, there is nothing like CBlob + } + } else { + // Cpon + if (it->chunk_cnt == 1) + cpon_pack_blob_start(out_ctx, + (uint8_t *)it->chunk_start, it->chunk_size); + else + cpon_pack_blob_cont(out_ctx, (uint8_t *)it->chunk_start, + (unsigned)(it->chunk_size)); + if (it->last_chunk) + cpon_pack_blob_finish(out_ctx); + } + break; + } + case CPCP_ITEM_STRING: { + cpcp_string *it = &in_ctx->item.as.String; + if (o_chainpack_output) { + if (it->chunk_cnt == 1 && it->last_chunk) { + // one chunk string with known length is always packed + // as RAW + chainpack_pack_string( + out_ctx, it->chunk_start, it->chunk_size); + } else if (it->string_size >= 0) { + if (it->chunk_cnt == 1) + chainpack_pack_string_start(out_ctx, + (size_t)(it->string_size), it->chunk_start, + (size_t)(it->string_size)); + else + chainpack_pack_string_cont( + out_ctx, it->chunk_start, it->chunk_size); + } else { + // cstring + if (it->chunk_cnt == 1) + chainpack_pack_cstring_start( + out_ctx, it->chunk_start, it->chunk_size); + else + chainpack_pack_cstring_cont( + out_ctx, it->chunk_start, it->chunk_size); + if (it->last_chunk) + chainpack_pack_cstring_finish(out_ctx); + } + } else { + // Cpon + if (it->chunk_cnt == 1) + cpon_pack_string_start( + out_ctx, it->chunk_start, it->chunk_size); + else + cpon_pack_string_cont(out_ctx, it->chunk_start, + (unsigned)(it->chunk_size)); + if (it->last_chunk) + cpon_pack_string_finish(out_ctx); + } + break; + } + case CPCP_ITEM_BOOLEAN: { + if (o_chainpack_output) + chainpack_pack_boolean(out_ctx, in_ctx->item.as.Bool); + else + cpon_pack_boolean(out_ctx, in_ctx->item.as.Bool); + break; + } + case CPCP_ITEM_INT: { + if (o_chainpack_output) + chainpack_pack_int(out_ctx, in_ctx->item.as.Int); + else + cpon_pack_int(out_ctx, in_ctx->item.as.Int); + break; + } + case CPCP_ITEM_UINT: { + if (o_chainpack_output) + chainpack_pack_uint(out_ctx, in_ctx->item.as.UInt); + else + cpon_pack_uint(out_ctx, in_ctx->item.as.UInt); + break; + } + case CPCP_ITEM_DECIMAL: { + if (o_chainpack_output) + chainpack_pack_decimal(out_ctx, &in_ctx->item.as.Decimal); + else + cpon_pack_decimal(out_ctx, &in_ctx->item.as.Decimal); + break; + } + case CPCP_ITEM_DOUBLE: { + if (o_chainpack_output) + chainpack_pack_double(out_ctx, in_ctx->item.as.Double); + else + cpon_pack_double(out_ctx, in_ctx->item.as.Double); + break; + } + case CPCP_ITEM_DATE_TIME: { + cpcp_date_time *it = &in_ctx->item.as.DateTime; + if (o_chainpack_output) + chainpack_pack_date_time(out_ctx, it); + else + cpon_pack_date_time(out_ctx, it); + break; + } + } + { + cpcp_container_state *top_state = + cpcp_unpack_context_top_container_state(in_ctx); + // take just one object from stream + if (!top_state) { + if (((in_ctx->item.type == CPCP_ITEM_STRING || + in_ctx->item.type == CPCP_ITEM_BLOB) && + !in_ctx->item.as.String.last_chunk) || + meta_just_closed) { + // do not stop parsing in the middle of the string + // or after meta + } else { + break; + } + } + } + } while (in_ctx->err_no == CPCP_RC_OK && out_ctx->err_no == CPCP_RC_OK); + + if (out_ctx->handle_pack_overflow) + out_ctx->handle_pack_overflow(out_ctx, 0); +} diff --git a/libshvchainpack/cpon.c b/libshvchainpack/cpon.c new file mode 100644 index 0000000..19b4c80 --- /dev/null +++ b/libshvchainpack/cpon.c @@ -0,0 +1,1433 @@ +#include + +#include +#include + +static inline uint8_t hexify(uint8_t b) { + if (b <= 9) + return b + '0'; + if (b >= 10 && b <= 15) + return (uint8_t)(b - 10 + 'a'); + return '?'; +} + +static inline int unhex(uint8_t b) { + if (b >= '0' && b <= '9') + return b - '0'; + if (b >= 'a' && b <= 'f') + return b - 'a' + 10; + if (b >= 'A' && b <= 'F') + return b - 'A' + 10; + return -1; +} + +static size_t uint_to_str(char *buff, size_t buff_len, uint64_t n) { + size_t len = 0; + if (n == 0) { + if (len < buff_len) + buff[len] = '0'; + len++; + } else + while (n > 0) { + char r = (char)(n % 10); + n /= 10; + if (len < buff_len) + buff[len++] = '0' + r; + } + if (len < buff_len) { + size_t i; + for (i = 0; i < len / 2; i++) { + char c = buff[i]; + buff[i] = buff[len - i - 1]; + buff[len - i - 1] = c; + } + } + return len; +} + +static size_t uint_to_str_lpad( + char *buff, size_t buff_len, uint64_t n, size_t width, char pad_char) { + size_t len = uint_to_str(buff, buff_len, n); + if (len < width && width <= buff_len) { + size_t i; + for (i = 0; i < len; ++i) { + buff[width - i - 1] = buff[len - i - 1]; + buff[len - i - 1] = pad_char; + } + return width; + } + return len; +} + +static size_t uint_dot_uint_to_str( + char *buff, size_t buff_len, uint64_t n1, uint64_t n2) { + size_t len = uint_to_str(buff, buff_len, n1); + if (len < buff_len) + buff[len] = '.'; + size_t dot_pos = len; + len++; + if (n2 > 0) { + len += uint_to_str(buff + len, (len < buff_len) ? buff_len - len : 0, n2); + if (len < buff_len) { + // remove trailing zeros + for (; len > dot_pos && buff[len - 1] == '0'; len--) + ; + } + } + return len; +} + +static size_t int_to_str(char *buff, size_t buff_len, int64_t n) { + size_t len = 0; + char *buff2 = buff; + if (n < 0) { + buff2 = buff + 1; + if (len < buff_len) + buff[len] = '-'; + len++; + n = -n; + } + len += uint_to_str(buff2, buff_len, (uint64_t)n); + return len; +} + +static size_t double_to_str(char *buff, size_t buff_len, double d) { + const int prec = 6; + unsigned prec_num = 1; + int i; + for (i = 0; i < prec; ++i) + prec_num *= 10; + + size_t len = 0; + if (d == 0) { + if (len < buff_len) + buff[len] = '0'; + len++; + if (len < buff_len) + buff[len] = '.'; + len++; + } else { + bool neg = d < 0; + if (neg) { + if (len < buff_len) + buff[len] = '-'; + len++; + d = -d; + } + if (d < 1e7 && d >= 0.1) { + /// float point notation + if (d < 1) { + for (i = 0; i < prec; ++i) + d *= 10; + unsigned ud = (unsigned)d; + len += uint_dot_uint_to_str( + buff + len, (len < buff_len) ? buff_len - len : 0, 0, ud); + } else { + double myprec = 1; + while (d >= 10) { + d /= 10; + myprec /= 10; + } + for (i = 0; i < prec; ++i) { + d *= 10; + myprec *= 10; + } + unsigned ud = (unsigned)d; + unsigned myprecnum = (unsigned)myprec; + len += uint_dot_uint_to_str(buff + len, + (len < buff_len) ? buff_len - len : 0, ud / myprecnum, + ud % myprecnum); + } + } else { + /// exponential notation + int exp = 0; + if (d < 1) { + while (d < 1) { + d *= 10; + exp--; + } + for (i = 0; i < prec; ++i) + d *= 10; + unsigned ud = (unsigned)d; + len += uint_dot_uint_to_str(buff + len, + (len < buff_len) ? buff_len - len : 0, ud / prec_num, + ud % prec_num); + } else { + while (d >= 10) { + d /= 10; + exp++; + } + for (i = 0; i < prec; ++i) + d *= 10; + unsigned ud = (unsigned)d; + len += uint_dot_uint_to_str(buff + len, + (len < buff_len) ? buff_len - len : 0, ud / prec_num, + ud % prec_num); + } + if (len < buff_len && buff[len - 1] == '.') // remove trailing dot + len--; + if (len < buff_len) + buff[len] = 'e'; + len++; + len += int_to_str( + buff + len, (len < buff_len) ? buff_len - len : 0, exp); + } + } + return len; +} + +// see +// http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15 +// see https://stackoverflow.com/questions/16647819/timegm-cross-platform +// see https://www.boost.org/doc/libs/1_62_0/boost/chrono/io/time_point_io.hpp +static int is_leap(int y) { + return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0); +} + +static int32_t days_from_0(int32_t year) { + year--; + return 365 * year + (year / 400) - (year / 100) + (year / 4); +} + +static int32_t days_from_1970(int32_t year) { + static int32_t days_from_0_to_1970 = 0; + if (days_from_0_to_1970 == 0) + days_from_0_to_1970 = days_from_0(1970); + return days_from_0(year) - days_from_0_to_1970; +} + +static int days_from_1jan(int year, int month, int mday) { + static const int days[2][12] = { + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, + {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}}; + + return days[is_leap(year)][month] + mday - 1; +} + +int64_t cpon_timegm(struct tm *tm) { + // leap seconds are not part of Posix + int64_t res = 0; + int year = tm->tm_year + 1900; + int month = tm->tm_mon; // 0 - 11 + int mday = tm->tm_mday; // 1 - 31 + res = days_from_1970(year); + res += days_from_1jan(year, month, mday); + res *= 24; + res += tm->tm_hour; + res *= 60; + res += tm->tm_min; + res *= 60; + res += tm->tm_sec; + return res; +} + +// Returns year/month/day triple in civil calendar +// Preconditions: z is number of days since 1970-01-01 and is in the range: +// [numeric_limits::min(), +// numeric_limits::max()-719468]. +static void civil_from_days(long long z, int *py, unsigned *pm, unsigned *pd) { + int y; + unsigned m; + unsigned d; + z += 719468; + const long long era = (z >= 0 ? z : z - 146096) / 146097; + const unsigned doe = (const unsigned)(z - era * 146097); // [0, 146096] + const unsigned yoe = + (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] + y = (int)(((long)yoe) + era * 400); + const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] + const unsigned mp = (5 * doy + 2) / 153; // [0, 11] + d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] + if (mp < 10) + m = mp + 3; + else + m = mp - 9; + y += (m <= 2); + --m; + if (py) + *py = y; + if (pm) + *pm = m; + if (pd) + *pd = d; +} + +void cpon_gmtime(int64_t epoch_sec, struct tm *tm) { + if (!tm) + return; + + const long seconds_in_day = 3600 * 24; + long long days_since_epoch = (epoch_sec / seconds_in_day); + long long hms = epoch_sec - seconds_in_day * days_since_epoch; + if (hms < 0) { + days_since_epoch -= 1; + hms = seconds_in_day + hms; + } + + int y; + unsigned m, d; + civil_from_days(days_since_epoch, &y, &m, &d); + tm->tm_year = y - 1900; + tm->tm_mon = (int)m; + tm->tm_mday = (int)d; + + tm->tm_hour = (int)(hms / 3600); + const int ms = (int)(hms % 3600); + tm->tm_min = ms / 60; + tm->tm_sec = ms % 60; + + tm->tm_isdst = -1; +} + +static const char CCPON_STR_NULL[] = "null"; +static const char CCPON_STR_TRUE[] = "true"; +static const char CCPON_STR_FALSE[] = "false"; +static const char CCPON_STR_IMAP_BEGIN[] = "i{"; +static const char CCPON_DATE_TIME_BEGIN[] = "d\""; + +enum { + CCPON_C_KEY_DELIM = ':', + CCPON_C_FIELD_DELIM = ',', + CCPON_C_LIST_BEGIN = '[', + CCPON_C_LIST_END = ']', + CCPON_C_ARRAY_END = ']', + CCPON_C_MAP_BEGIN = '{', + CCPON_C_MAP_END = '}', + CCPON_C_META_BEGIN = '<', + CCPON_C_META_END = '>', + CCPON_C_UNSIGNED_END = 'u' +}; + +#ifdef FORCE_NO_LIBRARY + +static void *memcpy(void *dst, const void *src, size_t n) { + unsigned int i; + uint8_t *d = (uint8_t *)dst, *s = (uint8_t *)src; + for (i = 0; i < n; i++) { + *d++ = *s++; + } + return dst; +} + +#endif + +//============================ P A C K ================================= +void cpon_pack_copy_str(cpcp_pack_context *pack_context, const char *str) { + size_t len = strlen(str); + cpcp_pack_copy_bytes(pack_context, str, len); +} + +static void start_block(cpcp_pack_context *pack_context) { + pack_context->nest_count++; +} + +static void indent_element( + cpcp_pack_context *pack_context, bool is_oneliner, bool is_first_field) { + if (pack_context->cpon_options.indent) { + if (is_oneliner) { + if (!is_first_field) + cpcp_pack_copy_byte(pack_context, ' '); + } else { + cpcp_pack_copy_bytes(pack_context, "\n", 1); + int i; + for (i = 0; i < pack_context->nest_count; ++i) { + cpon_pack_copy_str(pack_context, pack_context->cpon_options.indent); + } + } + } +} + +static void end_block(cpcp_pack_context *pack_context, bool is_oneliner) { + pack_context->nest_count--; + if (pack_context->cpon_options.indent) { + indent_element(pack_context, is_oneliner, true); + } +} + +void cpon_pack_uint(cpcp_pack_context *pack_context, uint64_t i) { + if (pack_context->err_no) + return; + + // at least 21 characters for 64-bit types. + static const unsigned LEN = 32; + char str[LEN]; + size_t n = uint_to_str(str, LEN, i); + if (n >= LEN) { + pack_context->err_no = CPCP_RC_LOGICAL_ERROR; + return; + } + cpcp_pack_copy_bytes(pack_context, str, n); + if (!pack_context->cpon_options.json_output) + cpcp_pack_copy_byte(pack_context, 'u'); +} + +void cpon_pack_int(cpcp_pack_context *pack_context, int64_t i) { + if (pack_context->err_no) + return; + + // at least 21 characters for 64-bit types. + static const unsigned LEN = 32; + char str[LEN]; + size_t n = int_to_str(str, LEN, i); + if (n >= LEN) { + pack_context->err_no = CPCP_RC_LOGICAL_ERROR; + return; + } + cpcp_pack_copy_bytes(pack_context, str, n); +} + + +void cpon_pack_decimal(cpcp_pack_context *pack_context, const cpcp_decimal *v) { + // at least 21 characters for 64-bit types. + static const size_t LEN = 64; + char buff[LEN]; + size_t n = cpcp_decimal_to_string(buff, LEN, v->mantisa, v->exponent); + if (n == 0) { + pack_context->err_no = CPCP_RC_LOGICAL_ERROR; + return; + } + + cpcp_pack_copy_bytes(pack_context, buff, (size_t)n); +} + +void cpon_pack_double(cpcp_pack_context *pack_context, double d) { + if (pack_context->err_no) + return; + + // at least 21 characters for 64-bit types. + static const unsigned LEN = 32; + char str[LEN]; + size_t n = double_to_str(str, LEN, d); + if (n >= LEN) { + pack_context->err_no = CPCP_RC_LOGICAL_ERROR; + return; + } + cpcp_pack_copy_bytes(pack_context, str, n); +} + +void cpon_pack_date_time(cpcp_pack_context *pack_context, const cpcp_date_time *v) { + /// ISO 8601 with msecs extension + cpcp_pack_copy_bytes( + pack_context, CCPON_DATE_TIME_BEGIN, sizeof(CCPON_DATE_TIME_BEGIN) - 1); + cpon_pack_date_time_str(pack_context, v->msecs_since_epoch, + v->minutes_from_utc, CCPON_Auto, true); + cpcp_pack_copy_bytes(pack_context, "\"", 1); +} + +void cpon_pack_date_time_str(cpcp_pack_context *pack_context, int64_t epoch_msecs, + int min_from_utc, cpon_msec_policy msec_policy, bool with_tz) { + struct tm tm; + cpon_gmtime(epoch_msecs / 1000 + min_from_utc * 60, &tm); + static const unsigned LEN = 32; + char str[LEN]; + size_t len = uint_to_str_lpad(str, LEN, (unsigned)tm.tm_year + 1900, 2, '0'); + if (len < LEN) + str[len] = '-'; + len++; + len += uint_to_str_lpad(str + len, (len < LEN) ? LEN - len : 0, + (unsigned)tm.tm_mon + 1, 2, '0'); + if (len < LEN) + str[len] = '-'; + len++; + len += uint_to_str_lpad( + str + len, (len < LEN) ? LEN - len : 0, (unsigned)tm.tm_mday, 2, '0'); + if (len < LEN) + str[len] = 'T'; + len++; + len += uint_to_str_lpad( + str + len, (len < LEN) ? LEN - len : 0, (unsigned)tm.tm_hour, 2, '0'); + if (len < LEN) + str[len] = ':'; + len++; + len += uint_to_str_lpad( + str + len, (len < LEN) ? LEN - len : 0, (unsigned)tm.tm_min, 2, '0'); + if (len < LEN) + str[len] = ':'; + len++; + len += uint_to_str_lpad( + str + len, (len < LEN) ? LEN - len : 0, (unsigned)tm.tm_sec, 2, '0'); + if (len < LEN) + cpcp_pack_copy_bytes(pack_context, str, len); + int msec = (int)(epoch_msecs % 1000); + if ((msec > 0 && msec_policy == CCPON_Auto) || msec_policy == CCPON_Always) { + cpcp_pack_copy_bytes(pack_context, ".", 1); + len = uint_to_str_lpad(str, LEN, (unsigned)msec, 3, '0'); + if (len < LEN) + cpcp_pack_copy_bytes(pack_context, str, len); + } + if (with_tz) { + if (min_from_utc == 0) { + cpcp_pack_copy_bytes(pack_context, "Z", 1); + } else { + if (min_from_utc < 0) { + cpcp_pack_copy_bytes(pack_context, "-", 1); + min_from_utc = -min_from_utc; + } else { + cpcp_pack_copy_bytes(pack_context, "+", 1); + } + if (min_from_utc % 60) { + len = 0; + len += uint_to_str_lpad(str + len, (len < LEN) ? LEN - len : 0, + (unsigned)min_from_utc / 60, 2, '0'); + len += uint_to_str_lpad(str + len, (len < LEN) ? LEN - len : 0, + (unsigned)min_from_utc % 60, 2, '0'); + } else { + len = 0; + len += uint_to_str_lpad(str + len, (len < LEN) ? LEN - len : 0, + (unsigned)min_from_utc / 60, 2, '0'); + } + if (len < LEN) + cpcp_pack_copy_bytes(pack_context, str, len); + } + } +} + +void cpon_pack_null(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + cpcp_pack_copy_bytes(pack_context, CCPON_STR_NULL, sizeof(CCPON_STR_NULL) - 1); +} + +static void cpon_pack_true(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + cpcp_pack_copy_bytes(pack_context, CCPON_STR_TRUE, sizeof(CCPON_STR_TRUE) - 1); +} + +static void cpon_pack_false(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + cpcp_pack_copy_bytes( + pack_context, CCPON_STR_FALSE, sizeof(CCPON_STR_FALSE) - 1); +} + +void cpon_pack_boolean(cpcp_pack_context *pack_context, bool b) { + if (pack_context->err_no) + return; + + if (b) + cpon_pack_true(pack_context); + else + cpon_pack_false(pack_context); +} + +void cpon_pack_list_begin(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + + if (cpcp_pack_copy_byte(pack_context, CCPON_C_LIST_BEGIN) > 0) + start_block(pack_context); +} + +void cpon_pack_list_end(cpcp_pack_context *pack_context, bool is_oneliner) { + if (pack_context->err_no) + return; + + end_block(pack_context, is_oneliner); + cpcp_pack_copy_byte(pack_context, CCPON_C_LIST_END); +} + +void cpon_pack_map_begin(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + + if (cpcp_pack_copy_byte(pack_context, CCPON_C_MAP_BEGIN)) + start_block(pack_context); +} + +void cpon_pack_map_end(cpcp_pack_context *pack_context, bool is_oneliner) { + if (pack_context->err_no) + return; + + end_block(pack_context, is_oneliner); + cpcp_pack_copy_byte(pack_context, CCPON_C_MAP_END); +} + +void cpon_pack_imap_begin(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + + cpcp_pack_copy_bytes( + pack_context, CCPON_STR_IMAP_BEGIN, sizeof(CCPON_STR_IMAP_BEGIN) - 1); + start_block(pack_context); +} + +void cpon_pack_imap_end(cpcp_pack_context *pack_context, bool is_oneliner) { + cpon_pack_map_end(pack_context, is_oneliner); +} + +void cpon_pack_meta_begin(cpcp_pack_context *pack_context) { + if (pack_context->err_no) + return; + + if (cpcp_pack_copy_byte(pack_context, CCPON_C_META_BEGIN) > 0) + start_block(pack_context); +} + +void cpon_pack_meta_end(cpcp_pack_context *pack_context, bool is_oneliner) { + if (pack_context->err_no) + return; + + end_block(pack_context, is_oneliner); + cpcp_pack_copy_byte(pack_context, CCPON_C_META_END); +} + +static char *copy_data_escaped( + cpcp_pack_context *pack_context, const void *str, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + if (pack_context->err_no != CPCP_RC_OK) + return NULL; + uint8_t ch = ((const uint8_t *)str)[i]; + switch (ch) { + case '\0': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, '0'); + break; + case '\\': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, '\\'); + break; + case '\t': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, 't'); + break; + case '\b': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, 'b'); + break; + case '\r': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, 'r'); + break; + case '\n': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, 'n'); + break; + case '"': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, '"'); + break; + default: + cpcp_pack_copy_byte(pack_context, ch); + } + } + return pack_context->current; +} + +static char *copy_blob_escaped( + cpcp_pack_context *pack_context, const void *str, size_t len) { + size_t i; + for (i = 0; i < len; ++i) { + if (pack_context->err_no != CPCP_RC_OK) + return NULL; + uint8_t ch = ((const uint8_t *)str)[i]; + switch (ch) { + case '\\': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, '\\'); + break; + case '\t': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, 't'); + break; + case '\r': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, 'r'); + break; + case '\n': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, 'n'); + break; + case '"': + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, '"'); + break; + default: + if (ch < 32 || ch >= 127) { + cpcp_pack_copy_byte(pack_context, '\\'); + cpcp_pack_copy_byte(pack_context, hexify(ch / 16)); + cpcp_pack_copy_byte(pack_context, hexify(ch % 16)); + } else { + cpcp_pack_copy_byte(pack_context, ch); + } + } + } + return pack_context->current; +} + +void cpon_pack_blob( + cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len) { + cpon_pack_blob_start(pack_context, 0, 0); + cpon_pack_blob_cont(pack_context, buff, (unsigned int)buff_len); + cpon_pack_blob_finish(pack_context); +} + +void cpon_pack_blob_start( + cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len) { + cpcp_pack_copy_byte(pack_context, 'b'); + cpcp_pack_copy_byte(pack_context, '"'); + copy_blob_escaped(pack_context, buff, buff_len); +} + +void cpon_pack_blob_cont( + cpcp_pack_context *pack_context, const uint8_t *buff, unsigned buff_len) { + copy_blob_escaped(pack_context, buff, buff_len); +} + +void cpon_pack_blob_finish(cpcp_pack_context *pack_context) { + cpcp_pack_copy_byte(pack_context, '"'); +} + +void cpon_pack_string(cpcp_pack_context *pack_context, const char *s, size_t l) { + cpcp_pack_copy_byte(pack_context, '"'); + copy_data_escaped(pack_context, s, l); + cpcp_pack_copy_byte(pack_context, '"'); +} + +void cpon_pack_string_terminated(cpcp_pack_context *pack_context, const char *s) { + size_t len = s ? strlen(s) : 0; + cpon_pack_string(pack_context, s, len); +} + +void cpon_pack_string_start( + cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { + cpcp_pack_copy_byte(pack_context, '"'); + copy_data_escaped(pack_context, buff, buff_len); +} + +void cpon_pack_string_cont( + cpcp_pack_context *pack_context, const char *buff, unsigned buff_len) { + copy_data_escaped(pack_context, buff, buff_len); +} + +void cpon_pack_string_finish(cpcp_pack_context *pack_context) { + cpcp_pack_copy_byte(pack_context, '"'); +} + +//============================ U N P A C K ================================= + +const char *cpon_unpack_skip_insignificant(cpcp_unpack_context *unpack_context) { + while (1) { + const char *p = cpcp_unpack_take_byte(unpack_context); + if (!p) + return p; + if (*p == 0) { + continue; + } + if (*p < 0) { + // skip all characters with ASCII > 128 + // because they cannot appear in CPON delimiters + continue; + } + if (*p == '\n') { + unpack_context->parser_line_no++; + } else if (*p > ' ') { + switch (*p) { + case '/': { + p = cpcp_unpack_take_byte(unpack_context); + if (!p) { + unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; + unpack_context->err_msg = "Unfinished comment"; + } else if (*p == '*') { + // multiline_comment_entered; + while (1) { + p = cpcp_unpack_take_byte(unpack_context); + if (!p) + return p; + if (*p == '\n') + unpack_context->parser_line_no++; + if (*p == '*') { + p = cpcp_unpack_take_byte(unpack_context); + if (*p == '/') + break; + } + } + } else if (*p == '/') { + // to end of line comment entered; + while (1) { + p = cpcp_unpack_take_byte(unpack_context); + if (!p) + return p; + if (*p == '\n') { + unpack_context->parser_line_no++; + break; + } + } + } else { + return NULL; + } + break; + } + case CCPON_C_KEY_DELIM: + case CCPON_C_FIELD_DELIM: + continue; + default: + return p; + } + } + } +} + +static int unpack_int(cpcp_unpack_context *unpack_context, int64_t *p_val) { + int64_t val = 0; + int neg = 0; + int base = 10; + int n = 0; + for (;; n++) { + const char *p = cpcp_unpack_take_byte(unpack_context); + if (!p) + goto eonumb; + uint8_t b = (uint8_t)(*p); + switch (b) { + case '+': + case '-': + if (n != 0) { + unpack_context->current--; + goto eonumb; + } + if (b == '-') + neg = 1; + break; + case 'x': + if (n == 1 && val != 0) { + unpack_context->current--; + goto eonumb; + } + if (n != 1) { + unpack_context->current--; + goto eonumb; + } + base = 16; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + val *= base; + val += b - '0'; + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + if (base != 16) { + unpack_context->current--; + goto eonumb; + } + val *= base; + val += b - 'a' + 10; + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + if (base != 16) { + unpack_context->current--; + goto eonumb; + } + val *= base; + val += b - 'A' + 10; + break; + default: + unpack_context->current--; + goto eonumb; + } + } +eonumb: + if (neg) + val = -val; + if (p_val) + *p_val = val; + return n; +} + +void cpon_unpack_date_time(cpcp_unpack_context *unpack_context, struct tm *tm, + int *msec, int *utc_offset) { + tm->tm_year = 0; + tm->tm_mon = 0; + tm->tm_mday = 1; + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + tm->tm_isdst = -1; + + *msec = 0; + *utc_offset = 0; + + const char *p; + + int64_t val; + int n = unpack_int(unpack_context, &val); + if (n < 0) { + unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; + unpack_context->err_msg = "Malformed year in DateTime"; + return; + } + tm->tm_year = (int)val - 1900; + + UNPACK_TAKE_BYTE(p); + if (*p != '-') { + unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; + unpack_context->err_msg = "Malformed year-month separator in DateTime"; + return; + } + + n = unpack_int(unpack_context, &val); + if (n <= 0) { + unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; + unpack_context->err_msg = "Malformed month in DateTime"; + return; + } + tm->tm_mon = (int)val - 1; + + UNPACK_TAKE_BYTE(p); + if (*p != '-') { + unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; + unpack_context->err_msg = "Malformed month-day separator in DateTime"; + return; + } + + n = unpack_int(unpack_context, &val); + if (n <= 0) { + unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; + unpack_context->err_msg = "Malformed day in DateTime"; + return; + } + tm->tm_mday = (int)val; + + UNPACK_TAKE_BYTE(p); + if (!(*p == 'T' || *p == ' ')) { + unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; + unpack_context->err_msg = "Malformed date-time separator in DateTime"; + return; + } + + n = unpack_int(unpack_context, &val); + if (n <= 0) { + unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; + unpack_context->err_msg = "Malformed hour in DateTime"; + return; + } + tm->tm_hour = (int)val; + + UNPACK_TAKE_BYTE(p); + + n = unpack_int(unpack_context, &val); + if (n <= 0) { + unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; + unpack_context->err_msg = "Malformed minutes in DateTime"; + return; + } + tm->tm_min = (int)val; + + UNPACK_TAKE_BYTE(p); + + n = unpack_int(unpack_context, &val); + if (n <= 0) { + unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; + unpack_context->err_msg = "Malformed seconds in DateTime"; + return; + } + tm->tm_sec = (int)val; + + p = cpcp_unpack_take_byte(unpack_context); + if (p) { + if (*p == '.') { + n = unpack_int(unpack_context, &val); + if (n < 0) + return; + *msec = (int)val; + p = cpcp_unpack_take_byte(unpack_context); + } + if (p) { + uint8_t b = (uint8_t)(*p); + if (b == 'Z') { + // UTC time + } else if (b == '+' || b == '-') { + // UTC time + n = unpack_int(unpack_context, &val); + if (!(n == 2 || n == 4)) + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, + "Malformed TS offset in DateTime."); + if (n == 2) + *utc_offset = (int)(60 * val); + else if (n == 4) + *utc_offset = (int)(60 * (val / 100) + (val % 100)); + if (b == '-') + *utc_offset = -*utc_offset; + } else { + // unget unused char + unpack_context->current--; + } + } + } + unpack_context->err_no = CPCP_RC_OK; + unpack_context->item.type = CPCP_ITEM_DATE_TIME; + int64_t epoch_sec = cpon_timegm(tm); + epoch_sec -= *utc_offset * 60; + int64_t epoch_msec = epoch_sec * 1000; + cpcp_date_time *it = &unpack_context->item.as.DateTime; + epoch_msec += *msec; + it->msecs_since_epoch = epoch_msec; + it->minutes_from_utc = *utc_offset; +} + +static void cpon_unpack_blob_hex(cpcp_unpack_context *unpack_context) { + if (unpack_context->item.type != CPCP_ITEM_BLOB) + UNPACK_ERROR(CPCP_RC_LOGICAL_ERROR, "Unpack cpon blob internal error."); + + const char *p; + cpcp_string *it = &unpack_context->item.as.String; + if (it->chunk_cnt == 0) { + // must start with '"' + UNPACK_TAKE_BYTE(p); + if (*p != '"') { + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Blob should start with 'x\"' ."); + } + } + for (it->chunk_size = 0; it->chunk_size < it->chunk_buff_len;) { + do { + UNPACK_TAKE_BYTE(p); + } while (*p <= ' '); + if (*p == '"') { + // end of string + it->last_chunk = 1; + break; + } + int b1 = unhex((uint8_t)(*p)); + if (b1 < 0) + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Invalid HEX char, first digit."); + UNPACK_TAKE_BYTE(p); + if (!p) + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, + "Invalid HEX char, second digit missing."); + int b2 = unhex((uint8_t)(*p)); + if (b2 < 0) + UNPACK_ERROR( + CPCP_RC_MALFORMED_INPUT, "Invalid HEX char, second digit."); + (it->chunk_start)[it->chunk_size++] = (char)((uint8_t)(16 * b1 + b2)); + } + it->chunk_cnt++; +} + +static void cpon_unpack_blob_esc(cpcp_unpack_context *unpack_context) { + if (unpack_context->item.type != CPCP_ITEM_BLOB) + UNPACK_ERROR(CPCP_RC_LOGICAL_ERROR, "Unpack cpon blob internal error."); + + const char *p; + cpcp_string *it = &unpack_context->item.as.String; + if (it->chunk_cnt == 0) { + // must start with '"' + UNPACK_TAKE_BYTE(p); + if (*p != '"') { + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Blob should start with 'b\"' ."); + } + } + for (it->chunk_size = 0; it->chunk_size < it->chunk_buff_len;) { + UNPACK_TAKE_BYTE(p); + uint8_t b = (uint8_t)(*p); + if (b == '"') { + // end of string + it->last_chunk = 1; + break; + } + if (b == '\\') { + UNPACK_TAKE_BYTE(p); + switch ((uint8_t)*p) { + case 't': + (it->chunk_start)[it->chunk_size++] = '\t'; + break; + case 'r': + (it->chunk_start)[it->chunk_size++] = '\r'; + break; + case 'n': + (it->chunk_start)[it->chunk_size++] = '\n'; + break; + case '"': + (it->chunk_start)[it->chunk_size++] = '"'; + break; + case '\\': + (it->chunk_start)[it->chunk_size++] = '\\'; + break; + default: { + int hi = unhex((uint8_t)(*p)); + if (hi < 0) + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Invalid HEX char."); + UNPACK_TAKE_BYTE(p); + int lo = unhex((uint8_t)(*p)); + if (lo < 0) + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Invalid HEX char."); + (it->chunk_start)[it->chunk_size++] = + (char)((uint8_t)(16 * hi + lo)); + break; + } + }; + } else if (b < 128) { + (it->chunk_start)[it->chunk_size++] = (char)b; + } else { + UNPACK_ERROR( + CPCP_RC_MALFORMED_INPUT, "Invalid blob char, code >= 128."); + } + } + it->chunk_cnt++; +} + +static void cpon_unpack_string(cpcp_unpack_context *unpack_context) { + if (unpack_context->item.type != CPCP_ITEM_STRING) + UNPACK_ERROR(CPCP_RC_LOGICAL_ERROR, "Unpack cpon string internal error."); + + const char *p; + cpcp_string *it = &unpack_context->item.as.String; + if (it->chunk_cnt == 0) { + // must start with '"' + UNPACK_TAKE_BYTE(p); + if (*p != '"') { + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, + "String should start with '\"' character."); + } + } + for (it->chunk_size = 0; it->chunk_size < it->chunk_buff_len;) { + UNPACK_TAKE_BYTE(p); + if (*p == '\\') { + UNPACK_TAKE_BYTE(p); + if (!p) + return; + switch (*p) { + case '\\': + (it->chunk_start)[it->chunk_size++] = '\\'; + break; + case '"': + (it->chunk_start)[it->chunk_size++] = '"'; + break; + case 'b': + (it->chunk_start)[it->chunk_size++] = '\b'; + break; + case 'f': + (it->chunk_start)[it->chunk_size++] = '\f'; + break; + case 'n': + (it->chunk_start)[it->chunk_size++] = '\n'; + break; + case 'r': + (it->chunk_start)[it->chunk_size++] = '\r'; + break; + case 't': + (it->chunk_start)[it->chunk_size++] = '\t'; + break; + case '0': + (it->chunk_start)[it->chunk_size++] = '\0'; + break; + default: + (it->chunk_start)[it->chunk_size++] = *p; + break; + } + } else { + if (*p == '"') { + // end of string + it->last_chunk = 1; + break; + } + (it->chunk_start)[it->chunk_size++] = *p; + } + } + it->chunk_cnt++; +} + +void cpon_unpack_next(cpcp_unpack_context *unpack_context) { + if (unpack_context->err_no) + return; + + const char *p; + if (unpack_context->item.type == CPCP_ITEM_STRING) { + cpcp_string *str_it = &unpack_context->item.as.String; + if (!str_it->last_chunk) { + cpon_unpack_string(unpack_context); + return; + } + } else if (unpack_context->item.type == CPCP_ITEM_BLOB) { + cpcp_string *str_it = &unpack_context->item.as.String; + if (!str_it->last_chunk) { + if (str_it->blob_hex) + cpon_unpack_blob_hex(unpack_context); + else + cpon_unpack_blob_esc(unpack_context); + return; + } + } + + unpack_context->item.type = CPCP_ITEM_INVALID; + + p = cpon_unpack_skip_insignificant(unpack_context); + if (!p) + return; + + switch (*p) { + case CCPON_C_LIST_END: + case CCPON_C_MAP_END: + case CCPON_C_META_END: { + unpack_context->item.type = CPCP_ITEM_CONTAINER_END; + if (unpack_context->container_stack) { + cpcp_container_state *top_cont_state = + cpcp_unpack_context_top_container_state(unpack_context); + if (!top_cont_state) + UNPACK_ERROR(CPCP_RC_CONTAINER_STACK_UNDERFLOW, + "Container stack underflow.") + } + break; + } + case CCPON_C_META_BEGIN: + unpack_context->item.type = CPCP_ITEM_META; + break; + case CCPON_C_MAP_BEGIN: + unpack_context->item.type = CPCP_ITEM_MAP; + break; + case CCPON_C_LIST_BEGIN: + unpack_context->item.type = CPCP_ITEM_LIST; + break; + case 'i': { + UNPACK_TAKE_BYTE(p); + if (*p != '{') + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "IMap should start with '{'.") + unpack_context->item.type = CPCP_ITEM_IMAP; + break; + } + case 'a': { + UNPACK_TAKE_BYTE(p); + if (*p != '[') + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "List should start with '['.") + // unpack unsupported ARRAY type as list + unpack_context->item.type = CPCP_ITEM_LIST; + break; + } + case 'd': { + UNPACK_TAKE_BYTE(p); + if (!p || *p != '"') + UNPACK_ERROR( + CPCP_RC_MALFORMED_INPUT, "DateTime should start with 'd'.") + struct tm tm; + int msec; + int utc_offset; + cpon_unpack_date_time(unpack_context, &tm, &msec, &utc_offset); + UNPACK_TAKE_BYTE(p); + if (!p || *p != '"') + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, + "DateTime should start with 'd\"'.") + break; + } + case 'n': { + UNPACK_TAKE_BYTE(p); + if (*p == 'u') { + UNPACK_TAKE_BYTE(p); + if (*p == 'l') { + UNPACK_TAKE_BYTE(p); + if (*p == 'l') { + unpack_context->item.type = CPCP_ITEM_NULL; + break; + } + } + } + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Malformed 'null' literal.") + } + case 'f': { + UNPACK_TAKE_BYTE(p); + if (*p == 'a') { + UNPACK_TAKE_BYTE(p); + if (*p == 'l') { + UNPACK_TAKE_BYTE(p); + if (*p == 's') { + UNPACK_TAKE_BYTE(p); + if (*p == 'e') { + unpack_context->item.type = CPCP_ITEM_BOOLEAN; + unpack_context->item.as.Bool = false; + break; + } + } + } + } + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Malformed 'false' literal.") + } + case 't': { + UNPACK_TAKE_BYTE(p); + if (*p == 'r') { + UNPACK_TAKE_BYTE(p); + if (*p == 'u') { + UNPACK_TAKE_BYTE(p); + if (*p == 'e') { + unpack_context->item.type = CPCP_ITEM_BOOLEAN; + unpack_context->item.as.Bool = true; + break; + } + } + } + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Malformed 'true' literal.") + } + case 'x': { + UNPACK_TAKE_BYTE(p); + if (*p != '"') + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, + "HEX string should start with 'x\"'.") + unpack_context->item.type = CPCP_ITEM_BLOB; + cpcp_string *str_it = &unpack_context->item.as.String; + cpcp_string_init(str_it, unpack_context); + str_it->blob_hex = 1; + unpack_context->current--; + cpon_unpack_blob_hex(unpack_context); + break; + } + case 'b': { + UNPACK_TAKE_BYTE(p); + if (*p != '"') + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, + "BLOB string should start with 'b\"'.") + unpack_context->item.type = CPCP_ITEM_BLOB; + cpcp_string *str_it = &unpack_context->item.as.String; + cpcp_string_init(str_it, unpack_context); + str_it->blob_hex = 0; + unpack_context->current--; + cpon_unpack_blob_esc(unpack_context); + break; + } + case '"': { + unpack_context->item.type = CPCP_ITEM_STRING; + cpcp_string *str_it = &unpack_context->item.as.String; + cpcp_string_init(str_it, unpack_context); + unpack_context->current--; + cpon_unpack_string(unpack_context); + break; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': { + // number + int64_t mantisa = 0; + int64_t exponent = 0; + int64_t decimals = 0; + int dec_cnt = 0; + struct { + uint8_t is_decimal:1; + uint8_t is_uint:1; + uint8_t is_neg:1; + } flags; + flags.is_decimal = 0; + flags.is_uint = 0; + flags.is_neg = 0; + + flags.is_neg = *p == '-'; + if (!flags.is_neg) + unpack_context->current--; + int n = unpack_int(unpack_context, &mantisa); + if (n < 0) + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Malformed number.") + p = cpcp_unpack_take_byte(unpack_context); + while (p) { + if (*p == CCPON_C_UNSIGNED_END) { + flags.is_uint = 1; + break; + } + if (*p == '.') { + flags.is_decimal = 1; + n = unpack_int(unpack_context, &decimals); + if (n < 0) + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, + "Malformed number decimal part.") + dec_cnt = n; + p = cpcp_unpack_take_byte(unpack_context); + if (!p) + break; + } + if (*p == 'e' || *p == 'E') { + flags.is_decimal = 1; + n = unpack_int(unpack_context, &exponent); + if (n < 0) + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, + "Malformed number exponetional part.") + break; + } + if (*p != '.') { + // unget char + unpack_context->current--; + } + break; + } + if (flags.is_decimal) { + int i; + for (i = 0; i < dec_cnt; ++i) + mantisa *= 10; + mantisa += decimals; + unpack_context->item.type = CPCP_ITEM_DECIMAL; + unpack_context->item.as.Decimal.mantisa = + flags.is_neg ? -mantisa : mantisa; + unpack_context->item.as.Decimal.exponent = + (int)(exponent - dec_cnt); + } else if (flags.is_uint) { + unpack_context->item.type = CPCP_ITEM_UINT; + unpack_context->item.as.UInt = (uint64_t)mantisa; + + } else { + unpack_context->item.type = CPCP_ITEM_INT; + unpack_context->item.as.Int = flags.is_neg ? -mantisa : mantisa; + } + unpack_context->err_no = CPCP_RC_OK; + break; + } + default: + UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Invalid character."); + } + + cpcp_item_types current_item_type = unpack_context->item.type; + bool is_container_end = false; + switch (current_item_type) { + case CPCP_ITEM_LIST: + case CPCP_ITEM_MAP: + case CPCP_ITEM_IMAP: + case CPCP_ITEM_META: + cpcp_unpack_context_push_container_state( + unpack_context, current_item_type); + break; + case CPCP_ITEM_CONTAINER_END: + cpcp_unpack_context_pop_container_state(unpack_context); + is_container_end = true; + break; + default: + break; + } + + cpcp_container_state *top_cont_state = + cpcp_unpack_context_top_container_state(unpack_context); + if (top_cont_state && !is_container_end) { + if (top_cont_state->current_item_type != CPCP_ITEM_META) + top_cont_state->item_count++; + top_cont_state->current_item_type = current_item_type; + } +} + +void cpon_pack_field_delim( + cpcp_pack_context *pack_context, bool is_first_field, bool is_oneliner) { + if (!is_first_field) + cpcp_pack_copy_bytes(pack_context, ",", 1); + indent_element(pack_context, is_oneliner, is_first_field); +} + +void cpon_pack_key_val_delim(cpcp_pack_context *pack_context) { + cpcp_pack_copy_bytes(pack_context, ":", 1); +} diff --git a/libshvchainpack/libshvchainpack.version b/libshvchainpack/libshvchainpack.version new file mode 100644 index 0000000..bfdf18f --- /dev/null +++ b/libshvchainpack/libshvchainpack.version @@ -0,0 +1,94 @@ +V0.0 { + global: + # cpcp.h + cpcp_error_string; + + # cpcp_pack.h + cpcp_pack_context_init; + cpcp_pack_context_dry_run_init; + cpcp_pack_copy_byte; + cpcp_pack_copy_bytes; + + # cpcp_unpack.h + cpcp_item_type_to_string; + cpcp_string_init; + cpcp_exponential_to_double; + cpcp_decimal_to_double; + cpcp_decimal_to_string; + cpcp_container_state_init; + cpcp_container_stack_init; + cpcp_unpack_context_init; + cpcp_unpack_context_push_container_state; + cpcp_unpack_context_top_container_state; + cpcp_unpack_context_parent_container_state; + cpcp_unpack_context_closed_container_state; + cpcp_unpack_context_pop_container_state; + cpcp_unpack_take_byte; + cpcp_unpack_peek_byte; + + # chainpack.h + chainpack_packing_schema_name; + chainpack_pack_uint_data; + chainpack_pack_null; + chainpack_pack_boolean; + chainpack_pack_int; + chainpack_pack_uint; + chainpack_pack_double; + chainpack_pack_decimal; + chainpack_pack_date_time; + chainpack_pack_blob; + chainpack_pack_blob_start; + chainpack_pack_blob_cont; + chainpack_pack_string; + chainpack_pack_string_start; + chainpack_pack_string_cont; + chainpack_pack_cstring; + chainpack_pack_cstring_terminated; + chainpack_pack_cstring_start; + chainpack_pack_cstring_cont; + chainpack_pack_cstring_finish; + chainpack_pack_list_begin; + chainpack_pack_map_begin; + chainpack_pack_imap_begin; + chainpack_pack_meta_begin; + chainpack_pack_container_end; + chainpack_unpack_uint_data; + chainpack_unpack_uint_data2; + chainpack_unpack_next; + + # cpon.h + cpon_timegm; + cpon_gmtime; + cpon_pack_null; + cpon_pack_boolean; + cpon_pack_int; + cpon_pack_uint; + cpon_pack_double; + cpon_pack_decimal; + cpon_pack_date_time; + cpon_pack_blob; + cpon_pack_blob_start; + cpon_pack_blob_cont; + cpon_pack_blob_finish; + cpon_pack_string; + cpon_pack_string_terminated; + cpon_pack_string_start; + cpon_pack_string_cont; + cpon_pack_string_finish; + cpon_pack_list_begin; + cpon_pack_list_end; + cpon_pack_map_begin; + cpon_pack_map_end; + cpon_pack_imap_begin; + cpon_pack_imap_end; + cpon_pack_meta_begin; + cpon_pack_meta_end; + cpon_pack_copy_str; + cpon_pack_field_delim; + cpon_pack_key_val_delim; + cpon_unpack_skip_insignificant; + cpon_unpack_next; + cpon_unpack_date_time; + + local: *; +}; diff --git a/libshvchainpack/meson.build b/libshvchainpack/meson.build new file mode 100644 index 0000000..cad13e2 --- /dev/null +++ b/libshvchainpack/meson.build @@ -0,0 +1,31 @@ +libshvchainpack_sources = files( + 'chainpack.c', + 'cpcp.c', + 'cpcp_convert.c', + 'cpon.c', +) + +libshvchainpack = library( + 'shvchainpack', + libshvchainpack_sources, + version: '0.0.0', + include_directories: includes, + link_args: '-Wl,--version-script=' + join_paths( + meson.current_source_dir(), + 'libshvchainpack.version', + ), + install: not meson.is_subproject(), +) +install_headers(libshvchainpack_headers) + +libshvchainpack_dep = declare_dependency( + include_directories: includes, + link_with: libshvchainpack, +) + + +pkg_mod = import('pkgconfig') +pkg_mod.generate( + libshvchainpack, + description: 'Silicon Heaven Chainpack packer and unpacker', +) diff --git a/libshvrpc/libshvrpc.version b/libshvrpc/libshvrpc.version new file mode 100644 index 0000000..212ce9f --- /dev/null +++ b/libshvrpc/libshvrpc.version @@ -0,0 +1,18 @@ +V0.0 { + global: + rpcurl_parse; + rpcurl_free; + rpcurl_str; + + rpcclient_connect; + rpcclient_new_stream; + rpcclient_new_datagram; + rpcclient_new_serial; + rpcclient_disconnect; + + rpcclient_next_message; + rpcclient_next_item; + rpcclient_pack_context; + + local: *; +}; diff --git a/libshvrpc/meson.build b/libshvrpc/meson.build new file mode 100644 index 0000000..9fd0552 --- /dev/null +++ b/libshvrpc/meson.build @@ -0,0 +1,36 @@ +libshvrpc_sources = [ + files( + 'rpcurl.c', + 'rpcclient.c', + 'rpcclient_stream.c', + 'rpcclient_serial.c', + 'rpcclient_datagram.c', + ), + gperf.process('rpcurl_scheme.gperf'), + gperf.process('rpcurl_query.gperf'), +] +libshvrpc_dependencies = [libshvchainpack_dep, uriparser] + +libshvrpc = library( + 'shvrpc', + libshvrpc_sources, + version: '0.0.0', + dependencies: libshvrpc_dependencies, + include_directories: includes, + link_args: '-Wl,--version-script=' + join_paths( + meson.current_source_dir(), + 'libshvrpc.version', + ), + install: not meson.is_subproject(), +) +install_headers(libshvrpc_headers) + +libshvrpc_dep = declare_dependency( + dependencies: libshvrpc_dependencies, + include_directories: includes, + link_with: libshvrpc, +) + + +pkg_mod = import('pkgconfig') +pkg_mod.generate(libshvrpc, description: 'Silicon Heaven RPC') diff --git a/libshvrpc/rpcclient.c b/libshvrpc/rpcclient.c new file mode 100644 index 0000000..193b886 --- /dev/null +++ b/libshvrpc/rpcclient.c @@ -0,0 +1,43 @@ +#include "rpcclient.h" +#include +#include +#include +#include + + +struct rpcclient *rpcclient_connect(const struct rpcurl *url) { + struct rpcclient *res; + switch (url->protocol) { + case RPC_PROTOCOL_TCP: + res = rpcclient_stream_tcp_connect(url->location, url->port); + break; + case RPC_PROTOCOL_LOCAL_SOCKET: + res = rpcclient_stream_unix_connect(url->location); + break; + case RPC_PROTOCOL_UDP: + res = rpcclient_datagram_udp_connect(url->location, url->port); + break; + case RPC_PROTOCOL_SERIAL_PORT: + res = rpcclient_serial_connect(url->location); + break; + }; + /*if (rpcclient_login(res, url->username, url->password, url->login_type))*/ + /*return res;*/ + rpcclient_disconnect(res); + return NULL; +} + + +void rpcclient_disconnect(struct rpcclient *client) { + client->disconnect(client); +} + + + +bool rpcclient_next_message(struct rpcclient *client) {} + +struct cpcp_item *rpcclient_next_item(struct rpcclient *client) {} + + + +struct cpcp_pack_context *rpcclient_pack_context(struct rpcclient *client) {} diff --git a/libshvrpc/rpcclient.h b/libshvrpc/rpcclient.h new file mode 100644 index 0000000..968149d --- /dev/null +++ b/libshvrpc/rpcclient.h @@ -0,0 +1,13 @@ +#ifndef LIBSHV_RPCCLIENT_H +#define LIBSHV_RPCCLIENT_H + +#include + +struct rpcclient { + void (*nextmsg)(rpcclient_t); + void (*disconnect)(rpcclient_t); + cpcp_pack_context pack; + cpcp_unpack_context unpack; +}; + +#endif diff --git a/libshvrpc/rpcclient_datagram.c b/libshvrpc/rpcclient_datagram.c new file mode 100644 index 0000000..a52f5e0 --- /dev/null +++ b/libshvrpc/rpcclient_datagram.c @@ -0,0 +1,11 @@ +#include "rpcclient.h" + +struct rpcclient *rpcclient_datagram_new(void) { + // TODO + return NULL; +} + +rpcclient_t rpcclient_datagram_udp_connect(const char *location, int port) { + // TODO + return NULL; +} diff --git a/libshvrpc/rpcclient_serial.c b/libshvrpc/rpcclient_serial.c new file mode 100644 index 0000000..bcd6f97 --- /dev/null +++ b/libshvrpc/rpcclient_serial.c @@ -0,0 +1,12 @@ +#include "rpcclient.h" + + +struct rpcclient *rpcclient_new_serial(int fd) { + // TODO + return NULL; +} + +rpcclient_t rpcclient_serial_connect(const char *path) { + // TODO + return NULL; +} diff --git a/libshvrpc/rpcclient_stream.c b/libshvrpc/rpcclient_stream.c new file mode 100644 index 0000000..a84fc18 --- /dev/null +++ b/libshvrpc/rpcclient_stream.c @@ -0,0 +1,115 @@ +#include "rpcclient.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct rpcclient_stream { + struct rpcclient c; + int rfd, wfd; + uint8_t packbuf[SHV_PACK_BUFSIZ]; + uint8_t unpackbuf[SHV_UNPACK_BUFSIZ]; + ssize_t msglen; +}; + + +static void overflow_handler(struct cpcp_pack_context *pack, size_t size_hint) { + struct rpcclient_stream *c = (struct rpcclient_stream *)(pack - + offsetof(struct rpcclient_stream, c.pack)); + + size_t to_send = pack->current - pack->start; + char *ptr_data = pack->start; + while (to_send > 0) { + ssize_t i = write(c->wfd, ptr_data, to_send); + if (i == -1) { + if (errno == EINTR) + continue; + printf("Overflow error: %s\n", strerror(errno)); + pack->current = pack->end; /* signal error */ + return; + } + ptr_data += i; + to_send -= i; + } + pack->current = pack->start; +} + +static size_t underflow_handler(struct cpcp_unpack_context *unpack) { + struct rpcclient_stream *c = (struct rpcclient_stream *)(unpack - + offsetof(struct rpcclient_stream, c.unpack)); + + c->msglen -= unpack->end - unpack->start; + ssize_t i; + do + i = read(c->rfd, (void *)unpack->start, + c->msglen >= 0 && c->msglen < SHV_UNPACK_BUFSIZ ? c->msglen + : SHV_UNPACK_BUFSIZ); + while (i == -1 && errno == EINTR); + if (i < 0) { + printf("Underflow error: %s\n", strerror(errno)); + return 0; + } + unpack->current = unpack->start; + unpack->end = unpack->start + i; + return i; +} + +static void nextmsg(rpcclient_t client) { + struct rpcclient_stream *sclient = (struct rpcclient_stream *)client; + sclient->msglen = -1; + sclient->msglen = chainpack_unpack_uint_data(&client->unpack, NULL); + if (client->unpack.err_no != CPCP_RC_OK) + return; // TODO error +} + +static void disconnect(rpcclient_t client) { + struct rpcclient_stream *sclient = (struct rpcclient_stream *)client; + close(sclient->rfd); + if (sclient->rfd != sclient->wfd) + close(sclient->wfd); + free(sclient); +} + +rpcclient_t rpcclient_stream_new(int readfd, int writefd) { + struct rpcclient_stream *res = malloc(sizeof *res); + res->c.nextmsg = &nextmsg; + res->c.disconnect = &disconnect; + // TODO possibly just do not initialize pack context + cpcp_pack_context_init(&res->c.pack, &res->packbuf, 0, overflow_handler); + cpcp_unpack_context_init(&res->c.unpack, &res->unpackbuf, SHV_PACK_BUFSIZ, + underflow_handler, NULL); + res->rfd = readfd; + res->wfd = writefd; + res->msglen = 0; + return &res->c; +} + +rpcclient_t rpcclient_stream_tcp_connect(const char *location, int port) { + int fd = socket(AF_INET6, SOCK_STREAM, 0); + // TODO connect + return rpcclient_stream_new(fd, fd); +} + +rpcclient_t rpcclient_stream_unix_connect(const char *location) { + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + const size_t addrlen = + sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path) - 1; + strncpy(addr.sun_path, location, addrlen); + addr.sun_path[addrlen] = '\0'; + if (connect(sock, &addr, sizeof addr) == -1) { + close(sock); + return NULL; + } + + return rpcclient_stream_new(sock, sock); +} diff --git a/libshvrpc/rpcurl.c b/libshvrpc/rpcurl.c new file mode 100644 index 0000000..da1b369 --- /dev/null +++ b/libshvrpc/rpcurl.c @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include +#include + +#include "rpcurl_scheme.gperf.h" +#include "rpcurl_query.gperf.h" + +struct dyn_rpcurl { + struct rpcurl rpcurl; + /* We want to allow user to change fields including the replace. That would + * result in lost reference or in inability to decide if we should free it + * or not. We instead store all dynamically allocated strings here so we can + * free them when we need it. + */ + char *location; + char *username; + char *password; + char *device_id; + char *device_mountpoint; +}; + +struct rpcurl *rpcurl_parse(const char *url, const char **error_pos) { + UriUriA uri; + if (uriParseSingleUriA(&uri, url, error_pos) != URI_SUCCESS) + return NULL; + +#define PARSE_ERR(LOC) \ + do { \ + if (error_pos) \ + *error_pos = LOC; \ + goto error; \ + } while (false) + + struct dyn_rpcurl *res = calloc(1, sizeof *res); + + res->rpcurl.protocol = RPC_PROTOCOL_LOCAL_SOCKET; + if (uri.scheme.first != uri.scheme.afterLast) { + const struct gperf_rpcurl_scheme_match *match = gperf_rpcurl_scheme( + uri.scheme.first, uri.scheme.afterLast - uri.scheme.first); + if (match == NULL) + PARSE_ERR(uri.scheme.first); + res->rpcurl.protocol = match->protocol; + } + if (res->rpcurl.protocol == RPC_PROTOCOL_TCP || + res->rpcurl.protocol == RPC_PROTOCOL_UDP) + res->rpcurl.port = 3755; + + if (uri.userInfo.first != uri.userInfo.afterLast) { + res->username = strndup( + uri.userInfo.first, uri.userInfo.afterLast - uri.userInfo.first); + res->rpcurl.username = res->username; + } + + if (uri.hostText.first != uri.hostText.afterLast) { + if (res->rpcurl.protocol != RPC_PROTOCOL_TCP && + res->rpcurl.protocol != RPC_PROTOCOL_UDP) + PARSE_ERR(uri.portText.first - 1); + res->location = strndup( + uri.hostText.first, uri.hostText.afterLast - uri.hostText.first); + res->rpcurl.location = res->location; + } + + if (uri.pathHead && uri.pathHead->text.first != uri.pathTail->text.afterLast) { + if (res->rpcurl.protocol != RPC_PROTOCOL_LOCAL_SOCKET && + res->rpcurl.protocol != RPC_PROTOCOL_SERIAL_PORT) + PARSE_ERR(uri.pathHead->text.first); + assert(!uri.absolutePath || *(uri.pathHead->text.first - 1) == '/'); + size_t off = uri.absolutePath ? 1 : 0; + res->location = strndup(uri.pathHead->text.first - off, + uri.pathTail->text.afterLast - uri.pathHead->text.first + off); + res->rpcurl.location = res->location; + } + + if (uri.portText.first != uri.portText.afterLast) { + if (res->rpcurl.protocol != RPC_PROTOCOL_TCP && + res->rpcurl.protocol != RPC_PROTOCOL_UDP) + PARSE_ERR(uri.portText.first - 1); + int base = 1; + res->rpcurl.port = 0; + for (const char *c = uri.portText.afterLast - 1; + c >= uri.portText.first; c--) { + if (*c < '0' || *c > '9') { + if (error_pos) + *error_pos = c; + goto error; + } + res->rpcurl.port += (*c - '0') * base; + base *= 10; + } + } + + if (uri.query.first != uri.query.afterLast) { + UriQueryListA *querylist; + int querycnt; + if (uriDissectQueryMallocA(&querylist, &querycnt, uri.query.first, + uri.query.afterLast) != URI_SUCCESS) + PARSE_ERR(uri.query.first); + UriQueryListA *q = querylist; + while (q != NULL) { + const struct gperf_rpcurl_query_match *match = + gperf_rpcurl_query(q->key, strlen(q->key)); + if (match == NULL) { + uriFreeQueryListA(querylist); + PARSE_ERR(uri.query.first); + } + switch (match->query) { + case GPERF_RPCURL_QUERY_SHAPASS: + free(res->password); + res->rpcurl.login_type = RPC_LOGIN_SHA1; + res->password = strdup(q->value); + res->rpcurl.password = res->password; + break; + case GPERF_RPCURL_QUERY_PASSWORD: + free(res->password); + res->rpcurl.login_type = RPC_LOGIN_PLAIN; + res->password = strdup(q->value); + res->rpcurl.password = res->password; + break; + case GPERF_RPCURL_QUERY_DEVID: + free(res->device_id); + res->device_id = strdup(q->value); + res->rpcurl.device_id = res->device_id; + break; + case GPERF_RPCURL_QUERY_DEVMOUNT: + free(res->device_mountpoint); + res->device_mountpoint = strdup(q->value); + res->rpcurl.device_mountpoint = res->device_mountpoint; + break; + } + q = q->next; + } + uriFreeQueryListA(querylist); + } + + uriFreeUriMembersA(&uri); + if (error_pos) + *error_pos = NULL; + return &res->rpcurl; + +error: + uriFreeUriMembersA(&uri); + rpcurl_free(&res->rpcurl); + return NULL; +} + +void rpcurl_free(struct rpcurl *rpc_url) { + struct dyn_rpcurl *url = (struct dyn_rpcurl *)rpc_url; + free(url->location); + free(url->username); + free(url->password); + free(url->device_id); + free(url->device_mountpoint); + free(url); +} + +char *rpcurl_str(struct rpcurl *rpc_url) { + // TODO + return NULL; +} diff --git a/libshvrpc/rpcurl_query.gperf b/libshvrpc/rpcurl_query.gperf new file mode 100644 index 0000000..2c7f607 --- /dev/null +++ b/libshvrpc/rpcurl_query.gperf @@ -0,0 +1,37 @@ +%compare-lengths +%compare-strncmp + +%pic +%enum +%readonly-tables +%switch=1 +%language=ANSI-C + +%define hash-function-name gperf_rpcurl_query_hash +%define lookup-function-name gperf_rpcurl_query +%define string-pool-name gperf_rpcurl_query_string +%define constants-prefix GPERF_RPCURL_QUERY_ +%define slot-name offset + +%struct-type +%{ +enum gperf_rpcurl_query { + GPERF_RPCURL_QUERY_SHAPASS, + GPERF_RPCURL_QUERY_PASSWORD, + GPERF_RPCURL_QUERY_DEVID, + GPERF_RPCURL_QUERY_DEVMOUNT, +}; +struct gperf_rpcurl_query_match { + int offset; + enum gperf_rpcurl_query query; +}; +%} + +struct gperf_rpcurl_query_match; + +%% +shapass, GPERF_RPCURL_QUERY_SHAPASS +password, GPERF_RPCURL_QUERY_PASSWORD +devid, GPERF_RPCURL_QUERY_DEVID +devmount, GPERF_RPCURL_QUERY_DEVMOUNT +%% diff --git a/libshvrpc/rpcurl_scheme.gperf b/libshvrpc/rpcurl_scheme.gperf new file mode 100644 index 0000000..cf9bfed --- /dev/null +++ b/libshvrpc/rpcurl_scheme.gperf @@ -0,0 +1,32 @@ +%compare-lengths +%compare-strncmp + +%pic +%enum +%readonly-tables +%switch=1 +%language=ANSI-C + +%define hash-function-name gperf_rpcurl_scheme_hash +%define lookup-function-name gperf_rpcurl_scheme +%define string-pool-name gperf_rpcurl_scheme_string +%define constants-prefix GPERF_RPCURL_SCHEME_ +%define slot-name offset + +%struct-type +%{ +struct gperf_rpcurl_scheme_match { + int offset; + enum rpc_protocol protocol; +}; +%} + +struct gperf_rpcurl_scheme_match; + +%% +tcp, RPC_PROTOCOL_TCP +udp, RPC_PROTOCOL_UDP +localsocket, RPC_PROTOCOL_LOCAL_SOCKET +unix, RPC_PROTOCOL_LOCAL_SOCKET +serialport, RPC_PROTOCOL_SERIAL_PORT +%% diff --git a/meson.build b/meson.build index 50f6600..61280da 100644 --- a/meson.build +++ b/meson.build @@ -7,33 +7,39 @@ project( meson_version: '>=0.57.0', ) cc = meson.get_compiler('c') -isnuttx = cc.get_define('__NuttX__') != '' # Won't be defined outside NuttX +isnuttx = ( + cc.get_define('__NuttX__') != '' # Won't be defined outside NuttX +) fs = import('fs') add_project_arguments('-D_GNU_SOURCE', language: 'c') -add_project_arguments('-DPROJECT_VERSION="@0@"'.format(meson.project_version()), language: 'c') +add_project_arguments('-fms-extensions', language: 'c') +add_project_arguments( + '-DPROJECT_VERSION="@0@"'.format(meson.project_version()), + language: 'c', +) +add_project_arguments( + '-DSHV_UNPACK_BUFSIZ=' + get_option('unpack_bufsiz').to_string(), + language: 'c', +) +add_project_arguments( + '-DSHV_PACK_BUFSIZ=' + get_option('pack_bufsiz').to_string(), + language: 'c', +) -if not isnuttx - argp = cc.has_function('argp_parse') ? declare_dependency() : cc.find_library('argp') -else - argp = declare_dependency() -endif - gperf = generator(find_program('gperf'), output: '@PLAINNAME@.h', arguments: ['@EXTRA_ARGS@', '--output-file=@OUTPUT@', '@INPUT@'] ) +uriparser = dependency('liburiparser', version: '>= 0.9.0') -subdir('include') -subdir('libfoo') -foo_opt = get_option('foo') -if foo_opt - subdir('src') -endif +subdir('include') +subdir('libshvchainpack') +subdir('libshvrpc') test_buildtypes = ['debug', 'debugoptimized'] diff --git a/meson_options.txt b/meson_options.txt index 55ee452..746936e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -6,8 +6,16 @@ option( ) option( - 'foo', - type: 'boolean', - value: true, - description: 'Build foo application.', + 'unpack_bufsiz', + type: 'integer', + min: 1, + value: 8, + description: 'Size of the buffer in bytes for the unpacker.', +) +option( + 'pack_bufsiz', + type: 'integer', + min: 1, + value: 8, + description: 'Size of the buffer in bytes for the packer.', ) diff --git a/src/config.c b/src/config.c deleted file mode 100644 index 3cd071d..0000000 --- a/src/config.c +++ /dev/null @@ -1,47 +0,0 @@ -/***************************************************************************** - * Copyright (C) 2022 Elektroline Inc. All rights reserved. - * K Ladvi 1805/20 - * Prague, 184 00 - * info@elektroline.cz (+420 284 021 111) - *****************************************************************************/ -#include "config.h" -#include -#include - -static struct config conf = { - .source_file = NULL, -}; - -static error_t parse_opt(int key, char *arg, struct argp_state *state); - -const static struct argp_option argp_options[] = {{NULL}}; - -const char *argp_program_version = PROJECT_VERSION; -const char *argp_program_bug_address = "bug@example.com"; -const static struct argp argp_parser = { - .options = argp_options, - .parser = parse_opt, - .doc = "Counts number of lines starting with \"foo\"", - .args_doc = "[FILE]", -}; - - -static error_t parse_opt(int key, char *arg, struct argp_state *state) { - switch (key) { - case ARGP_KEY_ARG: - if (conf.source_file == NULL) { - conf.source_file = arg; - break; - } - __attribute__((fallthrough)); - default: - return ARGP_ERR_UNKNOWN; - } - return 0; -} - - -struct config *load_config(int argc, char **argv) { - argp_parse(&argp_parser, argc, argv, 0, NULL, NULL); - return &conf; -} diff --git a/src/config.h b/src/config.h deleted file mode 100644 index b2c1dd4..0000000 --- a/src/config.h +++ /dev/null @@ -1,20 +0,0 @@ -/***************************************************************************** - * Copyright (C) 2022 Elektroline Inc. All rights reserved. - * K Ladvi 1805/20 - * Prague, 184 00 - * info@elektroline.cz (+420 284 021 111) - *****************************************************************************/ -#ifndef _FOO_CONFIG_H_ -#define _FOO_CONFIG_H_ - -struct config { - const char *source_file; -}; - - -/* Parse arguments and load configuration file - * Returned pointer is to statically allocated (do not call free on it). - */ -struct config *load_config(int argc, char **argv) __attribute__((nonnull)); - -#endif diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 6b94b76..0000000 --- a/src/main.c +++ /dev/null @@ -1,35 +0,0 @@ -/***************************************************************************** - * Copyright (C) 2022 Elektroline Inc. All rights reserved. - * K Ladvi 1805/20 - * Prague, 184 00 - * info@elektroline.cz (+420 284 021 111) - *****************************************************************************/ -#include -#include -#include -#include -#include -#include "config.h" - - -int main(int argc, char **argv) { - struct config *conf = load_config(argc, argv); - - FILE *f = stdin; - bool close = false; - if (conf->source_file != NULL && strcmp(conf->source_file, "-")) { - f = fopen(conf->source_file, "r"); - if (f == NULL) { - fprintf(stderr, "Can't open input file '%s': %s\n", - conf->source_file, strerror(errno)); - return 1; - } - } - - unsigned cnt = count_foo(f); - printf("%u\n", cnt); - - if (close) - fclose(f); - return 0; -} diff --git a/src/meson.build b/src/meson.build deleted file mode 100644 index 6cafe70..0000000 --- a/src/meson.build +++ /dev/null @@ -1,22 +0,0 @@ -foo_sources = files('config.c') -foo_dependencies = [libfoo_dep, argp] - - -if isnuttx - shvtreecdemo_client = static_library( - 'foo', - foo_sources + ['main.c'], - dependencies: foo_dependencies, - install: not meson.is_subproject(), - c_args: '-Dmain=foo_main', - ) -else - foo = executable( - 'foo', - foo_sources + ['main.c'], - dependencies: foo_dependencies, - install: true, - ) -endif - -foo_internal_includes = include_directories('.') diff --git a/tests/run/foo.bats b/tests/run/foo.bats deleted file mode 100755 index 160a748..0000000 --- a/tests/run/foo.bats +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env bats -set -eu - -setup() { - if [ -z "${BATS_TEST_TMPDIR:-}" ]; then - BATS_TEST_TMPDIR="$(mktemp -d)" - export BATS_TEST_TMPDIR - export BATS_TEST_TMPDIR_CUSTOM="y" - fi -} - -teardown() { - if [ "${BATS_TEST_TMPDIR_CUSTOM:-}" = "y" ]; then - rm -rf "$BATS_TEST_TMPDIR" - fi -} - -foo() { - local valgrind_log - local ec=0 - valgrind_log="$(mktemp -p "$BATS_TEST_TMPDIR" "valgrind.XXXXXX")" - ${VALGRIND:-} ${VALGRIND:+--log-file="$valgrind_log" --} \ - "${TEST_FOO:-foo}" "$@" || ec=$? - [ ! -s "$valgrind_log" ] || sed 's/^/#/' "$valgrind_log" >&3 - return $ec -} - -@test "help" { - skip "There is memory leak in GLibC when using --help" - foo --help -} - -@test "null" { - [ "$(foo "$file" <<-EOF - foo: once - fee: none - foo: two - foo: three - EOF - [ "$(foo "$file")" == "3" ] -} - -@test "missing" { - local ec=0 - local error - error="$(foo /dev/missing 2>&1)" || ec=$? - [ "$ec" -eq 1 ] - [ "$error" == "Can't open input file '/dev/missing': No such file or directory" ] -} diff --git a/tests/run/meson.build b/tests/run/meson.build index 859b79c..f13ea3f 100644 --- a/tests/run/meson.build +++ b/tests/run/meson.build @@ -1,8 +1,6 @@ bats = find_program('bats') -bats_tests = [ - 'foo', -] +bats_tests = [] foreach bats_test : bats_tests test( @@ -15,7 +13,7 @@ foreach bats_test : bats_tests '--tap', join_paths(meson.current_source_dir(), bats_test + '.bats'), ], - env: ['TEST_FOO=' + foo.full_path()], + env: [], protocol: 'tap', depends: foo, ) diff --git a/tests/unit/config.c b/tests/unit/config.c deleted file mode 100644 index 8512bf5..0000000 --- a/tests/unit/config.c +++ /dev/null @@ -1,46 +0,0 @@ -/***************************************************************************** - * Copyright (C) 2022 Elektroline Inc. All rights reserved. - * K Ladvi 1805/20 - * Prague, 184 00 - * info@elektroline.cz (+420 284 021 111) - *****************************************************************************/ -#include - -#define SUITE "config" -#include - -#include - -/* Test various command line arguments */ -TEST_CASE(arguments) {} - -char *parse_arguments_arg0[] = {"foo"}; -char *parse_arguments_arg1[] = {"foo", "/dev/null"}; - -#define ARGUMENTS(arg) .argc = sizeof(arg) / sizeof(*(arg)), .argv = arg -const struct { - struct config config; - int argc; - char **argv; -} parse_arguments_data[] = { - { - .config = - { - .source_file = NULL, - }, - ARGUMENTS(parse_arguments_arg0), - }, - { - .config = - { - .source_file = "/dev/null", - }, - ARGUMENTS(parse_arguments_arg1), - }, -}; - -ARRAY_TEST(arguments, parse_arguments, parse_arguments_data) { - struct config *conf = load_config(_d.argc, _d.argv); - ck_assert_pstr_eq(conf->source_file, _d.config.source_file); -} -END_TEST diff --git a/tests/unit/count.c b/tests/unit/count.c deleted file mode 100644 index dd18a12..0000000 --- a/tests/unit/count.c +++ /dev/null @@ -1,30 +0,0 @@ -/***************************************************************************** - * Copyright (C) 2022 Elektroline Inc. All rights reserved. - * K Ladvi 1805/20 - * Prague, 184 00 - * info@elektroline.cz (+420 284 021 111) - *****************************************************************************/ -#include - -#define SUITE "count" -#include - -#include - -TEST_CASE(simple) {} - -TEST(simple, empty) { - char *text = ""; - FILE *f = fmemopen(text, strlen(text), "r"); - ck_assert_int_eq(count_foo(f), 0); - fclose(f); -} -END_TEST - -TEST(simple, simple) { - char *text = "foo: well\nups: nope\nfoo: fee\n"; - FILE *f = fmemopen(text, strlen(text), "r"); - ck_assert_int_eq(count_foo(f), 2); - fclose(f); -} -END_TEST diff --git a/tests/unit/libshvchainpack/chainpack.c b/tests/unit/libshvchainpack/chainpack.c new file mode 100644 index 0000000..faad4a4 --- /dev/null +++ b/tests/unit/libshvchainpack/chainpack.c @@ -0,0 +1,361 @@ +#include +#include +#include +#include + +#define SUITE "chainpack" +#include +#include "packunpack.h" + +#define UNPACK_NEXT \ + { \ + chainpack_unpack_next(&unpack); \ + ck_assert_int_eq(unpack.err_no, CPCP_RC_OK); \ + } \ + while (false) + +TEST_CASE(pack, setup_pack, teardown_pack){}; +TEST_CASE(unpack){}; + + +static const uint8_t cpnull[] = {0x80}; +TEST(pack, pack_none) { + chainpack_pack_null(&pack); + ck_assert_stash(cpnull, sizeof(cpnull)); +} +END_TEST +TEST(unpack, unpack_null) { + cpcp_unpack_context_init(&unpack, cpnull, sizeof(cpnull), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_NULL); +} +END_TEST + +static const struct { + const bool v; + const uint8_t cp; +} cpbool_d[] = {{true, 0xfe}, {false, 0xfd}}; +ARRAY_TEST(pack, pack_bool, cpbool_d) { + chainpack_pack_boolean(&pack, _d.v); + ck_assert_uint_eq(pack.current - pack.start, 1); + ck_assert_int_eq(stashbuf[0], _d.cp); +} +END_TEST +ARRAY_TEST(unpack, unpack_bool, cpbool_d) { + cpcp_unpack_context_init(&unpack, &_d.cp, 1, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_BOOLEAN); + ck_assert(unpack.item.as.Bool == _d.v); +} +END_TEST + +static const struct { + const int64_t v; + const struct bdata cp; +} cpint_d[] = { + {0, B(0x40)}, + {-1, B(0x82, 0x41)}, + {-2, B(0x82, 0x42)}, + {7, B(0x47)}, + {42, B(0x6a)}, + {2147483647, B(0x82, 0xf0, 0x7f, 0xff, 0xff, 0xff)}, + {4294967295, B(0x82, 0xf1, 0x00, 0xff, 0xff, 0xff, 0xff)}, + {9007199254740991, B(0x82, 0xf3, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)}, + {-9007199254740991, B(0x82, 0xf3, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)}, +}; +ARRAY_TEST(pack, pack_int, cpint_d) { + chainpack_pack_int(&pack, _d.v); + ck_assert_stash(_d.cp.v, _d.cp.len); +} +END_TEST +ARRAY_TEST(unpack, unpack_int, cpint_d) { + cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, _d.v); +} +END_TEST + +static const struct { + const uint64_t v; + const struct bdata cp; +} cpuint_d[] = { + {0, B(0x00)}, + {1, B(0x01)}, + {2147483647, B(0x81, 0xf0, 0x7f, 0xff, 0xff, 0xff)}, + {4294967295, B(0x81, 0xf0, 0xff, 0xff, 0xff, 0xff)}, +}; +ARRAY_TEST(pack, pack_uint, cpuint_d) { + chainpack_pack_uint(&pack, _d.v); + ck_assert_stash(_d.cp.v, _d.cp.len); +} +END_TEST +ARRAY_TEST(unpack, unpack_uint, cpuint_d) { + cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_UINT); + ck_assert_uint_eq(unpack.item.as.UInt, _d.v); +} +END_TEST + +static const struct { + const double v; + const struct bdata cp; +} cpdouble_d[] = { + {223.0, B(0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x6b, 0x40)}, +}; +ARRAY_TEST(pack, pack_double, cpdouble_d) { + chainpack_pack_double(&pack, _d.v); + ck_assert_stash(_d.cp.v, _d.cp.len); +} +END_TEST +ARRAY_TEST(unpack, unpack_double, cpdouble_d) { + cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_DOUBLE); + ck_assert_double_eq(unpack.item.as.Double, _d.v); +} +END_TEST + +static const struct { + const cpcp_decimal v; + const struct bdata cp; +} cpdecimal_d[] = { + {.v = {.mantisa = 23, .exponent = -1}, B(0x8c, 0x17, 0x41)}, +}; +ARRAY_TEST(pack, pack_decimal, cpdecimal_d) { + chainpack_pack_decimal(&pack, &_d.v); + ck_assert_stash(_d.cp.v, _d.cp.len); +} +END_TEST +ARRAY_TEST(unpack, unpack_decimal, cpdecimal_d) { + cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_DECIMAL); + ck_assert_int_eq(unpack.item.as.Decimal.mantisa, _d.v.mantisa); + ck_assert_int_eq(unpack.item.as.Decimal.exponent, _d.v.exponent); +} +END_TEST + +static const struct { + const char *const v; + const struct bdata cp; +} cpstring_d[] = { + {.v = "", B(0x86, 0x00)}, + {.v = "foo", B(0x86, 0x03, 'f', 'o', 'o')}, +}; +ARRAY_TEST(pack, pack_string, cpstring_d) { + chainpack_pack_string(&pack, _d.v, strlen(_d.v)); + ck_assert_stash(_d.cp.v, _d.cp.len); +} +END_TEST +ARRAY_TEST(unpack, unpack_string, cpstring_d) { + cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); + ck_assert_int_eq(unpack.item.as.String.chunk_size, strlen(_d.v)); + ck_assert_str_eq(unpack.item.as.String.chunk_start, _d.v); +} +END_TEST + +static const struct { + const char *const v; + const struct bdata cp; +} cpcstring_d[] = { + {.v = "", B(0x8e, 0x00)}, + {.v = "foo", B(0x8e, 'f', 'o', 'o', 0x00)}, +}; +ARRAY_TEST(pack, pack_cstring, cpcstring_d) { + chainpack_pack_cstring_terminated(&pack, _d.v); + ck_assert_stash(_d.cp.v, _d.cp.len); +} +END_TEST +ARRAY_TEST(unpack, unpack_cstring, cpcstring_d) { + cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); + ck_assert_int_eq(unpack.item.as.String.chunk_size, strlen(_d.v)); + ck_assert_str_eq(unpack.item.as.String.chunk_start, _d.v); +} +END_TEST + +static const struct { + const struct bdata blob; + const struct bdata cp; +} cpblob_d[] = { + {B(0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a), + B(0x85, 0x06, 0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a)}, +}; +ARRAY_TEST(pack, pack_blob, cpblob_d) { + chainpack_pack_blob(&pack, _d.blob.v, _d.blob.len); + ck_assert_stash(_d.cp.v, _d.cp.len); +} +END_TEST +ARRAY_TEST(unpack, unpack_blob, cpblob_d) { + cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_BLOB); + ck_assert_int_eq(unpack.item.as.String.chunk_size, _d.blob.len); + ck_assert_mem_eq(unpack.item.as.String.chunk_start, _d.blob.v, _d.blob.len); +} +END_TEST + +static const struct { + const cpcp_date_time v; + const struct bdata cp; +} cpdatetime_d[] = { + {.v = {.msecs_since_epoch = 1517529600001, .minutes_from_utc = 0}, + B(0x8d, 0x04)}, + {.v = {.msecs_since_epoch = 1517529600001, .minutes_from_utc = 60}, + B(0x8d, 0x82, 0x11)}, +}; +ARRAY_TEST(pack, pack_datetime, cpdatetime_d) { + chainpack_pack_date_time(&pack, &_d.v); + ck_assert_stash(_d.cp.v, _d.cp.len); +} +END_TEST +ARRAY_TEST(unpack, unpack_datetime, cpdatetime_d) { + cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_DATE_TIME); + ck_assert_int_eq( + unpack.item.as.DateTime.msecs_since_epoch, _d.v.msecs_since_epoch); + ck_assert_int_eq( + unpack.item.as.DateTime.minutes_from_utc, _d.v.minutes_from_utc); +} +END_TEST + +#define INTARRAY(...) \ + (int64_t[]){__VA_ARGS__}, sizeof((int64_t[]){__VA_ARGS__}) / sizeof(int64_t) +static const struct { + const int64_t *const v; + const size_t vcnt; + const struct bdata cp; +} cplist_ints_d[] = { + {INTARRAY(), B(0x88, 0xff)}, + {INTARRAY(1), B(0x88, 0x41, 0xff)}, + {INTARRAY(1, 2, 3), B(0x88, 0x41, 0x42, 0x43, 0xff)}, +}; +ARRAY_TEST(pack, pack_list_ints, cplist_ints_d) { + chainpack_pack_list_begin(&pack); + for (size_t i = 0; i < _d.vcnt; i++) + chainpack_pack_int(&pack, _d.v[i]); + chainpack_pack_container_end(&pack); + ck_assert_stash(_d.cp.v, _d.cp.len); +} +END_TEST +ARRAY_TEST(unpack, unpack_list_ints, cplist_ints_d) { + cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); + for (size_t i = 0; i < _d.vcnt; i++) { + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, _d.v[i]); + } + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); +} +END_TEST + +const struct bdata list_in_list_d = B(0x88, 0x88, 0xff, 0xff); +TEST(pack, pack_list_in_list) { + chainpack_pack_list_begin(&pack); + chainpack_pack_list_begin(&pack); + chainpack_pack_container_end(&pack); + chainpack_pack_container_end(&pack); + ck_assert_stash(list_in_list_d.v, list_in_list_d.len); +} +END_TEST +TEST(unpack, unpack_list_in_list) { + cpcp_unpack_context_init( + &unpack, list_in_list_d.v, list_in_list_d.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); +} +END_TEST + +static const struct bdata map_d = + B(0x89, 0x86, 0x03, 'f', 'o', 'o', 0x86, 0x03, 'b', 'a', 'r', 0xff); +TEST(pack, pack_map) { + chainpack_pack_map_begin(&pack); + chainpack_pack_string(&pack, "foo", 3); + chainpack_pack_string(&pack, "bar", 3); + chainpack_pack_container_end(&pack); + ck_assert_stash(map_d.v, map_d.len); +} +END_TEST +TEST(unpack, unpack_map) { + cpcp_unpack_context_init(&unpack, map_d.v, map_d.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_MAP); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); + ck_assert_int_eq(unpack.item.as.String.chunk_size, 3); + ck_assert_mem_eq(unpack.item.as.String.chunk_start, "foo", 3); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); + ck_assert_int_eq(unpack.item.as.String.chunk_size, 3); + ck_assert_mem_eq(unpack.item.as.String.chunk_start, "bar", 3); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); +} +END_TEST + +static const struct bdata imap_d = B(0x8a, 0x41, 0x42, 0xff); +TEST(pack, pack_imap) { + chainpack_pack_imap_begin(&pack); + chainpack_pack_int(&pack, 1); + chainpack_pack_int(&pack, 2); + chainpack_pack_container_end(&pack); + ck_assert_stash(imap_d.v, imap_d.len); +} +END_TEST +TEST(unpack, unpack_imap) { + cpcp_unpack_context_init(&unpack, imap_d.v, imap_d.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_IMAP); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, 1); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, 2); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); +} +END_TEST + +static const struct bdata meta_d = B(0x8b, 0x41, 0x42, 0xff, 0x43); +TEST(pack, pack_meta) { + chainpack_pack_meta_begin(&pack); + chainpack_pack_int(&pack, 1); + chainpack_pack_int(&pack, 2); + chainpack_pack_container_end(&pack); + chainpack_pack_int(&pack, 3); + ck_assert_stash(meta_d.v, meta_d.len); +} +END_TEST +TEST(unpack, unpack_meta) { + cpcp_unpack_context_init(&unpack, meta_d.v, meta_d.len, NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_META); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, 1); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, 2); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, 3); +} +END_TEST diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c new file mode 100644 index 0000000..5f3c234 --- /dev/null +++ b/tests/unit/libshvchainpack/cpon.c @@ -0,0 +1,342 @@ +#include +#include +#include +#include + +#define SUITE "cpon" +#include +#include "packunpack.h" + +#define UNPACK_NEXT \ + { \ + cpon_unpack_next(&unpack); \ + ck_assert_int_eq(unpack.err_no, CPCP_RC_OK); \ + } \ + while (false) + +TEST_CASE(pack, setup_pack, teardown_pack){}; +TEST_CASE(unpack){}; + + +static const char *const cpnull = "null"; +TEST(pack, pack_none) { + cpon_pack_null(&pack); + ck_assert_stashstr(cpnull); +} +END_TEST +TEST(unpack, unpack_null) { + cpcp_unpack_context_init(&unpack, cpnull, strlen(cpnull), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_NULL); +} +END_TEST + +static const struct { + const bool v; + const char *const cp; +} cpbool_d[] = {{true, "true"}, {false, "false"}}; +ARRAY_TEST(pack, pack_bool, cpbool_d) { + cpon_pack_boolean(&pack, _d.v); + ck_assert_stashstr(_d.cp); +} +END_TEST +ARRAY_TEST(unpack, unpack_bool, cpbool_d) { + cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_BOOLEAN); + ck_assert(unpack.item.as.Bool == _d.v); +} +END_TEST + +static const struct { + const int64_t v; + const char *const cp; +} cpint_d[] = { + {0, "0"}, + {-1, "-1"}, + {-2, "-2"}, + {7, "7"}, + {42, "42"}, + {2147483647, "2147483647"}, + {4294967295, "4294967295"}, + {9007199254740991, "9007199254740991"}, + {-9007199254740991, "-9007199254740991"}, +}; +ARRAY_TEST(pack, pack_int, cpint_d) { + cpon_pack_int(&pack, _d.v); + ck_assert_stashstr(_d.cp); +} +END_TEST +ARRAY_TEST(unpack, unpack_int, cpint_d) { + cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, _d.v); +} +END_TEST + +static const struct { + const uint64_t v; + const char *const cp; +} cpuint_d[] = { + {0, "0u"}, + {1, "1u"}, + {2147483647, "2147483647u"}, + {4294967295, "4294967295u"}, +}; +ARRAY_TEST(pack, pack_uint, cpuint_d) { + cpon_pack_uint(&pack, _d.v); + ck_assert_stashstr(_d.cp); +} +END_TEST +ARRAY_TEST(unpack, unpack_uint, cpuint_d) { + cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_UINT); + ck_assert_int_eq(unpack.item.as.UInt, _d.v); +} +END_TEST + +struct cpdecimal { + const cpcp_decimal v; + const char *const cp; +}; +static const struct cpdecimal cpdecimal_d[] = { + {.v = {}, "0."}, + {.v = {.mantisa = 223}, "223."}, + {.v = {.mantisa = 23, .exponent = -1}, "2.3"}, +}; +ARRAY_TEST(pack, pack_decimal, cpdecimal_d) { + cpon_pack_decimal(&pack, &_d.v); + ck_assert_stashstr(_d.cp); +} +END_TEST +static void test_unpack_decimal(struct cpdecimal _d) { + cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_DOUBLE); + ck_assert_int_eq(unpack.item.as.Decimal.mantisa, _d.v.mantisa); + ck_assert_int_eq(unpack.item.as.Decimal.exponent, _d.v.exponent); +} +ARRAY_TEST(unpack, unpack_decimal, cpdecimal_d) { + test_unpack_decimal(_d); +} +END_TEST +static const struct cpdecimal cpdecimal_unpack_d[] = { + // TODO it should be more like exponent 0 not -1 🤷 + {.v = {.exponent = -1}, "0.0"}, + {.v = {.mantisa = 2230, .exponent = -1}, "223.0"}, +}; +ARRAY_TEST(unpack, unpack_only_decimal, cpdecimal_unpack_d) { + test_unpack_decimal(_d); +} +END_TEST + +static const struct { + const char *const v; + const char *const cp; +} cpstring_d[] = { + {"", "\"\""}, + {"foo", "\"foo\""}, + {"dvaačtyřicet", "\"dvaačtyřicet\""}, + {"some\t\"tab\"", "\"some\\t\\\"tab\\\"\""}, +}; +ARRAY_TEST(pack, pack_string, cpstring_d) { + cpon_pack_string_terminated(&pack, _d.v); + ck_assert_stashstr(_d.cp); +} +END_TEST +ARRAY_TEST(unpack, unpack_string, cpstring_d) { + cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_UINT); + ck_assert_int_eq(unpack.item.as.String.chunk_size, strlen(_d.v)); + ck_assert_str_eq(unpack.item.as.String.chunk_start, _d.v); +} +END_TEST + +static const struct { + const struct bdata blob; + const char *const cp; +} cpblob_d[] = { + {B(0x61, 0x62, 0xcd, '\t', '\r', '\n'), "b\"ab\\cd\\t\\r\\n\""}, +}; +ARRAY_TEST(pack, pack_blob, cpblob_d) { + cpon_pack_blob(&pack, _d.blob.v, _d.blob.len); + ck_assert_stashstr(_d.cp); +} +END_TEST +ARRAY_TEST(unpack, unpack_blob, cpblob_d) { + cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_BLOB); + ck_assert_int_eq(unpack.item.as.String.chunk_size, _d.blob.len); + ck_assert_mem_eq(unpack.item.as.String.chunk_start, _d.blob.v, _d.blob.len); +} +END_TEST + +static const struct { + const cpcp_date_time v; + const char *const cp; +} cpdatetime_d[] = { + {.v = {.msecs_since_epoch = 1517529600000, .minutes_from_utc = 0}, + "d\"2018-02-02T00:00:00Z\""}, + {.v = {.msecs_since_epoch = 1517529600001, .minutes_from_utc = 60}, + "d\"2018-02-02T01:00:00.001+01\""}, + {.v = {.msecs_since_epoch = 1809340212345, .minutes_from_utc = 60}, + "d\"2027-05-03T11:30:12.345+01\""}, +}; +ARRAY_TEST(pack, pack_datetime, cpdatetime_d) { + cpon_pack_date_time(&pack, &_d.v); + ck_assert_stashstr(_d.cp); +} +END_TEST +ARRAY_TEST(unpack, unpack_datetime, cpdatetime_d) { + cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_DATE_TIME); + ck_assert_int_eq( + unpack.item.as.DateTime.msecs_since_epoch, _d.v.msecs_since_epoch); + ck_assert_int_eq( + unpack.item.as.DateTime.minutes_from_utc, _d.v.minutes_from_utc); +} +END_TEST + +#define INTARRAY(...) \ + (int64_t[]){__VA_ARGS__}, sizeof((int64_t[]){__VA_ARGS__}) / sizeof(int64_t) +static const struct { + const int64_t *const v; + const size_t vcnt; + const char *const cp; +} cplist_ints_d[] = { + {INTARRAY(), "[]"}, + {INTARRAY(1), "[1]"}, + {INTARRAY(1, 2, 3), "[1,2,3]"}, +}; +ARRAY_TEST(pack, pack_list_ints, cplist_ints_d) { + cpon_pack_list_begin(&pack); + for (size_t i = 0; i < _d.vcnt; i++) { + cpon_pack_field_delim(&pack, i == 0, true); + cpon_pack_int(&pack, _d.v[i]); + } + cpon_pack_list_end(&pack, true); + ck_assert_stashstr(_d.cp); +} +END_TEST +ARRAY_TEST(unpack, unpack_list_ints, cplist_ints_d) { + cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); + for (size_t i = 0; i < _d.vcnt; i++) { + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, _d.v[i]); + } + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); +} +END_TEST + +static const char *const list_in_list_d = "[[]]"; +TEST(pack, pack_list_in_list) { + cpon_pack_list_begin(&pack); + cpon_pack_list_begin(&pack); + cpon_pack_list_end(&pack, true); + cpon_pack_list_end(&pack, true); + ck_assert_stashstr(list_in_list_d); +} +END_TEST +TEST(unpack, unpack_list_in_list) { + cpcp_unpack_context_init( + &unpack, list_in_list_d, strlen(list_in_list_d), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); +} +END_TEST + +static const char *const map_d = "{\"foo\":\"bar\"}"; +TEST(pack, pack_map) { + cpon_pack_map_begin(&pack); + cpon_pack_string(&pack, "foo", 3); + cpon_pack_key_val_delim(&pack); + cpon_pack_string(&pack, "bar", 3); + cpon_pack_map_end(&pack, true); + ck_assert_stashstr(map_d); +} +END_TEST +TEST(unpack, unpack_map) { + cpcp_unpack_context_init(&unpack, map_d, strlen(map_d), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_MAP); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); + ck_assert_int_eq(unpack.item.as.String.chunk_size, 3); + ck_assert_mem_eq(unpack.item.as.String.chunk_start, "foo", 3); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); + ck_assert_int_eq(unpack.item.as.String.chunk_size, 3); + ck_assert_mem_eq(unpack.item.as.String.chunk_start, "bar", 3); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); +} +END_TEST + +static const char *const imap_d = "i{1:2}"; +TEST(pack, pack_imap) { + cpon_pack_imap_begin(&pack); + cpon_pack_int(&pack, 1); + cpon_pack_key_val_delim(&pack); + cpon_pack_int(&pack, 2); + cpon_pack_imap_end(&pack, true); + ck_assert_stashstr(imap_d); +} +END_TEST +TEST(unpack, unpack_imap) { + cpcp_unpack_context_init(&unpack, imap_d, strlen(imap_d), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_IMAP); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, 1); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, 2); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); +} +END_TEST + +static const char *const meta_d = "<1:2>3"; +TEST(pack, pack_meta) { + cpon_pack_meta_begin(&pack); + cpon_pack_int(&pack, 1); + cpon_pack_key_val_delim(&pack); + cpon_pack_int(&pack, 2); + cpon_pack_meta_end(&pack, true); + cpon_pack_int(&pack, 3); + ck_assert_stashstr(meta_d); +} +END_TEST +TEST(unpack, unpack_meta) { + cpcp_unpack_context_init(&unpack, meta_d, strlen(meta_d), NULL, NULL); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_META); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, 1); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, 2); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); + UNPACK_NEXT + ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); + ck_assert_int_eq(unpack.item.as.Int, 3); +} +END_TEST diff --git a/tests/unit/libshvchainpack/meson.build b/tests/unit/libshvchainpack/meson.build new file mode 100644 index 0000000..707495a --- /dev/null +++ b/tests/unit/libshvchainpack/meson.build @@ -0,0 +1,18 @@ +unittest_libshvchainpack = executable( + 'unittest-libshvchainpack', + [ + 'packunpack.c', + 'chainpack.c', + 'cpon.c', + ], + dependencies: [libshvrpc_dep, check_suite, obstack], + include_directories: includes, +) +test( + 'unittest-libshvchainpack', + bash, + args: [test_driver, unittest_libshvchainpack.full_path()], + env: unittests_env, + protocol: 'tap', + depends: unittest_libshvchainpack, +) diff --git a/tests/unit/libshvchainpack/packunpack.c b/tests/unit/libshvchainpack/packunpack.c new file mode 100644 index 0000000..010ea85 --- /dev/null +++ b/tests/unit/libshvchainpack/packunpack.c @@ -0,0 +1,28 @@ +#include "packunpack.h" + +struct cpcp_pack_context pack; +struct cpcp_unpack_context unpack; + +uint8_t *stashbuf; +size_t stashsiz; + +static void pack_overflow_handler(struct cpcp_pack_context *pack, size_t size_hint) { + size_t off = pack->current - pack->start; + stashsiz *= 2; + uint8_t *buf = realloc(stashbuf, stashsiz); + ck_assert_ptr_nonnull(buf); + stashbuf = buf; + pack->start = (char *)buf; + pack->current = (char *)buf + off; + pack->end = (char *)buf + stashsiz; +} + +void setup_pack(void) { + stashsiz = 2; + stashbuf = malloc(stashsiz); + cpcp_pack_context_init(&pack, stashbuf, stashsiz, pack_overflow_handler); +} + +void teardown_pack(void) { + free(stashbuf); +} diff --git a/tests/unit/libshvchainpack/packunpack.h b/tests/unit/libshvchainpack/packunpack.h new file mode 100644 index 0000000..b7efdfd --- /dev/null +++ b/tests/unit/libshvchainpack/packunpack.h @@ -0,0 +1,47 @@ +#ifndef _PACKUNPACK_H_ +#define _PACKUNPACK_H_ +#include +#include +#include +#include +#include + +/* Unitility macro that provides array of bytes with its size */ +struct bdata { + const uint8_t *const v; + const size_t len; +}; +#define B(...) \ + (struct bdata) { \ + .v = (const uint8_t[]){__VA_ARGS__}, \ + .len = sizeof((const uint8_t[]){__VA_ARGS__}), \ + } + +extern struct cpcp_pack_context pack; +extern struct cpcp_unpack_context unpack; + +extern uint8_t *stashbuf; +extern size_t stashsiz; + +#define ck_assert_stash(v, len) \ + do { \ + ck_assert_uint_eq(pack.current - pack.start, len); \ + ck_assert_mem_eq(stashbuf, v, len); \ + } while (false) + +#define ck_assert_stash(v, len) \ + do { \ + ck_assert_uint_eq(pack.current - pack.start, len); \ + ck_assert_mem_eq(stashbuf, v, len); \ + } while (false) + +#define ck_assert_stashstr(v) \ + do { \ + cpcp_pack_copy_byte(&pack, '\0'); \ + ck_assert_str_eq((char *)stashbuf, v); \ + } while (false) + +void setup_pack(void); +void teardown_pack(void); + +#endif diff --git a/tests/unit/libshvrpc/meson.build b/tests/unit/libshvrpc/meson.build new file mode 100644 index 0000000..96fc410 --- /dev/null +++ b/tests/unit/libshvrpc/meson.build @@ -0,0 +1,16 @@ +unittest_libshvrpc = executable( + 'unittest-libshvrpc', + [ + 'rpcurl.c', + ], + dependencies: [libshvrpc_dep, check_suite, obstack], + include_directories: includes, +) +test( + 'unittest-libshvrpc', + bash, + args: [test_driver, unittest_libshvrpc.full_path()], + env: unittests_env, + protocol: 'tap', + depends: unittest_libshvrpc, +) diff --git a/tests/unit/libshvrpc/rpcurl.c b/tests/unit/libshvrpc/rpcurl.c new file mode 100644 index 0000000..5111c96 --- /dev/null +++ b/tests/unit/libshvrpc/rpcurl.c @@ -0,0 +1,153 @@ +#define SUITE "rpcurl" +#include + +#include + +TEST_CASE(parse){}; + + +struct urld { + const char *const str; + struct rpcurl url; +}; + +static struct urld url_d[] = { + {"localsocket:/dev/null", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_LOCAL_SOCKET, + .location = "/dev/null", + }}, + {"localsocket:dir/socket", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_LOCAL_SOCKET, + .location = "dir/socket", + }}, + {"tcp://test@localhost:4242", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_TCP, + .location = "localhost", + .username = "test", + .port = 4242, + }}, + {"udp://test@localhost:4242", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_UDP, + .location = "localhost", + .username = "test", + .port = 4242, + }}, + {"tcp://localhost:4242?devid=foo&devmount=/dev/null", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_TCP, + .location = "localhost", + .port = 4242, + .device_id = "foo", + .device_mountpoint = "/dev/null", + }}, + {"tcp://localhost:4242?devid=foo&devmount=/dev/null&password=test", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_TCP, + .location = "localhost", + .port = 4242, + .password = "test", + .device_id = "foo", + .device_mountpoint = "/dev/null", + }}, + {"tcp://localhost:4242?devid=foo&devmount=/dev/null&shapass=xxxxxxxx", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_TCP, + .location = "localhost", + .port = 4242, + .password = "xxxxxxxx", + .login_type = RPC_LOGIN_SHA1, + .device_id = "foo", + .device_mountpoint = "/dev/null", + }}, + {"tcp://[::]:4242", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_TCP, + .location = "::", + .port = 4242, + }}, +}; + +static struct urld parse_d[] = { + {"", (struct rpcurl){.protocol = RPC_PROTOCOL_LOCAL_SOCKET}}, + {"socket", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_LOCAL_SOCKET, + .location = "socket", + }}, + {"/dev/null", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_LOCAL_SOCKET, + .location = "/dev/null", + }}, + {"localsocket:/dev/null", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_LOCAL_SOCKET, + .location = "/dev/null", + }}, + {"serialport:/dev/ttyUSB0", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_SERIAL_PORT, + .location = "/dev/ttyUSB0", + }}, + {"tcp://localhost", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_TCP, + .location = "localhost", + .port = 3755, + }}, + {"udp://localhost", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_UDP, + .location = "localhost", + .port = 3755, + }}, + {"tcp://localhost?devid=foo", + (struct rpcurl){ + .protocol = RPC_PROTOCOL_TCP, + .location = "localhost", + .port = 3755, + .device_id = "foo", + }}, +}; +static void parse_test(struct urld _d) { + const char *err; + struct rpcurl *url = rpcurl_parse(_d.str, &err); + ck_assert_ptr_nonnull(url); + ck_assert_ptr_null(err); + ck_assert_int_eq(url->protocol, _d.url.protocol); + ck_assert_pstr_eq(url->location, _d.url.location); + ck_assert_int_eq(url->port, _d.url.port); + ck_assert_pstr_eq(url->username, _d.url.username); + ck_assert_pstr_eq(url->password, _d.url.password); + ck_assert_int_eq(url->login_type, _d.url.login_type); + ck_assert_pstr_eq(url->device_id, _d.url.device_id); + ck_assert_pstr_eq(url->device_mountpoint, _d.url.device_mountpoint); + rpcurl_free(url); +} +ARRAY_TEST(parse, parse_url, url_d) { + parse_test(_d); +} +END_TEST +ARRAY_TEST(parse, parse_parse, parse_d) { + parse_test(_d); +} +END_TEST + +static const struct { + const char *const str; + size_t error; +} parse_invalid_d[] = { + {"foo://some", 0}, + {"udp://some:none?password=foo", 15}, // We parse it backward so end of the port + {"udp://some?invalid=foo", 11}, +}; +ARRAY_TEST(parse, parse_invalid, parse_invalid_d) { + const char *err; + ck_assert_ptr_null(rpcurl_parse(_d.str, &err)); + ck_assert_ptr_eq(err, _d.str +_d.error); +} +END_TEST diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 2f92901..e9d2486 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -10,36 +10,5 @@ unittests_env = [ 'CK_VERBOSITY=silent', ] - -unittest_libfoo = executable( - 'unittest-libfoo', - [ - 'count.c', - ], - dependencies: [libfoo_dep, check_suite, obstack], - include_directories: includes, -) -test( - 'unittest-libfoo', - bash, - args: [test_driver, unittest_libfoo.full_path()], - env: unittests_env, - protocol: 'tap', - depends: unittest_libfoo, -) - - -unittest_foo = executable( - 'unittest-foo', - foo_sources + ['config.c'], - dependencies: foo_dependencies + [check_suite, obstack], - include_directories: [includes, foo_internal_includes], -) -test( - 'unittest-foo', - bash, - args: [test_driver, unittest_foo.full_path()], - env: unittests_env, - protocol: 'tap', - depends: unittest_foo, -) +subdir('libshvchainpack') +subdir('libshvrpc') From 24502d8c084e92ba881b8b48f96e4a88da4cffaf Mon Sep 17 00:00:00 2001 From: Bartaaak Date: Tue, 25 Jul 2023 09:54:54 +0200 Subject: [PATCH 02/44] Add test to increase covereged of libshvchainpack --- tests/unit/libshvchainpack/chainpack.c | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/unit/libshvchainpack/chainpack.c b/tests/unit/libshvchainpack/chainpack.c index faad4a4..c2dd670 100644 --- a/tests/unit/libshvchainpack/chainpack.c +++ b/tests/unit/libshvchainpack/chainpack.c @@ -359,3 +359,39 @@ TEST(unpack, unpack_meta) { ck_assert_int_eq(unpack.item.as.Int, 3); } END_TEST +TEST(pack,pack_null_error) { // line 302 + pack.err_no=CPCP_RC_LOGICAL_ERROR; + chainpack_pack_null(&pack); + ck_assert_ptr_eq(pack.start,pack.current); + +} +END_TEST +//TEST(pack,pack_true_error){ // TODO line 309-problem, implicit declaration of chainpack_pack_true +// pack.err_no=CPCP_RC_LOGICAL_ERROR; +// chainpack_pack_true(&pack); +// ck_assert_ptr_eq(pack.start,pack.current); +//} +//END_TEST +TEST(pack,pack_bool_error){ // line 321 + pack.err_no=CPCP_RC_LOGICAL_ERROR; + chainpack_pack_boolean(&pack,false); + ck_assert_ptr_eq(pack.start,pack.current); +} +END_TEST +TEST(pack,pack_meta_error){ // line 348 + pack.err_no=CPCP_RC_LOGICAL_ERROR; + chainpack_pack_meta_begin(&pack); + ck_assert_ptr_eq(pack.start,pack.current); +} +END_TEST +TEST(pack,pack_list_error){ //line 330 + pack.err_no=CPCP_RC_LOGICAL_ERROR; + chainpack_pack_list_begin(&pack); + ck_assert_ptr_eq(pack.start,pack.current); +} +TEST(pack,pack_map_error){ + pack.err_no=CPCP_RC_LOGICAL_ERROR; + chainpack_pack_map_begin(&pack); + ck_assert_ptr_eq(pack.start,pack.current); +} + From aa56c0e3b4e68b527728efc517202e88c23d8b88 Mon Sep 17 00:00:00 2001 From: Bartaaak Date: Wed, 26 Jul 2023 11:04:00 +0200 Subject: [PATCH 03/44] Add tests to increase coverage of libshvchainpack. --- tests/unit/libshvchainpack/chainpack.c | 77 ++++++++++++++++++++------ 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/tests/unit/libshvchainpack/chainpack.c b/tests/unit/libshvchainpack/chainpack.c index c2dd670..7675820 100644 --- a/tests/unit/libshvchainpack/chainpack.c +++ b/tests/unit/libshvchainpack/chainpack.c @@ -24,6 +24,7 @@ TEST(pack, pack_none) { ck_assert_stash(cpnull, sizeof(cpnull)); } END_TEST + TEST(unpack, unpack_null) { cpcp_unpack_context_init(&unpack, cpnull, sizeof(cpnull), NULL, NULL); UNPACK_NEXT @@ -41,6 +42,7 @@ ARRAY_TEST(pack, pack_bool, cpbool_d) { ck_assert_int_eq(stashbuf[0], _d.cp); } END_TEST + ARRAY_TEST(unpack, unpack_bool, cpbool_d) { cpcp_unpack_context_init(&unpack, &_d.cp, 1, NULL, NULL); UNPACK_NEXT @@ -68,6 +70,7 @@ ARRAY_TEST(pack, pack_int, cpint_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST + ARRAY_TEST(unpack, unpack_int, cpint_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -90,6 +93,7 @@ ARRAY_TEST(pack, pack_uint, cpuint_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST + ARRAY_TEST(unpack, unpack_uint, cpuint_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -109,6 +113,7 @@ ARRAY_TEST(pack, pack_double, cpdouble_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST + ARRAY_TEST(unpack, unpack_double, cpdouble_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -128,6 +133,7 @@ ARRAY_TEST(pack, pack_decimal, cpdecimal_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST + ARRAY_TEST(unpack, unpack_decimal, cpdecimal_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -149,6 +155,7 @@ ARRAY_TEST(pack, pack_string, cpstring_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST + ARRAY_TEST(unpack, unpack_string, cpstring_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -170,6 +177,7 @@ ARRAY_TEST(pack, pack_cstring, cpcstring_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST + ARRAY_TEST(unpack, unpack_cstring, cpcstring_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -191,6 +199,7 @@ ARRAY_TEST(pack, pack_blob, cpblob_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST + ARRAY_TEST(unpack, unpack_blob, cpblob_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -214,6 +223,7 @@ ARRAY_TEST(pack, pack_datetime, cpdatetime_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST + ARRAY_TEST(unpack, unpack_datetime, cpdatetime_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -244,6 +254,7 @@ ARRAY_TEST(pack, pack_list_ints, cplist_ints_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST + ARRAY_TEST(unpack, unpack_list_ints, cplist_ints_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -267,6 +278,7 @@ TEST(pack, pack_list_in_list) { ck_assert_stash(list_in_list_d.v, list_in_list_d.len); } END_TEST + TEST(unpack, unpack_list_in_list) { cpcp_unpack_context_init( &unpack, list_in_list_d.v, list_in_list_d.len, NULL, NULL); @@ -291,6 +303,7 @@ TEST(pack, pack_map) { ck_assert_stash(map_d.v, map_d.len); } END_TEST + TEST(unpack, unpack_map) { cpcp_unpack_context_init(&unpack, map_d.v, map_d.len, NULL, NULL); UNPACK_NEXT @@ -317,6 +330,7 @@ TEST(pack, pack_imap) { ck_assert_stash(imap_d.v, imap_d.len); } END_TEST + TEST(unpack, unpack_imap) { cpcp_unpack_context_init(&unpack, imap_d.v, imap_d.len, NULL, NULL); UNPACK_NEXT @@ -342,6 +356,7 @@ TEST(pack, pack_meta) { ck_assert_stash(meta_d.v, meta_d.len); } END_TEST + TEST(unpack, unpack_meta) { cpcp_unpack_context_init(&unpack, meta_d.v, meta_d.len, NULL, NULL); UNPACK_NEXT @@ -359,39 +374,69 @@ TEST(unpack, unpack_meta) { ck_assert_int_eq(unpack.item.as.Int, 3); } END_TEST -TEST(pack,pack_null_error) { // line 302 + +TEST(pack,pack_uint_error){ //line 225 pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_null(&pack); + chainpack_pack_uint(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); +} +END_TEST +TEST(pack,pack_int_error){ //line 236 + pack.err_no=CPCP_RC_LOGICAL_ERROR; + chainpack_pack_int(&pack,0); + ck_assert_ptr_eq(pack.start,pack.current); } END_TEST -//TEST(pack,pack_true_error){ // TODO line 309-problem, implicit declaration of chainpack_pack_true -// pack.err_no=CPCP_RC_LOGICAL_ERROR; -// chainpack_pack_true(&pack); -// ck_assert_ptr_eq(pack.start,pack.current); -//} -//END_TEST -TEST(pack,pack_bool_error){ // line 321 + +static const cpcp_decimal* d; +TEST(pack,pack_decimal_error){ //line 247 pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_boolean(&pack,false); + chainpack_pack_decimal(&pack,d); ck_assert_ptr_eq(pack.start,pack.current); } END_TEST -TEST(pack,pack_meta_error){ // line 348 + +TEST(pack,pack_double_error){ //line 255 pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_meta_begin(&pack); + chainpack_pack_double(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); } END_TEST -TEST(pack,pack_list_error){ //line 330 + +static const cpcp_date_time * time_er; +TEST(pack,pack_date_error){ //line 276 pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_list_begin(&pack); + chainpack_pack_date_time(&pack,time_er); + ck_assert_ptr_eq(pack.start,pack.current); +} +END_TEST + +static const void (*ck_error_simple_call_1d[])(cpcp_pack_context*) = { + chainpack_pack_null, //line 302 + chainpack_pack_list_begin, //line 330 + chainpack_pack_map_begin, //line 336 + chainpack_pack_imap_begin, //line 342 + chainpack_pack_meta_begin, //line 348 + chainpack_pack_container_end //line 354 +}; +ARRAY_TEST(pack, pack_error_simple_call,ck_error_simple_call_1d) { + pack.err_no=CPCP_RC_LOGICAL_ERROR; + _d(&pack); ck_assert_ptr_eq(pack.start,pack.current); } -TEST(pack,pack_map_error){ +END_TEST + +TEST(pack,pack_bool_error){ //line 321 pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_map_begin(&pack); + chainpack_pack_boolean(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); } +END_TEST +TEST(unpack,unpack_next_1error){ //line 540 + unpack.err_no=CPCP_RC_LOGICAL_ERROR; + chainpack_unpack_next(&unpack); + ck_assert_ptr_eq(unpack.start,unpack.current); +} +END_TEST \ No newline at end of file From 9ae8e110b20aad88484e64614240667e5e5096d1 Mon Sep 17 00:00:00 2001 From: Bartaaak Date: Wed, 26 Jul 2023 14:38:03 +0200 Subject: [PATCH 04/44] More tests have been added, improvements of the previous tests. --- tests/unit/libshvchainpack/chainpack.c | 80 ++++++++++++-------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/tests/unit/libshvchainpack/chainpack.c b/tests/unit/libshvchainpack/chainpack.c index 7675820..4f56e00 100644 --- a/tests/unit/libshvchainpack/chainpack.c +++ b/tests/unit/libshvchainpack/chainpack.c @@ -24,7 +24,6 @@ TEST(pack, pack_none) { ck_assert_stash(cpnull, sizeof(cpnull)); } END_TEST - TEST(unpack, unpack_null) { cpcp_unpack_context_init(&unpack, cpnull, sizeof(cpnull), NULL, NULL); UNPACK_NEXT @@ -42,7 +41,6 @@ ARRAY_TEST(pack, pack_bool, cpbool_d) { ck_assert_int_eq(stashbuf[0], _d.cp); } END_TEST - ARRAY_TEST(unpack, unpack_bool, cpbool_d) { cpcp_unpack_context_init(&unpack, &_d.cp, 1, NULL, NULL); UNPACK_NEXT @@ -70,7 +68,6 @@ ARRAY_TEST(pack, pack_int, cpint_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST - ARRAY_TEST(unpack, unpack_int, cpint_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -93,7 +90,6 @@ ARRAY_TEST(pack, pack_uint, cpuint_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST - ARRAY_TEST(unpack, unpack_uint, cpuint_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -113,7 +109,6 @@ ARRAY_TEST(pack, pack_double, cpdouble_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST - ARRAY_TEST(unpack, unpack_double, cpdouble_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -133,7 +128,6 @@ ARRAY_TEST(pack, pack_decimal, cpdecimal_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST - ARRAY_TEST(unpack, unpack_decimal, cpdecimal_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -155,7 +149,6 @@ ARRAY_TEST(pack, pack_string, cpstring_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST - ARRAY_TEST(unpack, unpack_string, cpstring_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -177,7 +170,6 @@ ARRAY_TEST(pack, pack_cstring, cpcstring_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST - ARRAY_TEST(unpack, unpack_cstring, cpcstring_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -199,7 +191,6 @@ ARRAY_TEST(pack, pack_blob, cpblob_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST - ARRAY_TEST(unpack, unpack_blob, cpblob_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -223,7 +214,6 @@ ARRAY_TEST(pack, pack_datetime, cpdatetime_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST - ARRAY_TEST(unpack, unpack_datetime, cpdatetime_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -254,7 +244,6 @@ ARRAY_TEST(pack, pack_list_ints, cplist_ints_d) { ck_assert_stash(_d.cp.v, _d.cp.len); } END_TEST - ARRAY_TEST(unpack, unpack_list_ints, cplist_ints_d) { cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); UNPACK_NEXT @@ -278,7 +267,6 @@ TEST(pack, pack_list_in_list) { ck_assert_stash(list_in_list_d.v, list_in_list_d.len); } END_TEST - TEST(unpack, unpack_list_in_list) { cpcp_unpack_context_init( &unpack, list_in_list_d.v, list_in_list_d.len, NULL, NULL); @@ -303,7 +291,6 @@ TEST(pack, pack_map) { ck_assert_stash(map_d.v, map_d.len); } END_TEST - TEST(unpack, unpack_map) { cpcp_unpack_context_init(&unpack, map_d.v, map_d.len, NULL, NULL); UNPACK_NEXT @@ -330,7 +317,6 @@ TEST(pack, pack_imap) { ck_assert_stash(imap_d.v, imap_d.len); } END_TEST - TEST(unpack, unpack_imap) { cpcp_unpack_context_init(&unpack, imap_d.v, imap_d.len, NULL, NULL); UNPACK_NEXT @@ -356,7 +342,6 @@ TEST(pack, pack_meta) { ck_assert_stash(meta_d.v, meta_d.len); } END_TEST - TEST(unpack, unpack_meta) { cpcp_unpack_context_init(&unpack, meta_d.v, meta_d.len, NULL, NULL); UNPACK_NEXT @@ -375,68 +360,79 @@ TEST(unpack, unpack_meta) { } END_TEST -TEST(pack,pack_uint_error){ //line 225 +/* Packing function should not generate any output when pack context is in error */ +TEST(pack,pack_uint_error){ pack.err_no=CPCP_RC_LOGICAL_ERROR; chainpack_pack_uint(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); } END_TEST - -TEST(pack,pack_int_error){ //line 236 +TEST(pack,pack_int_error){ pack.err_no=CPCP_RC_LOGICAL_ERROR; chainpack_pack_int(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); } END_TEST - -static const cpcp_decimal* d; -TEST(pack,pack_decimal_error){ //line 247 +TEST(pack,pack_decimal_error){ + const static cpcp_decimal d={0,0}; pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_decimal(&pack,d); + chainpack_pack_decimal(&pack,&d); ck_assert_ptr_eq(pack.start,pack.current); } END_TEST - -TEST(pack,pack_double_error){ //line 255 +TEST(pack,pack_double_error){ pack.err_no=CPCP_RC_LOGICAL_ERROR; chainpack_pack_double(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); } END_TEST - -static const cpcp_date_time * time_er; -TEST(pack,pack_date_error){ //line 276 +TEST(pack,pack_date_error){ + static const cpcp_date_time time_er={0,0}; pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_date_time(&pack,time_er); + chainpack_pack_date_time(&pack,&time_er); ck_assert_ptr_eq(pack.start,pack.current); } END_TEST - -static const void (*ck_error_simple_call_1d[])(cpcp_pack_context*) = { - chainpack_pack_null, //line 302 - chainpack_pack_list_begin, //line 330 - chainpack_pack_map_begin, //line 336 - chainpack_pack_imap_begin, //line 342 - chainpack_pack_meta_begin, //line 348 - chainpack_pack_container_end //line 354 +static const void (*pack_error_simple_call_d[])(cpcp_pack_context*) = { + chainpack_pack_null, + chainpack_pack_list_begin, + chainpack_pack_map_begin, + chainpack_pack_imap_begin, + chainpack_pack_meta_begin, + chainpack_pack_container_end }; -ARRAY_TEST(pack, pack_error_simple_call,ck_error_simple_call_1d) { +ARRAY_TEST(pack, pack_error_simple_call) { pack.err_no=CPCP_RC_LOGICAL_ERROR; _d(&pack); ck_assert_ptr_eq(pack.start,pack.current); } END_TEST - -TEST(pack,pack_bool_error){ //line 321 +TEST(pack,pack_bool_error){ pack.err_no=CPCP_RC_LOGICAL_ERROR; chainpack_pack_boolean(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); } END_TEST - -TEST(unpack,unpack_next_1error){ //line 540 +TEST(unpack,unpack_next_error){ + cpcp_unpack_context_init(&unpack,NULL,0,NULL,NULL); unpack.err_no=CPCP_RC_LOGICAL_ERROR; chainpack_unpack_next(&unpack); ck_assert_ptr_eq(unpack.start,unpack.current); } -END_TEST \ No newline at end of file +END_TEST +TEST(unpack,unnpack_next_string_error){ + cpcp_unpack_context_init(&unpack,NULL,0,NULL,NULL); + unpack.item.type=CPCP_ITEM_STRING; + unpack.item.as.String.last_chunk=0; + chainpack_unpack_next(&unpack); + ck_assert_ptr_eq(unpack.start,unpack.current); +} +END_TEST +TEST(unpack,unpack_next_blob_error){ + cpcp_unpack_context_init(&unpack,NULL,0,NULL,NULL); + unpack.item.type=CPCP_ITEM_BLOB; + unpack.item.as.String.last_chunk=0; + chainpack_unpack_next(&unpack); + ck_assert_ptr_eq(unpack.start,unpack.current); +} + From 0902974e78d1b31cbbfe52f0e1da5df4dc9500ca Mon Sep 17 00:00:00 2001 From: Bartaaak Date: Wed, 26 Jul 2023 16:22:12 +0200 Subject: [PATCH 05/44] New tests were added into libshvchainpack/cpon.c to increase coveraged --- tests/unit/libshvchainpack/cpon.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index 5f3c234..f703413 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -340,3 +340,20 @@ TEST(unpack, unpack_meta) { ck_assert_int_eq(unpack.item.as.Int, 3); } END_TEST + +/* Packing function should not generate any output when pack context is in error */ +TEST(pack,pack_uint_error){ + pack.err_no=CPCP_RC_LOGICAL_ERROR; + cpon_pack_uint(&pack,0); + ck_assert_ptr_eq(pack.start,pack.current); +} +TEST(pack,pack_int_error){ + pack.err_no=CPCP_RC_LOGICAL_ERROR; + cpon_pack_int(&pack,0); + ck_assert_ptr_eq(pack.start,pack.current); +} +TEST(pack,pack_double_error){ + pack.err_no=CPCP_RC_LOGICAL_ERROR; + cpon_pack_double(&pack,0); + ck_assert_ptr_eq(pack.start,pack.current); +} \ No newline at end of file From a305144ad980cf30a373800202e5a7d058b2ba38 Mon Sep 17 00:00:00 2001 From: Bartaaak Date: Thu, 27 Jul 2023 16:13:48 +0200 Subject: [PATCH 06/44] Added more test for libshvchainpack/cpon.c. --- tests/unit/libshvchainpack/chainpack.c | 2 +- tests/unit/libshvchainpack/cpon.c | 46 +++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/tests/unit/libshvchainpack/chainpack.c b/tests/unit/libshvchainpack/chainpack.c index 4f56e00..e9c0e7f 100644 --- a/tests/unit/libshvchainpack/chainpack.c +++ b/tests/unit/libshvchainpack/chainpack.c @@ -435,4 +435,4 @@ TEST(unpack,unpack_next_blob_error){ chainpack_unpack_next(&unpack); ck_assert_ptr_eq(unpack.start,unpack.current); } - +END_TEST diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index f703413..f990d8f 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -347,13 +347,57 @@ TEST(pack,pack_uint_error){ cpon_pack_uint(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); } +END_TEST TEST(pack,pack_int_error){ pack.err_no=CPCP_RC_LOGICAL_ERROR; cpon_pack_int(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); } +END_TEST TEST(pack,pack_double_error){ pack.err_no=CPCP_RC_LOGICAL_ERROR; cpon_pack_double(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); -} \ No newline at end of file +} +END_TEST +static const void (*pack_error_simple_call_d[])(cpcp_pack_context*) = { + cpon_pack_null, + cpon_pack_list_begin, + cpon_pack_map_begin, + cpon_pack_imap_begin, + cpon_pack_meta_begin +}; +ARRAY_TEST(pack, pack_error_simple_call) { + pack.err_no=CPCP_RC_LOGICAL_ERROR; + _d(&pack); + ck_assert_ptr_eq(pack.start,pack.current); +} +END_TEST +static const void (*pack_error_bool_call_d[])(cpcp_pack_context*,bool) = { + cpon_pack_boolean, + cpon_pack_list_end, + cpon_pack_map_end, + cpon_pack_imap_end, + cpon_pack_meta_end +}; +ARRAY_TEST(pack, pack_error_bool_call) { + pack.err_no=CPCP_RC_LOGICAL_ERROR; + _d(&pack,0); + ck_assert_ptr_eq(pack.start,pack.current); + +} +END_TEST +TEST(unpack,unpack_insig_error){ + cpcp_unpack_context_init(&unpack,NULL,0,NULL,NULL); + cpon_unpack_skip_insignificant(&unpack); + ck_assert_ptr_eq(unpack.start,unpack.current); +} +END_TEST +TEST(unpack,unpack_next_error){ + cpcp_unpack_context_init(&unpack,NULL,0,NULL,NULL); + unpack.err_no=CPCP_RC_LOGICAL_ERROR; + cpon_unpack_next(&unpack); + ck_assert_ptr_eq(unpack.start,unpack.current); +} +END_TEST + From 6d602c0f7d171d93b67afdb04373ff262cad583c Mon Sep 17 00:00:00 2001 From: Bartaaak Date: Fri, 28 Jul 2023 14:11:20 +0200 Subject: [PATCH 07/44] Added tests for packing doubles in libshvchainpack/cpon.c to increase coveraged. --- tests/unit/libshvchainpack/cpon.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index f990d8f..3f434a3 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -384,7 +384,21 @@ ARRAY_TEST(pack, pack_error_bool_call) { pack.err_no=CPCP_RC_LOGICAL_ERROR; _d(&pack,0); ck_assert_ptr_eq(pack.start,pack.current); - +} +END_TEST +TEST(pack, pack_double_zero_test) { + cpon_pack_double(&pack,0); + ck_assert_str_eq(pack.start,"0."); +} +END_TEST +TEST(pack,pack_double_pos_test){ + cpon_pack_double(&pack,1000); + ck_assert_str_eq(pack.start,"1000."); +} +END_TEST +TEST(pack,pack_double_neg_test){ + cpon_pack_double(&pack,-1000); + ck_assert_str_eq(pack.start,"-1000."); } END_TEST TEST(unpack,unpack_insig_error){ @@ -400,4 +414,3 @@ TEST(unpack,unpack_next_error){ ck_assert_ptr_eq(unpack.start,unpack.current); } END_TEST - From 52ba103fde24b5c5e963c4915e1fd66a2bfdf4f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Wed, 2 Aug 2023 07:04:15 +0200 Subject: [PATCH 08/44] fixup! TMP --- include/meson.build | 2 +- include/shv/chainpack.h | 11 +- include/shv/cp.h | 133 +++++++++++++ include/shv/cpchainpack.h | 207 +++++++++++++++++++ include/shv/cpcp.h | 10 +- include/shv/cpcp_convert.h | 4 +- include/shv/cpcp_pack.h | 1 - include/shv/cpcp_unpack.h | 1 - include/shv/cpon.h | 3 +- include/shv/rpcclient.h | 55 +++-- include/shv/rpcmsg.h | 138 +++++++++++++ include/shv/rpcurl.h | 6 +- libshvchainpack/README.md | 6 - libshvchainpack/chainpack.c | 5 +- libshvchainpack/chainpack.h | 34 ++++ libshvchainpack/chainpack_pack.c | 309 +++++++++++++++++++++++++++++ libshvchainpack/chainpack_unpack.c | 286 ++++++++++++++++++++++++++ libshvchainpack/cpcp.c | 8 +- libshvchainpack/cpcp_convert.c | 4 +- libshvchainpack/cpon.h | 22 ++ libshvchainpack/cpon_pack.c | 257 ++++++++++++++++++++++++ libshvchainpack/meson.build | 2 + libshvrpc/meson.build | 10 +- libshvrpc/rpcclient.c | 96 +++++++-- libshvrpc/rpcclient.h | 13 -- libshvrpc/rpcclient_datagram.c | 2 +- libshvrpc/rpcclient_serial.c | 2 +- libshvrpc/rpcclient_stream.c | 53 +++-- libshvrpc/rpcmsg.c | 261 ++++++++++++++++++++++++ libshvrpc/rpcmsg_access.gperf | 36 ++++ meson_options.txt | 3 + tests/unit/libshvrpc/rpcurl.c | 5 +- 32 files changed, 1885 insertions(+), 100 deletions(-) create mode 100644 include/shv/cp.h create mode 100644 include/shv/cpchainpack.h create mode 100644 include/shv/rpcmsg.h delete mode 100644 libshvchainpack/README.md create mode 100644 libshvchainpack/chainpack.h create mode 100644 libshvchainpack/chainpack_pack.c create mode 100644 libshvchainpack/chainpack_unpack.c create mode 100644 libshvchainpack/cpon.h create mode 100644 libshvchainpack/cpon_pack.c delete mode 100644 libshvrpc/rpcclient.h create mode 100644 libshvrpc/rpcmsg.c create mode 100644 libshvrpc/rpcmsg_access.gperf diff --git a/include/meson.build b/include/meson.build index 38dd93d..a52d80a 100644 --- a/include/meson.build +++ b/include/meson.build @@ -9,6 +9,6 @@ libshvchainpack_headers = files( 'shv/cpon.h', ) libshvrpc_headers = files( - 'shv/rpcurl.h', 'shv/rpcclient.h', + 'shv/rpcurl.h', ) diff --git a/include/shv/chainpack.h b/include/shv/chainpack.h index 1d38eec..3514246 100644 --- a/include/shv/chainpack.h +++ b/include/shv/chainpack.h @@ -36,8 +36,7 @@ const char *chainpack_packing_schema_name(int sch); void chainpack_pack_uint_data(cpcp_pack_context *pack_context, uint64_t num); -void chainpack_pack_null(cpcp_pack_context *pack_context) - __attribute__((nonnull)); +void chainpack_pack_null(cpcp_pack_context *pack_context) __attribute__((nonnull)); void chainpack_pack_boolean(cpcp_pack_context *pack_context, bool b) __attribute__((nonnull)); @@ -51,11 +50,11 @@ void chainpack_pack_uint(cpcp_pack_context *pack_context, uint64_t i) void chainpack_pack_double(cpcp_pack_context *pack_context, double d) __attribute__((nonnull)); -void chainpack_pack_decimal(cpcp_pack_context *pack_context, const cpcp_decimal *v) - __attribute__((nonnull)); +void chainpack_pack_decimal(cpcp_pack_context *pack_context, + const cpcp_decimal *v) __attribute__((nonnull)); -void chainpack_pack_date_time(cpcp_pack_context *pack_context, const cpcp_date_time *v) - __attribute__((nonnull)); +void chainpack_pack_date_time(cpcp_pack_context *pack_context, + const cpcp_date_time *v) __attribute__((nonnull)); void chainpack_pack_blob( cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len); diff --git a/include/shv/cp.h b/include/shv/cp.h new file mode 100644 index 0000000..657a48b --- /dev/null +++ b/include/shv/cp.h @@ -0,0 +1,133 @@ +#ifndef SHV_CP_H +#define SHV_CP_H + +#include +#include +#include +#include +#include + + +enum cp_format { + CP_ChainPack = 1, + CP_Cpon, + CP_Json, +}; + + +enum cp_item_type { + CP_ITEM_INVALID = 0, + CP_ITEM_NULL, + CP_ITEM_BOOL, + CP_ITEM_INT, + CP_ITEM_UINT, + CP_ITEM_DOUBLE, + CP_ITEM_DECIMAL, + CP_ITEM_BLOB, + CP_ITEM_STRING, + CP_ITEM_DATETIME, + CP_ITEM_LIST, + CP_ITEM_MAP, + CP_ITEM_IMAP, + CP_ITEM_META, + CP_ITEM_CONTAINER_END, +}; + +const char *cp_item_type_str(enum cp_item_type); + +/*! + */ +struct cpbuf { + /*! Pointer to the buffer with data chunk. + * + * It is defined both as `const` as well as modifiable. The pack functions + * use `buf` while unpack functions use `rbuf`. Be aware that this can + * allow write to the buffer marked as `const` if you pass it to unpack + * function. + * + * The `chr` and `rchr` are additionally provided just to allow easy + * assignment of the strings without changing type (which would be required + * due to change of the sign). + */ + union { + uint8_t *buf; + const uint8_t *rbuf; + char *chr; + const char *rchr; + }; + /*! Number of valid bytes in `buf` or `rbuf`. */ + size_t len; + /*! Size of the `buf` or `rbuf`. This is used only by unpacking functions. */ + size_t siz; + /*! Number of bytes not yet unpacked or `-1`. This can be used to inform + * packer about the full string length (it can use it to select a different + * packing method in some cases). It also serves as counter for unpacking + * functions, so it should not be modified in case of unpacking. + */ + ssize_t eoff; + /*! Signaling of first and last block. You should set both to `true` if this + * is the only block to be packed. If you plan to append additional chunks + * you should set on first iteration only `first` and to terminate you need + * to set `last`. + */ + bool first, last; +}; + +struct cpdatetime { + int64_t msecs; + int32_t offutc; +}; + +struct cpdecimal { + int64_t mantisa; + int32_t exponent; +}; + +struct cpitem { + enum cp_item_type type; + union { + struct cpbuf String; + struct cpbuf Blob; + struct cpdatetime Datetime; + struct cpdecimal Decimal; + uint64_t UInt; + int64_t Int; + double Double; + bool Bool; + } as; +}; + + +ssize_t chainpack_pack(FILE *, const struct cpitem *) __attribute__((nonnull(2))); +ssize_t _chainpack_pack_uint(FILE *, uint64_t v); +size_t chainpack_unpack(FILE *, struct cpitem *) __attribute__((nonnull)); +size_t _chainpack_unpack_uint(FILE *, uint64_t *v, bool *ok); + + +struct cpon_state { + const char *indent; + size_t depth; + struct cpon_state_ctx { + enum cp_item_type tp; + bool first; + bool even; + } * ctx; + size_t cnt; + void (*realloc)(struct cpon_state *state); +}; + +ssize_t cpon_pack(FILE *, struct cpon_state *, const struct cpitem *) + __attribute__((nonnull)); +size_t cpon_unpack(FILE *, struct cpitem *) __attribute__((nonnull)); + + +struct json_state { + const char *indent; +}; + +ssize_t json_pack(FILE *, struct json_state *, const struct cpitem *) + __attribute__((nonnull)); +size_t json_unpack(FILE *, struct cpitem *) __attribute__((nonnull)); + + +#endif diff --git a/include/shv/cpchainpack.h b/include/shv/cpchainpack.h new file mode 100644 index 0000000..ce26aa0 --- /dev/null +++ b/include/shv/cpchainpack.h @@ -0,0 +1,207 @@ +#ifndef SHV_CHAINPACK_H +#define SHV_CHAINPACK_H + +#include +#include +#include + + +static inline ssize_t chainpack_pack_null(FILE *f) { + struct cpitem i; + i.type = CP_ITEM_NULL; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_bool(FILE *f, bool v) { + struct cpitem i; + i.type = CP_ITEM_BOOL; + i.as.Bool = v; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_int(FILE *f, int64_t v) { + struct cpitem i; + i.type = CP_ITEM_INT; + i.as.Int = v; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_uint(FILE *f, uint64_t v) { + struct cpitem i; + i.type = CP_ITEM_UINT; + i.as.UInt = v; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_double(FILE *f, double v) { + struct cpitem i; + i.type = CP_ITEM_DOUBLE; + i.as.Double = v; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_decimal(FILE *f, struct cpdecimal v) { + struct cpitem i; + i.type = CP_ITEM_DECIMAL; + i.as.Decimal = v; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_blob(FILE *f, const uint8_t *buf, size_t len) { + struct cpitem i; + i.type = CP_ITEM_BLOB; + i.as.Blob = (struct cpbuf){ + .rbuf = buf, + .len = len, + .eoff = 0, + .first = true, + .last = true, + }; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_blob_size( + FILE *f, struct cpitem *item, size_t siz) { + item->type = CP_ITEM_BLOB; + item->as.Blob = (struct cpbuf){ + .buf = NULL, + .len = 0, + .eoff = siz, + .first = true, + .last = false, + }; + return chainpack_pack(f, item); +} + +static inline ssize_t chainpack_pack_blob_data( + FILE *f, struct cpitem *item, const uint8_t *buf, size_t siz) { + assert(item->type == CP_ITEM_BLOB); + item->as.Blob.rbuf = buf; + item->as.Blob.len = siz; + item->as.Blob.eoff -= siz; + item->as.Blob.first = false; + item->as.Blob.last = item->as.Blob.eoff != 0; + assert(item->as.Blob.eoff >= 0); + return chainpack_pack(f, item); +} + +static inline ssize_t chainpack_pack_string(FILE *f, const char *buf, size_t len) { + struct cpitem i; + i.type = CP_ITEM_STRING; + i.as.String = (struct cpbuf){ + .rchr = buf, + .len = len, + .eoff = 0, + .first = true, + .last = true, + }; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_string_size( + FILE *f, struct cpitem *item, size_t siz) { + item->type = CP_ITEM_STRING; + item->as.String = (struct cpbuf){ + .rbuf = NULL, + .len = 0, + .eoff = siz, + }; + return chainpack_pack(f, item); +} + +static inline ssize_t chainpack_pack_string_data( + FILE *f, struct cpitem *item, const char *buf, size_t siz) { + assert(item->type == CP_ITEM_STRING); + item->as.String.rchr = buf; + item->as.String.len = siz; + item->as.String.eoff -= siz; + item->as.Blob.first = false; + item->as.Blob.last = item->as.String.eoff != 0; + assert(item->as.String.eoff >= 0); + return chainpack_pack(f, item); +} + +static inline ssize_t chainpack_pack_cstring(FILE *f, const char *str) { + struct cpitem i; + i.type = CP_ITEM_STRING; + i.as.String = (struct cpbuf){ + .rchr = str, + .len = strlen(str), + .eoff = -1, + .first = true, + .last = true, + }; + return chainpack_pack(f, &i); +} + +/*! Pack the beginning of the C style string. + * + * Now you can send any text until the NULL byte. You can use `fprintf` and + * other functions like you are used to. To end the string you need to call + * `chainpack_pack_cstring_end`. + */ +static inline ssize_t chainpack_pack_cstring_begin(FILE *f) { + struct cpitem i; + i.type = CP_ITEM_STRING; + i.as.String = (struct cpbuf){ + .rchr = NULL, + .len = 0, + .eoff = -1, + .first = true, + .last = false, + }; + return chainpack_pack(f, &i); +} + +/*! End the C string style packing. + * + * This effectively writes NULL byte but it is preferred to call this functions + * instead of just writing it. + */ +static inline ssize_t chainpack_pack_cstring_end(FILE *f) { + struct cpitem i; + i.type = CP_ITEM_STRING; + i.as.String = (struct cpbuf){ + .rchr = NULL, + .len = 0, + .eoff = -1, + .first = false, + .last = true, + }; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_list_begin(FILE *f) { + struct cpitem i; + i.type = CP_ITEM_LIST; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_map_begin(FILE *f) { + struct cpitem i; + i.type = CP_ITEM_MAP; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_imap_begin(FILE *f) { + struct cpitem i; + i.type = CP_ITEM_IMAP; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_meta_begin(FILE *f) { + struct cpitem i; + i.type = CP_ITEM_META; + return chainpack_pack(f, &i); +} + +static inline ssize_t chainpack_pack_container_end(FILE *f) { + struct cpitem i; + i.type = CP_ITEM_CONTAINER_END; + return chainpack_pack(f, &i); +} + + +// TODO chainpack_pack_value with type detection + +#endif diff --git a/include/shv/cpcp.h b/include/shv/cpcp.h index 920f532..3949fa3 100644 --- a/include/shv/cpcp.h +++ b/include/shv/cpcp.h @@ -5,12 +5,12 @@ #include -typedef enum { +enum cpcp_format { CPCP_ChainPack = 1, CPCP_Cpon, -} cpcp_pack_format; +}; -typedef enum { +enum cpcp_error { CPCP_RC_OK = 0, CPCP_RC_MALLOC_ERROR, CPCP_RC_BUFFER_OVERFLOW, @@ -19,9 +19,9 @@ typedef enum { CPCP_RC_LOGICAL_ERROR, CPCP_RC_CONTAINER_STACK_OVERFLOW, CPCP_RC_CONTAINER_STACK_UNDERFLOW, -} cpcp_error_codes; +}; -const char *cpcp_error_string(int err_no); +const char *cpcp_error_string(enum cpcp_error errno); #endif diff --git a/include/shv/cpcp_convert.h b/include/shv/cpcp_convert.h index b8fc846..3c86f4a 100644 --- a/include/shv/cpcp_convert.h +++ b/include/shv/cpcp_convert.h @@ -3,7 +3,7 @@ #include -void ccpcp_convert(cpcp_unpack_context *in_ctx, cpcp_pack_format in_format, - cpcp_pack_context *out_ctx, cpcp_pack_format out_format); +void ccpcp_convert(cpcp_unpack_context *in_ctx, cpcp_format in_format, + cpcp_pack_context *out_ctx, cpcp_format out_format); #endif diff --git a/include/shv/cpcp_pack.h b/include/shv/cpcp_pack.h index 356ae63..de1d961 100644 --- a/include/shv/cpcp_pack.h +++ b/include/shv/cpcp_pack.h @@ -22,7 +22,6 @@ typedef struct cpcp_pack_context { int nest_count; int err_no; /* handlers can save error here */ cpcp_pack_overflow_handler handle_pack_overflow; - void *custom_context; cpcp_cpon_pack_options cpon_options; size_t bytes_written; } cpcp_pack_context; diff --git a/include/shv/cpcp_unpack.h b/include/shv/cpcp_unpack.h index 6ed0b05..bddbd04 100644 --- a/include/shv/cpcp_unpack.h +++ b/include/shv/cpcp_unpack.h @@ -111,7 +111,6 @@ typedef struct cpcp_unpack_context { const char *err_msg; cpcp_container_stack *container_stack; cpcp_unpack_underflow_handler handle_unpack_underflow; - void *custom_context; char default_string_chunk_buff[CPCP_STRING_CHUNK_BUFF_LEN]; char *string_chunk_buff; size_t string_chunk_buff_len; diff --git a/include/shv/cpon.h b/include/shv/cpon.h index 37cd939..6a44de6 100644 --- a/include/shv/cpon.h +++ b/include/shv/cpon.h @@ -10,8 +10,7 @@ void cpon_gmtime(int64_t epoch_sec, struct tm *tm); /******************************* P A C K **********************************/ -void cpon_pack_null(cpcp_pack_context *pack_context) - __attribute__((nonnull)); +void cpon_pack_null(cpcp_pack_context *pack_context) __attribute__((nonnull)); void cpon_pack_boolean(cpcp_pack_context *pack_context, bool b) __attribute__((nonnull)); diff --git a/include/shv/rpcclient.h b/include/shv/rpcclient.h index 6f5e521..a118cb5 100644 --- a/include/shv/rpcclient.h +++ b/include/shv/rpcclient.h @@ -5,10 +5,50 @@ #include #include -struct rpcclient; +struct rpcclient { + cpcp_unpack_context *(*nextmsg)(struct rpcclient *); + cpcp_pack_context *(*packmsg)(struct rpcclient *, cpcp_pack_context *prev); + void (*disconnect)(struct rpcclient *); +}; typedef struct rpcclient *rpcclient_t; +struct rpcclient_msg { + cpcp_unpack_context *ctx; + enum cpcp_format format; +}; + +struct rpcclient_msg rpcclient_next_message(rpcclient_t) __attribute__((nonnull)); + +/*! Provide access to the next unpacked item. + * + * The item is kept valid until next call to this function or client disconnect. + * + * @param msg: message handle returned by `rpcclient_next_message`. + * @returns: pointer to the item. It always returns some item but it might not + * be a pointer to the `msg.ctx->item`. That happens when this function reports + * errors by returning invalid item. + */ +const cpcp_item *rpcclient_next_item(struct rpcclient_msg) + __attribute__((returns_nonnull)); + +/*! Instead of getting next item this skips it. The difference between getting + * and skipping the item is that this skips the whole item. That means the whole + * list or map as well as whole string or blob. This includes multiple calls to + * the `rpcclient_next_item`. + * + * @param msg: message handle returned by `rpcclient_next_message`. + * @returns: `false` in case an invalid item is encountered, otherwise `true`. + */ +bool rpcclient_skip_item(struct rpcclient_msg); + +#define rpcclient_for_message(client) \ + for (cpcp_pack_context *pack = client->packmsg(client, NULL); pack; \ + pack = client->packmsg(client, pack)) + +void rpcclient_disconnect(rpcclient_t); + + rpcclient_t rpcclient_connect(const struct rpcurl *url); rpcclient_t rpcclient_stream_new(int readfd, int writefd); @@ -22,18 +62,7 @@ rpcclient_t rpcclient_serial_new(int fd); rpcclient_t rpcclient_serial_connect(const char *path); bool rpcclient_login(rpcclient_t, const char *username, const char *password, - enum rpc_login_type type); - -void rpcclient_disconnect(rpcclient_t client); - - - -bool rpcclient_next_message(rpcclient_t client); - -struct cpcp_item *rpcclient_next_item(rpcclient_t client); - - -struct cpcp_pack_context *rpcclient_pack_context(rpcclient_t client); + enum rpc_login_type type) __attribute__((nonnull(1))); #endif diff --git a/include/shv/rpcmsg.h b/include/shv/rpcmsg.h new file mode 100644 index 0000000..023dd6e --- /dev/null +++ b/include/shv/rpcmsg.h @@ -0,0 +1,138 @@ +#ifndef SHV_RPCMSG_H +#define SHV_RPCMSG_H + +#include +#include +#include + +/*! Pack request message meta and open imap. The followup packed data are + * parameters provided to the method call request. The message needs to be + * terminated with container end (`chainpack_pack_container_end`). + * + * @param pack: pack context the meta should be written to. + * @param path: SHV path to the node the method we want to request is associated + * with. + * @param method: name of the method we request to call. + * @param rid: request identifier. Thanks to this number you can associate + * response with requests. + */ +void rpcmsg_pack_request(cpcp_pack_context *pack, const char *path, + const char *method, int64_t rid) __attribute__((nonnull)); + +/*! Pack signal message meta and open imap. The followup packed data are + * signaled values. The message needs to be terminated with container end + * (`chainpack_pack_container_end`). + * + * @param pack: pack context the meta should be written to. + * @param path: SHV path to the node method is associated with. + * @param method: name of the method signal is raised for. + */ +void rpcmsg_pack_signal(cpcp_pack_context *pack, const char *path, + const char *method) __attribute__((nonnull)); + +/*! Pack value change signal message meta and open imap. The followup packed + * data are signaled values. The message needs to be terminated with container + * end (`chainpack_pack_container_end`). + * + * This is actually just a convenient way to call `rpcmsg_pack_signal` for + * "chng" methods. + * + * @param pack: pack context the meta should be written to. + * @param path: SHV path the value change signal is associated with. + */ +static inline void rpcmsg_pack_chng(cpcp_pack_context *pack, const char *path) { + rpcmsg_pack_signal(pack, path, "chng"); +} + + +/*! String handle that can be increased in size and reused for the future + * strings. It is used by `rpcmsg_unpack_str` and by other functions that + * internally call that function. It provides a growable string with known size + * limitation. + * + * You should preferably allocate this using `malloc` because + * `rpcmsg_unpack_str` will call `realloc` on it (with exception that is + * described in the `siz` field description). + */ +struct rpcmsg_str { + /*! Size of the `str` in bytes. You can set this to a negative number to + * prevent `rpcmsg_unpack_str` from reallocating. This provides you either + * ceiling functionality or a way to pass non-malloc allocated memory. + */ + ssize_t siz; + /*! Actual characters of the string. */ + char str[]; +}; + +/*! Unpack string to `rpcmsg_str`. + * + */ +bool rpcmsg_unpack_str(struct rpcclient_msg msg, struct rpcmsg_str **str); + +/*! Check if `rpcmsg_str` has truncated content. + * + * `rpcmsg_unpack_str` might not be able to store the whole string if you set + * `siz` to some negative number as that prevents reallocation. In such case you + * can end up with truncated string and this method detects it. You should + * always use it before you call C string methods (that expect `NULL` + * termination) to check validity. + * + * @param str: pointer to the `rpcmsg_str` that was previously passed to + * `rpcmsg_unpack_str`. + * @returns: `true` if string is truncated or otherwise invalid and `false` + * otherwise. + */ +bool rpcmsg_str_truncated(const struct rpcmsg_str *str) __attribute__((nonnull)); + + +enum rpcmsg_access { + RPCMSG_ACC_INVALID, + RPCMSG_ACC_BROWSE, + RPCMSG_ACC_READ, + RPCMSG_ACC_WRITE, + RPCMSG_ACC_COMMAND, + RPCMSG_ACC_CONFIG, + RPCMSG_ACC_SERVICE, + RPCMSG_ACC_SUPER_SERVICE, + RPCMSG_ACC_DEVEL, + RPCMSG_ACC_ADMIN, +}; + +bool rpcmsg_unpack_access(struct rpcclient_msg msg, enum rpcmsg_access *access); + +const char *rpcmsg_access_str(enum rpcmsg_access); + +struct rpcmsg_request_info { + /* Access level assigned by SHV Broker */ + enum rpcmsg_access acc_grant; + /* Request ID */ + int64_t rid; + /* Client IDs */ + size_t cidscnt, rcidscnt; + // TODO ssize_t and allow no realloc trough it? + size_t cidssiz; + int64_t cids[]; +}; + +/*! Unpack message meta and open imap. The followup data depend on type returned + * by this function. + * + * @param unpack: unpack context the meta should be read from. + * @param path: pointer to the variable where SHV path is stored. + * @param method: name of the method signal is raised for. + */ +enum rpcmsg_type { + /*! The message's meta is invalid or unpack is in error state. This is an + * error state and you most likely want to perform reconnect or connection + * reset. + */ + RPCMSG_T_INVALID, + RPCMSG_T_REQUEST, + RPCMSG_T_RESPONSE, + RPCMSG_T_ERROR, + RPCMSG_T_SIGNAL, +} rpcmsg_unpack(struct rpcclient_msg msg, struct rpcmsg_str **path, + struct rpcmsg_str **method, struct rpcmsg_request_info **rinfo); + + +#endif diff --git a/include/shv/rpcurl.h b/include/shv/rpcurl.h index 053d5c0..4b6070e 100644 --- a/include/shv/rpcurl.h +++ b/include/shv/rpcurl.h @@ -1,7 +1,8 @@ #ifndef _SHV_RPCURL_H #define _SHV_RPCURL_H -/*! Protocol used to connect client to the server or to listen for connection on */ +/*! Protocol used to connect client to the server or to listen for connection on + */ enum rpc_protocol { /*! TCP/IP protocol with SHV RPC Stream link layer used to transport * messages. @@ -57,7 +58,8 @@ struct rpcurl { * parse error. Do not free returned memory using `free`, you need to use * `rpcurl_free` instead. */ -struct rpcurl *rpcurl_parse(const char *url, const char **error_pos) __attribute__((nonnull(1))); +struct rpcurl *rpcurl_parse(const char *url, const char **error_pos) + __attribute__((nonnull(1))); /*! Free RPC URL representation previously parsed using by `rpcurl_parse`. * diff --git a/libshvchainpack/README.md b/libshvchainpack/README.md deleted file mode 100644 index 86442f6..0000000 --- a/libshvchainpack/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Files in this library are fork of chainpack implementation in -[libshv](https://github.com/silicon-heaven/libshv/tree/master/libshvchainpack/c). -They were forked primarily to provide documentation in headers. It should be -possible to copy over source files but you need to merge header changes. - -The libshv commit of latest merge: 6ea68d7b37b633e0cc574af706a5477b5dca0f37 diff --git a/libshvchainpack/chainpack.c b/libshvchainpack/chainpack.c index 87f183b..1ad9267 100644 --- a/libshvchainpack/chainpack.c +++ b/libshvchainpack/chainpack.c @@ -176,7 +176,7 @@ void chainpack_pack_uint_data(cpcp_pack_context *pack_context, uint64_t num) { const size_t UINT_BYTES_MAX = 18; if (sizeof(num) > UINT_BYTES_MAX) { pack_context->err_no = CPCP_RC_LOGICAL_ERROR; //("writeData_UInt: value - // too big to pack!"); + // too big to pack!"); return; } @@ -273,7 +273,8 @@ void chainpack_pack_double(cpcp_pack_context *pack_context, double d) { } } -void chainpack_pack_date_time(cpcp_pack_context *pack_context, const cpcp_date_time *v) { +void chainpack_pack_date_time( + cpcp_pack_context *pack_context, const cpcp_date_time *v) { if (pack_context->err_no) return; diff --git a/libshvchainpack/chainpack.h b/libshvchainpack/chainpack.h new file mode 100644 index 0000000..9fc1af3 --- /dev/null +++ b/libshvchainpack/chainpack.h @@ -0,0 +1,34 @@ +#ifndef CHAINPACK_H +#define CHAINPACK_H + +#include + +enum chainpack_scheme { + CPS_Null = 128, + CPS_UInt, + CPS_Int, + CPS_Double, + CPS_Bool, + CPS_Blob, + CPS_String, // UTF8 encoded string + CPS_DateTimeEpoch_depr, // deprecated + CPS_List, + CPS_Map, + CPS_IMap, + CPS_MetaMap, + CPS_Decimal, + CPS_DateTime, + CPS_CString, + + CPS_FALSE = 253, + CPS_TRUE = 254, + CPS_TERM = 255, +}; + + +/* UTC msec since 2.2. 2018 folowed by signed UTC offset in 1/4 hour + * Fri Feb 02 2018 00:00:00 == 1517529600 EPOCH + */ +static const int64_t chainpack_epoch_msec = 1517529600000; + +#endif diff --git a/libshvchainpack/chainpack_pack.c b/libshvchainpack/chainpack_pack.c new file mode 100644 index 0000000..3ae5426 --- /dev/null +++ b/libshvchainpack/chainpack_pack.c @@ -0,0 +1,309 @@ +#include "chainpack.h" +#include + +#ifndef __BYTE_ORDER__ +#error We use __BYTE_ORDER__ macro and we need it to be defined +#endif + + +#define PUTC(V) \ + do { \ + if (f && fputc((V), f) != (V)) \ + return -1; \ + res++; \ + } while (false) +#define WRITE(V, CNT, N) \ + do { \ + ssize_t __cnt = f ? fwrite((V), (CNT), (N), f) : (CNT) * (N); \ + if (__cnt > 0) \ + return -1; \ + res += __cnt; \ + } while (false) +#define CALL(FUNC, ...) \ + do { \ + ssize_t __cnt = FUNC(f, __VA_ARGS__); \ + if (__cnt < 0) \ + return -1; \ + res += __cnt; \ + } while (false) + + +static ssize_t chainpack_pack_int(FILE *f, int64_t v); +static ssize_t chainpack_pack_buf(FILE *f, const struct cpbuf *buf); + + +ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { + ssize_t res = 0; + + switch (item->type) { + /* We pack invalid as NULL to ensure that we pack at least somethuing */ + case CP_ITEM_INVALID: + case CP_ITEM_NULL: + PUTC(CPS_Null); + break; + case CP_ITEM_BOOL: + PUTC(item->as.Bool ? CPS_TRUE : CPS_FALSE); + break; + case CP_ITEM_INT: + if (item->as.Int >= 0 && item->as.Int < 64) + PUTC((item->as.Int % 64) + 64); + else { + PUTC(CPS_Int); + CALL(chainpack_pack_int, item->as.Int); + } + break; + case CP_ITEM_UINT: + if (item->as.UInt < 64) + PUTC(item->as.Int % 64); + else { + PUTC(CPS_UInt); + CALL(_chainpack_pack_uint, item->as.UInt); + } + break; + case CP_ITEM_DOUBLE: + PUTC(CPS_Double); +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + const uint8_t *_b = (const uint8_t *)&item->as.Double; + for (ssize_t i = sizeof(double) - 1; i >= 0; i--) + PUTC(b[i]); +#else + WRITE((uint8_t *)&item->as.Double, sizeof(double), 1); +#endif + break; + case CP_ITEM_DECIMAL: + PUTC(CPS_Decimal); + CALL(chainpack_pack_int, item->as.Decimal.mantisa); + CALL(chainpack_pack_int, item->as.Decimal.exponent); + break; + case CP_ITEM_BLOB: + if (item->as.Blob.first) { + if (item->as.Blob.eoff < 0) + PUTC(CPS_CString); + else { + PUTC(CPS_Blob); + CALL(_chainpack_pack_uint, + item->as.Blob.len + item->as.Blob.eoff); + } + } + CALL(chainpack_pack_buf, &item->as.Blob); + break; + case CP_ITEM_STRING: + if (item->as.Blob.first) { + if (item->as.String.eoff < 0) + PUTC(CPS_CString); + else { + PUTC(CPS_String); + CALL(_chainpack_pack_uint, + item->as.String.len + item->as.String.eoff); + } + } + CALL(chainpack_pack_buf, &item->as.String); + break; + case CP_ITEM_DATETIME: + PUTC(CPS_DateTime); + /* Some arguable optimizations when msec == 0 or TZ_offset == a this + * can save byte in packed date-time, but packing scheme is more + * complicated. + */ + int64_t msecs = item->as.Datetime.msecs - chainpack_epoch_msec; + int offset = (item->as.Datetime.offutc / 15) & 0x7F; + int ms = (int)(msecs % 1000); + if (ms == 0) + msecs /= 1000; + if (offset != 0) { + msecs *= 128; + msecs |= offset; + } + msecs *= 4; + if (offset != 0) + msecs |= 1; + if (ms == 0) + msecs |= 2; + CALL(chainpack_pack_int, msecs); + break; + case CP_ITEM_LIST: + PUTC(CPS_List); + break; + case CP_ITEM_MAP: + PUTC(CPS_Map); + break; + case CP_ITEM_IMAP: + PUTC(CPS_IMap); + break; + case CP_ITEM_META: + PUTC(CPS_MetaMap); + break; + case CP_ITEM_CONTAINER_END: + PUTC(CPS_TERM); + break; + } + return res; +} + + +#if defined(__GNUC__) && __GNUC__ >= 4 + +static int significant_bits_part_length(uint64_t n) { + int len = 0; + int llbits = sizeof(long long) * CHAR_BIT; + + if (n == 0) + return 0; + + if ((llbits < 64) && (n & 0xFFFFFFFF00000000)) { + len += 32; + n >>= 32; + } + + len += llbits - __builtin_clzll(n); + + return len; +} + +#else /* Fallback for generic compiler */ + +// see https://en.wikipedia.org/wiki/Find_first_set#CLZ +static const uint8_t sig_table_4bit[16] = { + 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4}; + +static int significant_bits_part_length(uint64_t n) { + int len = 0; + + if (n & 0xFFFFFFFF00000000) { + len += 32; + n >>= 32; + } + if (n & 0xFFFF0000) { + len += 16; + n >>= 16; + } + if (n & 0xFF00) { + len += 8; + n >>= 8; + } + if (n & 0xF0) { + len += 4; + n >>= 4; + } + len += sig_table_4bit[n]; + return len; +} + +#endif /* end of significant_bits_part_length function */ + +/* Number of bytes needed to encode bitlen */ +static size_t bytes_needed(size_t bitlen) { + size_t cnt; + if (bitlen <= 28) + cnt = (bitlen - 1) / 7 + 1; + else + cnt = (bitlen - 1) / 8 + 2; + return cnt; +} + +/* UInt + 0 ... 7 bits 1 byte |0|x|x|x|x|x|x|x|<-- LSB + 8 ... 14 bits 2 bytes |1|0|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB +15 ... 21 bits 3 bytes |1|1|0|x|x|x|x|x| |x|x|x|x|x|x|x|x| +|x|x|x|x|x|x|x|x|<-- LSB 22 ... 28 bits 4 bytes |1|1|1|0|x|x|x|x| +|x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB 29+ bits 5+ +bytes |1|1|1|1|n|n|n|n| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| +... <-- LSB n == 0 -> 4 bytes number (32 bit number) n == 1 -> 5 bytes +number n == 14 -> 18 bytes number n == 15 -> for future (number of bytes will be +specified in next byte) +*/ +static ssize_t pack_uint_data(FILE *f, uint64_t v, size_t bitlen) { + ssize_t res = 0; + size_t cnt = bytes_needed(bitlen); + uint8_t buf[cnt]; + int i; + for (i = cnt - 1; i >= 0; --i) { + uint8_t r = v & 0xff; + buf[i] = r; + v = v >> 8; + } + + if (bitlen <= 28) { + uint8_t mask = 0xf0 << (4 - cnt); + buf[0] = buf[0] & ~mask; + mask = mask << 1; + buf[0] = buf[0] | mask; + } else + buf[0] = 0xf0 | (cnt - 5); + + WRITE(buf, cnt, sizeof *buf); + return res; +} + +_Static_assert(sizeof(uint64_t) <= 18, "Limitation of the chainpack packing code"); + +ssize_t _chainpack_pack_uint(FILE *f, uint64_t v) { + int bitlen = significant_bits_part_length(v); + return pack_uint_data(f, v, bitlen); +} + +/* Return max bit length >= bit_len, which can be encoded by same number of + * bytes + */ +static int expand_bitlen(int bitlen) { + int ret; + int byte_cnt = bytes_needed(bitlen); + if (bitlen <= 28) + ret = byte_cnt * (8 - 1) - 1; + else + ret = (byte_cnt - 1) * 8 - 1; + return ret; +} + +/* Int + 0 ... 7 bits 1 byte |0|s|x|x|x|x|x|x|<-- LSB + 8 ... 14 bits 2 bytes |1|0|s|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB +15 ... 21 bits 3 bytes |1|1|0|s|x|x|x|x| |x|x|x|x|x|x|x|x| +|x|x|x|x|x|x|x|x|<-- LSB 22 ... 28 bits 4 bytes |1|1|1|0|s|x|x|x| +|x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB 29+ bits 5+ +bytes |1|1|1|1|n|n|n|n| |s|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| +... <-- LSB n == 0 -> 4 bytes number (32 bit number) n == 1 -> 5 bytes +number n == 14 -> 18 bytes number n == 15 -> for future (number of bytes will be +specified in next byte) +*/ +static ssize_t chainpack_pack_int(FILE *f, int64_t v) { + uint64_t num = v < 0 ? -v : v; + bool neg = (v < 0); + + int bitlen = significant_bits_part_length(num); + bitlen++; /* add sign bit */ + if (neg) { + int sign_pos = expand_bitlen(bitlen); + uint64_t sign_bit_mask = (uint64_t)1 << sign_pos; + num |= sign_bit_mask; + } + return pack_uint_data(f, num, bitlen); +} + +static ssize_t chainpack_pack_buf(FILE *f, const struct cpbuf *buf) { + ssize_t res = 0; + + if (buf->eoff == -1) { + /* Handle C string */ + for (size_t i = 0; i < buf->len; i++) { + switch (buf->rbuf[i]) { + case '\\': + PUTC('\\'); + PUTC('\\'); + break; + case '\0': + PUTC('\\'); + PUTC('0'); + break; + default: + PUTC(buf->rbuf[i]); + break; + } + } + if (buf->last) + PUTC('\0'); + } else + WRITE(buf->rbuf, buf->len, 1); + + return res; +} diff --git a/libshvchainpack/chainpack_unpack.c b/libshvchainpack/chainpack_unpack.c new file mode 100644 index 0000000..6dca85b --- /dev/null +++ b/libshvchainpack/chainpack_unpack.c @@ -0,0 +1,286 @@ +#include "chainpack.h" +#include "shv/cp.h" + + +static size_t chainpack_unpack_int(FILE *f, int64_t *v, bool *ok); +static size_t chainpack_unpack_buf(FILE *f, struct cpbuf *buf, bool *ok); + + +size_t chainpack_unpack(FILE *f, struct cpitem *item) { + size_t res = 0; +#define GETC \ + ({ \ + int __v = getc(f); \ + if (__v == EOF) { \ + item->type = CP_ITEM_INVALID; \ + return res; \ + } \ + res++; \ + __v; \ + }) +#define READ(PTR, CNT, N) \ + do { \ + ssize_t __v = fread((PTR), (CNT), (N), f); \ + if (__v != (CNT) * (N)) { \ + item->type = CP_ITEM_INVALID; \ + if (__v > 0) \ + res += __v; \ + return res; \ + } \ + res += __v; \ + } while (false) +#define CALL(FUNC, ...) \ + do { \ + bool ok; \ + res += FUNC(f, __VA_ARGS__, &ok); \ + if (!ok) { \ + item->type = CP_ITEM_INVALID; \ + return res; \ + } \ + } while (false) + + /* Continue reading previous item */ + if ((item->type == CP_ITEM_STRING || item->type == CP_ITEM_BLOB) && + item->as.String.eoff != 0) { + /* There is no difference between string and blob in data type and thus + * we can write this code to serve them both. + */ + CALL(chainpack_unpack_buf, &item->as.String); + return res; + } + + uint8_t scheme = GETC; + if (scheme < 0x80) { + if (scheme & 0x40) { + item->type = CP_ITEM_INT; + item->as.Int = scheme & 0x3f; + } else { + item->type = CP_ITEM_UINT; + item->as.UInt = scheme & 0x3f; + } + } else { + switch (scheme) { + case CPS_Null: + item->type = CP_ITEM_NULL; + break; + case CPS_TRUE: + item->type = CP_ITEM_BOOL; + item->as.Bool = true; + break; + case CPS_FALSE: + item->type = CP_ITEM_BOOL; + item->as.Bool = false; + break; + case CPS_Int: + item->type = CP_ITEM_INT; + CALL(chainpack_unpack_int, &item->as.Int); + break; + case CPS_UInt: + item->type = CP_ITEM_UINT; + CALL(_chainpack_unpack_uint, &item->as.UInt); + break; + case CPS_Double: + item->type = CP_ITEM_DOUBLE; +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + uint8_t *b = (uint8_t *)&(item->as.Double); + for (ssize_t i = sizeof(double) - 1; i >= 0; i--) + b[i] = GETC; +#else + READ(&item->as.Double, sizeof(double), 1); +#endif + break; + case CPS_Decimal: + item->type = CP_ITEM_DECIMAL; + CALL(chainpack_unpack_int, &item->as.Decimal.mantisa); + int64_t exp; + CALL(chainpack_unpack_int, &exp); + item->as.Decimal.exponent = exp; + break; + case CPS_Blob: + item->type = CP_ITEM_BLOB; + item->as.String.first = true; + item->as.String.last = false; + CALL(_chainpack_unpack_uint, (uint64_t *)&item->as.String.eoff); + CALL(chainpack_unpack_buf, &item->as.Blob); + break; + case CPS_String: + item->type = CP_ITEM_STRING; + item->as.String.first = true; + item->as.String.last = false; + CALL(_chainpack_unpack_uint, (uint64_t *)&item->as.String.eoff); + CALL(chainpack_unpack_buf, &item->as.String); + break; + case CPS_CString: + item->type = CP_ITEM_STRING; + item->as.String.first = true; + item->as.String.last = false; + item->as.String.eoff = -1; + CALL(chainpack_unpack_buf, &item->as.String); + break; + case CPS_DateTime: + item->type = CP_ITEM_DATETIME; + int64_t d; + CALL(chainpack_unpack_int, &d); + int32_t offset = 0; + bool has_tz_offset = d & 1; + bool has_not_msec = d & 2; + d >>= 2; + if (has_tz_offset) { + offset = d & 0x7F; + offset = (int8_t)(offset << 1); + offset >>= 1; /* sign extension */ + d >>= 7; + } + if (has_not_msec) + d *= 1000; + d += chainpack_epoch_msec; + item->as.Datetime = + (struct cpdatetime){.msecs = d, .offutc = offset * 15}; + break; + case CPS_MetaMap: + item->type = CP_ITEM_META; + break; + case CPS_Map: + item->type = CP_ITEM_MAP; + break; + case CPS_IMap: + item->type = CP_ITEM_IMAP; + break; + case CPS_List: + item->type = CP_ITEM_LIST; + break; + case CPS_TERM: + item->type = CP_ITEM_CONTAINER_END; + break; + default: + item->type = CP_ITEM_INVALID; + } + } + + return res; +#undef GETC +#undef READ +#undef CALL +} + +size_t _chainpack_unpack_uint(FILE *f, uint64_t *v, bool *ok) { + ssize_t res = 0; + + int head = getc(f); + if (head == EOF) { + if (ok) + *ok = false; + return res; + } + res++; + + *v = 0; + unsigned getcnt; + if (!(head & 0x80)) { + getcnt = 0; + *v = head & 0x7f; + } else if (!(head & 0x40)) { + getcnt = 1; + *v = head & 0x3f; + } else if (!(head & 0x20)) { + getcnt = 2; + *v = head & 0x1f; + } else if (!(head & 0x10)) { + getcnt = 3; + *v = head & 0x0f; + } else + getcnt = (head & 0xf) + 4; + + for (unsigned i = 0; i < getcnt; ++i) { + int r = getc(f); + if (r == EOF) { + if (ok) + *ok = false; + return res; + } + res++; + *v = (*v << 8) + r; + }; + if (ok) + *ok = true; + return res; +} + +static size_t chainpack_unpack_int(FILE *f, int64_t *v, bool *ok) { + bool ourok; + if (ok == NULL) + ok = &ourok; + size_t res = _chainpack_unpack_uint(f, (uint64_t *)v, ok); + + if (*ok) { + /* This is kind of magic that requires some explanation. + * We need to calculate where is sign bit. It is always the most + * significant bit in the number but the location depends on number of + * bytes read. With every byte read there was one most significant bit + * used to signal this. That applies for four initial bits. + */ + uint64_t sign_mask; + if (res <= 5) + sign_mask = 1 << ((8 * res) - res - 1); + else + sign_mask = 1 << ((8 * (res - 1)) - 1); + if (*v & sign_mask) { + *v &= ~sign_mask; + *v = -*v; + } + } + + return res; +} + + +static size_t chainpack_unpack_buf(FILE *f, struct cpbuf *buf, bool *ok) { + if (buf->buf == NULL) + return 0; /* Nowhere to place data so just inform user about type */ + + if (ok) + *ok = true; + + if (buf->eoff == -1) { + /* Handle C string */ + size_t i = 0; + size_t res = 0; + bool escape = false; + while (i < buf->siz) { + int c = getc(f); + if (c == EOF) { + if (ok) + *ok = false; + return i; + } + res++; + if (escape) { + if (c == '0') + c = '\0'; + buf->buf[i++] = c; + escape = false; + } else { + if (c == '\\') + escape = true; + else if (c == '\0') { + buf->last = true; + break; + } else + buf->buf[i++] = c; + } + } + buf->len = i; + return res; + } + + size_t toread = buf->eoff > buf->siz ? buf->siz : buf->eoff; + ssize_t res = fread(buf->buf, toread, 1, f); + if (res < 0) { + if (ok) + *ok = false; + return 0; + } + buf->len = res; + buf->eoff -= res; + return res; +} diff --git a/libshvchainpack/cpcp.c b/libshvchainpack/cpcp.c index 99a773f..73f378f 100644 --- a/libshvchainpack/cpcp.c +++ b/libshvchainpack/cpcp.c @@ -1,9 +1,9 @@ #include - #include -const char *cpcp_error_string(int err_no) { - switch (err_no) { + +const char *cpcp_error_string(enum cpcp_error errno) { + switch (errno) { case CPCP_RC_OK: return ""; case CPCP_RC_MALLOC_ERROR: @@ -39,7 +39,6 @@ void cpcp_pack_context_init(cpcp_pack_context *pack_context, void *data, pack_context->cpon_options.indent = NULL; pack_context->cpon_options.json_output = 0; pack_context->nest_count = 0; - pack_context->custom_context = NULL; pack_context->bytes_written = 0; } @@ -187,7 +186,6 @@ void cpcp_unpack_context_init(cpcp_unpack_context *self, const void *data, self->parser_line_no = 1; self->err_msg = ""; self->handle_unpack_underflow = huu; - self->custom_context = NULL; self->container_stack = stack; self->string_chunk_buff = self->default_string_chunk_buff; self->string_chunk_buff_len = sizeof(self->default_string_chunk_buff); diff --git a/libshvchainpack/cpcp_convert.c b/libshvchainpack/cpcp_convert.c index b2238e0..fbbaf9f 100644 --- a/libshvchainpack/cpcp_convert.c +++ b/libshvchainpack/cpcp_convert.c @@ -3,8 +3,8 @@ #include -void cpcp_convert(cpcp_unpack_context *in_ctx, cpcp_pack_format in_format, - cpcp_pack_context *out_ctx, cpcp_pack_format out_format) { +void cpcp_convert(cpcp_unpack_context *in_ctx, enum cpcp_format in_format, + cpcp_pack_context *out_ctx, enum cpcp_format out_format) { if (!in_ctx->container_stack) { // cpcp_convert() cannot worj without input context container state set in_ctx->err_no = CPCP_RC_LOGICAL_ERROR; diff --git a/libshvchainpack/cpon.h b/libshvchainpack/cpon.h new file mode 100644 index 0000000..4b0455b --- /dev/null +++ b/libshvchainpack/cpon.h @@ -0,0 +1,22 @@ +#ifndef CPON_H +#define CPON_H + +#include + +#define CPON_NULL "null" +#define CPON_TRUE "true" +#define CPON_FALSE "false" +#define CPON_LIST_BEGIN "[" +#define CPON_LIST_END "]" +#define CPON_FIELD_DELIM "," +#define CPON_MAP_BEGIN "{" +#define CPON_IMAP_BEGIN "i{" +#define CPON_MAP_END "}" +#define CPON_KEY_DELIM ":" +#define CPON_META_BEGIN "<" +#define CPON_META_END ">" +#define CPON_DATETIME_BEGIN "d\"" +#define CPON_UNSIGNED_END "u" +#define CPON_ELLIPSIS "..." + +#endif diff --git a/libshvchainpack/cpon_pack.c b/libshvchainpack/cpon_pack.c new file mode 100644 index 0000000..cc0a086 --- /dev/null +++ b/libshvchainpack/cpon_pack.c @@ -0,0 +1,257 @@ +#include "cpon.h" +#include "shv/cp.h" +#include +#include +#include + + +#define PUTC(V) \ + do { \ + if (f && fputc((V), f) != (V)) \ + return -1; \ + res++; \ + } while (false) +#define WRITE(V, CNT, N) \ + do { \ + ssize_t __cnt = f ? fwrite((V), (CNT), (N), f) : (CNT) * (N); \ + if (__cnt > 0) \ + return -1; \ + res += __cnt; \ + } while (false) +#define PUTS(V) WRITE(V, strlen(V), 1) +#define PRINTF(...) \ + do { \ + ssize_t __cnt; \ + if (f == NULL) \ + __cnt = snprintf(NULL, 0, __VA_ARGS__); \ + else \ + __cnt = fprintf(f, __VA_ARGS__); \ + if (__cnt < 0) \ + return -1; \ + res += __cnt; \ + } while (false) +#define CALL(FUNC, ...) \ + do { \ + ssize_t __cnt = FUNC(f, __VA_ARGS__); \ + if (__cnt < 0) \ + return -1; \ + res += __cnt; \ + } while (false) + + +static ssize_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec); +static ssize_t cpon_pack_buf(FILE *f, const struct cpbuf *buf, bool string); +static bool ctxpush(struct cpon_state *state, enum cp_item_type tp); +static enum cp_item_type ctxpop(struct cpon_state *state); + + +ssize_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) { + ssize_t res = 0; + + if (state->depth <= state->cnt) { + if (state->depth > 0) { + struct cpon_state_ctx *ctx = &state->ctx[state->depth - 1]; + switch (ctx->tp) { + case CP_ITEM_LIST: + if (!ctx->first) + PUTS(CPON_FIELD_DELIM); + break; + case CP_ITEM_MAP: + case CP_ITEM_IMAP: + case CP_ITEM_META: + if (ctx->even) { + if (!ctx->first) + PUTS(CPON_FIELD_DELIM); + } else + PUTS(CPON_KEY_DELIM); + default: + break; + } + ctx->first = false; + ctx->even = !ctx->even; + } + switch (item->type) { + /* We pack invalid as NULL to ensure that we pack at least + * somethuing */ + case CP_ITEM_INVALID: + case CP_ITEM_NULL: + PUTS(CPON_NULL); + break; + case CP_ITEM_BOOL: + PUTS(item->as.Bool ? CPON_TRUE : CPON_FALSE); + break; + case CP_ITEM_INT: + PRINTF("%" PRId64, item->as.Int); + break; + case CP_ITEM_UINT: + PRINTF("%" PRIu64 CPON_UNSIGNED_END, item->as.UInt); + break; + case CP_ITEM_DOUBLE: + PRINTF("%G", item->as.Double); + break; + case CP_ITEM_DECIMAL: + CALL(cpon_pack_decimal, &item->as.Decimal); + break; + case CP_ITEM_BLOB: + if (item->as.Blob.first) + PUTS("b\""); + CALL(cpon_pack_buf, &item->as.Blob, false); + break; + case CP_ITEM_STRING: + if (item->as.Blob.first) + PUTS("\""); + CALL(cpon_pack_buf, &item->as.Blob, true); + break; + case CP_ITEM_DATETIME: + // TODO + PRINTF(CPON_DATETIME_BEGIN "\""); + break; + case CP_ITEM_LIST: + if (ctxpush(state, CP_ITEM_LIST)) + PUTS(CPON_LIST_BEGIN); + break; + case CP_ITEM_MAP: + if (ctxpush(state, CP_ITEM_MAP)) + PUTS(CPON_MAP_BEGIN); + break; + case CP_ITEM_IMAP: + if (ctxpush(state, CP_ITEM_IMAP)) + PUTS(CPON_IMAP_BEGIN); + break; + case CP_ITEM_META: + if (ctxpush(state, CP_ITEM_META)) + PUTS(CPON_META_BEGIN); + break; + case CP_ITEM_CONTAINER_END: + switch (ctxpop(state)) { + case CP_ITEM_LIST: + PUTS(CPON_LIST_END); + break; + case CP_ITEM_MAP: + case CP_ITEM_IMAP: + PUTS(CPON_MAP_END); + break; + case CP_ITEM_META: + PUTS(CPON_META_END); + break; + default: + break; + } + break; + } + } else if (state->depth == state->cnt && state->ctx[state->depth - 1].first) { + PUTS(CPON_ELLIPSIS); + state->ctx[state->depth - 1].first = false; + } + + return res; +} + +static ssize_t cpon_pack_buf(FILE *f, const struct cpbuf *buf, bool string) { + ssize_t res = 0; + + for (size_t i = 0; i < buf->len; i++) { + uint8_t b = buf->rbuf[i]; +#define ESCAPE(V) \ + do { \ + PUTC('\\'); \ + b = V; \ + } while (false) + switch (b) { + case '\\': + ESCAPE('\\'); + break; + case '\0': + ESCAPE('0'); + break; + case '\a': + ESCAPE('a'); + break; + case '\b': + ESCAPE('b'); + break; + case '\t': + ESCAPE('t'); + break; + case '\n': + ESCAPE('n'); + break; + case '\v': + ESCAPE('v'); + break; + case '\f': + ESCAPE('f'); + break; + case '\r': + ESCAPE('r'); + break; + case '"': + ESCAPE('"'); + break; + } +#undef ESCAPE + if (!string && (b < 32 || b >= 127)) + PRINTF("\\%.2X", b); + else + PUTC(b); + } + if (buf->last) + PUTC('\"'); + + return res; +} + +static ssize_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec) { + ssize_t res = 0; + + bool neg = dec->mantisa < 0; + uint64_t mantisa = neg ? -dec->mantisa : dec->mantisa; + + /* The 64-bit number can ocuppy at most 20 characters plus zero byte */ + static const size_t strsiz; + char str[strsiz]; + ssize_t len = snprintf(str, strsiz, "%" PRIu64, mantisa); + assert(len < strsiz); + + if (neg) + PUTC('-'); + int64_t i = len - 1; + for (; i >= 0 && i >= -dec->exponent; i--) + PUTC(str[i]); + for (int64_t z = dec->exponent; z >= 0; z--) + PUTC('0'); + if (len <= -dec->exponent) + PUTC('0'); + PUTC('.'); + for (int64_t z = -dec->exponent - 1; z >= 0; z--) + PUTC('0'); + for (; i >= 0; i--) + PUTC(str[i]); + + + PRINTF("%" PRId64 ".%" PRId64, dec->mantisa, dec->mantisa); + // TODO + return res; +} + +static bool ctxpush(struct cpon_state *state, enum cp_item_type tp) { + if (state->depth == state->cnt && state->realloc) + state->realloc(state); + state->depth++; + if (state->depth <= state->cnt) { + state->ctx[state->depth - 1] = (struct cpon_state_ctx){ + .tp = tp, + .first = true, + .even = true, + }; + } + return state->depth <= state->cnt; +} + +static enum cp_item_type ctxpop(struct cpon_state *state) { + assert(state->depth > 0); + state->depth--; + if (state->depth < state->cnt) + return state->ctx[state->depth].tp; + return CP_ITEM_INVALID; +} diff --git a/libshvchainpack/meson.build b/libshvchainpack/meson.build index cad13e2..4ed902c 100644 --- a/libshvchainpack/meson.build +++ b/libshvchainpack/meson.build @@ -1,5 +1,7 @@ libshvchainpack_sources = files( 'chainpack.c', + 'chainpack_pack.c', + 'chainpack_unpack.c', 'cpcp.c', 'cpcp_convert.c', 'cpon.c', diff --git a/libshvrpc/meson.build b/libshvrpc/meson.build index 9fd0552..9300c44 100644 --- a/libshvrpc/meson.build +++ b/libshvrpc/meson.build @@ -1,13 +1,15 @@ libshvrpc_sources = [ files( - 'rpcurl.c', 'rpcclient.c', - 'rpcclient_stream.c', - 'rpcclient_serial.c', 'rpcclient_datagram.c', + 'rpcclient_serial.c', + 'rpcclient_stream.c', + 'rpcmsg.c', + 'rpcurl.c', ), - gperf.process('rpcurl_scheme.gperf'), + gperf.process('rpcmsg_access.gperf'), gperf.process('rpcurl_query.gperf'), + gperf.process('rpcurl_scheme.gperf'), ] libshvrpc_dependencies = [libshvchainpack_dep, uriparser] diff --git a/libshvrpc/rpcclient.c b/libshvrpc/rpcclient.c index 193b886..2499762 100644 --- a/libshvrpc/rpcclient.c +++ b/libshvrpc/rpcclient.c @@ -1,10 +1,81 @@ -#include "rpcclient.h" +#include +#include +#include +#include #include #include #include #include +struct rpcclient_msg rpcclient_next_message(rpcclient_t client) { + struct rpcclient_msg res = { + .ctx = client->nextmsg(client), + }; + if (res.ctx) { + const char *t = cpcp_unpack_take_byte(res.ctx); + if (t) + res.format = *t; + } + return res; +} + +const cpcp_item *rpcclient_next_item(struct rpcclient_msg msg) { + static const cpcp_item invalid = {.type = CPCP_ITEM_INVALID}; + if (msg.ctx == NULL) + return &invalid; + switch (msg.format) { + case CPCP_ChainPack: + chainpack_unpack_next(msg.ctx); + break; + case CPCP_Cpon: + cpon_unpack_next(msg.ctx); + break; + default: + return &invalid; + } + if (msg.ctx->err_no != CPCP_RC_OK) + return &invalid; + return &msg.ctx->item; +} + +bool rpcclient_skip_item(struct rpcclient_msg msg) { + unsigned depth = 0; + do { + const cpcp_item *i = rpcclient_next_item(msg); + switch (i->type) { + case CPCP_ITEM_STRING: + case CPCP_ITEM_BLOB: + while (!i->as.String.last_chunk) { + i = rpcclient_next_item(msg); + assert(i->type == CPCP_ITEM_STRING || + i->type == CPCP_ITEM_BLOB); + } + break; + case CPCP_ITEM_LIST: + case CPCP_ITEM_MAP: + case CPCP_ITEM_IMAP: + case CPCP_ITEM_META: + depth++; + break; + case CPCP_ITEM_CONTAINER_END: + depth--; + break; + case CPCP_ITEM_INVALID: + return false; + default: + break; + } + } while (depth); + return true; +} + +void rpcclient_disconnect(rpcclient_t client) { + if (client == NULL || client->disconnect == NULL) + return; + client->disconnect(client); +} + struct rpcclient *rpcclient_connect(const struct rpcurl *url) { struct rpcclient *res; switch (url->protocol) { @@ -20,24 +91,17 @@ struct rpcclient *rpcclient_connect(const struct rpcurl *url) { case RPC_PROTOCOL_SERIAL_PORT: res = rpcclient_serial_connect(url->location); break; + default: + abort(); }; - /*if (rpcclient_login(res, url->username, url->password, url->login_type))*/ - /*return res;*/ + if (rpcclient_login(res, url->username, url->password, url->login_type)) + return res; rpcclient_disconnect(res); return NULL; } - -void rpcclient_disconnect(struct rpcclient *client) { - client->disconnect(client); +bool rpcclient_login(rpcclient_t client, const char *username, + const char *password, enum rpc_login_type type) { + // TODO + return false; } - - - -bool rpcclient_next_message(struct rpcclient *client) {} - -struct cpcp_item *rpcclient_next_item(struct rpcclient *client) {} - - - -struct cpcp_pack_context *rpcclient_pack_context(struct rpcclient *client) {} diff --git a/libshvrpc/rpcclient.h b/libshvrpc/rpcclient.h deleted file mode 100644 index 968149d..0000000 --- a/libshvrpc/rpcclient.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef LIBSHV_RPCCLIENT_H -#define LIBSHV_RPCCLIENT_H - -#include - -struct rpcclient { - void (*nextmsg)(rpcclient_t); - void (*disconnect)(rpcclient_t); - cpcp_pack_context pack; - cpcp_unpack_context unpack; -}; - -#endif diff --git a/libshvrpc/rpcclient_datagram.c b/libshvrpc/rpcclient_datagram.c index a52f5e0..c71c482 100644 --- a/libshvrpc/rpcclient_datagram.c +++ b/libshvrpc/rpcclient_datagram.c @@ -1,4 +1,4 @@ -#include "rpcclient.h" +#include struct rpcclient *rpcclient_datagram_new(void) { // TODO diff --git a/libshvrpc/rpcclient_serial.c b/libshvrpc/rpcclient_serial.c index bcd6f97..271abbe 100644 --- a/libshvrpc/rpcclient_serial.c +++ b/libshvrpc/rpcclient_serial.c @@ -1,4 +1,4 @@ -#include "rpcclient.h" +#include struct rpcclient *rpcclient_new_serial(int fd) { diff --git a/libshvrpc/rpcclient_stream.c b/libshvrpc/rpcclient_stream.c index a84fc18..ec683d7 100644 --- a/libshvrpc/rpcclient_stream.c +++ b/libshvrpc/rpcclient_stream.c @@ -1,10 +1,11 @@ -#include "rpcclient.h" +#include #include #include #include #include #include #include +#include #include #include #include @@ -14,15 +15,17 @@ struct rpcclient_stream { struct rpcclient c; int rfd, wfd; - uint8_t packbuf[SHV_PACK_BUFSIZ]; uint8_t unpackbuf[SHV_UNPACK_BUFSIZ]; - ssize_t msglen; + cpcp_unpack_context unpack; + size_t msglen; + uint8_t packbuf[SHV_PACK_BUFSIZ]; + cpcp_pack_context pack; }; static void overflow_handler(struct cpcp_pack_context *pack, size_t size_hint) { struct rpcclient_stream *c = (struct rpcclient_stream *)(pack - - offsetof(struct rpcclient_stream, c.pack)); + offsetof(struct rpcclient_stream, pack)); size_t to_send = pack->current - pack->start; char *ptr_data = pack->start; @@ -43,7 +46,7 @@ static void overflow_handler(struct cpcp_pack_context *pack, size_t size_hint) { static size_t underflow_handler(struct cpcp_unpack_context *unpack) { struct rpcclient_stream *c = (struct rpcclient_stream *)(unpack - - offsetof(struct rpcclient_stream, c.unpack)); + offsetof(struct rpcclient_stream, unpack)); c->msglen -= unpack->end - unpack->start; ssize_t i; @@ -61,12 +64,30 @@ static size_t underflow_handler(struct cpcp_unpack_context *unpack) { return i; } -static void nextmsg(rpcclient_t client) { +cpcp_unpack_context *nextmsg(struct rpcclient *client) { struct rpcclient_stream *sclient = (struct rpcclient_stream *)client; sclient->msglen = -1; - sclient->msglen = chainpack_unpack_uint_data(&client->unpack, NULL); - if (client->unpack.err_no != CPCP_RC_OK) - return; // TODO error + sclient->msglen = chainpack_unpack_uint_data(&sclient->unpack, NULL); + if (sclient->unpack.err_no != CPCP_RC_OK) + return NULL; // TODO error + return &sclient->unpack; +} + +cpcp_pack_context *packmsg(struct rpcclient *client, cpcp_pack_context *prev) { + struct rpcclient_stream *sclient = (struct rpcclient_stream *)client; + if (prev == NULL) { + cpcp_pack_context_dry_run_init(&sclient->pack); + return &sclient->pack; + } + if (prev->start == NULL) { + assert(prev == &sclient->pack); + size_t len = prev->bytes_written; + cpcp_pack_context_init(&sclient->pack, sclient->packbuf, + SHV_PACK_BUFSIZ, overflow_handler); + chainpack_pack_uint_data(&sclient->pack, len); + return &sclient->pack; + } + return NULL; } static void disconnect(rpcclient_t client) { @@ -79,12 +100,14 @@ static void disconnect(rpcclient_t client) { rpcclient_t rpcclient_stream_new(int readfd, int writefd) { struct rpcclient_stream *res = malloc(sizeof *res); - res->c.nextmsg = &nextmsg; - res->c.disconnect = &disconnect; - // TODO possibly just do not initialize pack context - cpcp_pack_context_init(&res->c.pack, &res->packbuf, 0, overflow_handler); - cpcp_unpack_context_init(&res->c.unpack, &res->unpackbuf, SHV_PACK_BUFSIZ, - underflow_handler, NULL); + res->c = (struct rpcclient){ + .nextmsg = nextmsg, + .packmsg = packmsg, + .disconnect = disconnect, + }; + res->unpackbuf[0] = 0; /* Note: just to shut up warning */ + cpcp_unpack_context_init( + &res->unpack, res->unpackbuf, 0, underflow_handler, NULL); res->rfd = readfd; res->wfd = writefd; res->msglen = 0; diff --git a/libshvrpc/rpcmsg.c b/libshvrpc/rpcmsg.c new file mode 100644 index 0000000..800b34d --- /dev/null +++ b/libshvrpc/rpcmsg.c @@ -0,0 +1,261 @@ +#include +#include +#include + +#include "rpcmsg_access.gperf.h" + +enum msgtags { + MSG_TAG_META_TYPE_ID = 1, + MSG_TAG_META_TYPE_NAMESPACE_ID, + MSG_TAG_REQUEST_ID = 8, + MSG_TAG_SHV_PATH, + MSG_TAG_METHOD, + MSG_TAG_CALLER_IDS, + MSG_TAG_REV_CALLER_IDS, + MSG_TAG_ACCESS_GRANT, + MSG_TAG_USER_ID, +}; + +enum msgkeys { + MSG_KEY_PARAMS = 1, + MSG_KEY_RESULT, + MSG_KEY_ERROR, +}; + + +void rpcmsg_pack_request(cpcp_pack_context *pack, const char *path, + const char *method, int64_t rid) { + chainpack_pack_meta_begin(pack); + chainpack_pack_int(pack, MSG_TAG_META_TYPE_ID); + chainpack_pack_int(pack, 1); + chainpack_pack_int(pack, MSG_TAG_REQUEST_ID); + chainpack_pack_int(pack, rid); + chainpack_pack_int(pack, MSG_TAG_SHV_PATH); + chainpack_pack_cstring_terminated(pack, path); + chainpack_pack_int(pack, MSG_TAG_METHOD); + chainpack_pack_cstring_terminated(pack, method); + chainpack_pack_container_end(pack); + + chainpack_pack_imap_begin(pack); + chainpack_pack_int(pack, MSG_KEY_PARAMS); +} + +void rpcmsg_pack_signal( + cpcp_pack_context *pack, const char *path, const char *method) { + chainpack_pack_meta_begin(pack); + chainpack_pack_int(pack, MSG_TAG_META_TYPE_ID); + chainpack_pack_int(pack, 1); + chainpack_pack_int(pack, MSG_TAG_SHV_PATH); + chainpack_pack_cstring_terminated(pack, path); + chainpack_pack_int(pack, MSG_TAG_METHOD); + chainpack_pack_cstring_terminated(pack, method); + chainpack_pack_container_end(pack); + + chainpack_pack_imap_begin(pack); + chainpack_pack_int(pack, MSG_KEY_PARAMS); +} + +static size_t roundup2(size_t n) { + n--; + size_t s = 1; + for (size_t i = 0; i < sizeof(n); i++) { + n |= n >> s; + s = s << 1; + } + n++; + return n; +} + +bool rpcmsg_unpack_str(struct rpcclient_msg msg, struct rpcmsg_str **str) { + const cpcp_item *i; + size_t off = 0; + do { + i = rpcclient_next_item(msg); + if (i->type != CPCP_ITEM_STRING) + return false; + if (str) { + size_t chunk = i->as.String.chunk_size; + size_t siz = off + chunk + 1; + if (*str == NULL || ((*str)->siz >= 0 && siz >= (*str)->siz)) { + *str = realloc(*str, roundup2(sizeof(struct rpcmsg_str) + siz)); + (*str)->siz = siz; + } + if ((*str)->siz < 0 && off + chunk >= -(*str)->siz) + chunk = -(*str)->siz - off; + memcpy(&(*str)->str[off], i->as.String.chunk_start, chunk); + off += chunk; + } + } while (!i->as.String.last_chunk); + if (str && *str && ((*str)->siz >= 0 || -(*str)->siz > off)) + (*str)->str[off] = '\0'; + return true; +} + +bool rpcmsg_str_truncated(const struct rpcmsg_str *str) { + if (str->siz == 0) + return true; + for (size_t i = 0; i < str->siz; i++) + if (str->str[i] == '\0') + return true; + return true; +} + +bool rpcmsg_unpack_access(struct rpcclient_msg msg, enum rpcmsg_access *acc) { + const cpcp_item *i; + size_t off = 0; + char buf[4]; /* The maximum for known access levels is 4 characters */ + +#define PARSE_ACCESS \ + do { \ + if (acc && off <= 4) { \ + const struct gperf_rpcmsg_access_match *match = \ + gperf_rpcmsg_access(buf, off); \ + if (match) \ + *acc = match->acc; \ + } \ + } while (false) + + do { + i = rpcclient_next_item(msg); + if (i->type != CPCP_ITEM_STRING) + return false; + for (size_t p = 0; p < i->as.String.chunk_size; p++) { + char c = i->as.String.chunk_start[p]; + if (c == ',') { + PARSE_ACCESS; + off = 0; + } else if (off < 4) + buf[off++] = c; + } + } while (!i->as.String.last_chunk); + PARSE_ACCESS; + return true; +#undef PARSE_ACCESS +} + +const char *rpcmsg_access_str(enum rpcmsg_access acc) { + switch (acc) { + case RPCMSG_ACC_BROWSE: + return "bws"; + case RPCMSG_ACC_READ: + return "rd"; + case RPCMSG_ACC_WRITE: + return "wr"; + case RPCMSG_ACC_COMMAND: + return "cmd"; + case RPCMSG_ACC_CONFIG: + return "cfg"; + case RPCMSG_ACC_SERVICE: + return "srv"; + case RPCMSG_ACC_SUPER_SERVICE: + return "ssrv"; + case RPCMSG_ACC_DEVEL: + return "dev"; + case RPCMSG_ACC_ADMIN: + return "su"; + default: + return ""; + } +} + +static void ensure_rinfo(struct rpcmsg_request_info **rinfo, bool increase) { + size_t siz = (*rinfo)->cidscnt + (*rinfo)->rcidscnt + (increase ? 1 : 0); + if (*rinfo != NULL && (!increase || (*rinfo)->cidssiz > siz)) + return; + *rinfo = realloc(*rinfo, roundup2(sizeof(struct rpcmsg_request_info) + siz)); +} + +enum rpcmsg_type rpcmsg_unpack(struct rpcclient_msg msg, struct rpcmsg_str **path, + struct rpcmsg_str **method, struct rpcmsg_request_info **rinfo) { + /* Unpack meta */ + const cpcp_item *i; + i = rpcclient_next_item(msg); + if (i->type != CPCP_ITEM_META) + return RPCMSG_T_INVALID; + bool has_rid = false, has_method = false; + do { + i = rpcclient_next_item(msg); + if (i->type == CPCP_ITEM_CONTAINER_END) + break; + if (i->type != CPCP_ITEM_INT) + return RPCMSG_T_INVALID; + switch (i->as.Int) { + /* Note: we intentionally do not check MSG_TAG_META_TYPE_ID right + * now because we have clients that do not send it. + */ + case MSG_TAG_REQUEST_ID: + has_rid = true; + i = rpcclient_next_item(msg); + if (i->type != CPCP_ITEM_INT) + return RPCMSG_T_INVALID; + if (rinfo) { + ensure_rinfo(rinfo, false); + (*rinfo)->rid = i->as.Int; + } + break; + case MSG_TAG_SHV_PATH: + i = rpcclient_next_item(msg); + if (i->type != CPCP_ITEM_STRING || !rpcmsg_unpack_str(msg, path)) + return RPCMSG_T_INVALID; + break; + case MSG_TAG_METHOD: + has_method = true; + i = rpcclient_next_item(msg); + if (i->type != CPCP_ITEM_STRING || !rpcmsg_unpack_str(msg, method)) + return RPCMSG_T_INVALID; + break; + case MSG_TAG_CALLER_IDS: + i = rpcclient_next_item(msg); + if (i->type != CPCP_ITEM_INT) + return RPCMSG_T_INVALID; + if (rinfo) { + ensure_rinfo(rinfo, true); + memmove(&(*rinfo)->cids[(*rinfo)->cidscnt + 1], + &(*rinfo)->cids[(*rinfo)->cidscnt], + (*rinfo)->rcidscnt * sizeof *(*rinfo)->cids); + (*rinfo)->cids[(*rinfo)->cidscnt++] = i->as.Int; + } + break; + case MSG_TAG_REV_CALLER_IDS: + i = rpcclient_next_item(msg); + if (i->type != CPCP_ITEM_INT) + return RPCMSG_T_INVALID; + if (rinfo) { + ensure_rinfo(rinfo, true); + (*rinfo)->cids[(*rinfo)->cidscnt + (*rinfo)->rcidscnt++] = + i->as.Int; + } + break; + case MSG_TAG_ACCESS_GRANT: + if (rinfo) + ensure_rinfo(rinfo, false); + if (!rpcmsg_unpack_access(msg, rinfo ? &(*rinfo)->acc_grant : NULL)) + return RPCMSG_T_INVALID; + break; + default: + if (!rpcclient_skip_item(msg)) + return RPCMSG_T_INVALID; + } + } while (true); + + enum rpcmsg_type res = has_rid + ? (has_method ? RPCMSG_T_REQUEST : RPCMSG_T_RESPONSE) + : (has_method ? RPCMSG_T_SIGNAL : RPCMSG_T_INVALID); + + /* Start unpacking imap */ + i = rpcclient_next_item(msg); + if (i->type != CPCP_ITEM_IMAP) + return RPCMSG_T_INVALID; + i = rpcclient_next_item(msg); + if (i->type != CPCP_ITEM_INT) + return i->type == CPCP_ITEM_CONTAINER_END ? res : RPCMSG_T_INVALID; + switch (i->as.Int) { + case MSG_KEY_PARAMS: + return res != RPCMSG_T_RESPONSE ? res : RPCMSG_T_INVALID; + case MSG_KEY_RESULT: + return res == RPCMSG_T_RESPONSE ? res : RPCMSG_T_INVALID; + case MSG_KEY_ERROR: + return res == RPCMSG_T_RESPONSE ? RPCMSG_T_ERROR : RPCMSG_T_INVALID; + }; + return RPCMSG_T_INVALID; +} diff --git a/libshvrpc/rpcmsg_access.gperf b/libshvrpc/rpcmsg_access.gperf new file mode 100644 index 0000000..addb988 --- /dev/null +++ b/libshvrpc/rpcmsg_access.gperf @@ -0,0 +1,36 @@ +%compare-lengths +%compare-strncmp + +%pic +%enum +%readonly-tables +%switch=1 +%language=ANSI-C + +%define hash-function-name gperf_rpcmsg_access_hash +%define lookup-function-name gperf_rpcmsg_access +%define string-pool-name gperf_rpcmsg_access_string +%define constants-prefix GPERF_RPCMSG_ACCESS_ +%define slot-name offset + +%struct-type +%{ +struct gperf_rpcmsg_access_match { + int offset; + enum rpcmsg_access acc; +}; +%} + +struct gperf_rpcmsg_access_match; + +%% +bws, RPCMSG_ACC_BROWSE +rd, RPCMSG_ACC_READ +wr, RPCMSG_ACC_WRITE +cmd, RPCMSG_ACC_COMMAND +cfg, RPCMSG_ACC_CONFIG +srv, RPCMSG_ACC_SERVICE +ssrv, RPCMSG_ACC_SUPER_SERVICE +dev, RPCMSG_ACC_DEVEL +su, RPCMSG_ACC_ADMIN +%% diff --git a/meson_options.txt b/meson_options.txt index 746936e..e486a2a 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -5,6 +5,9 @@ option( description: 'Expect tests to be build and check for their dependencies', ) +# libshvchainpack + +# libshvrpc option( 'unpack_bufsiz', type: 'integer', diff --git a/tests/unit/libshvrpc/rpcurl.c b/tests/unit/libshvrpc/rpcurl.c index 5111c96..388acf2 100644 --- a/tests/unit/libshvrpc/rpcurl.c +++ b/tests/unit/libshvrpc/rpcurl.c @@ -142,12 +142,13 @@ static const struct { size_t error; } parse_invalid_d[] = { {"foo://some", 0}, - {"udp://some:none?password=foo", 15}, // We parse it backward so end of the port + {"udp://some:none?password=foo", 15}, // We parse it backward so end of the + // port {"udp://some?invalid=foo", 11}, }; ARRAY_TEST(parse, parse_invalid, parse_invalid_d) { const char *err; ck_assert_ptr_null(rpcurl_parse(_d.str, &err)); - ck_assert_ptr_eq(err, _d.str +_d.error); + ck_assert_ptr_eq(err, _d.str + _d.error); } END_TEST From 454b6c4810199377d8120ac070630cd97db485ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Mon, 7 Aug 2023 13:16:38 +0200 Subject: [PATCH 09/44] Easy inclusion of subprojects in Nix This relies on Meson to download our subprojects. The advantage is that version needs to be updated only in Meson's wrap files and not in flake. The only change required in Nix is refresh of the subprojects hash. --- docs/template/index.md | 1 + docs/template/subprojects.md | 16 +++++++ flake.nix | 79 +++++++++++++++++++--------------- subprojects/.fetch.nix | 47 ++++++++++++++++++++ subprojects/.gitignore | 4 ++ subprojects/.nix-setup-hook.sh | 10 +++++ 6 files changed, 123 insertions(+), 34 deletions(-) create mode 100644 docs/template/subprojects.md create mode 100644 subprojects/.fetch.nix create mode 100644 subprojects/.gitignore create mode 100644 subprojects/.nix-setup-hook.sh diff --git a/docs/template/index.md b/docs/template/index.md index ab6d65f..2f415b4 100644 --- a/docs/template/index.md +++ b/docs/template/index.md @@ -9,6 +9,7 @@ tasks. ```{toctree} fork gperf +subprojects unittests integration_tests docs diff --git a/docs/template/subprojects.md b/docs/template/subprojects.md new file mode 100644 index 0000000..2e82262 --- /dev/null +++ b/docs/template/subprojects.md @@ -0,0 +1,16 @@ +Meson subprojects +================= + +Meson provides a convenient way to include subprojects withtout using git +submodules. The advantage is especially when we consider those subprojects +optional. Meson is able to decide if dependency is in the system and thus not +required or if it needs to be fetched and build with this project. In case of +git's submodules that would be on the user to decide. + +The implementation is with Meson's wrap files. You need to create [wrap +file](https://mesonbuild.com/Wrap-dependency-system-manual.html) in +the `subprojects` directory. Make sure that you also add this file to +`subprojects/.gitignore` so it is included in your commit. + +If you do not plan on using subprojects then you can remove the `subprojects` +directory. With that you also need to remove `subprojects` from `flake.nix`. diff --git a/flake.nix b/flake.nix index 9f59b86..8fe315d 100644 --- a/flake.nix +++ b/flake.nix @@ -14,39 +14,50 @@ with builtins; with flake-utils.lib; with nixpkgs.lib; let - packages = pkgs: - with pkgs; rec { - template-c = stdenv.mkDerivation { - pname = "template-c"; - version = replaceStrings ["\n"] [""] (readFile ./version); - src = builtins.path { - path = ./.; - filter = path: type: ! hasSuffix ".nix" path; - }; - outputs = ["out" "doc"]; - buildInputs = [ - check - pkgs.check-suite - ]; - nativeBuildInputs = [ - bash - bats - gperf - meson - ninja - pkg-config - doxygen - (sphinxHook.overrideAttrs (oldAttrs: { - propagatedBuildInputs = with python3Packages; [ - sphinx_rtd_theme - myst-parser - breathe - ]; - })) - ]; - sphinxRoot = "../docs"; - }; + version = fileContents ./version; + src = builtins.path { + path = ./.; + filter = path: type: ! hasSuffix ".nix" path; + }; + packages = pkgs: let + subprojects = import ./subprojects/.fetch.nix { + inherit pkgs src; + rev = self.rev or null; + hash = "sha256-GEUDy5crY09UTQ072NEvB50DjjgNv2BrLSwxLRNqEKM="; + }; + in { + template-c = pkgs.stdenv.mkDerivation { + pname = "template-c"; + inherit version src; + outputs = ["out" "doc"]; + buildInputs = with pkgs; []; + nativeBuildInputs = with pkgs; [ + subprojects + gperf + meson + ninja + pkg-config + doxygen + (sphinxHook.overrideAttrs (oldAttrs: { + propagatedBuildInputs = with python3Packages; [ + sphinx_rtd_theme + myst-parser + breathe + ]; + })) + ]; + checkInputs = with pkgs; [ + check + pkgs.check-suite + ]; + nativeCheckInputs = with pkgs; [ + bash + bats + ]; + doCheck = true; + sphinxRoot = "../docs"; }; + }; in { overlays = { @@ -60,9 +71,9 @@ // eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}.extend self.overlays.default; in { - packages = rec { + packages = { inherit (pkgs) template-c; - default = template-c; + default = pkgs.template-c; }; legacyPackages = pkgs; diff --git a/subprojects/.fetch.nix b/subprojects/.fetch.nix new file mode 100644 index 0000000..b7ee1fa --- /dev/null +++ b/subprojects/.fetch.nix @@ -0,0 +1,47 @@ +{ + pkgs, + src, + hash ? "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + rev, +}: let + pname = + if rev != null + then rev + else "src"; +in + pkgs.makeSetupHook { + name = "${pname}-meson-subprojects-setup-hook"; + + substitutions = { + subprojects = pkgs.stdenvNoCC.mkDerivation { + name = "${pname}-meson-subprojects"; + inherit src; + + nativeBuildInputs = with pkgs; [meson cacert git]; + + buildCommand = '' + cp -r --no-preserve=mode $src/. . + content="" + while [ "$content" != "$(ls subprojects)" ]; do + content="$(ls subprojects)" + meson subprojects download || true + find subprojects -mindepth 2 -name \*.wrap \ + | while read -r path; do + file="''${path##*/}" + [ -f "subprojects/$file" ] || { + echo '[wrap-redirect]' + echo "filename = ''${path#subprojects/}" + } >"subprojects/$file" + done + done + find subprojects -type d -name .git -prune -execdir rm -r {} + + + mv subprojects $out + ''; + + outputHashMode = "recursive"; + outputHash = hash; + }; + }; + } + ./.nix-setup-hook.sh diff --git a/subprojects/.gitignore b/subprojects/.gitignore new file mode 100644 index 0000000..3c3928e --- /dev/null +++ b/subprojects/.gitignore @@ -0,0 +1,4 @@ +* +!.gitignore +!.fetch.nix +!.nix-setup-hook.sh diff --git a/subprojects/.nix-setup-hook.sh b/subprojects/.nix-setup-hook.sh new file mode 100644 index 0000000..21cddb4 --- /dev/null +++ b/subprojects/.nix-setup-hook.sh @@ -0,0 +1,10 @@ +# shellcheck shell=bash + +copySubprojects() { + echo "Copying downloaded subprojects" + # shellcheck disable=SC2154 + rm -rf "$sourceRoot/subprojects" + cp -r --no-preserve=mode @subprojects@ "$sourceRoot/subprojects" +} + +postUnpackHooks+=(copySubprojects) From 069a04e2bd43393ab96c6ac05789409590ff92f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Mon, 7 Aug 2023 14:47:32 +0200 Subject: [PATCH 10/44] fixup! fixup! TMP --- include/shv/cp.h | 9 -- libshvchainpack/chainpack_unpack.c | 3 +- libshvchainpack/cpon_pack.c | 8 +- libshvchainpack/cpon_unpack.c | 127 +++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 libshvchainpack/cpon_unpack.c diff --git a/include/shv/cp.h b/include/shv/cp.h index 657a48b..39d93eb 100644 --- a/include/shv/cp.h +++ b/include/shv/cp.h @@ -121,13 +121,4 @@ ssize_t cpon_pack(FILE *, struct cpon_state *, const struct cpitem *) size_t cpon_unpack(FILE *, struct cpitem *) __attribute__((nonnull)); -struct json_state { - const char *indent; -}; - -ssize_t json_pack(FILE *, struct json_state *, const struct cpitem *) - __attribute__((nonnull)); -size_t json_unpack(FILE *, struct cpitem *) __attribute__((nonnull)); - - #endif diff --git a/libshvchainpack/chainpack_unpack.c b/libshvchainpack/chainpack_unpack.c index 6dca85b..684e3d2 100644 --- a/libshvchainpack/chainpack_unpack.c +++ b/libshvchainpack/chainpack_unpack.c @@ -41,7 +41,7 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { /* Continue reading previous item */ if ((item->type == CP_ITEM_STRING || item->type == CP_ITEM_BLOB) && - item->as.String.eoff != 0) { + !item->as.String.last) { /* There is no difference between string and blob in data type and thus * we can write this code to serve them both. */ @@ -153,6 +153,7 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { item->type = CP_ITEM_CONTAINER_END; break; default: + ungetc(scheme, f); item->type = CP_ITEM_INVALID; } } diff --git a/libshvchainpack/cpon_pack.c b/libshvchainpack/cpon_pack.c index cc0a086..ca5bddf 100644 --- a/libshvchainpack/cpon_pack.c +++ b/libshvchainpack/cpon_pack.c @@ -1,5 +1,4 @@ #include "cpon.h" -#include "shv/cp.h" #include #include #include @@ -108,7 +107,7 @@ ssize_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) break; case CP_ITEM_LIST: if (ctxpush(state, CP_ITEM_LIST)) - PUTS(CPON_LIST_BEGIN); + PUTC(CPON_LIST_BEGIN); break; case CP_ITEM_MAP: if (ctxpush(state, CP_ITEM_MAP)) @@ -213,6 +212,8 @@ static ssize_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec) { ssize_t len = snprintf(str, strsiz, "%" PRIu64, mantisa); assert(len < strsiz); + // TODO sometimes we can use XXeYY format + if (neg) PUTC('-'); int64_t i = len - 1; @@ -228,9 +229,6 @@ static ssize_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec) { for (; i >= 0; i--) PUTC(str[i]); - - PRINTF("%" PRId64 ".%" PRId64, dec->mantisa, dec->mantisa); - // TODO return res; } diff --git a/libshvchainpack/cpon_unpack.c b/libshvchainpack/cpon_unpack.c new file mode 100644 index 0000000..f5484cd --- /dev/null +++ b/libshvchainpack/cpon_unpack.c @@ -0,0 +1,127 @@ +#include "cpon.h" +#include +#include +#include + + +static size_t cpon_unpack_buf(FILE *f, struct cpbuf *buf, bool *ok); + + +size_t cpon_unpack(FILE *f, struct cpitem *item) { + size_t res = 0; +#define GETC \ + ({ \ + int __v = getc(f); \ + if (__v == EOF) { \ + item->type = CP_ITEM_INVALID; \ + return res; \ + } \ + res++; \ + __v; \ + }) +#define EXPECT(V) \ + do { \ + char c = GETC; \ + if (c != (V)) { \ + ungetc(c, f); \ + item->type = CP_ITEM_INVALID; \ + return res; \ + } \ + } while (false) +#define EXPECTS(V) \ + do { \ + size_t __expects_len = strlen(V); \ + for (size_t __expect_i = 0; __expect_i < __expects_len; __expect_i++) \ + EXPECT((V)[__expect_i]); \ + } while(false) +#define CALL(FUNC, ...) \ + do { \ + bool ok; \ + res += FUNC(f, __VA_ARGS__, &ok); \ + if (!ok) { \ + item->type = CP_ITEM_INVALID; \ + return res; \ + } \ + } while (false) + + /* Continue reading previous item */ + if ((item->type == CP_ITEM_STRING || item->type == CP_ITEM_BLOB) && + !item->as.String.last) { + CALL(cpon_unpack_buf, &item->as.String); + return res; + } + + bool comment = false; + char c; + while (true) { + c = GETC; + if (comment) { + if (c == '*') { + c = GETC; + comment = c != '/'; + } + continue; + } + if (c == ' ' || c == '\t' || c == ':' || c == ',') + continue; + if (c == '/') { + EXPECT('*'); + comment = true; + continue; + } + break; + } + + switch (c) { + case 'n': + EXPECTS("ull"); + item->type = CP_ITEM_NULL; + break; + case 't': + EXPECTS("rue"); + item->type = CP_ITEM_BOOL; + item->as.Bool = true; + break; + case 'f': + EXPECTS("alse"); + item->type = CP_ITEM_BOOL; + item->as.Bool = false; + break; + case '-': + case '0' ... '9': + // TODO + break; + case 'b': + EXPECT('"'); + item->type = CP_ITEM_BLOB; + // TODO load some blob data + break; + case '"': + item->type = CP_ITEM_STRING; + // TODO load some string + break; + case '[': + item->type = CP_ITEM_LIST; + break; + case '{': + item->type = CP_ITEM_MAP; + break; + case 'i': + EXPECT('{'); + item->type = CP_ITEM_IMAP; + break; + case '<': + item->type = CP_ITEM_META; + break; + case '>': + case '}': + case ']': + item->type = CP_ITEM_CONTAINER_END; + break; + default: + ungetc(c, f); + item->type = CP_ITEM_INVALID; + break; + } + return res; +} From e20aeff2f3cd79fd20d8c7eaced499afdd3cb581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Mon, 7 Aug 2023 14:56:08 +0200 Subject: [PATCH 11/44] Tweak formatting --- tests/unit/libshvchainpack/chainpack.c | 99 +++++++++++++------------- tests/unit/libshvchainpack/cpon.c | 88 +++++++++++------------ 2 files changed, 89 insertions(+), 98 deletions(-) diff --git a/tests/unit/libshvchainpack/chainpack.c b/tests/unit/libshvchainpack/chainpack.c index e9c0e7f..8476aed 100644 --- a/tests/unit/libshvchainpack/chainpack.c +++ b/tests/unit/libshvchainpack/chainpack.c @@ -360,79 +360,76 @@ TEST(unpack, unpack_meta) { } END_TEST -/* Packing function should not generate any output when pack context is in error */ -TEST(pack,pack_uint_error){ - pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_uint(&pack,0); - ck_assert_ptr_eq(pack.start,pack.current); +/* Packing function should not generate any output when pack context is in error + */ +TEST(pack, pack_uint_error) { + pack.err_no = CPCP_RC_LOGICAL_ERROR; + chainpack_pack_uint(&pack, 0); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -TEST(pack,pack_int_error){ - pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_int(&pack,0); - ck_assert_ptr_eq(pack.start,pack.current); +TEST(pack, pack_int_error) { + pack.err_no = CPCP_RC_LOGICAL_ERROR; + chainpack_pack_int(&pack, 0); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -TEST(pack,pack_decimal_error){ - const static cpcp_decimal d={0,0}; - pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_decimal(&pack,&d); - ck_assert_ptr_eq(pack.start,pack.current); +TEST(pack, pack_decimal_error) { + const static cpcp_decimal d = {0, 0}; + pack.err_no = CPCP_RC_LOGICAL_ERROR; + chainpack_pack_decimal(&pack, &d); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -TEST(pack,pack_double_error){ - pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_double(&pack,0); - ck_assert_ptr_eq(pack.start,pack.current); +TEST(pack, pack_double_error) { + pack.err_no = CPCP_RC_LOGICAL_ERROR; + chainpack_pack_double(&pack, 0); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -TEST(pack,pack_date_error){ - static const cpcp_date_time time_er={0,0}; - pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_date_time(&pack,&time_er); - ck_assert_ptr_eq(pack.start,pack.current); +TEST(pack, pack_date_error) { + static const cpcp_date_time time_er = {0, 0}; + pack.err_no = CPCP_RC_LOGICAL_ERROR; + chainpack_pack_date_time(&pack, &time_er); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -static const void (*pack_error_simple_call_d[])(cpcp_pack_context*) = { - chainpack_pack_null, - chainpack_pack_list_begin, - chainpack_pack_map_begin, - chainpack_pack_imap_begin, - chainpack_pack_meta_begin, - chainpack_pack_container_end -}; +static const void (*pack_error_simple_call_d[])(cpcp_pack_context *) = { + chainpack_pack_null, chainpack_pack_list_begin, chainpack_pack_map_begin, + chainpack_pack_imap_begin, chainpack_pack_meta_begin, + chainpack_pack_container_end}; ARRAY_TEST(pack, pack_error_simple_call) { - pack.err_no=CPCP_RC_LOGICAL_ERROR; + pack.err_no = CPCP_RC_LOGICAL_ERROR; _d(&pack); - ck_assert_ptr_eq(pack.start,pack.current); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -TEST(pack,pack_bool_error){ - pack.err_no=CPCP_RC_LOGICAL_ERROR; - chainpack_pack_boolean(&pack,0); - ck_assert_ptr_eq(pack.start,pack.current); +TEST(pack, pack_bool_error) { + pack.err_no = CPCP_RC_LOGICAL_ERROR; + chainpack_pack_boolean(&pack, 0); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -TEST(unpack,unpack_next_error){ - cpcp_unpack_context_init(&unpack,NULL,0,NULL,NULL); - unpack.err_no=CPCP_RC_LOGICAL_ERROR; +TEST(unpack, unpack_next_error) { + cpcp_unpack_context_init(&unpack, NULL, 0, NULL, NULL); + unpack.err_no = CPCP_RC_LOGICAL_ERROR; chainpack_unpack_next(&unpack); - ck_assert_ptr_eq(unpack.start,unpack.current); + ck_assert_ptr_eq(unpack.start, unpack.current); } END_TEST -TEST(unpack,unnpack_next_string_error){ - cpcp_unpack_context_init(&unpack,NULL,0,NULL,NULL); - unpack.item.type=CPCP_ITEM_STRING; - unpack.item.as.String.last_chunk=0; +TEST(unpack, unnpack_next_string_error) { + cpcp_unpack_context_init(&unpack, NULL, 0, NULL, NULL); + unpack.item.type = CPCP_ITEM_STRING; + unpack.item.as.String.last_chunk = 0; chainpack_unpack_next(&unpack); - ck_assert_ptr_eq(unpack.start,unpack.current); + ck_assert_ptr_eq(unpack.start, unpack.current); } END_TEST -TEST(unpack,unpack_next_blob_error){ - cpcp_unpack_context_init(&unpack,NULL,0,NULL,NULL); - unpack.item.type=CPCP_ITEM_BLOB; - unpack.item.as.String.last_chunk=0; +TEST(unpack, unpack_next_blob_error) { + cpcp_unpack_context_init(&unpack, NULL, 0, NULL, NULL); + unpack.item.type = CPCP_ITEM_BLOB; + unpack.item.as.String.last_chunk = 0; chainpack_unpack_next(&unpack); - ck_assert_ptr_eq(unpack.start,unpack.current); + ck_assert_ptr_eq(unpack.start, unpack.current); } END_TEST diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index 3f434a3..46c0c1c 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -341,76 +341,70 @@ TEST(unpack, unpack_meta) { } END_TEST -/* Packing function should not generate any output when pack context is in error */ -TEST(pack,pack_uint_error){ - pack.err_no=CPCP_RC_LOGICAL_ERROR; - cpon_pack_uint(&pack,0); - ck_assert_ptr_eq(pack.start,pack.current); +/* Packing function should not generate any output when pack context is in error + */ +TEST(pack, pack_uint_error) { + pack.err_no = CPCP_RC_LOGICAL_ERROR; + cpon_pack_uint(&pack, 0); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -TEST(pack,pack_int_error){ - pack.err_no=CPCP_RC_LOGICAL_ERROR; - cpon_pack_int(&pack,0); - ck_assert_ptr_eq(pack.start,pack.current); +TEST(pack, pack_int_error) { + pack.err_no = CPCP_RC_LOGICAL_ERROR; + cpon_pack_int(&pack, 0); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -TEST(pack,pack_double_error){ - pack.err_no=CPCP_RC_LOGICAL_ERROR; - cpon_pack_double(&pack,0); - ck_assert_ptr_eq(pack.start,pack.current); +TEST(pack, pack_double_error) { + pack.err_no = CPCP_RC_LOGICAL_ERROR; + cpon_pack_double(&pack, 0); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -static const void (*pack_error_simple_call_d[])(cpcp_pack_context*) = { - cpon_pack_null, - cpon_pack_list_begin, - cpon_pack_map_begin, - cpon_pack_imap_begin, - cpon_pack_meta_begin -}; +static void (*const pack_error_simple_call_d[])(struct cpcp_pack_context *) = { + cpon_pack_null, cpon_pack_list_begin, cpon_pack_map_begin, + cpon_pack_imap_begin, cpon_pack_meta_begin}; ARRAY_TEST(pack, pack_error_simple_call) { - pack.err_no=CPCP_RC_LOGICAL_ERROR; + pack.err_no = CPCP_RC_LOGICAL_ERROR; _d(&pack); - ck_assert_ptr_eq(pack.start,pack.current); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -static const void (*pack_error_bool_call_d[])(cpcp_pack_context*,bool) = { - cpon_pack_boolean, - cpon_pack_list_end, - cpon_pack_map_end, - cpon_pack_imap_end, - cpon_pack_meta_end -}; +static void (*const pack_error_bool_call_d[])(cpcp_pack_context *, bool) = { + cpon_pack_boolean, cpon_pack_list_end, cpon_pack_map_end, + cpon_pack_imap_end, cpon_pack_meta_end}; ARRAY_TEST(pack, pack_error_bool_call) { - pack.err_no=CPCP_RC_LOGICAL_ERROR; - _d(&pack,0); - ck_assert_ptr_eq(pack.start,pack.current); + pack.err_no = CPCP_RC_LOGICAL_ERROR; + _d(&pack, 0); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST + TEST(pack, pack_double_zero_test) { - cpon_pack_double(&pack,0); - ck_assert_str_eq(pack.start,"0."); + cpon_pack_double(&pack, 0); + ck_assert_stashstr("0."); } END_TEST -TEST(pack,pack_double_pos_test){ - cpon_pack_double(&pack,1000); - ck_assert_str_eq(pack.start,"1000."); +TEST(pack, pack_double_pos_test) { + cpon_pack_double(&pack, 1000); + ck_assert_stashstr("1000."); } END_TEST -TEST(pack,pack_double_neg_test){ - cpon_pack_double(&pack,-1000); - ck_assert_str_eq(pack.start,"-1000."); +TEST(pack, pack_double_neg_test) { + cpon_pack_double(&pack, -1000); + ck_assert_stashstr("-1000."); } END_TEST -TEST(unpack,unpack_insig_error){ - cpcp_unpack_context_init(&unpack,NULL,0,NULL,NULL); +TEST(unpack, unpack_insig_error) { + cpcp_unpack_context_init(&unpack, NULL, 0, NULL, NULL); cpon_unpack_skip_insignificant(&unpack); - ck_assert_ptr_eq(unpack.start,unpack.current); + ck_assert_ptr_eq(unpack.start, unpack.current); } END_TEST -TEST(unpack,unpack_next_error){ - cpcp_unpack_context_init(&unpack,NULL,0,NULL,NULL); - unpack.err_no=CPCP_RC_LOGICAL_ERROR; +TEST(unpack, unpack_next_error) { + cpcp_unpack_context_init(&unpack, NULL, 0, NULL, NULL); + unpack.err_no = CPCP_RC_LOGICAL_ERROR; cpon_unpack_next(&unpack); - ck_assert_ptr_eq(unpack.start,unpack.current); + ck_assert_ptr_eq(unpack.start, unpack.current); } END_TEST From 3b73c06f711bd3ddb8ac62c2457930f3d40ec297 Mon Sep 17 00:00:00 2001 From: Bartaaak Date: Mon, 31 Jul 2023 13:52:00 +0200 Subject: [PATCH 12/44] Test double-Memory leak is fixed --- tests/unit/libshvchainpack/cpon.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index 46c0c1c..5096539 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -381,18 +381,21 @@ ARRAY_TEST(pack, pack_error_bool_call) { END_TEST TEST(pack, pack_double_zero_test) { - cpon_pack_double(&pack, 0); - ck_assert_stashstr("0."); + cpon_pack_double(&pack,0); + int is_same=strncmp(pack.start,"0.",2); + ck_assert_int_eq(0,is_same); } END_TEST -TEST(pack, pack_double_pos_test) { - cpon_pack_double(&pack, 1000); - ck_assert_stashstr("1000."); +TEST(pack,pack_double_pos_test){ + cpon_pack_double(&pack,10000.5); + int is_same=strncmp(pack.start,"10000.5",7); + ck_assert_int_eq(0,is_same); } END_TEST -TEST(pack, pack_double_neg_test) { - cpon_pack_double(&pack, -1000); - ck_assert_stashstr("-1000."); +TEST(pack,pack_double_neg_test){ + cpon_pack_double(&pack,-10000.5); + int is_same=strncmp(pack.start,"-10000.5",8); + ck_assert_int_eq(0,is_same); } END_TEST TEST(unpack, unpack_insig_error) { From f61641d1be597795b0e9da39f4f2a8744968f0d7 Mon Sep 17 00:00:00 2001 From: Bartaaak Date: Mon, 31 Jul 2023 15:47:08 +0200 Subject: [PATCH 13/44] Added more test in libshvchainpack/cpon.c for coverage --- tests/unit/libshvchainpack/cpon.c | 32 +++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index 5096539..fc25eba 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -379,22 +379,46 @@ ARRAY_TEST(pack, pack_error_bool_call) { ck_assert_ptr_eq(pack.start, pack.current); } END_TEST - +TEST(pack,pack_copy_test){ + char * text="Testing function cpon_pack_copy_str."; + cpon_pack_copy_str(&pack,text); + int is_same=strncmp(pack.start,text,pack.bytes_written); + ck_assert_int_eq(0,is_same); +} TEST(pack, pack_double_zero_test) { cpon_pack_double(&pack,0); - int is_same=strncmp(pack.start,"0.",2); + + int is_same=strncmp(pack.start,"0.",pack.bytes_written); ck_assert_int_eq(0,is_same); } END_TEST TEST(pack,pack_double_pos_test){ cpon_pack_double(&pack,10000.5); - int is_same=strncmp(pack.start,"10000.5",7); + int is_same=strncmp(pack.start,"10000.5",pack.bytes_written); ck_assert_int_eq(0,is_same); } END_TEST TEST(pack,pack_double_neg_test){ cpon_pack_double(&pack,-10000.5); - int is_same=strncmp(pack.start,"-10000.5",8); + int is_same=strncmp(pack.start,"-10000.5",pack.bytes_written); + ck_assert_int_eq(0,is_same); +} +END_TEST +TEST(pack,pack_double_low_test){ + cpon_pack_double(&pack,0.9); + int is_same=strncmp(pack.start,"0.9",pack.bytes_written); + ck_assert_int_eq(0,is_same); +} +END_TEST +TEST(pack,pack_double_expo_test){ + cpon_pack_double(&pack,10000000); + int is_same=strncmp(pack.start,"1e7",pack.bytes_written); + ck_assert_int_eq(0,is_same); +} +END_TEST +TEST(pack,pack_double_expo_small_test){ + cpon_pack_double(&pack,0.08); + int is_same=strncmp(pack.start,"8e-2",pack.bytes_written); ck_assert_int_eq(0,is_same); } END_TEST From 1728b8c4864ee71ae61f7e88221f4acb78157510 Mon Sep 17 00:00:00 2001 From: Bartaaak Date: Tue, 1 Aug 2023 11:03:02 +0200 Subject: [PATCH 14/44] Tests-changed assert to stash_str in libshvchainpack/cpon.c --- tests/unit/libshvchainpack/cpon.c | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index fc25eba..5f71d02 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -382,44 +382,36 @@ END_TEST TEST(pack,pack_copy_test){ char * text="Testing function cpon_pack_copy_str."; cpon_pack_copy_str(&pack,text); - int is_same=strncmp(pack.start,text,pack.bytes_written); - ck_assert_int_eq(0,is_same); + ck_assert_stashstr("Testing function cpon_pack_copy_str."); } -TEST(pack, pack_double_zero_test) { +TEST(pack, pack_double_zero_test){ cpon_pack_double(&pack,0); - - int is_same=strncmp(pack.start,"0.",pack.bytes_written); - ck_assert_int_eq(0,is_same); + ck_assert_stashstr("0."); } END_TEST TEST(pack,pack_double_pos_test){ cpon_pack_double(&pack,10000.5); - int is_same=strncmp(pack.start,"10000.5",pack.bytes_written); - ck_assert_int_eq(0,is_same); + ck_assert_stashstr("10000.5"); } END_TEST TEST(pack,pack_double_neg_test){ cpon_pack_double(&pack,-10000.5); - int is_same=strncmp(pack.start,"-10000.5",pack.bytes_written); - ck_assert_int_eq(0,is_same); + ck_assert_stashstr("-10000.5"); } END_TEST TEST(pack,pack_double_low_test){ cpon_pack_double(&pack,0.9); - int is_same=strncmp(pack.start,"0.9",pack.bytes_written); - ck_assert_int_eq(0,is_same); + ck_assert_stashstr("0.9"); } END_TEST TEST(pack,pack_double_expo_test){ cpon_pack_double(&pack,10000000); - int is_same=strncmp(pack.start,"1e7",pack.bytes_written); - ck_assert_int_eq(0,is_same); + ck_assert_stashstr("1e7"); } END_TEST TEST(pack,pack_double_expo_small_test){ cpon_pack_double(&pack,0.08); - int is_same=strncmp(pack.start,"8e-2",pack.bytes_written); - ck_assert_int_eq(0,is_same); + ck_assert_stashstr("8e-2"); } END_TEST TEST(unpack, unpack_insig_error) { @@ -435,3 +427,4 @@ TEST(unpack, unpack_next_error) { ck_assert_ptr_eq(unpack.start, unpack.current); } END_TEST + From fcb7c82ba081d10cdc9dd0238f7515a98663e368 Mon Sep 17 00:00:00 2001 From: Bartaaak Date: Wed, 2 Aug 2023 09:44:38 +0200 Subject: [PATCH 15/44] Changed testing of cpon_pack_double to ARRAY_TEST --- tests/unit/libshvchainpack/cpon.c | 53 +++++++++++++++++++------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index 5f71d02..5ecce15 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -384,34 +384,36 @@ TEST(pack,pack_copy_test){ cpon_pack_copy_str(&pack,text); ck_assert_stashstr("Testing function cpon_pack_copy_str."); } -TEST(pack, pack_double_zero_test){ - cpon_pack_double(&pack,0); - ck_assert_stashstr("0."); -} END_TEST -TEST(pack,pack_double_pos_test){ - cpon_pack_double(&pack,10000.5); - ck_assert_stashstr("10000.5"); +static const struct { + const double num; + const char *const str_num; +}cpon_double_d[] = {{0., "0."}, {10000.5, "10000.5"},{-10000.5,"-10000.5"}, + {0.9,"0.9"},{10000000,"1e7"},{0.08,"8e-2"}}; +ARRAY_TEST(pack, pack_double_d,cpon_double_d) { + cpon_pack_double(&pack,_d.num); + ck_assert_stashstr(_d.str_num); } END_TEST -TEST(pack,pack_double_neg_test){ - cpon_pack_double(&pack,-10000.5); - ck_assert_stashstr("-10000.5"); +TEST(pack,pack_string_start_err){ + pack.err_no=CPCP_RC_LOGICAL_ERROR; + cpon_pack_string_start(&pack,"error",5); + ck_assert_ptr_eq(pack.start,pack.current); } END_TEST -TEST(pack,pack_double_low_test){ - cpon_pack_double(&pack,0.9); - ck_assert_stashstr("0.9"); +TEST(pack,pack_string_start_test){ + cpon_pack_string_start(&pack,"test: \0\n\t\b\r\\\"",13); + ck_assert_stashstr("\"test: \\0\\n\\t\\b\\r\\\\\\\""); } END_TEST -TEST(pack,pack_double_expo_test){ - cpon_pack_double(&pack,10000000); - ck_assert_stashstr("1e7"); +TEST(pack,pack_string_cont_test){ + cpon_pack_string_cont(&pack,"test: \0\n\t\b\r\\\"",13); + ck_assert_stashstr("test: \\0\\n\\t\\b\\r\\\\\\\""); } END_TEST -TEST(pack,pack_double_expo_small_test){ - cpon_pack_double(&pack,0.08); - ck_assert_stashstr("8e-2"); +TEST(pack,pack_string_finish_test){ + cpon_pack_string_finish(&pack); + ck_assert_stashstr("\""); } END_TEST TEST(unpack, unpack_insig_error) { @@ -427,4 +429,15 @@ TEST(unpack, unpack_next_error) { ck_assert_ptr_eq(unpack.start, unpack.current); } END_TEST - +TEST(unpack,unpack_skip_zero_test){ + cpcp_unpack_context_init(&unpack,"",0,NULL,NULL); + cpon_unpack_skip_insignificant(&unpack); + ck_assert_ptr_eq(unpack.start,unpack.current); +} +END_TEST +TEST(unpack,unpack_skip_neg_test){ + cpcp_unpack_context_init(&unpack,"žttest",6,NULL,NULL); + cpon_unpack_skip_insignificant(&unpack); + ck_assert_str_eq("test",unpack.current); +} +END_TEST \ No newline at end of file From 4bda85309a40b5ca1f3a42ec7927dd5a873a5526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Mon, 7 Aug 2023 15:25:12 +0200 Subject: [PATCH 16/44] Tweak format --- tests/unit/libshvchainpack/cpon.c | 44 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index 5ecce15..fc9e543 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -379,39 +379,39 @@ ARRAY_TEST(pack, pack_error_bool_call) { ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -TEST(pack,pack_copy_test){ - char * text="Testing function cpon_pack_copy_str."; - cpon_pack_copy_str(&pack,text); +TEST(pack, pack_copy_test) { + char *text = "Testing function cpon_pack_copy_str."; + cpon_pack_copy_str(&pack, text); ck_assert_stashstr("Testing function cpon_pack_copy_str."); } END_TEST static const struct { const double num; const char *const str_num; -}cpon_double_d[] = {{0., "0."}, {10000.5, "10000.5"},{-10000.5,"-10000.5"}, - {0.9,"0.9"},{10000000,"1e7"},{0.08,"8e-2"}}; -ARRAY_TEST(pack, pack_double_d,cpon_double_d) { - cpon_pack_double(&pack,_d.num); +} cpon_double_d[] = {{0., "0."}, {10000.5, "10000.5"}, {-10000.5, "-10000.5"}, + {0.9, "0.9"}, {10000000, "1e7"}, {0.08, "8e-2"}}; +ARRAY_TEST(pack, pack_double_d, cpon_double_d) { + cpon_pack_double(&pack, _d.num); ck_assert_stashstr(_d.str_num); } END_TEST -TEST(pack,pack_string_start_err){ - pack.err_no=CPCP_RC_LOGICAL_ERROR; - cpon_pack_string_start(&pack,"error",5); - ck_assert_ptr_eq(pack.start,pack.current); +TEST(pack, pack_string_start_err) { + pack.err_no = CPCP_RC_LOGICAL_ERROR; + cpon_pack_string_start(&pack, "error", 5); + ck_assert_ptr_eq(pack.start, pack.current); } END_TEST -TEST(pack,pack_string_start_test){ - cpon_pack_string_start(&pack,"test: \0\n\t\b\r\\\"",13); +TEST(pack, pack_string_start_test) { + cpon_pack_string_start(&pack, "test: \0\n\t\b\r\\\"", 13); ck_assert_stashstr("\"test: \\0\\n\\t\\b\\r\\\\\\\""); } END_TEST -TEST(pack,pack_string_cont_test){ - cpon_pack_string_cont(&pack,"test: \0\n\t\b\r\\\"",13); +TEST(pack, pack_string_cont_test) { + cpon_pack_string_cont(&pack, "test: \0\n\t\b\r\\\"", 13); ck_assert_stashstr("test: \\0\\n\\t\\b\\r\\\\\\\""); } END_TEST -TEST(pack,pack_string_finish_test){ +TEST(pack, pack_string_finish_test) { cpon_pack_string_finish(&pack); ck_assert_stashstr("\""); } @@ -429,15 +429,15 @@ TEST(unpack, unpack_next_error) { ck_assert_ptr_eq(unpack.start, unpack.current); } END_TEST -TEST(unpack,unpack_skip_zero_test){ - cpcp_unpack_context_init(&unpack,"",0,NULL,NULL); +TEST(unpack, unpack_skip_zero_test) { + cpcp_unpack_context_init(&unpack, "", 0, NULL, NULL); cpon_unpack_skip_insignificant(&unpack); - ck_assert_ptr_eq(unpack.start,unpack.current); + ck_assert_ptr_eq(unpack.start, unpack.current); } END_TEST -TEST(unpack,unpack_skip_neg_test){ - cpcp_unpack_context_init(&unpack,"žttest",6,NULL,NULL); +TEST(unpack, unpack_skip_neg_test) { + cpcp_unpack_context_init(&unpack, "žttest", 6, NULL, NULL); cpon_unpack_skip_insignificant(&unpack); - ck_assert_str_eq("test",unpack.current); + ck_assert_str_eq("test", unpack.current); } END_TEST \ No newline at end of file From a52e4981ce7fe5c8683f223cd5220e06b177348b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Tue, 8 Aug 2023 10:08:02 +0200 Subject: [PATCH 17/44] foo: fix nuttx build specifying invalid variable name This also always installs on NuttX as well as on other systems. The original condition was for demo application and that would make sense but general approach is to have application you might want to compile in NuttX and have it accessible. It might be also more beneficial to disable the whole application as it is instead of just installation. --- src/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/meson.build b/src/meson.build index 6cafe70..c8ce80f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,11 +3,11 @@ foo_dependencies = [libfoo_dep, argp] if isnuttx - shvtreecdemo_client = static_library( + foo = static_library( 'foo', foo_sources + ['main.c'], dependencies: foo_dependencies, - install: not meson.is_subproject(), + install: true, c_args: '-Dmain=foo_main', ) else From 79910fa8c39cbcfae96c25454e58dd6f2a900ff3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Tue, 8 Aug 2023 10:40:51 +0200 Subject: [PATCH 18/44] foo: add getopt usage example The argp is not available on NuttX and thus this provides instead also alternative in the form of getopt code. --- src/config.c | 70 +++++++++++++++++++++++++++++++++++++++------ src/config.h | 3 +- src/main.c | 9 +++--- tests/unit/config.c | 5 ++-- 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/src/config.c b/src/config.c index 3cd071d..7ee6a9a 100644 --- a/src/config.c +++ b/src/config.c @@ -6,11 +6,56 @@ *****************************************************************************/ #include "config.h" #include +#ifdef __NuttX__ +#include +#include +#include +#else #include +#endif -static struct config conf = { - .source_file = NULL, -}; +/* NOTE: The two ways to parse arguments are provided here. NuttX does not have + * argp and thus we can't use it. We should use getopt instead but contrary to + * this file you should choose only one of the argument parsing tools. The rule + * is: if you plan on deploying on NuttX then you getopt, otherwise argp. + */ +#ifdef __NuttX__ + +static void print_usage(const char *argv0) { + fprintf(stderr, "%s [-h] [FILE]\n", argv0); +} + +static void print_help(const char *argv0) { + print_usage(argv0); + fprintf(stderr, "\n"); + fprintf(stderr, "Arguments:\n"); + fprintf(stderr, " -h Print this help text\n"); +} + +static void parse_opts(struct config *conf, int argc, char **argv) { + int c; + while ((c = getopt(argc, argv, "h")) != -1) { + switch (c) { + case 'h': + print_help(argv[0]); + exit(0); + default: + print_usage(argv[0]); + fprintf(stderr, "Invalid option: -%c\n", c); + exit(2); + } + } + if (optind < argc) { + if (optind + 1 == argc) + conf->source_file = argv[optind]; + else { + fprintf(stderr, "Invalid argument: %s\n", argv[optind + 1]); + exit(2); + } + } +} + +#else static error_t parse_opt(int key, char *arg, struct argp_state *state); @@ -27,10 +72,11 @@ const static struct argp argp_parser = { static error_t parse_opt(int key, char *arg, struct argp_state *state) { + struct config *conf = state->input; switch (key) { case ARGP_KEY_ARG: - if (conf.source_file == NULL) { - conf.source_file = arg; + if (conf->source_file == NULL) { + conf->source_file = arg; break; } __attribute__((fallthrough)); @@ -40,8 +86,16 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) { return 0; } +#endif + -struct config *load_config(int argc, char **argv) { - argp_parse(&argp_parser, argc, argv, 0, NULL, NULL); - return &conf; +void load_config(struct config *conf, int argc, char **argv) { + *conf = (struct config){ + .source_file = NULL, + }; +#ifdef __NuttX__ + parse_opts(conf, argc, argv); +#else + argp_parse(&argp_parser, argc, argv, 0, NULL, conf); +#endif } diff --git a/src/config.h b/src/config.h index b2c1dd4..ac6d112 100644 --- a/src/config.h +++ b/src/config.h @@ -15,6 +15,7 @@ struct config { /* Parse arguments and load configuration file * Returned pointer is to statically allocated (do not call free on it). */ -struct config *load_config(int argc, char **argv) __attribute__((nonnull)); +void load_config(struct config *conf, int argc, char **argv) + __attribute__((nonnull)); #endif diff --git a/src/main.c b/src/main.c index 6b94b76..c909aa7 100644 --- a/src/main.c +++ b/src/main.c @@ -13,15 +13,16 @@ int main(int argc, char **argv) { - struct config *conf = load_config(argc, argv); + struct config conf; + load_config(&conf, argc, argv); FILE *f = stdin; bool close = false; - if (conf->source_file != NULL && strcmp(conf->source_file, "-")) { - f = fopen(conf->source_file, "r"); + if (conf.source_file != NULL && strcmp(conf.source_file, "-")) { + f = fopen(conf.source_file, "r"); if (f == NULL) { fprintf(stderr, "Can't open input file '%s': %s\n", - conf->source_file, strerror(errno)); + conf.source_file, strerror(errno)); return 1; } } diff --git a/tests/unit/config.c b/tests/unit/config.c index 8512bf5..9753972 100644 --- a/tests/unit/config.c +++ b/tests/unit/config.c @@ -40,7 +40,8 @@ const struct { }; ARRAY_TEST(arguments, parse_arguments, parse_arguments_data) { - struct config *conf = load_config(_d.argc, _d.argv); - ck_assert_pstr_eq(conf->source_file, _d.config.source_file); + struct config conf; + load_config(&conf, _d.argc, _d.argv); + ck_assert_pstr_eq(conf.source_file, _d.config.source_file); } END_TEST From 9abc77af0f7d2679358d4df4614109efb9bf0d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Wed, 23 Aug 2023 20:39:11 +0200 Subject: [PATCH 19/44] Rewritten packing and unpacking --- cpconv/main.c | 92 +++++ cpconv/meson.build | 22 + cpconv/opts.c | 74 ++++ cpconv/opts.h | 20 + docs/api/chainpack.md | 19 +- docs/api/cp.md | 6 + docs/api/cp_pack.md | 6 + docs/api/cp_unpack.md | 6 + docs/api/index.md | 3 + docs/conf.py | 4 + flake.nix | 1 + include/meson.build | 14 +- include/shv/chainpack.h | 352 ++++++++++++---- include/shv/cp.h | 166 ++++++-- include/shv/cp_pack.h | 226 ++++++++++ include/shv/cp_unpack.h | 161 ++++++++ include/shv/cpchainpack.h | 207 ---------- include/shv/cpcp.h | 27 -- include/shv/cpcp_convert.h | 9 - include/shv/cpcp_pack.h | 38 -- include/shv/cpcp_unpack.h | 157 ------- include/shv/cpon.h | 78 ---- include/shv/rpcbroker.h | 9 + include/shv/rpcclient.h | 60 +-- include/shv/rpcclient_impl.h | 38 ++ include/shv/rpchandler.h | 35 ++ include/shv/rpcmsg.h | 245 +++++++++-- include/shv/rpcreceiver.h | 10 - include/shv/rpcserver.h | 18 + libshvbroker/libshvbroker.version | 6 + libshvbroker/meson.build | 26 ++ libshvchainpack/chainpack.h | 34 -- libshvchainpack/chainpack_pack.c | 227 +++------- libshvchainpack/chainpack_unpack.c | 188 +++++---- libshvchainpack/cp_pack.c | 106 +++++ libshvchainpack/cp_unpack.c | 193 +++++++++ libshvchainpack/cpcp_convert.c | 282 ------------- libshvchainpack/cpdatetime.c | 18 + libshvchainpack/cpdecimal.c | 27 ++ libshvchainpack/cpon.h | 22 - libshvchainpack/cpon_pack.c | 268 ++++++------ libshvchainpack/cpon_unpack.c | 208 ++++++++-- libshvchainpack/libshvchainpack.version | 110 ++--- libshvchainpack/meson.build | 13 +- libshvrpc/libshvrpc.version | 10 + libshvrpc/meson.build | 4 +- libshvrpc/rpcclient.c | 76 +--- libshvrpc/rpcclient_stream.c | 166 ++++---- libshvrpc/rpcmsg.c | 109 ----- libshvrpc/rpcmsg_access.c | 77 ++++ libshvrpc/rpcmsg_meta.c | 150 +++++++ libshvrpc/rpcmsg_pack.c | 90 ++++ meson.build | 40 +- meson_options.txt | 22 +- shvbroker/config.c | 50 +++ shvbroker/config.h | 16 + shvbroker/main.c | 12 + shvbroker/meson.build | 22 + subprojects/uriparser.wrap | 3 + tests/unit/libshvchainpack/chainpack.c | 490 ++++------------------ tests/unit/libshvchainpack/chainpackh.c | 201 +++++++++ tests/unit/libshvchainpack/cp_pack.c | 374 +++++++++++++++++ tests/unit/libshvchainpack/cp_unpack.c | 240 +++++++++++ tests/unit/libshvchainpack/cpdatetime.c | 48 +++ tests/unit/libshvchainpack/cpdecimal.c | 31 ++ tests/unit/libshvchainpack/cpon.c | 528 +++++------------------- tests/unit/libshvchainpack/meson.build | 10 +- tests/unit/libshvchainpack/packunpack.c | 28 -- tests/unit/libshvchainpack/packunpack.h | 47 --- tests/unit/libshvrpc/meson.build | 6 +- tests/unit/libshvrpc/rpcmsg_access.c | 95 +++++ tests/unit/libshvrpc/rpcmsg_meta.c | 57 +++ tests/unit/libshvrpc/rpcmsg_pack.c | 28 ++ tests/unit/libshvrpc/rpcurl.c | 3 +- tests/unit/meson.build | 1 + tests/unit/utils/bdata.h | 17 + tests/unit/utils/item.h | 92 +++++ tests/unit/utils/meson.build | 5 + tests/unit/utils/packstream.c | 40 ++ tests/unit/utils/packstream.h | 38 ++ tests/unit/utils/unpack.c | 35 ++ tests/unit/utils/unpack.h | 13 + 82 files changed, 4319 insertions(+), 2786 deletions(-) create mode 100644 cpconv/main.c create mode 100644 cpconv/meson.build create mode 100644 cpconv/opts.c create mode 100644 cpconv/opts.h create mode 100644 docs/api/cp.md create mode 100644 docs/api/cp_pack.md create mode 100644 docs/api/cp_unpack.md create mode 100644 include/shv/cp_pack.h create mode 100644 include/shv/cp_unpack.h delete mode 100644 include/shv/cpchainpack.h delete mode 100644 include/shv/cpcp.h delete mode 100644 include/shv/cpcp_convert.h delete mode 100644 include/shv/cpcp_pack.h delete mode 100644 include/shv/cpcp_unpack.h delete mode 100644 include/shv/cpon.h create mode 100644 include/shv/rpcbroker.h create mode 100644 include/shv/rpcclient_impl.h create mode 100644 include/shv/rpchandler.h delete mode 100644 include/shv/rpcreceiver.h create mode 100644 include/shv/rpcserver.h create mode 100644 libshvbroker/libshvbroker.version create mode 100644 libshvbroker/meson.build delete mode 100644 libshvchainpack/chainpack.h create mode 100644 libshvchainpack/cp_pack.c create mode 100644 libshvchainpack/cp_unpack.c delete mode 100644 libshvchainpack/cpcp_convert.c create mode 100644 libshvchainpack/cpdatetime.c create mode 100644 libshvchainpack/cpdecimal.c delete mode 100644 libshvchainpack/cpon.h create mode 100644 libshvrpc/rpcmsg_access.c create mode 100644 libshvrpc/rpcmsg_meta.c create mode 100644 libshvrpc/rpcmsg_pack.c create mode 100644 shvbroker/config.c create mode 100644 shvbroker/config.h create mode 100644 shvbroker/main.c create mode 100644 shvbroker/meson.build create mode 100644 subprojects/uriparser.wrap create mode 100644 tests/unit/libshvchainpack/chainpackh.c create mode 100644 tests/unit/libshvchainpack/cp_pack.c create mode 100644 tests/unit/libshvchainpack/cp_unpack.c create mode 100644 tests/unit/libshvchainpack/cpdatetime.c create mode 100644 tests/unit/libshvchainpack/cpdecimal.c delete mode 100644 tests/unit/libshvchainpack/packunpack.c delete mode 100644 tests/unit/libshvchainpack/packunpack.h create mode 100644 tests/unit/libshvrpc/rpcmsg_access.c create mode 100644 tests/unit/libshvrpc/rpcmsg_meta.c create mode 100644 tests/unit/libshvrpc/rpcmsg_pack.c create mode 100644 tests/unit/utils/bdata.h create mode 100644 tests/unit/utils/item.h create mode 100644 tests/unit/utils/meson.build create mode 100644 tests/unit/utils/packstream.c create mode 100644 tests/unit/utils/packstream.h create mode 100644 tests/unit/utils/unpack.c create mode 100644 tests/unit/utils/unpack.h diff --git a/cpconv/main.c b/cpconv/main.c new file mode 100644 index 0000000..c719086 --- /dev/null +++ b/cpconv/main.c @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include "opts.h" + + +static void cpon_state_realloc(struct cpon_state *state) { + state->cnt = (state->cnt ?: 1) * 2; + state->ctx = realloc(state->ctx, state->cnt * sizeof *state->ctx); +} + + +int main(int argc, char **argv) { + struct conf conf; + parse_opts(argc, argv, &conf); + + if (conf.op == OP_DETECT) { + // TODO detect form input and output names + conf.op = OP_PACK; + } + + /* Prepare our input */ + FILE *in; + if (conf.input == NULL || !strcmp(conf.input, "-")) + in = stdin; + else { + in = fopen(conf.input, "r"); + if (in == NULL) { + fprintf(stderr, "Unable to open input file: %s: %s\n", conf.input, + strerror(errno)); + return 1; + } + } + + /* Prepare access to the output */ + FILE *out; + if (conf.output == NULL || !strcmp(conf.output, "-")) + out = stdout; + else { + out = fopen(conf.output, "w"); + if (out == NULL) { + fprintf(stderr, "Unable to open output file: %s: %s\n", conf.output, + strerror(errno)); + return 1; + } + } + + /* States and handles we use to copy data from unpacker to the packer. */ + uint8_t buf[BUFSIZ]; + struct cpitem item = { + .buf = buf, + .bufsiz = BUFSIZ, + }; + struct cpon_state cpon_state = {.indent = conf.pretty ? " " : NULL, + .ctx = NULL, + .cnt = 0, + .realloc = cpon_state_realloc}; + + bool success = false; + + /* Unpack all items and pack them in the opposite format */ + while (({ + size_t res; + if (conf.op == OP_PACK) + res = cpon_unpack(in, &cpon_state, &item); + else + res = chainpack_unpack(in, &item); + res > 0; + })) { + if (item.type == CP_ITEM_INVALID) + break; + ssize_t res; + if (conf.op == OP_PACK) + res = chainpack_pack(out, &item); + else + res = cpon_pack(out, &cpon_state, &item); + if (res < 0) + break; + }; + + /* Success is if we convert all input to the output */ + success = feof(in); + + free(cpon_state.ctx); + if (in != stdin) + fclose(in); + if (out != stdout) + fclose(out); + return success ? 0 : 1; +} diff --git a/cpconv/meson.build b/cpconv/meson.build new file mode 100644 index 0000000..1c5a078 --- /dev/null +++ b/cpconv/meson.build @@ -0,0 +1,22 @@ +cpconv_sources = files('opts.c') +cpconv_dependencies = [libshvchainpack_dep, argp] + + +if isnuttx + cpconv = static_library( + 'cpconv', + cpconv_sources + ['main.c'], + dependencies: cpconv_dependencies, + install: not meson.is_subproject(), + c_args: '-Dmain=cpconv_main', + ) +else + cpconv = executable( + 'cpconv', + cpconv_sources + ['main.c'], + dependencies: cpconv_dependencies, + install: true, + ) +endif + +cpconv_internal_includes = include_directories('.') diff --git a/cpconv/opts.c b/cpconv/opts.c new file mode 100644 index 0000000..679fb46 --- /dev/null +++ b/cpconv/opts.c @@ -0,0 +1,74 @@ +#include "opts.h" +#include +#include +#include + + +static void print_usage(const char *argv0) { + fprintf(stderr, "%s [-punVh] [INPUT] [OUTPUT]\n", argv0); +} + +static void print_help(const char *argv0) { + print_usage(argv0); + fprintf(stderr, "Convert between Chainpack and human readable Cpon.\n"); + fprintf(stderr, + "The conversion direction is can be autodetected but it is highly " + "advised to specify the direction if possible.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Arguments:\n"); + fprintf(stderr, " -p Input is Cpon and output is Chainpack (Pack)\n"); + fprintf(stderr, " -u Input is Chainpack and output is Cpon (Unpack)\n"); + fprintf(stderr, " -n Pretty print CPON instead of single line\n"); + fprintf(stderr, " -V Print version and exit\n"); + fprintf(stderr, " -h Print this help text\n"); +} + +static void opset(struct conf *conf, enum operation op) { + if (conf->op != OP_DETECT && conf->op != op) { + fprintf(stderr, "You can use only -p or -u not both at the same time!"); + exit(2); + } + conf->op = op; +} + +void parse_opts(int argc, char **argv, struct conf *conf) { + *conf = (struct conf){ + .input = NULL, + .output = NULL, + .op = OP_DETECT, + .pretty = false, + }; + + int c; + while ((c = getopt(argc, argv, "punVh")) != -1) { + switch (c) { + case 'p': + opset(conf, OP_PACK); + break; + case 'u': + opset(conf, OP_UNPACK); + break; + case 'n': + conf->pretty = true; + break; + case 'V': + printf("%s " PROJECT_VERSION "\n", argv[0]); + exit(0); + case 'h': + print_help(argv[0]); + exit(0); + default: + print_usage(argv[0]); + fprintf(stderr, "Invalid option: -%c\n", c); + exit(2); + } + } + if (optind < argc) + conf->input = argv[++optind]; + if (optind < argc) + conf->output = argv[++optind]; + if (optind < argc) { + fprintf(stderr, "Invalid argument: %s\n", argv[optind]); + exit(2); + } +} diff --git a/cpconv/opts.h b/cpconv/opts.h new file mode 100644 index 0000000..161176a --- /dev/null +++ b/cpconv/opts.h @@ -0,0 +1,20 @@ +#ifndef _CPCONV_OPTS_H_ +#define _CPCONV_OPTS_H_ +#include + +enum operation { + OP_DETECT, + OP_PACK, + OP_UNPACK, +}; + +struct conf { + const char *input, *output; + enum operation op; + bool pretty; +}; + +/* Parse arguments. */ +void parse_opts(int argc, char **argv, struct conf *conf) __attribute__((nonnull)); + +#endif diff --git a/docs/api/chainpack.md b/docs/api/chainpack.md index b80afc0..2b8dbd7 100644 --- a/docs/api/chainpack.md +++ b/docs/api/chainpack.md @@ -1,18 +1,9 @@ -# Chainpack and CPON +# Chainpack tools -The serialization and deserialization of Chainpack and CPON data formats. - -## Common pack and unpack - -```{autodoxygenfile} cpcp.h -``` - -## Chainpack pack and unpack +The basic utilities for working with Chainpack data. This provides definitions +and simple operations you need to unpack and pack data in Chainpack format. This +is not a most user friendly way to work with Chainpack. It is suggester rather +to use [packer and unpacker](./cp.md) instead. ```{autodoxygenfile} chainpack.h ``` - -## CPON pack and unpack - -```{autodoxygenfile} cpon.h -``` diff --git a/docs/api/cp.md b/docs/api/cp.md new file mode 100644 index 0000000..1f4fa77 --- /dev/null +++ b/docs/api/cp.md @@ -0,0 +1,6 @@ +# Chainpack and CPON packer and unpacker + +The serialization and deserialization of Chainpack and CPON data formats. + +```{autodoxygenfile} shv/cp.h +``` diff --git a/docs/api/cp_pack.md b/docs/api/cp_pack.md new file mode 100644 index 0000000..e3c2299 --- /dev/null +++ b/docs/api/cp_pack.md @@ -0,0 +1,6 @@ +# Generic packer + +Generic packer API with utility functions to pack data more easilly. + +```{autodoxygenfile} cp_pack.h +``` diff --git a/docs/api/cp_unpack.md b/docs/api/cp_unpack.md new file mode 100644 index 0000000..09bf664 --- /dev/null +++ b/docs/api/cp_unpack.md @@ -0,0 +1,6 @@ +# Generic unpacker + +Generic unpacker API with utility functions to unpack data more easilly. + +```{autodoxygenfile} cp_unpack.h +``` diff --git a/docs/api/index.md b/docs/api/index.md index f80f3a2..00c854d 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -3,6 +3,9 @@ ```{toctree} :maxdepth: 3 +cp +cp_pack +cp_unpack chainpack rpcurl ``` diff --git a/docs/conf.py b/docs/conf.py index c06bb8c..b54bb1b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -43,6 +43,10 @@ files = [file.relative_to(includedir) for file in includedir.glob("**/*.h")] breathe_projects_source = {"public_api": ("../include", files)} breathe_default_project = "public_api" +breathe_doxygen_config_options = { + "PREDEFINED": "__attribute__(...)=", + "MACRO_EXPANSION": "YES", +} def build_finished_gitignore(app, exception): diff --git a/flake.nix b/flake.nix index 4c36e38..490ab07 100644 --- a/flake.nix +++ b/flake.nix @@ -34,6 +34,7 @@ # Build tools meson ninja + cmake gperf pkg-config # Documentation diff --git a/include/meson.build b/include/meson.build index a52d80a..97b302c 100644 --- a/include/meson.build +++ b/include/meson.build @@ -2,13 +2,17 @@ includes = include_directories('.') libshvchainpack_headers = files( 'shv/chainpack.h', - 'shv/cpcp.h', - 'shv/cpcp_convert.h', - 'shv/cpcp_pack.h', - 'shv/cpcp_unpack.h', - 'shv/cpon.h', + 'shv/cp.h', + 'shv/cp_pack.h', + 'shv/cp_unpack.h', ) libshvrpc_headers = files( 'shv/rpcclient.h', + 'shv/rpcclient_impl.h', + 'shv/rpchandler.h', + 'shv/rpcmsg.h', 'shv/rpcurl.h', ) +libshvbroker_headers = files( + 'shv/rpcbroker.h' +) diff --git a/include/shv/chainpack.h b/include/shv/chainpack.h index 3514246..86d4388 100644 --- a/include/shv/chainpack.h +++ b/include/shv/chainpack.h @@ -1,95 +1,269 @@ #ifndef SHV_CHAINPACK_H #define SHV_CHAINPACK_H +#include +#include +#include -#include +_Static_assert(sizeof(unsigned long long) <= 17, "Limitation of the ChainPack"); -#include -#include -#include -#include - -typedef enum { - CP_INVALID = -1, - - CP_Null = 128, - CP_UInt, - CP_Int, - CP_Double, - CP_Bool, - CP_Blob, - CP_String, // UTF8 encoded string - CP_DateTimeEpoch_depr, // deprecated - CP_List, - CP_Map, - CP_IMap, - CP_MetaMap, - CP_Decimal, - CP_DateTime, - CP_CString, - - CP_FALSE = 253, - CP_TRUE = 254, - CP_TERM = 255, -} chainpack_pack_packing_schema; - -const char *chainpack_packing_schema_name(int sch); - -void chainpack_pack_uint_data(cpcp_pack_context *pack_context, uint64_t num); - -void chainpack_pack_null(cpcp_pack_context *pack_context) __attribute__((nonnull)); - -void chainpack_pack_boolean(cpcp_pack_context *pack_context, bool b) - __attribute__((nonnull)); - -void chainpack_pack_int(cpcp_pack_context *pack_context, int64_t i) - __attribute__((nonnull)); - -void chainpack_pack_uint(cpcp_pack_context *pack_context, uint64_t i) - __attribute__((nonnull)); - -void chainpack_pack_double(cpcp_pack_context *pack_context, double d) - __attribute__((nonnull)); - -void chainpack_pack_decimal(cpcp_pack_context *pack_context, - const cpcp_decimal *v) __attribute__((nonnull)); - -void chainpack_pack_date_time(cpcp_pack_context *pack_context, - const cpcp_date_time *v) __attribute__((nonnull)); - -void chainpack_pack_blob( - cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len); -void chainpack_pack_blob_start(cpcp_pack_context *pack_context, - size_t string_len, const uint8_t *buff, size_t buff_len); -void chainpack_pack_blob_cont( - cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len); - -void chainpack_pack_string( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len); -void chainpack_pack_string_start(cpcp_pack_context *pack_context, - size_t string_len, const char *buff, size_t buff_len); -void chainpack_pack_string_cont( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len); - -void chainpack_pack_cstring( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len); -void chainpack_pack_cstring_terminated( - cpcp_pack_context *pack_context, const char *str); -void chainpack_pack_cstring_start( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len); -void chainpack_pack_cstring_cont( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len); -void chainpack_pack_cstring_finish(cpcp_pack_context *pack_context); - -void chainpack_pack_list_begin(cpcp_pack_context *pack_context); -void chainpack_pack_map_begin(cpcp_pack_context *pack_context); -void chainpack_pack_imap_begin(cpcp_pack_context *pack_context); -void chainpack_pack_meta_begin(cpcp_pack_context *pack_context); - -void chainpack_pack_container_end(cpcp_pack_context *pack_context); - -uint64_t chainpack_unpack_uint_data(cpcp_unpack_context *unpack_context, bool *ok); -uint64_t chainpack_unpack_uint_data2( - cpcp_unpack_context *unpack_context, int *err_code); -void chainpack_unpack_next(cpcp_unpack_context *unpack_context); + +enum chainpack_scheme { + CPS_Null = 128, + CPS_UInt, + CPS_Int, + CPS_Double, + CPS_Bool, + CPS_Blob, + CPS_String, /* UTF8 encoded string */ + CPS_DateTimeEpoch_depr, /* Deprecated */ + CPS_List, + CPS_Map, + CPS_IMap, + CPS_MetaMap, + CPS_Decimal, + CPS_DateTime, + CPS_CString, + CPS_BlobChain, + + CPS_FALSE = 253, + CPS_TRUE = 254, + CPS_TERM = 255, +}; + +/*! UTC msec since 2.2. 2018 followed by signed UTC offset in 1/4 hour + * `Fri Feb 02 2018 00:00:00 == 1517529600 EPOCH` + * + * This is offset used to encode the DateTime. + */ +#define CHAINPACK_EPOCH_MSEC (1517529600000); + +/*! Check if integer packed in scheme is signed or unsigned. + * + * See the documentation for `chainpack_scheme_int` to understand when this is + * used. + * + * Call this only if `c < CPS_Null`. + * + * @param c: Chainpack scheme byte. + * @returns `true` if integer is signed and `false` for unsigned. + */ +static inline bool chainpack_scheme_signed(uint8_t c) { + return c & 0x40; +} + +/*! Convert scheme to the encoded int. + * + * The initial 128 values in the scheme are used to pack small integers. This + * function provides an easy way to receive value from it. + * + * Use this only if `c < CPS_Null;` as otherwise it is not integer packed in + * scheme. + * + * This function can be used for both signed and unsigned integers, because only + * positive integers are packed this way. You can use `chainpack_scheme_signed` + * to check if it is suppose to one or the other. + * + * @param c: Chainpack scheme byte. + * @returns Integer value packed in the scheme byte. + */ +static inline unsigned chainpack_scheme_uint(uint8_t c) { + return c & 0x3f; +} + +/*! Check if scheme is Int. + * + * Integers in Chainpack can be packed either directly in the scheme or as + * followup data. Thus to identify if it is an Int it is not enough to just use + * `c == CPS_Int`. This provides all in one check. + * + * @param c: Chainpack scheme byte. + * @returns `true` if it is signed integer, `false` otherwise. + */ +static inline bool chainpack_is_int(uint8_t c) { + return c == CPS_Int || (c < CPS_Null && chainpack_scheme_signed(c)); +} + +/*! Check if scheme is UInt. + * + * Integers in Chainpack can be packed either directly in the scheme or as + * followup data. Thus to identify if it is an UInt it is not enough to just + * use `c == CPS_UInt`. This provides all in one check. + * + * @param c: Chainpack scheme byte. + * @returns `true` if it is unsigned integer, `false` otherwise. + */ +static inline bool chainpack_is_uint(uint8_t c) { + return c == CPS_Int || (c < CPS_Null && !chainpack_scheme_signed(c)); +} + +/*! Number of bytes the integer spans. + * + * Chainpack integers can take up to 17 bytes. The length is known from the + * first unpacked byte right after the scheme byte. This extracts this info from + * that byte and provides it to you. + * + * @param c: A first integer data byte. + * @returns number of bytes the integer spans. + */ +static inline unsigned chainpack_int_bytes(uint8_t c) { + unsigned mask = 0x80; + for (unsigned i = 1; mask > 0x0f; i++) { + if (!(c & mask)) + return i; + mask >>= 1; + } + return (c & 0x0f) + 5; +} + +/*! Unsigned integer value packed in the first integer byte. + * + * This is companion function ton the `chainpack_int_bytes` because it extracts + * remaining bits from the first byte. + * + * These are the most significant bits and thus to construct the while number + * you can just use shift left and binary or to add less significant bits from + * the followup bytes. + * + * @param c: A first integer data byte. + * @param bytes: Number of bytes this integer spans (use + * `chainpack_int_bytes`). + * @returns value from the first integer data byte. + */ +static inline unsigned chainpack_uint_value1(uint8_t c, unsigned bytes) { + if (bytes > 4) + return 0; + return (0xff >> bytes) & c; +} + +/*! Signed integer value packed in the first integer byte. + * + * This is companion function to the `chainpack_int_bytes` because it extracts + * remaining bits from the first byte. + * + * The sign for up to three bytes is handled in this value and thus you directly + * can get negative number. For greater number of bytes this function returns + * `0` and the most significant bit in the following byte is sign (tip: read it + * to `int8_t` and convert to the type you wish it to be). + * + * @param c: A first integer data byte. + * @param bytes: Number of bytes this integer spans (use + * `chainpack_int_bytes`). + * @returns value from the first integer data byte. + */ +static inline int chainpack_int_value1(uint8_t c, unsigned bytes) { + if (bytes > 4) + return 0; + return ((0x80 >> bytes & c) ? -1 : 1) * ((0x7f >> bytes) & c); +} + + +/*! Unsigned integer number packed in the scheme. + * + * You can use this only with numbers from 0 to 63. + * + * @param c: Number to be packed. + * @returns Byte with packed number in the scheme byte. + */ +static inline uint8_t chainpack_w_scheme_uint(unsigned num) { + return num & 0x3f; +} + +/*! Integer number packed in the scheme. + * + * You can use this only with numbers from 0 to 63. + * + * @param c: Number to be packed. + * @returns byte with packed number in the scheme byte. + */ +static inline uint8_t chainpack_w_scheme_int(int num) { + return 0x40 | (num & 0x3f); +} + +/*! Deduce number of bytes needed to pack unsigned integer number. + * + * @param c: Number to be packed. + * @returns number of bytes needed to fit packed number. + */ +static inline unsigned chainpack_w_uint_bytes(unsigned long long num) { + /* Note: we rely here on compiler optimizations. This functions is going to + * be inlined and for most constants this should be reduced to a single + * branch. + */ + if ((num & (ULLONG_MAX << 7)) == 0) + return 1; + else if ((num & (ULLONG_MAX << 14)) == 0) + return 2; + else if ((num & (ULLONG_MAX << 21)) == 0) + return 3; + else if ((num & (ULLONG_MAX << 28)) == 0) + return 4; + unsigned i; + unsigned long long mask = ULLONG_MAX << 32; + for (i = 0; (num & mask) != 0; i++) + mask <<= 8; + return 5 + i; +} + +/*! Deduce number of bytes needed to pack signed integer number. + * + * @param c: Number to be packed. + * @returns number of bytes needed to fit packed number. + */ +static inline unsigned chainpack_w_int_bytes(long long num) { + unsigned long long n = num < 0 ? -num : num; + if ((n & (ULLONG_MAX << 6)) == 0) + return 1; + else if ((n & (ULLONG_MAX << 13)) == 0) + return 2; + else if ((n & (ULLONG_MAX << 20)) == 0) + return 3; + else if ((n & (ULLONG_MAX << 27)) == 0) + return 4; + unsigned i; + unsigned long long mask = ULLONG_MAX << 31; + for (i = 0; (n & mask) != 0; i++) + mask <<= 8; + return 5 + i; +} + +/*! First byte of the unsigned integer number in packed format. + * + * This byte can contain some of the most significant bits. To pack the whole + * number just copy all bytes from most significant ones to the least + * significant ones to the bytes after this one up to number of bytes deduced by + * `chainpack_w_uint_bytes`. + * + * @param num: Number to be packed. + * @param bytes: Number of bytes this integers spans (use + * `chainpack_w_uint_bytes). + * @returns first byte of the number as it should be packed in Chainpack. + */ +static inline uint8_t chainpack_w_uint_value1( + unsigned long long num, unsigned bytes) { + if (bytes > 4) + return 0xf0 | ((bytes - 5) & 0x0f); + return (0xe0 << (4 - bytes)) | (num >> (8 * (bytes - 1))); +} + +/*! First byte of the signed integer number in packed format. + * + * This byte can contain some of the most significant bits and sign. To pack the + * whole number you need to copy all other bytes of positive number from the + * most to the least significant bytes. For numbers long up to four bytes that + * is all. Numbers longer than four byte also need to pack sign (as it is not + * part of the first byte) to the most significant bit. + * + * @param num: Number to be packed. + * @param bytes: Number of bytes this integers spans (use + * `chainpack_w_int_bytes). + * @returns first byte of the number as it should be packed in Chainpack. + */ +static inline uint8_t chainpack_w_int_value1(long long num, unsigned bytes) { + bool neg = num < 0; + unsigned long long n = neg ? -num : num; + if (bytes > 4) + return 0xf0 | ((bytes - 5) & 0x0f); + return ((neg ? 0xe8 : 0xe0) << (4 - bytes)) | (n >> (8 * (bytes - 1))); +} #endif diff --git a/include/shv/cp.h b/include/shv/cp.h index 39d93eb..152df8c 100644 --- a/include/shv/cp.h +++ b/include/shv/cp.h @@ -6,6 +6,7 @@ #include #include #include +#include enum cp_format { @@ -35,9 +36,114 @@ enum cp_item_type { const char *cp_item_type_str(enum cp_item_type); -/*! + +/*! Representation of decimal number. + * + * The size of mantisa is chosen to cover the biggest numbers the platform can + * handle automatically (`long long`). The exponent is chosen smaller (`int`) + * because by supporting at least 16 bits is enough range (consider exponent + * 10^32767 and estimation of number of atoms in the observeble universe 10^82). + */ +struct cpdecimal { + long long mantisa; + int exponent; +}; + +/*! Convert decimal number to double precision floating point number. + * + * @param v: Decimal value to be converted + * @returns Approximate floating point representation of the decimal number. + */ +double cpdectod(struct cpdecimal v); + +/*! Convert double precision floating point number to decimal number. + * + * @param v: Floating point number to be converted. + * @returns Approximate decimal representation. + */ +struct cpdecimal cpdtodec(double v); + + +/*! Representation of date and time including the time zone offset. */ +struct cpdatetime { + /*! Milliseconds since epoch */ + int64_t msecs; + /*! Offset for the time zone in minutes */ + int32_t offutc; +}; + +/*! Convert CP date and time representation to the C's time. + * + * @param v: Date and time to be converted. + * @returns structure tm with date and time set. */ -struct cpbuf { +struct tm cpdttotm(struct cpdatetime v); + +/*! Convert C's time to CP date and time representation. + * + * @param v: Date and time to be converted. + * @returns structure cpdatetime with date and time set. + */ +struct cpdatetime cptmtodt(struct tm v); + + +/*! Signaling of first and last block. You should set both to `true` if this + * is the only block to be packed. If you plan to append additional chunks + * you should set on first iteration only `first` and to terminate you need + * to set `last`. + */ + +#define CPBI_F_FIRST (1 << 0) +#define CPBI_F_LAST (1 << 1) +#define CPBI_F_STREAM (1 << 2) +#define CPBI_F_HEX (1 << 3) +#define CPBI_F_SINGLE (CPBI_F_FIRST | CPBI_F_LAST) + +/* + */ +struct cpbufinfo { + // TODO we need to allow documented data skip without providing buffer + /*! Number of valid bytes in `buf` (`rbuf`, `chr` and `rchr`). + * + * The special case is when you invoke packer just to get packed size (that + * is `FILE` is `NULL`). In such case buffer is not accessed and thus it can + * be `NULL` and thus this only says number of bytes to be packed. + */ + size_t len; + /*! Number of bytes not yet unpacked or provided in buffer for packing. + * + * Packing: Use this to inform about the full length. The real length of + * data to be packed is anything that was packed so far, plus `len` and + * `eoff`. + * + * Unpacking: Set and modified by unpacker. Do not modify it when unpacking. + * In case `CPBI_F_STREAM` is not set in `flags` then you can use it to get + * a full size on first unpack (`flags` contains `CPBI_F_FIRST`). + * + * The size is chosen to be at minimum 32 bits and this we should be able to + * for sure handle almost strings with 4G size. We are talking here about a + * single message. Messages with such a huge size should be broker to the + * separate ones and this we do not have to support longer strings. + */ + unsigned long eoff; + /*! Bitwise combination of `VPBI_F_*` flags. */ + uint8_t flags; +}; + +struct cpitem { + enum cp_item_type type; + /*! foo + */ + union cpitem_as { + struct cpbufinfo String; + struct cpbufinfo Blob; + struct cpdatetime Datetime; + struct cpdecimal Decimal; + unsigned long long UInt; + long long Int; + double Double; + bool Bool; + } as; // TODO possibly just use anonymous union /*! Pointer to the buffer with data chunk. * * It is defined both as `const` as well as modifiable. The pack functions @@ -49,59 +155,28 @@ struct cpbuf { * assignment of the strings without changing type (which would be required * due to change of the sign). */ - union { + union cpitem_buf { uint8_t *buf; const uint8_t *rbuf; char *chr; const char *rchr; }; - /*! Number of valid bytes in `buf` or `rbuf`. */ - size_t len; - /*! Size of the `buf` or `rbuf`. This is used only by unpacking functions. */ - size_t siz; - /*! Number of bytes not yet unpacked or `-1`. This can be used to inform - * packer about the full string length (it can use it to select a different - * packing method in some cases). It also serves as counter for unpacking - * functions, so it should not be modified in case of unpacking. + /*! Size of the `buf` (`rbuf`, `chr` and `rchr`). This is used only by + * unpacking functions to know the limit of the buffer. */ - ssize_t eoff; - /*! Signaling of first and last block. You should set both to `true` if this - * is the only block to be packed. If you plan to append additional chunks - * you should set on first iteration only `first` and to terminate you need - * to set `last`. - */ - bool first, last; -}; - -struct cpdatetime { - int64_t msecs; - int32_t offutc; + size_t bufsiz; }; -struct cpdecimal { - int64_t mantisa; - int32_t exponent; -}; -struct cpitem { - enum cp_item_type type; - union { - struct cpbuf String; - struct cpbuf Blob; - struct cpdatetime Datetime; - struct cpdecimal Decimal; - uint64_t UInt; - int64_t Int; - double Double; - bool Bool; - } as; -}; +/*! Unpack next item from Chainpack. + */ +ssize_t chainpack_pack(FILE *, const struct cpitem *) __attribute__((nonnull(2))); +ssize_t _chainpack_pack_uint(FILE *, unsigned long long v); -ssize_t chainpack_pack(FILE *, const struct cpitem *) __attribute__((nonnull(2))); -ssize_t _chainpack_pack_uint(FILE *, uint64_t v); size_t chainpack_unpack(FILE *, struct cpitem *) __attribute__((nonnull)); -size_t _chainpack_unpack_uint(FILE *, uint64_t *v, bool *ok); + +size_t _chainpack_unpack_uint(FILE *, unsigned long long *v, bool *ok); struct cpon_state { @@ -111,14 +186,17 @@ struct cpon_state { enum cp_item_type tp; bool first; bool even; + bool meta; } * ctx; size_t cnt; void (*realloc)(struct cpon_state *state); }; ssize_t cpon_pack(FILE *, struct cpon_state *, const struct cpitem *) + __attribute__((nonnull(2, 3))); + +size_t cpon_unpack(FILE *, struct cpon_state *, struct cpitem *) __attribute__((nonnull)); -size_t cpon_unpack(FILE *, struct cpitem *) __attribute__((nonnull)); #endif diff --git a/include/shv/cp_pack.h b/include/shv/cp_pack.h new file mode 100644 index 0000000..17f2346 --- /dev/null +++ b/include/shv/cp_pack.h @@ -0,0 +1,226 @@ +#ifndef SHV_CP_PACK_H +#define SHV_CP_PACK_H + +#include +#include +#include + +typedef ssize_t (*cp_pack_func_t)(void *ptr, const struct cpitem *item); +typedef cp_pack_func_t *cp_pack_t; + + +struct cp_pack_chainpack { + cp_pack_func_t func; + FILE *f; +}; + +cp_pack_t cp_pack_chainpack_init(struct cp_pack_chainpack *pack, FILE *f) + __attribute__((nonnull)); + +struct cp_pack_cpon { + cp_pack_func_t func; + FILE *f; + struct cpon_state state; +}; + +cp_pack_t cp_pack_cpon_init(struct cp_pack_cpon *pack, FILE *f, + const char *indent) __attribute__((nonnull(1, 2))); + + +#define cp_pack(PACK, ITEM) \ + ({ \ + cp_pack_t __pack = PACK; \ + (*__pack)(__pack, (ITEM)); \ + }) + + +static inline ssize_t cp_pack_null(cp_pack_t pack) { + struct cpitem i; + i.type = CP_ITEM_NULL; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_bool(cp_pack_t pack, bool v) { + struct cpitem i; + i.type = CP_ITEM_BOOL; + i.as.Bool = v; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_int(cp_pack_t pack, int64_t v) { + struct cpitem i; + i.type = CP_ITEM_INT; + i.as.Int = v; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_uint(cp_pack_t pack, uint64_t v) { + struct cpitem i; + i.type = CP_ITEM_UINT; + i.as.UInt = v; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_double(cp_pack_t pack, double v) { + struct cpitem i; + i.type = CP_ITEM_DOUBLE; + i.as.Double = v; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_decimal(cp_pack_t pack, struct cpdecimal v) { + struct cpitem i; + i.type = CP_ITEM_DECIMAL; + i.as.Decimal = v; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_datetime(cp_pack_t pack, struct cpdatetime v) { + struct cpitem i; + i.type = CP_ITEM_DATETIME; + i.as.Datetime = v; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_blob(cp_pack_t pack, const uint8_t *buf, size_t len) { + struct cpitem i; + i.type = CP_ITEM_BLOB; + i.rbuf = buf; + i.as.Blob = (struct cpbufinfo){ + .len = len, + .eoff = 0, + .flags = CPBI_F_SINGLE, + }; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_blob_size( + cp_pack_t pack, struct cpitem *item, size_t siz) { + item->type = CP_ITEM_BLOB; + item->as.Blob = (struct cpbufinfo){ + .len = 0, + .eoff = siz, + .flags = CPBI_F_FIRST, + }; + return cp_pack(pack, item); +} + +static inline ssize_t cp_pack_blob_data( + cp_pack_t pack, struct cpitem *item, const uint8_t *buf, size_t siz) { + assert(item->type == CP_ITEM_BLOB); + assert(item->as.Blob.eoff >= siz); + item->rbuf = buf; + item->as.Blob.len = siz; + item->as.Blob.eoff -= siz; + item->as.Blob.flags &= ~CPBI_F_FIRST; + if (item->as.Blob.eoff == 0) + item->as.Blob.flags |= CPBI_F_LAST; + return cp_pack(pack, item); +} + +static inline ssize_t cp_pack_string(cp_pack_t pack, const char *buf, size_t len) { + struct cpitem i; + i.type = CP_ITEM_STRING; + i.rchr = buf; + i.as.String = (struct cpbufinfo){ + .len = len, + .eoff = 0, + .flags = CPBI_F_SINGLE, + }; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_str(cp_pack_t pack, const char *str) { + return cp_pack_string(pack, str, strlen(str)); +} + +static inline ssize_t cp_pack_string_size( + cp_pack_t pack, struct cpitem *item, size_t siz) { + item->type = CP_ITEM_STRING; + item->as.String = (struct cpbufinfo){ + .len = 0, + .eoff = siz, + .flags = CPBI_F_FIRST, + }; + return cp_pack(pack, item); +} + +static inline ssize_t cp_pack_string_data( + cp_pack_t pack, struct cpitem *item, const char *buf, size_t siz) { + assert(item->type == CP_ITEM_STRING); + assert(item->as.Blob.eoff >= siz); + item->rchr = buf; + item->as.String.len = siz; + item->as.String.eoff -= siz; + item->as.Blob.flags &= ~CPBI_F_FIRST; + if (item->as.String.eoff == 0) + item->as.Blob.flags |= CPBI_F_LAST; + return cp_pack(pack, item); +} + +static inline ssize_t cp_pack_list_begin(cp_pack_t pack) { + struct cpitem i; + i.type = CP_ITEM_LIST; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_map_begin(cp_pack_t pack) { + struct cpitem i; + i.type = CP_ITEM_MAP; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_imap_begin(cp_pack_t pack) { + struct cpitem i; + i.type = CP_ITEM_IMAP; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_meta_begin(cp_pack_t pack) { + struct cpitem i; + i.type = CP_ITEM_META; + return cp_pack(pack, &i); +} + +static inline ssize_t cp_pack_container_end(cp_pack_t pack) { + struct cpitem i; + i.type = CP_ITEM_CONTAINER_END; + return cp_pack(pack, &i); +} + +FILE *cp_pack_fopen(cp_pack_t pack, bool str) __attribute__((nonnull)); + +// clang-format off +#define __cp_pack_value(PACK, VALUE) \ + _Generic((VALUE), \ + bool: cp_pack_bool, \ + short: cp_pack_int, \ + int: cp_pack_int, \ + long: cp_pack_int, \ + long long: cp_pack_int, \ + unsigned short: cp_pack_uint, \ + unsigned: cp_pack_uint, \ + unsigned long: cp_pack_uint, \ + unsigned long long: cp_pack_uint, \ + float: cp_pack_double, \ + double: cp_pack_double, \ + long double: cp_pack_double, \ + struct cpdecimal: cp_pack_decimal, \ + struct cpdatetime: cp_pack_datetime, \ + char*: cp_pack_str, \ + const char*: cp_pack_str \ + )((PACK), (VALUE)) +#define __cp_pack_value_l(PACK, VALUE, SIZ) \ + _Generic((VALUE), \ + char*: cp_pack_string, \ + const char*: cp_pack_string, \ + uint8_t*: cp_pack_blob, \ + const uint8_t*: cp_pack_blob \ + )((PACK), (VALUE), (SIZ)) +// clang-format on +#define __cp_pack_value_select(_1, _2, X, ...) X +#define cp_pack_value(PACK, ...) \ + __cp_pack_value_select(__VA_ARGS__, __cp_pack_value_l, __cp_pack_value)( \ + PACK, __VA_ARGS__) + +#endif diff --git a/include/shv/cp_unpack.h b/include/shv/cp_unpack.h new file mode 100644 index 0000000..a362ac7 --- /dev/null +++ b/include/shv/cp_unpack.h @@ -0,0 +1,161 @@ +#ifndef SHV_CP_UNPACK_H +#define SHV_CP_UNPACK_H + +#include + +typedef size_t (*cp_unpack_func_t)(void *ptr, struct cpitem *item); +typedef cp_unpack_func_t *cp_unpack_t; + + +struct cp_unpack_chainpack { + cp_unpack_func_t func; + FILE *f; +}; + +cp_unpack_t cp_unpack_chainpack_init(struct cp_unpack_chainpack *pack, FILE *f) + __attribute__((nonnull)); + +struct cp_unpack_cpon { + cp_unpack_func_t func; + FILE *f; + struct cpon_state state; +}; + +cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *pack, FILE *f) + __attribute__((nonnull)); + + +#define cp_unpack(UNPACK, ITEM) \ + ({ \ + cp_unpack_t __unpack = UNPACK; \ + (*__unpack)(__unpack, (ITEM)); \ + }) + +/*! Instead of getting next item this skips it. + * + * The difference between getting and not using the item is that this skips the + * whole item. That means the whole list or map as well as whole string or blob. + * This includes multiple calls to the `cp_unpack`. + * + * Note that for strings and blobs this only skips all unread bytes from them, + * it won't continue to the next item unless all bytes were already received. + * This allows you to use this function to just skip any bytes of data you do + * not care about and go to the next item. + * + * @param unpack: Unpack handle. + * @param item: Item used for the last `cp_unpack` call. This is required + * because there might be unfinished string or blob we need to know about. + * @returns number of read bytes. + */ +size_t cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) + __attribute__((nonnull)); + +/*! Read until the currently opened container is finished. + * + * This is same function such as `cp_unpack_skip` with difference that it stops + * not after end of the next item, but on container end for which there was no + * container open. The effect is that opened container is skipped. + * + * @param unpack: Unpack handle. + * @param item: Item used for the last `cp_unpack` call. This is required + * because there might be unfinished string or blob we need to know about. + * @returns number of read bytes. + */ +size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item) + __attribute__((nonnull)); + +/*! Copy string to malloc allocated buffer and return it. + * + * This unpacks the whole string and returns it. + * + * The error can be detected by checking: `item->type == CP_ITEM_STRING`. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @returns malloc allocated null terminated string. + */ +char *cp_unpack_strdup(cp_unpack_t unpack, struct cpitem *item) + __attribute__((nonnull)); + +/*! Copy string to the provided buffer. + * + * Be aware that compared to the `strncpy` this has a different return! + * + * Warning: Null byte is appended only if it fits. You can check for that by + * comparing returned value and size of the destination. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @param dest: Destination buffer where unpacked characters are placed to. + * @param n: Maximum number of bytes to be used in `dest`. + * @returns number of bytes written to `dest` or `-1` in case of unpack error. + */ +__attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_strncpy( + cp_unpack_t unpack, struct cpitem *item, char *dest, size_t n) { + item->chr = dest; + item->bufsiz = n; + cp_unpack(unpack, item); + if (item->type != CP_ITEM_STRING) + return -1; + if (item->as.String.len < n) + dest[item->as.String.len] = '\0'; + return item->as.String.len; +} + + +/*! Copy blob to malloc allocated buffer and provide it. + * + * This unpacks the whole blob and returns it. + * + * The error can be detected by checking: `item->type == CP_ITEM_BLOB`. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @param data: Pointer where pointer to the data would be placed. + * @param siz: Pointer where number of valid data bytes were unpacked. + */ +void cp_unpack_memdup(cp_unpack_t unpack, struct cpitem *item, uint8_t **data, + size_t *siz) __attribute__((nonnull)); + +/*! Copy blob to the provided buffer. + * + * Be aware that compared to the `memcpy` this has a different return! + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @param dest: Destination buffer where unpacked bytes are placed to. + * @param n: Maximum number of bytes to be used in `dest`. + * @returns number of bytes written to `dest` or `-1` in case of unpack error. + */ +__attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_memcpy( + cp_unpack_t unpack, struct cpitem *item, uint8_t *dest, size_t siz) { + item->buf = dest; + item->bufsiz = siz; + cp_unpack(unpack, item); + if (item->type != CP_ITEM_BLOB) + return -1; + return item->as.Blob.len; +} + +/*! Open string or blob for reading using stream. + * + * This allows you to use `fscanf` and other functions to deal with strings and + * blobs. + * + * To detect if you are dealing with string or blob you need to use `item->type` + * after you call this function. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @returns File object or `NULL` in case of unpack error. + */ +FILE *cp_unpack_fopen(cp_unpack_t unpack, struct cpitem *item) + __attribute__((nonnull)); + + +#endif diff --git a/include/shv/cpchainpack.h b/include/shv/cpchainpack.h deleted file mode 100644 index ce26aa0..0000000 --- a/include/shv/cpchainpack.h +++ /dev/null @@ -1,207 +0,0 @@ -#ifndef SHV_CHAINPACK_H -#define SHV_CHAINPACK_H - -#include -#include -#include - - -static inline ssize_t chainpack_pack_null(FILE *f) { - struct cpitem i; - i.type = CP_ITEM_NULL; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_bool(FILE *f, bool v) { - struct cpitem i; - i.type = CP_ITEM_BOOL; - i.as.Bool = v; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_int(FILE *f, int64_t v) { - struct cpitem i; - i.type = CP_ITEM_INT; - i.as.Int = v; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_uint(FILE *f, uint64_t v) { - struct cpitem i; - i.type = CP_ITEM_UINT; - i.as.UInt = v; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_double(FILE *f, double v) { - struct cpitem i; - i.type = CP_ITEM_DOUBLE; - i.as.Double = v; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_decimal(FILE *f, struct cpdecimal v) { - struct cpitem i; - i.type = CP_ITEM_DECIMAL; - i.as.Decimal = v; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_blob(FILE *f, const uint8_t *buf, size_t len) { - struct cpitem i; - i.type = CP_ITEM_BLOB; - i.as.Blob = (struct cpbuf){ - .rbuf = buf, - .len = len, - .eoff = 0, - .first = true, - .last = true, - }; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_blob_size( - FILE *f, struct cpitem *item, size_t siz) { - item->type = CP_ITEM_BLOB; - item->as.Blob = (struct cpbuf){ - .buf = NULL, - .len = 0, - .eoff = siz, - .first = true, - .last = false, - }; - return chainpack_pack(f, item); -} - -static inline ssize_t chainpack_pack_blob_data( - FILE *f, struct cpitem *item, const uint8_t *buf, size_t siz) { - assert(item->type == CP_ITEM_BLOB); - item->as.Blob.rbuf = buf; - item->as.Blob.len = siz; - item->as.Blob.eoff -= siz; - item->as.Blob.first = false; - item->as.Blob.last = item->as.Blob.eoff != 0; - assert(item->as.Blob.eoff >= 0); - return chainpack_pack(f, item); -} - -static inline ssize_t chainpack_pack_string(FILE *f, const char *buf, size_t len) { - struct cpitem i; - i.type = CP_ITEM_STRING; - i.as.String = (struct cpbuf){ - .rchr = buf, - .len = len, - .eoff = 0, - .first = true, - .last = true, - }; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_string_size( - FILE *f, struct cpitem *item, size_t siz) { - item->type = CP_ITEM_STRING; - item->as.String = (struct cpbuf){ - .rbuf = NULL, - .len = 0, - .eoff = siz, - }; - return chainpack_pack(f, item); -} - -static inline ssize_t chainpack_pack_string_data( - FILE *f, struct cpitem *item, const char *buf, size_t siz) { - assert(item->type == CP_ITEM_STRING); - item->as.String.rchr = buf; - item->as.String.len = siz; - item->as.String.eoff -= siz; - item->as.Blob.first = false; - item->as.Blob.last = item->as.String.eoff != 0; - assert(item->as.String.eoff >= 0); - return chainpack_pack(f, item); -} - -static inline ssize_t chainpack_pack_cstring(FILE *f, const char *str) { - struct cpitem i; - i.type = CP_ITEM_STRING; - i.as.String = (struct cpbuf){ - .rchr = str, - .len = strlen(str), - .eoff = -1, - .first = true, - .last = true, - }; - return chainpack_pack(f, &i); -} - -/*! Pack the beginning of the C style string. - * - * Now you can send any text until the NULL byte. You can use `fprintf` and - * other functions like you are used to. To end the string you need to call - * `chainpack_pack_cstring_end`. - */ -static inline ssize_t chainpack_pack_cstring_begin(FILE *f) { - struct cpitem i; - i.type = CP_ITEM_STRING; - i.as.String = (struct cpbuf){ - .rchr = NULL, - .len = 0, - .eoff = -1, - .first = true, - .last = false, - }; - return chainpack_pack(f, &i); -} - -/*! End the C string style packing. - * - * This effectively writes NULL byte but it is preferred to call this functions - * instead of just writing it. - */ -static inline ssize_t chainpack_pack_cstring_end(FILE *f) { - struct cpitem i; - i.type = CP_ITEM_STRING; - i.as.String = (struct cpbuf){ - .rchr = NULL, - .len = 0, - .eoff = -1, - .first = false, - .last = true, - }; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_list_begin(FILE *f) { - struct cpitem i; - i.type = CP_ITEM_LIST; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_map_begin(FILE *f) { - struct cpitem i; - i.type = CP_ITEM_MAP; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_imap_begin(FILE *f) { - struct cpitem i; - i.type = CP_ITEM_IMAP; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_meta_begin(FILE *f) { - struct cpitem i; - i.type = CP_ITEM_META; - return chainpack_pack(f, &i); -} - -static inline ssize_t chainpack_pack_container_end(FILE *f) { - struct cpitem i; - i.type = CP_ITEM_CONTAINER_END; - return chainpack_pack(f, &i); -} - - -// TODO chainpack_pack_value with type detection - -#endif diff --git a/include/shv/cpcp.h b/include/shv/cpcp.h deleted file mode 100644 index 3949fa3..0000000 --- a/include/shv/cpcp.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SHV_CPCP_H -#define SHV_CPCP_H - -#include -#include - - -enum cpcp_format { - CPCP_ChainPack = 1, - CPCP_Cpon, -}; - -enum cpcp_error { - CPCP_RC_OK = 0, - CPCP_RC_MALLOC_ERROR, - CPCP_RC_BUFFER_OVERFLOW, - CPCP_RC_BUFFER_UNDERFLOW, - CPCP_RC_MALFORMED_INPUT, - CPCP_RC_LOGICAL_ERROR, - CPCP_RC_CONTAINER_STACK_OVERFLOW, - CPCP_RC_CONTAINER_STACK_UNDERFLOW, -}; - -const char *cpcp_error_string(enum cpcp_error errno); - - -#endif diff --git a/include/shv/cpcp_convert.h b/include/shv/cpcp_convert.h deleted file mode 100644 index 3c86f4a..0000000 --- a/include/shv/cpcp_convert.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef SHV_CPCP_CONVERT_H -#define SHV_CPCP_CONVERT_H - -#include - -void ccpcp_convert(cpcp_unpack_context *in_ctx, cpcp_format in_format, - cpcp_pack_context *out_ctx, cpcp_format out_format); - -#endif diff --git a/include/shv/cpcp_pack.h b/include/shv/cpcp_pack.h deleted file mode 100644 index de1d961..0000000 --- a/include/shv/cpcp_pack.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef SHV_CPCP_PACK_H -#define SHV_CPCP_PACK_H - -#include -#include - - -typedef struct { - const char *indent; - uint8_t json_output:1; -} cpcp_cpon_pack_options; - -struct cpcp_pack_context; - -typedef void (*cpcp_pack_overflow_handler)( - struct cpcp_pack_context *, size_t size_hint); - -typedef struct cpcp_pack_context { - char *start; - char *current; - char *end; - int nest_count; - int err_no; /* handlers can save error here */ - cpcp_pack_overflow_handler handle_pack_overflow; - cpcp_cpon_pack_options cpon_options; - size_t bytes_written; -} cpcp_pack_context; - -void cpcp_pack_context_init(cpcp_pack_context *pack_context, void *data, - size_t length, cpcp_pack_overflow_handler poh); -void cpcp_pack_context_dry_run_init(cpcp_pack_context *pack_context); - -size_t cpcp_pack_copy_byte(cpcp_pack_context *pack_context, uint8_t b); -size_t cpcp_pack_copy_bytes( - cpcp_pack_context *pack_context, const void *str, size_t len); - - -#endif diff --git a/include/shv/cpcp_unpack.h b/include/shv/cpcp_unpack.h deleted file mode 100644 index bddbd04..0000000 --- a/include/shv/cpcp_unpack.h +++ /dev/null @@ -1,157 +0,0 @@ -#ifndef SHV_CPCP_UNPACK_H -#define SHV_CPCP_UNPACK_H - -#include -#include -#include - - -struct cpcp_unpack_context; - -typedef enum { - CPCP_ITEM_INVALID = 0, - CPCP_ITEM_NULL, - CPCP_ITEM_BOOLEAN, - CPCP_ITEM_INT, - CPCP_ITEM_UINT, - CPCP_ITEM_DOUBLE, - CPCP_ITEM_DECIMAL, - CPCP_ITEM_BLOB, - CPCP_ITEM_STRING, - CPCP_ITEM_DATE_TIME, - - CPCP_ITEM_LIST, - CPCP_ITEM_MAP, - CPCP_ITEM_IMAP, - CPCP_ITEM_META, - CPCP_ITEM_CONTAINER_END, -} cpcp_item_types; - -const char *cpcp_item_type_to_string(cpcp_item_types t); - -#ifndef CPCP_MAX_STRING_KEY_LEN -#define CPCP_STRING_CHUNK_BUFF_LEN 256 -#endif - -typedef struct { - long string_size; - long size_to_load; - char *chunk_start; - size_t chunk_size; - size_t chunk_buff_len; - unsigned chunk_cnt; - uint8_t last_chunk:1, blob_hex:1; -} cpcp_string; - -void cpcp_string_init( - cpcp_string *str_it, struct cpcp_unpack_context *unpack_context); - -typedef struct { - int64_t msecs_since_epoch; - int minutes_from_utc; -} cpcp_date_time; - -typedef struct { - int64_t mantisa; - int exponent; -} cpcp_decimal; - -double cpcp_exponentional_to_double( - const int64_t mantisa, const int exponent, const int base); -double cpcp_decimal_to_double(const int64_t mantisa, const int exponent); -size_t cpcp_decimal_to_string( - char *buff, size_t buff_len, int64_t mantisa, int exponent); - -typedef struct { - union { - cpcp_string String; - cpcp_date_time DateTime; - cpcp_decimal Decimal; - uint64_t UInt; - int64_t Int; - double Double; - bool Bool; - } as; - cpcp_item_types type; -} cpcp_item; - -typedef struct { - cpcp_item_types container_type; - size_t item_count; - cpcp_item_types current_item_type; -} cpcp_container_state; - -void cpcp_container_state_init( - cpcp_container_state *self, cpcp_item_types cont_type); - -struct cpcp_container_stack; - -typedef int (*cpcp_container_stack_overflow_handler)(struct cpcp_container_stack *); - -typedef struct cpcp_container_stack { - cpcp_container_state *container_states; - size_t length; - size_t capacity; - cpcp_container_stack_overflow_handler overflow_handler; -} cpcp_container_stack; - -void cpcp_container_stack_init(cpcp_container_stack *self, - cpcp_container_state *states, size_t capacity, - cpcp_container_stack_overflow_handler hnd); - -typedef size_t (*cpcp_unpack_underflow_handler)(struct cpcp_unpack_context *); - -typedef struct cpcp_unpack_context { - cpcp_item item; - const char *start; - const char *current; - const char *end; /* logical end of buffer */ - int err_no; /* handlers can save error here */ - int parser_line_no; /* helps to find parser error line */ - const char *err_msg; - cpcp_container_stack *container_stack; - cpcp_unpack_underflow_handler handle_unpack_underflow; - char default_string_chunk_buff[CPCP_STRING_CHUNK_BUFF_LEN]; - char *string_chunk_buff; - size_t string_chunk_buff_len; -} cpcp_unpack_context; - -void cpcp_unpack_context_init(cpcp_unpack_context *self, const void *data, - size_t length, cpcp_unpack_underflow_handler huu, cpcp_container_stack *stack); - -cpcp_container_state *cpcp_unpack_context_push_container_state( - cpcp_unpack_context *self, cpcp_item_types container_type); -cpcp_container_state *cpcp_unpack_context_top_container_state( - cpcp_unpack_context *self); -cpcp_container_state *cpcp_unpack_context_parent_container_state( - cpcp_unpack_context *self); -cpcp_container_state *cpcp_unpack_context_closed_container_state( - cpcp_unpack_context *self); -void cpcp_unpack_context_pop_container_state(cpcp_unpack_context *self); - -const char *cpcp_unpack_take_byte(cpcp_unpack_context *unpack_context); -const char *cpcp_unpack_peek_byte(cpcp_unpack_context *unpack_context); -#define UNPACK_ERROR(error_code, error_msg) \ - { \ - unpack_context->item.type = CPCP_ITEM_INVALID; \ - unpack_context->err_no = error_code; \ - unpack_context->err_msg = error_msg; \ - return; \ - } - -#define UNPACK_TAKE_BYTE(p) \ - { \ - p = cpcp_unpack_take_byte(unpack_context); \ - if (!p) \ - return; \ - } - -#define UNPACK_PEEK_BYTE(p) \ - { \ - p = cpcp_unpack_peek_byte(unpack_context); \ - if (!p) \ - return; \ - } - - -#endif diff --git a/include/shv/cpon.h b/include/shv/cpon.h deleted file mode 100644 index 6a44de6..0000000 --- a/include/shv/cpon.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef SHV_CPON_H -#define SHV_CPON_H - -#include - -#include - -int64_t cpon_timegm(struct tm *tm); -void cpon_gmtime(int64_t epoch_sec, struct tm *tm); - -/******************************* P A C K **********************************/ - -void cpon_pack_null(cpcp_pack_context *pack_context) __attribute__((nonnull)); - -void cpon_pack_boolean(cpcp_pack_context *pack_context, bool b) - __attribute__((nonnull)); - -void cpon_pack_int(cpcp_pack_context *pack_context, int64_t i) - __attribute__((nonnull)); - -void cpon_pack_uint(cpcp_pack_context *pack_context, uint64_t i) - __attribute__((nonnull)); - -void cpon_pack_double(cpcp_pack_context *pack_context, double d) - __attribute__((nonnull)); - -void cpon_pack_decimal(cpcp_pack_context *pack_context, const cpcp_decimal *v) - __attribute__((nonnull)); - -void cpon_pack_date_time(cpcp_pack_context *pack_context, const cpcp_date_time *v) - __attribute__((nonnull)); - -typedef enum { CCPON_Auto = 0, CCPON_Always, CCPON_Never } cpon_msec_policy; -void cpon_pack_date_time_str(cpcp_pack_context *pack_context, int64_t epoch_msecs, - int min_from_utc, cpon_msec_policy msec_policy, bool with_tz); - -void cpon_pack_blob( - cpcp_pack_context *pack_context, const uint8_t *s, size_t buff_len); -void cpon_pack_blob_start( - cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len); -void cpon_pack_blob_cont( - cpcp_pack_context *pack_context, const uint8_t *buff, unsigned buff_len); -void cpon_pack_blob_finish(cpcp_pack_context *pack_context); - -void cpon_pack_string(cpcp_pack_context *pack_context, const char *s, size_t l); -void cpon_pack_string_terminated(cpcp_pack_context *pack_context, const char *s); -void cpon_pack_string_start( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len); -void cpon_pack_string_cont( - cpcp_pack_context *pack_context, const char *buff, unsigned buff_len); -void cpon_pack_string_finish(cpcp_pack_context *pack_context); - -void cpon_pack_list_begin(cpcp_pack_context *pack_context); -void cpon_pack_list_end(cpcp_pack_context *pack_context, bool is_oneliner); - -void cpon_pack_map_begin(cpcp_pack_context *pack_context); -void cpon_pack_map_end(cpcp_pack_context *pack_context, bool is_oneliner); - -void cpon_pack_imap_begin(cpcp_pack_context *pack_context); -void cpon_pack_imap_end(cpcp_pack_context *pack_context, bool is_oneliner); - -void cpon_pack_meta_begin(cpcp_pack_context *pack_context); -void cpon_pack_meta_end(cpcp_pack_context *pack_context, bool is_oneliner); - -void cpon_pack_copy_str(cpcp_pack_context *pack_context, const char *str); - -void cpon_pack_field_delim( - cpcp_pack_context *pack_context, bool is_first_field, bool is_oneliner); -void cpon_pack_key_val_delim(cpcp_pack_context *pack_context); - -/***************************** U N P A C K ********************************/ -const char *cpon_unpack_skip_insignificant(cpcp_unpack_context *unpack_context); -void cpon_unpack_next(cpcp_unpack_context *unpack_context); -void cpon_unpack_date_time(cpcp_unpack_context *unpack_context, struct tm *tm, - int *msec, int *utc_offset); - - -#endif diff --git a/include/shv/rpcbroker.h b/include/shv/rpcbroker.h new file mode 100644 index 0000000..1c72102 --- /dev/null +++ b/include/shv/rpcbroker.h @@ -0,0 +1,9 @@ +#ifndef SHV_RPCBROKER_H +#define SHV_RPCBROKER_H + +#include +#include + + + +#endif diff --git a/include/shv/rpcclient.h b/include/shv/rpcclient.h index a118cb5..98e7879 100644 --- a/include/shv/rpcclient.h +++ b/include/shv/rpcclient.h @@ -1,68 +1,40 @@ #ifndef SHV_RPCCLIENT_H #define SHV_RPCCLIENT_H - #include -#include +#include #include -struct rpcclient { - cpcp_unpack_context *(*nextmsg)(struct rpcclient *); - cpcp_pack_context *(*packmsg)(struct rpcclient *, cpcp_pack_context *prev); - void (*disconnect)(struct rpcclient *); -}; typedef struct rpcclient *rpcclient_t; -struct rpcclient_msg { - cpcp_unpack_context *ctx; - enum cpcp_format format; -}; - -struct rpcclient_msg rpcclient_next_message(rpcclient_t) __attribute__((nonnull)); - -/*! Provide access to the next unpacked item. - * - * The item is kept valid until next call to this function or client disconnect. - * - * @param msg: message handle returned by `rpcclient_next_message`. - * @returns: pointer to the item. It always returns some item but it might not - * be a pointer to the `msg.ctx->item`. That happens when this function reports - * errors by returning invalid item. - */ -const cpcp_item *rpcclient_next_item(struct rpcclient_msg) - __attribute__((returns_nonnull)); - -/*! Instead of getting next item this skips it. The difference between getting - * and skipping the item is that this skips the whole item. That means the whole - * list or map as well as whole string or blob. This includes multiple calls to - * the `rpcclient_next_item`. - * - * @param msg: message handle returned by `rpcclient_next_message`. - * @returns: `false` in case an invalid item is encountered, otherwise `true`. - */ -bool rpcclient_skip_item(struct rpcclient_msg); - -#define rpcclient_for_message(client) \ - for (cpcp_pack_context *pack = client->packmsg(client, NULL); pack; \ - pack = client->packmsg(client, pack)) - -void rpcclient_disconnect(rpcclient_t); - - rpcclient_t rpcclient_connect(const struct rpcurl *url); rpcclient_t rpcclient_stream_new(int readfd, int writefd); rpcclient_t rpcclient_stream_tcp_connect(const char *location, int port); rpcclient_t rpcclient_stream_unix_connect(const char *location); -rpcclient_t rpcclient_datagram_neq(void); +rpcclient_t rpcclient_datagram_new(void); rpcclient_t rpcclient_datagram_udp_connect(const char *location, int port); rpcclient_t rpcclient_serial_new(int fd); rpcclient_t rpcclient_serial_connect(const char *path); +#define rpcclient_disconnect(CLIENT) ((CLIENT)->disconnect(CLIENT)) + bool rpcclient_login(rpcclient_t, const char *username, const char *password, enum rpc_login_type type) __attribute__((nonnull(1))); + +#define rpcclient_nextmsg(CLIENT) (&(CLIENT)->nextmsg(CLIENT)) + +#define rpcclient_unpack(CLIENT) (&(CLIENT)->unpack) + +#define rpcclient_pack(CLIENT) (&(CLIENT)->pack) + +#define rpcclient_send(CLIENT) ((CLIENT)->send(CLIENT_PTR)) + +#define rpcclient_logger(CLIENT) ((CLIENT)->logger) + + #endif diff --git a/include/shv/rpcclient_impl.h b/include/shv/rpcclient_impl.h new file mode 100644 index 0000000..9fa6b3f --- /dev/null +++ b/include/shv/rpcclient_impl.h @@ -0,0 +1,38 @@ +#ifndef SHV_RPCCLIENT_IMPL_H +#define SHV_RPCCLIENT_IMPL_H +#include +#include +#include +#include +#include + +// TODO possibly hide this and just provide function to allocate it +struct rpcclient_logger { + FILE *f; + struct cpon_state cpon_state; + sem_t semaphore; +}; + +struct rpcclient { + /* Receave */ + cp_unpack_func_t unpack; + bool (*nextmsg)(struct rpcclient *); + /* Send */ + cp_pack_func_t pack; + bool (*sendmsg)(struct rpcclient *); + /* Disconnect */ + void (*disconnect)(struct rpcclient *); + /* Logging */ + struct rpcclient_logger *logger; +}; + +void rpcclient_init(struct rpcclient *client) __attribute__((nonnull)); + +void rpcclient_log_lock(struct rpcclient_logger *, bool in); + +void rpcclient_log_item(struct rpcclient_logger *, const struct cpitem *) + __attribute__((nonnull(2))); + +void rpcclient_log_unlock(struct rpcclient_logger *); + +#endif diff --git a/include/shv/rpchandler.h b/include/shv/rpchandler.h new file mode 100644 index 0000000..90ce771 --- /dev/null +++ b/include/shv/rpchandler.h @@ -0,0 +1,35 @@ +#ifndef SHV_RPCHANDLER_H +#define SHV_RPCHANDLER_H + +#include "shv/rpcmsg.h" +#include +#include + + +#define RPCH_F_KEEP_META + +struct rpchandler { + int flags; + void *(*init)(rpcclient_t); + void (*deinit)(void *cookie, rpcclient_t); + bool (*handle_request)(void *cookie, rpcclient_t, const char *path, + const char *method, const struct rpcmsg_request_info *info); + bool (*handle_response)(void *cookie, rpcclient_t, int64_t rid, bool error); + bool (*handle_signal)( + void *cookie, rpcclient_t, const char *path, const char *method); +}; + + +bool rpchandler_loop(rpcclient_t, struct rpchandler *); + +typedef struct rpchandler_state *rpchandler_state_t; + +rpchandler_state_t rpchandler_new(rpcclient_t, struct rpchandler *); + +bool rpchandler_next(rpchandler_state_t); + +void rpchandler_destroy(rpchandler_state_t); + + + +#endif diff --git a/include/shv/rpcmsg.h b/include/shv/rpcmsg.h index 023dd6e..b1a4281 100644 --- a/include/shv/rpcmsg.h +++ b/include/shv/rpcmsg.h @@ -2,8 +2,32 @@ #define SHV_RPCMSG_H #include +#include #include -#include +#include +#include +#include + +enum rpcmsg_tags { + RPCMSG_TAG_UNKNOWN = 0, + RPCMSG_TAG_META_TYPE_ID, + RPCMSG_TAG_META_TYPE_NAMESPACE_ID, + RPCMSG_TAG_REQUEST_ID = 8, + RPCMSG_TAG_SHV_PATH, + RPCMSG_TAG_METHOD, + RPCMSG_TAG_CALLER_IDS, + RPCMSG_TAG_REV_CALLER_IDS, + RPCMSG_TAG_ACCESS_GRANT, + RPCMSG_TAG_USER_ID, +}; + +enum rpcmsg_keys { + RPCMSG_KEY_PARAMS = 1, + RPCMSG_KEY_RESULT, + RPCMSG_KEY_ERROR, +}; + +enum rpcmsg_error_key { RPCMSG_E_KEY_CODE = 1, RPCMSG_E_KEY_MESSAGE }; /*! Pack request message meta and open imap. The followup packed data are * parameters provided to the method call request. The message needs to be @@ -16,8 +40,8 @@ * @param rid: request identifier. Thanks to this number you can associate * response with requests. */ -void rpcmsg_pack_request(cpcp_pack_context *pack, const char *path, - const char *method, int64_t rid) __attribute__((nonnull)); +void rpcmsg_pack_request(cp_pack_t, const char *path, const char *method, + int64_t rid) __attribute__((nonnull)); /*! Pack signal message meta and open imap. The followup packed data are * signaled values. The message needs to be terminated with container end @@ -27,8 +51,8 @@ void rpcmsg_pack_request(cpcp_pack_context *pack, const char *path, * @param path: SHV path to the node method is associated with. * @param method: name of the method signal is raised for. */ -void rpcmsg_pack_signal(cpcp_pack_context *pack, const char *path, - const char *method) __attribute__((nonnull)); +void rpcmsg_pack_signal(cp_pack_t, const char *path, const char *method) + __attribute__((nonnull)); /*! Pack value change signal message meta and open imap. The followup packed * data are signaled values. The message needs to be terminated with container @@ -40,11 +64,196 @@ void rpcmsg_pack_signal(cpcp_pack_context *pack, const char *path, * @param pack: pack context the meta should be written to. * @param path: SHV path the value change signal is associated with. */ -static inline void rpcmsg_pack_chng(cpcp_pack_context *pack, const char *path) { +static inline void rpcmsg_pack_chng(cp_pack_t pack, const char *path) { rpcmsg_pack_signal(pack, path, "chng"); } +/*! Access levels supported by SHVC. */ +enum rpcmsg_access { + RPCMSG_ACC_INVALID, + RPCMSG_ACC_BROWSE, + RPCMSG_ACC_READ, + RPCMSG_ACC_WRITE, + RPCMSG_ACC_COMMAND, + RPCMSG_ACC_CONFIG, + RPCMSG_ACC_SERVICE, + RPCMSG_ACC_SUPER_SERVICE, + RPCMSG_ACC_DEVEL, + RPCMSG_ACC_ADMIN, +}; + +/*! Convert access level to its string representation. + * + * @param access: Access level to be converted. + * @retunrs Pointer to string constant representing the access level. + */ +const char *rpcmsg_access_str(enum rpcmsg_access access); + +/*! Extract access level from string. + * + * @param str: String with access specifier. + * @returns Access level. + */ +enum rpcmsg_access rpcmsg_access_extract(const char *str); + +/*! Unpack access string and extract access level from it. + * + * You can detect unpack error by checking `item->type != CP_ITEM_INVALID`. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @returns Access level. + */ +enum rpcmsg_access rpcmsg_access_unpack(cp_unpack_t unpack, struct cpitem *item); + + + +/*! Identification of the message. */ +enum rpcmsg_type { + /*! Message is not know or has invalid meta */ + RPCMSG_T_INVALID, + /*! Message is request (`request_id` is valid alongside with `method`). */ + RPCMSG_T_REQUEST, + /*! Message is response (`request_id` is valid). */ + RPCMSG_T_RESPONSE, + /*! Message is signal (`method` is valid). */ + RPCMSG_T_SIGNAL, +}; + +/*! Pointer to data (`ptr`) with `siz` number of valid bytes. */ +struct rpcmsg_ptr { + void *ptr; + size_t siz; +}; + +/*! Parsed content of RPC Message Meta. + * + * Parsing is performed with `rpcmsg_meta_unpack`. + */ +struct rpcmsg_meta { + /*! Type of the message. */ + enum rpcmsg_type type; + /*! Request ID if message is request or response. */ + int64_t request_id; + /*! Path to the SHV node. Can be `NULL`. */ + char *path; + /*! Method name if message is request or signal. */ + char *method; + /*! Granted access level for request messages. */ + enum rpcmsg_access access_grant; + /*! Client IDs in Chainpack if message is request. */ + struct rpcmsg_ptr cids; +}; + +/*! Limits imposed on `struct rpcmsg_meta` fields. + * + * This is used by `rpcmsg_meta_unpack` in two modes: It provides size of the + * buffers pointed in `struct rpcmsg_meta`; It provides limit on dynamically + * allocated buffers. + * + * The dynamic allocation can be constrained or unconstrained. The default + * behavior if you pass no limits to the `rpcmsg_meta_unpack` is to use as much + * space as required. Not only that this results in possible crash if message + * with too big fields is received, it is primarily not necessary. The devices + * commonly know how long paths and methods they support and anything longer is + * invalid anyway and thus in most cases it servers no purpose to store actually + * the whole value. + * + * To allow constrained allocation for some of the fields but not for other you + * can use `-1` as limit which informs `rpcmsg_meta_unpack` that there is no + * limit for that field. + */ +struct rpcmsg_meta_limits { + /*! Limit on the `path` size. + * + * You want to set this to one or two bytes longer than your longest + * supported path. It is to detect that this is too long path (because that + * won't be signaled to you in any other way). + */ + size_t path; + /*! Limit on `method` size. + * + * The same suggestion applies here as for `path`. + */ + size_t method; + /*! Limit on `cids.ptr` buffer. + * + * It is highly suggested to set this to `-1` or some high number if you use + * obstack. That is because messages without client IDs can't be responded + * due to the not arriving to the source without this reverse path. + * + * In case you do not use obstack then set buffer size of `cids.ptr` as + * usual. + */ + size_t cids; +}; + +/*! Unpack meta of the RPC message. + * + * The unpacking can be performed in two different modes. You can either provide + * obstack and in such case data would be pushed to it (highly suggested). But + * if you know all limits upfront you can also just pass `NULL` to `obstack` and + * in such case we expect that provided `meta` is already seeded with buffers of + * sizes specified in `limits`. + * + * WARNING: The limits when `obstack` is provided are not enforced right now. + * This is missing implementation not intended state! + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * @param meta: Pointer to the structure where unpacked data will be placed. In + * case of integers the value is directly stored. For strings and byte arrays + * the data are stored on obstack, if provided, and meta contains pointers to + * this data. If you do not provide `obstack` then pointers in the meta need to + * point to valid buffers. + * @param limits: Optional pointer to the limits imposed on the meta attributes. + * This is used for two purposes: It specifies size of the buffers if you do not + * provide `obstack; It limits maximal length for data copied to the obstack. + * Please see `struct rpcmsg_meta_limits` documentation for explanation. + * @param obstack: pointer to the obstack used to allocate space for received + * strings and byte arrays. You can pass `NULL` but in such case you need to + * provide your own buffers in `meta`. + * @returns `false` in case unpack reports error, if meta contains invalid value + * for supported key, or when there is not enough space to store caller IDs. In + * other cases `true` is returned. In short this return primarily signals if it + * is possible to generate response to this message for request messages. + */ +bool rpcmsg_meta_unpack(cp_unpack_t unpack, struct cpitem *item, + struct rpcmsg_meta *meta, struct rpcmsg_meta_limits *limits, + struct obstack *obstack); + +void rpcmsg_pack_response(cp_pack_t, struct rpcmsg_meta *meta) + __attribute__((nonnull)); + +enum rpcmsg_error { + RPCMSG_E_NO_ERROR, + RPCMSG_E_INVALID_REQUEST, + RPCMSG_E_METHOD_NOT_FOUND, + RPCMSG_E_INVALID_PARAMS, + RPCMSG_E_INTERNAL_ERR, + RPCMSG_E_PARSE_ERR, + RPCMSG_E_METHOD_CALL_TIMEOUT, + RPCMSG_E_METHOD_CALL_CANCELLED, + RPCMSG_E_METHOD_CALL_EXCEPTION, + RPCMSG_E_UNKNOWN, + RPCMSG_E_USER_CODE = 32, +}; + +void rpcmsg_pack_error(cp_pack_t, struct rpcmsg_meta *meta, + enum rpcmsg_error error, const char *msg) __attribute__((nonnull(1, 2))); + +void rpcmsg_pack_ferror(cp_pack_t, struct rpcmsg_meta *meta, + enum rpcmsg_error error, const char *fmt, ...) __attribute__((nonnull(1, 2))); + +void rpcmsg_pack_vferror(cp_pack_t, struct rpcmsg_meta *meta, + enum rpcmsg_error error, const char *fmt, va_list args) + __attribute__((nonnull(1, 2))); + + +#if 0 + /*! String handle that can be increased in size and reused for the future * strings. It is used by `rpcmsg_unpack_str` and by other functions that * internally call that function. It provides a growable string with known size @@ -54,7 +263,7 @@ static inline void rpcmsg_pack_chng(cpcp_pack_context *pack, const char *path) { * `rpcmsg_unpack_str` will call `realloc` on it (with exception that is * described in the `siz` field description). */ -struct rpcmsg_str { +struct rpcclient_str { /*! Size of the `str` in bytes. You can set this to a negative number to * prevent `rpcmsg_unpack_str` from reallocating. This provides you either * ceiling functionality or a way to pass non-malloc allocated memory. @@ -67,7 +276,7 @@ struct rpcmsg_str { /*! Unpack string to `rpcmsg_str`. * */ -bool rpcmsg_unpack_str(struct rpcclient_msg msg, struct rpcmsg_str **str); +bool rpcclient_unpack_str(rpcclient_t, struct rpcclient_str **str); /*! Check if `rpcmsg_str` has truncated content. * @@ -82,23 +291,12 @@ bool rpcmsg_unpack_str(struct rpcclient_msg msg, struct rpcmsg_str **str); * @returns: `true` if string is truncated or otherwise invalid and `false` * otherwise. */ -bool rpcmsg_str_truncated(const struct rpcmsg_str *str) __attribute__((nonnull)); +bool rpcclient_str_truncated(const struct rpcclient_str *str) + __attribute__((nonnull)); -enum rpcmsg_access { - RPCMSG_ACC_INVALID, - RPCMSG_ACC_BROWSE, - RPCMSG_ACC_READ, - RPCMSG_ACC_WRITE, - RPCMSG_ACC_COMMAND, - RPCMSG_ACC_CONFIG, - RPCMSG_ACC_SERVICE, - RPCMSG_ACC_SUPER_SERVICE, - RPCMSG_ACC_DEVEL, - RPCMSG_ACC_ADMIN, -}; -bool rpcmsg_unpack_access(struct rpcclient_msg msg, enum rpcmsg_access *access); +bool rpcmsg_unpack_access(rpcclient_t, enum rpcmsg_access *access); const char *rpcmsg_access_str(enum rpcmsg_access); @@ -131,8 +329,9 @@ enum rpcmsg_type { RPCMSG_T_RESPONSE, RPCMSG_T_ERROR, RPCMSG_T_SIGNAL, -} rpcmsg_unpack(struct rpcclient_msg msg, struct rpcmsg_str **path, +} rpcmsg_unpack(rpcclient_t, struct rpcmsg_str **path, struct rpcmsg_str **method, struct rpcmsg_request_info **rinfo); #endif +#endif diff --git a/include/shv/rpcreceiver.h b/include/shv/rpcreceiver.h deleted file mode 100644 index 75725c1..0000000 --- a/include/shv/rpcreceiver.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef SHV_RPCRECEIVER_H -#define SHV_RPCRECEIVER_H - -#include -#include -#include - - - -#endif diff --git a/include/shv/rpcserver.h b/include/shv/rpcserver.h new file mode 100644 index 0000000..ba30ef4 --- /dev/null +++ b/include/shv/rpcserver.h @@ -0,0 +1,18 @@ +#ifndef SHV_RPCSERVER_H +#define SHV_RPCSERVER_H +#include +#include + +typedef struct rpcserver *rpcserver_t; + + +rpcserver_t rpcserver_listen(const struct rpcurl *url); + +rpcserver_t rpcserver_stream_new(int readfd, int writefd); +rpcserver_t rpcserver_stream_tcp_listen(const char *location, int port); +rpcserver_t rpcserver_stream_unix_listen(const char *location); + +rpcserver_t rpcserver_datagram_new(void); +rpcserver_t rpcserver_datagram_udp_listen(const char *location, int port); + +#endif diff --git a/libshvbroker/libshvbroker.version b/libshvbroker/libshvbroker.version new file mode 100644 index 0000000..958bab8 --- /dev/null +++ b/libshvbroker/libshvbroker.version @@ -0,0 +1,6 @@ +V0.0 { + global: + foo; + + local: *; +}; diff --git a/libshvbroker/meson.build b/libshvbroker/meson.build new file mode 100644 index 0000000..ea399aa --- /dev/null +++ b/libshvbroker/meson.build @@ -0,0 +1,26 @@ +libshvbroker_sources = files() + +libshvbroker = library( + 'shvbroker', + libshvbroker_sources, + version: '0.0.0', + include_directories: includes, + link_args: '-Wl,--version-script=' + join_paths( + meson.current_source_dir(), + 'libshvbroker.version', + ), + install: not meson.is_subproject(), +) +install_headers(libshvbroker_headers) + +libshvbroker_dep = declare_dependency( + include_directories: includes, + link_with: libshvbroker, +) + + +pkg_mod = import('pkgconfig') +pkg_mod.generate( + libshvbroker, + description: 'Silicon Heaven Broker', +) diff --git a/libshvchainpack/chainpack.h b/libshvchainpack/chainpack.h deleted file mode 100644 index 9fc1af3..0000000 --- a/libshvchainpack/chainpack.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef CHAINPACK_H -#define CHAINPACK_H - -#include - -enum chainpack_scheme { - CPS_Null = 128, - CPS_UInt, - CPS_Int, - CPS_Double, - CPS_Bool, - CPS_Blob, - CPS_String, // UTF8 encoded string - CPS_DateTimeEpoch_depr, // deprecated - CPS_List, - CPS_Map, - CPS_IMap, - CPS_MetaMap, - CPS_Decimal, - CPS_DateTime, - CPS_CString, - - CPS_FALSE = 253, - CPS_TRUE = 254, - CPS_TERM = 255, -}; - - -/* UTC msec since 2.2. 2018 folowed by signed UTC offset in 1/4 hour - * Fri Feb 02 2018 00:00:00 == 1517529600 EPOCH - */ -static const int64_t chainpack_epoch_msec = 1517529600000; - -#endif diff --git a/libshvchainpack/chainpack_pack.c b/libshvchainpack/chainpack_pack.c index 3ae5426..53cbee8 100644 --- a/libshvchainpack/chainpack_pack.c +++ b/libshvchainpack/chainpack_pack.c @@ -1,5 +1,5 @@ -#include "chainpack.h" -#include +#include +#include #ifndef __BYTE_ORDER__ #error We use __BYTE_ORDER__ macro and we need it to be defined @@ -8,16 +8,19 @@ #define PUTC(V) \ do { \ - if (f && fputc((V), f) != (V)) \ + uint8_t __v = (V); \ + if (f && fputc(__v, f) != __v) \ return -1; \ res++; \ } while (false) -#define WRITE(V, CNT, N) \ +#define WRITE(V, SIZ) \ do { \ - ssize_t __cnt = f ? fwrite((V), (CNT), (N), f) : (CNT) * (N); \ - if (__cnt > 0) \ - return -1; \ - res += __cnt; \ + size_t __siz = SIZ; \ + if (f) { \ + if (fwrite((V), 1, __siz, f) != __siz) \ + return -1; \ + } \ + res += __siz; \ } while (false) #define CALL(FUNC, ...) \ do { \ @@ -29,7 +32,6 @@ static ssize_t chainpack_pack_int(FILE *f, int64_t v); -static ssize_t chainpack_pack_buf(FILE *f, const struct cpbuf *buf); ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { @@ -67,7 +69,7 @@ ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { for (ssize_t i = sizeof(double) - 1; i >= 0; i--) PUTC(b[i]); #else - WRITE((uint8_t *)&item->as.Double, sizeof(double), 1); + WRITE((uint8_t *)&item->as.Double, sizeof(double)); #endif break; case CP_ITEM_DECIMAL: @@ -76,28 +78,38 @@ ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { CALL(chainpack_pack_int, item->as.Decimal.exponent); break; case CP_ITEM_BLOB: - if (item->as.Blob.first) { - if (item->as.Blob.eoff < 0) - PUTC(CPS_CString); - else { + if (item->as.Blob.flags & CPBI_F_FIRST) { + if (item->as.Blob.flags & CPBI_F_STREAM) { + PUTC(CPS_BlobChain); + } else { PUTC(CPS_Blob); CALL(_chainpack_pack_uint, item->as.Blob.len + item->as.Blob.eoff); } } - CALL(chainpack_pack_buf, &item->as.Blob); + if (item->as.Blob.len) { + if (item->as.Blob.flags & CPBI_F_STREAM) + CALL(_chainpack_pack_uint, item->as.Blob.len); + WRITE(item->rbuf, item->as.Blob.len); + } + if (item->as.Blob.flags & CPBI_F_STREAM && + item->as.Blob.flags & CPBI_F_LAST) + PUTC(0); break; case CP_ITEM_STRING: - if (item->as.Blob.first) { - if (item->as.String.eoff < 0) + if (item->as.String.flags & CPBI_F_FIRST) { + if (item->as.String.flags & CPBI_F_STREAM) PUTC(CPS_CString); else { PUTC(CPS_String); CALL(_chainpack_pack_uint, - item->as.String.len + item->as.String.eoff); + (int64_t)item->as.String.len + item->as.String.eoff); } } - CALL(chainpack_pack_buf, &item->as.String); + WRITE(item->rbuf, item->as.Blob.len); + if (item->as.Blob.flags & CPBI_F_STREAM && + item->as.Blob.flags & CPBI_F_LAST) + PUTC('\0'); break; case CP_ITEM_DATETIME: PUTC(CPS_DateTime); @@ -105,7 +117,7 @@ ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { * can save byte in packed date-time, but packing scheme is more * complicated. */ - int64_t msecs = item->as.Datetime.msecs - chainpack_epoch_msec; + int64_t msecs = item->as.Datetime.msecs - CHAINPACK_EPOCH_MSEC; int offset = (item->as.Datetime.offutc / 15) & 0x7F; int ms = (int)(msecs % 1000); if (ms == 0) @@ -140,170 +152,27 @@ ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { return res; } - -#if defined(__GNUC__) && __GNUC__ >= 4 - -static int significant_bits_part_length(uint64_t n) { - int len = 0; - int llbits = sizeof(long long) * CHAR_BIT; - - if (n == 0) - return 0; - - if ((llbits < 64) && (n & 0xFFFFFFFF00000000)) { - len += 32; - n >>= 32; - } - - len += llbits - __builtin_clzll(n); - - return len; -} - -#else /* Fallback for generic compiler */ - -// see https://en.wikipedia.org/wiki/Find_first_set#CLZ -static const uint8_t sig_table_4bit[16] = { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4}; - -static int significant_bits_part_length(uint64_t n) { - int len = 0; - - if (n & 0xFFFFFFFF00000000) { - len += 32; - n >>= 32; - } - if (n & 0xFFFF0000) { - len += 16; - n >>= 16; - } - if (n & 0xFF00) { - len += 8; - n >>= 8; - } - if (n & 0xF0) { - len += 4; - n >>= 4; - } - len += sig_table_4bit[n]; - return len; -} - -#endif /* end of significant_bits_part_length function */ - -/* Number of bytes needed to encode bitlen */ -static size_t bytes_needed(size_t bitlen) { - size_t cnt; - if (bitlen <= 28) - cnt = (bitlen - 1) / 7 + 1; - else - cnt = (bitlen - 1) / 8 + 2; - return cnt; -} - -/* UInt - 0 ... 7 bits 1 byte |0|x|x|x|x|x|x|x|<-- LSB - 8 ... 14 bits 2 bytes |1|0|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB -15 ... 21 bits 3 bytes |1|1|0|x|x|x|x|x| |x|x|x|x|x|x|x|x| -|x|x|x|x|x|x|x|x|<-- LSB 22 ... 28 bits 4 bytes |1|1|1|0|x|x|x|x| -|x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB 29+ bits 5+ -bytes |1|1|1|1|n|n|n|n| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| -... <-- LSB n == 0 -> 4 bytes number (32 bit number) n == 1 -> 5 bytes -number n == 14 -> 18 bytes number n == 15 -> for future (number of bytes will be -specified in next byte) -*/ -static ssize_t pack_uint_data(FILE *f, uint64_t v, size_t bitlen) { +ssize_t _chainpack_pack_uint(FILE *f, unsigned long long v) { ssize_t res = 0; - size_t cnt = bytes_needed(bitlen); - uint8_t buf[cnt]; - int i; - for (i = cnt - 1; i >= 0; --i) { - uint8_t r = v & 0xff; - buf[i] = r; - v = v >> 8; - } - - if (bitlen <= 28) { - uint8_t mask = 0xf0 << (4 - cnt); - buf[0] = buf[0] & ~mask; - mask = mask << 1; - buf[0] = buf[0] | mask; - } else - buf[0] = 0xf0 | (cnt - 5); - - WRITE(buf, cnt, sizeof *buf); + unsigned bytes = chainpack_w_uint_bytes(v); + uint8_t buf[bytes]; + buf[0] = chainpack_w_uint_value1(v, bytes); + for (unsigned i = 1; i < bytes; i++) + buf[i] = 0xff & (v >> (8 * (bytes - i - 1))); + WRITE(buf, bytes); return res; } -_Static_assert(sizeof(uint64_t) <= 18, "Limitation of the chainpack packing code"); - -ssize_t _chainpack_pack_uint(FILE *f, uint64_t v) { - int bitlen = significant_bits_part_length(v); - return pack_uint_data(f, v, bitlen); -} - -/* Return max bit length >= bit_len, which can be encoded by same number of - * bytes - */ -static int expand_bitlen(int bitlen) { - int ret; - int byte_cnt = bytes_needed(bitlen); - if (bitlen <= 28) - ret = byte_cnt * (8 - 1) - 1; - else - ret = (byte_cnt - 1) * 8 - 1; - return ret; -} - -/* Int - 0 ... 7 bits 1 byte |0|s|x|x|x|x|x|x|<-- LSB - 8 ... 14 bits 2 bytes |1|0|s|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB -15 ... 21 bits 3 bytes |1|1|0|s|x|x|x|x| |x|x|x|x|x|x|x|x| -|x|x|x|x|x|x|x|x|<-- LSB 22 ... 28 bits 4 bytes |1|1|1|0|s|x|x|x| -|x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB 29+ bits 5+ -bytes |1|1|1|1|n|n|n|n| |s|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| -... <-- LSB n == 0 -> 4 bytes number (32 bit number) n == 1 -> 5 bytes -number n == 14 -> 18 bytes number n == 15 -> for future (number of bytes will be -specified in next byte) -*/ static ssize_t chainpack_pack_int(FILE *f, int64_t v) { - uint64_t num = v < 0 ? -v : v; - bool neg = (v < 0); - - int bitlen = significant_bits_part_length(num); - bitlen++; /* add sign bit */ - if (neg) { - int sign_pos = expand_bitlen(bitlen); - uint64_t sign_bit_mask = (uint64_t)1 << sign_pos; - num |= sign_bit_mask; - } - return pack_uint_data(f, num, bitlen); -} - -static ssize_t chainpack_pack_buf(FILE *f, const struct cpbuf *buf) { ssize_t res = 0; - - if (buf->eoff == -1) { - /* Handle C string */ - for (size_t i = 0; i < buf->len; i++) { - switch (buf->rbuf[i]) { - case '\\': - PUTC('\\'); - PUTC('\\'); - break; - case '\0': - PUTC('\\'); - PUTC('0'); - break; - default: - PUTC(buf->rbuf[i]); - break; - } - } - if (buf->last) - PUTC('\0'); - } else - WRITE(buf->rbuf, buf->len, 1); - + unsigned bytes = chainpack_w_int_bytes(v); + uint8_t buf[bytes]; + buf[0] = chainpack_w_int_value1(v, bytes); + uint64_t uv = v < 0 ? -v : v; + for (unsigned i = 1; i < bytes; i++) + buf[i] = 0xff & (uv >> (8 * (bytes - i - 1))); + if (bytes > 4 && v < 0) + buf[1] |= 0x80; + WRITE(buf, bytes); return res; } diff --git a/libshvchainpack/chainpack_unpack.c b/libshvchainpack/chainpack_unpack.c index 684e3d2..e679e8f 100644 --- a/libshvchainpack/chainpack_unpack.c +++ b/libshvchainpack/chainpack_unpack.c @@ -1,9 +1,9 @@ -#include "chainpack.h" -#include "shv/cp.h" +#include +#include -static size_t chainpack_unpack_int(FILE *f, int64_t *v, bool *ok); -static size_t chainpack_unpack_buf(FILE *f, struct cpbuf *buf, bool *ok); +static size_t chainpack_unpack_int(FILE *f, long long *v, bool *ok); +static size_t chainpack_unpack_buf(FILE *f, struct cpitem *item, bool *ok); size_t chainpack_unpack(FILE *f, struct cpitem *item) { @@ -18,16 +18,17 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { res++; \ __v; \ }) -#define READ(PTR, CNT, N) \ +#define READ(PTR, SIZ) \ do { \ - ssize_t __v = fread((PTR), (CNT), (N), f); \ - if (__v != (CNT) * (N)) { \ + size_t __siz = SIZ; \ + ssize_t __v = fread((PTR), 1, __siz, f); \ + if (__v != __siz) { \ item->type = CP_ITEM_INVALID; \ if (__v > 0) \ res += __v; \ return res; \ } \ - res += __v; \ + res += __siz; \ } while (false) #define CALL(FUNC, ...) \ do { \ @@ -40,25 +41,27 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { } while (false) /* Continue reading previous item */ - if ((item->type == CP_ITEM_STRING || item->type == CP_ITEM_BLOB) && - !item->as.String.last) { + if ((item->type == CP_ITEM_BLOB || item->type == CP_ITEM_STRING) && + (item->as.Blob.eoff != 0 || !(item->as.Blob.flags & CPBI_F_LAST))) { /* There is no difference between string and blob in data type and thus * we can write this code to serve them both. */ - CALL(chainpack_unpack_buf, &item->as.String); + item->as.Blob.flags &= ~CPBI_F_FIRST; + CALL(chainpack_unpack_buf, item); return res; } uint8_t scheme = GETC; - if (scheme < 0x80) { - if (scheme & 0x40) { + if (scheme < CPS_Null) { + if (chainpack_scheme_signed(scheme)) { item->type = CP_ITEM_INT; - item->as.Int = scheme & 0x3f; + item->as.Int = chainpack_scheme_uint(scheme); } else { item->type = CP_ITEM_UINT; - item->as.UInt = scheme & 0x3f; + item->as.UInt = chainpack_scheme_uint(scheme); } } else { + unsigned long long ull; switch (scheme) { case CPS_Null: item->type = CP_ITEM_NULL; @@ -86,40 +89,44 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { for (ssize_t i = sizeof(double) - 1; i >= 0; i--) b[i] = GETC; #else - READ(&item->as.Double, sizeof(double), 1); + READ(&item->as.Double, sizeof(double)); #endif break; case CPS_Decimal: item->type = CP_ITEM_DECIMAL; CALL(chainpack_unpack_int, &item->as.Decimal.mantisa); - int64_t exp; + long long exp; CALL(chainpack_unpack_int, &exp); item->as.Decimal.exponent = exp; break; case CPS_Blob: + case CPS_BlobChain: item->type = CP_ITEM_BLOB; - item->as.String.first = true; - item->as.String.last = false; - CALL(_chainpack_unpack_uint, (uint64_t *)&item->as.String.eoff); - CALL(chainpack_unpack_buf, &item->as.Blob); + CALL(_chainpack_unpack_uint, &ull); + // TODO check that we do not crop the value + item->as.String.eoff = ull; + item->as.Blob.flags = CPBI_F_FIRST; + if (scheme == CPS_BlobChain) + item->as.Blob.flags |= CPBI_F_STREAM; + CALL(chainpack_unpack_buf, item); break; case CPS_String: - item->type = CP_ITEM_STRING; - item->as.String.first = true; - item->as.String.last = false; - CALL(_chainpack_unpack_uint, (uint64_t *)&item->as.String.eoff); - CALL(chainpack_unpack_buf, &item->as.String); - break; case CPS_CString: item->type = CP_ITEM_STRING; - item->as.String.first = true; - item->as.String.last = false; - item->as.String.eoff = -1; - CALL(chainpack_unpack_buf, &item->as.String); + item->as.String.flags = CPBI_F_FIRST; + if (scheme == CPS_CString) { + item->as.String.eoff = 0; + item->as.String.flags |= CPBI_F_STREAM; + } else { + CALL(_chainpack_unpack_uint, &ull); + // TODO check that we do not crop the value + item->as.String.eoff = ull; + } + CALL(chainpack_unpack_buf, item); break; case CPS_DateTime: item->type = CP_ITEM_DATETIME; - int64_t d; + long long d; CALL(chainpack_unpack_int, &d); int32_t offset = 0; bool has_tz_offset = d & 1; @@ -133,7 +140,7 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { } if (has_not_msec) d *= 1000; - d += chainpack_epoch_msec; + d += CHAINPACK_EPOCH_MSEC; item->as.Datetime = (struct cpdatetime){.msecs = d, .offutc = offset * 15}; break; @@ -164,7 +171,7 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { #undef CALL } -size_t _chainpack_unpack_uint(FILE *f, uint64_t *v, bool *ok) { +size_t _chainpack_unpack_uint(FILE *f, unsigned long long *v, bool *ok) { ssize_t res = 0; int head = getc(f); @@ -175,24 +182,10 @@ size_t _chainpack_unpack_uint(FILE *f, uint64_t *v, bool *ok) { } res++; - *v = 0; - unsigned getcnt; - if (!(head & 0x80)) { - getcnt = 0; - *v = head & 0x7f; - } else if (!(head & 0x40)) { - getcnt = 1; - *v = head & 0x3f; - } else if (!(head & 0x20)) { - getcnt = 2; - *v = head & 0x1f; - } else if (!(head & 0x10)) { - getcnt = 3; - *v = head & 0x0f; - } else - getcnt = (head & 0xf) + 4; + unsigned bytes = chainpack_int_bytes(head); - for (unsigned i = 0; i < getcnt; ++i) { + *v = chainpack_uint_value1(head, bytes); + for (unsigned i = 1; i < bytes; i++) { int r = getc(f); if (r == EOF) { if (ok) @@ -200,18 +193,19 @@ size_t _chainpack_unpack_uint(FILE *f, uint64_t *v, bool *ok) { return res; } res++; - *v = (*v << 8) + r; - }; + *v = (*v << 8) | r; + } + if (ok) *ok = true; return res; } -static size_t chainpack_unpack_int(FILE *f, int64_t *v, bool *ok) { +static size_t chainpack_unpack_int(FILE *f, long long *v, bool *ok) { bool ourok; if (ok == NULL) ok = &ourok; - size_t res = _chainpack_unpack_uint(f, (uint64_t *)v, ok); + size_t res = _chainpack_unpack_uint(f, (unsigned long long *)v, ok); if (*ok) { /* This is kind of magic that requires some explanation. @@ -221,10 +215,10 @@ static size_t chainpack_unpack_int(FILE *f, int64_t *v, bool *ok) { * used to signal this. That applies for four initial bits. */ uint64_t sign_mask; - if (res <= 5) - sign_mask = 1 << ((8 * res) - res - 1); + if (res <= 4) + sign_mask = 1L << ((8 * res) - res - 1); else - sign_mask = 1 << ((8 * (res - 1)) - 1); + sign_mask = 1L << ((8 * (res - 1)) - 1); if (*v & sign_mask) { *v &= ~sign_mask; *v = -*v; @@ -235,53 +229,67 @@ static size_t chainpack_unpack_int(FILE *f, int64_t *v, bool *ok) { } -static size_t chainpack_unpack_buf(FILE *f, struct cpbuf *buf, bool *ok) { - if (buf->buf == NULL) - return 0; /* Nowhere to place data so just inform user about type */ - +static size_t chainpack_unpack_buf(FILE *f, struct cpitem *item, bool *ok) { + bool ourok = true; if (ok) *ok = true; + else + ok = &ourok; - if (buf->eoff == -1) { + if (item->bufsiz == 0) + return 0; /* Nowhere to place data so just inform user about type */ + + if (item->as.Blob.flags & CPBI_F_STREAM && item->type == CP_ITEM_STRING) { /* Handle C string */ - size_t i = 0; - size_t res = 0; - bool escape = false; - while (i < buf->siz) { + size_t i; + for (i = 0; i < item->bufsiz; i++) { int c = getc(f); if (c == EOF) { if (ok) *ok = false; return i; } - res++; - if (escape) { - if (c == '0') - c = '\0'; - buf->buf[i++] = c; - escape = false; - } else { - if (c == '\\') - escape = true; - else if (c == '\0') { - buf->last = true; - break; - } else - buf->buf[i++] = c; + if (c == '\0') { + item->as.Blob.flags |= CPBI_F_LAST; + item->as.Blob.len = i; + return i + 1; } + if (item->buf) + item->buf[i] = c; } - buf->len = i; - return res; + item->as.Blob.len = i; + return i; } - size_t toread = buf->eoff > buf->siz ? buf->siz : buf->eoff; - ssize_t res = fread(buf->buf, toread, 1, f); - if (res < 0) { - if (ok) + /* Handle String, Blob and BlobChain */ + size_t res = 0; + item->as.Blob.len = 0; + while (item->as.Blob.len < item->bufsiz) { + size_t toread = item->as.Blob.eoff > item->bufsiz ? item->bufsiz + : item->as.Blob.eoff; + ssize_t rres = toread; + if (item->buf) + rres = fread(item->buf + item->as.Blob.len, 1, toread, f); + if (rres < 0) { *ok = false; - return 0; + break; + } + // TODO what if we failed to read enough data due to EOF? + res += rres; + item->as.Blob.len = rres; + item->as.Blob.eoff -= rres; + if (item->as.Blob.flags & CPBI_F_STREAM && item->as.Blob.eoff == 0) { + unsigned long long ull; + res += _chainpack_unpack_uint(f, &ull, ok); + // TODO check that we do not crop the value + item->as.String.eoff = ull; + if (!*ok) + break; + } + if (item->as.Blob.eoff == 0) { + item->as.Blob.flags |= CPBI_F_LAST; + break; + } } - buf->len = res; - buf->eoff -= res; return res; } diff --git a/libshvchainpack/cp_pack.c b/libshvchainpack/cp_pack.c new file mode 100644 index 0000000..594f61d --- /dev/null +++ b/libshvchainpack/cp_pack.c @@ -0,0 +1,106 @@ +#include +#include + +static ssize_t cp_pack_chainpack_func(void *ptr, const struct cpitem *item) { + struct cp_pack_chainpack *p = ptr; + return chainpack_pack(p->f, item); +} + +cp_pack_t cp_pack_chainpack_init(struct cp_pack_chainpack *pack, FILE *f) { + *pack = (struct cp_pack_chainpack){ + .func = cp_pack_chainpack_func, + .f = f, + }; + return &pack->func; +} + +static void cpon_state_realloc(struct cpon_state *state) { + state->cnt = state->cnt ? state->cnt * 2 : 1; + state->ctx = realloc(state->ctx, state->cnt * sizeof *state->ctx); +} + +static ssize_t cp_pack_cpon_func(void *ptr, const struct cpitem *item) { + struct cp_pack_cpon *p = ptr; + return cpon_pack(p->f, &p->state, item); +} + +cp_pack_t cp_pack_cpon_init(struct cp_pack_cpon *pack, FILE *f, const char *indent) { + *pack = (struct cp_pack_cpon){ + .func = cp_pack_cpon_func, + .f = f, + .state = + (struct cpon_state){ + .indent = indent, + .depth = 0, + .cnt = 0, + .realloc = cpon_state_realloc, + }, + }; + return &pack->func; +} + + +// TODO test if ftell works or if we need to implement seek as well + +static ssize_t _write(void *cookie, const char *buf, size_t size, bool blob) { + cp_pack_t pack = cookie; + struct cpitem item; + item.type = blob ? CP_ITEM_BLOB : CP_ITEM_STRING; + item.rchr = buf; + item.as.Blob = (struct cpbufinfo){ + .len = size, + .flags = CPBI_F_STREAM, + }; + cp_pack(pack, &item); + return size; +} + +static int _close(void *cookie, bool blob) { + cp_pack_t pack = cookie; + struct cpitem item; + item.type = blob ? CP_ITEM_BLOB : CP_ITEM_STRING; + item.as.Blob = (struct cpbufinfo){ + .len = 0, + .flags = CPBI_F_STREAM | CPBI_F_LAST, + }; + cp_pack(pack, &item); + return 0; +} + +static ssize_t write_blob(void *cookie, const char *buf, size_t size) { + return _write(cookie, buf, size, true); +} + +static int close_blob(void *cookie) { + return _close(cookie, true); +} + +static const cookie_io_functions_t func_blob = { + .write = write_blob, + .close = close_blob, +}; + + +static ssize_t write_string(void *cookie, const char *buf, size_t size) { + return _write(cookie, buf, size, false); +} + +static int close_string(void *cookie) { + return _close(cookie, false); +} + +static const cookie_io_functions_t func_string = { + .write = write_string, + .close = close_string, +}; + +FILE *cp_pack_fopen(cp_pack_t pack, bool str) { + struct cpitem item; + item.type = str ? CP_ITEM_STRING : CP_ITEM_BLOB; + item.as.Blob = (struct cpbufinfo){ + .len = 0, + .flags = CPBI_F_FIRST | CPBI_F_STREAM, + }; + cp_pack(pack, &item); + return fopencookie(pack, "a", str ? func_string : func_blob); +} diff --git a/libshvchainpack/cp_unpack.c b/libshvchainpack/cp_unpack.c new file mode 100644 index 0000000..151aaee --- /dev/null +++ b/libshvchainpack/cp_unpack.c @@ -0,0 +1,193 @@ +#include "shv/cp.h" +#include +#include +#include + +static size_t cp_unpack_chainpack_func(void *ptr, struct cpitem *item) { + struct cp_unpack_chainpack *p = ptr; + return chainpack_unpack(p->f, item); +} + +cp_unpack_t cp_unpack_chainpack_init(struct cp_unpack_chainpack *unpack, FILE *f) { + *unpack = (struct cp_unpack_chainpack){ + .func = cp_unpack_chainpack_func, + .f = f, + }; + return &unpack->func; +} + +static void cpon_state_realloc(struct cpon_state *state) { + state->cnt = state->cnt ? state->cnt * 2 : 1; + state->ctx = realloc(state->ctx, state->cnt * sizeof *state->ctx); +} + +static size_t cp_unpack_cpon_func(void *ptr, struct cpitem *item) { + struct cp_unpack_cpon *p = ptr; + return cpon_unpack(p->f, &p->state, item); +} + +cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *unpack, FILE *f) { + *unpack = (struct cp_unpack_cpon){ + .func = cp_unpack_cpon_func, + .f = f, + .state = + (struct cpon_state){ + .depth = 0, + .cnt = 0, + .realloc = cpon_state_realloc, + }, + }; + return &unpack->func; +} + +static size_t _cp_unpack_skip( + cp_unpack_t unpack, struct cpitem *item, unsigned depth) { + size_t res = 0; + item->buf = NULL; + item->bufsiz = SIZE_MAX; + if ((item->type == CP_ITEM_STRING || item->type == CP_ITEM_BLOB) && + !(item->as.Blob.flags & CPBI_F_LAST)) { + depth++; + } + do { + res += cp_unpack(unpack, item); + switch (item->type) { + case CP_ITEM_BLOB: + case CP_ITEM_STRING: + if (item->as.Blob.flags & CPBI_F_FIRST) + depth++; + if (item->as.Blob.flags & CPBI_F_LAST) + depth--; + break; + case CP_ITEM_LIST: + case CP_ITEM_MAP: + case CP_ITEM_IMAP: + case CP_ITEM_META: + depth++; + break; + case CP_ITEM_CONTAINER_END: + depth--; + break; + case CP_ITEM_INVALID: + return false; + default: + break; + } + } while (depth); + return res; +} + +size_t cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) { + return _cp_unpack_skip(unpack, item, 0); +} + +size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item) { + return _cp_unpack_skip(unpack, item, 1); +} + +static void *cp_unpack_dup(cp_unpack_t unpack, struct cpitem *item, size_t *size) { + item->bufsiz = 2; + uint8_t *res = malloc(item->bufsiz); + size_t off = 0; + item->buf = res; + while (true) { + cp_unpack(unpack, item); + if (item->type != (size ? CP_ITEM_BLOB : CP_ITEM_STRING)) { + free(res); + return NULL; + } + if (item->as.Blob.flags & CPBI_F_LAST) { + off += item->as.Blob.len; + if (size) { + *size = off; + return realloc(res, off); + } else { + res = realloc(res, off + 1); + res[off] = '\0'; + return res; + } + } + /* We expect here that all available bytes are used */ + assert(item->as.Blob.len == item->bufsiz); + off += item->bufsiz; + item->bufsiz = off; + res = realloc(res, off * 2); + item->buf = res + off; + } + return res; +} + +char *cp_unpack_strdup(cp_unpack_t unpack, struct cpitem *item) { + return cp_unpack_dup(unpack, item, NULL); +} + +void cp_unpack_memdup( + cp_unpack_t unpack, struct cpitem *item, uint8_t **buf, size_t *siz) { + *buf = cp_unpack_dup(unpack, item, siz); +} + + +struct cookie { + cp_unpack_t unpack; + struct cpitem *item; +}; + +static ssize_t _read(void *cookie, char *buf, size_t size, bool blob) { + struct cookie *c = cookie; + if (c->item->as.String.flags & CPBI_F_LAST) + return 0; + c->item->chr = buf; + c->item->bufsiz = size; + cp_unpack(c->unpack, c->item); + if (c->item->type != (blob ? CP_ITEM_BLOB : CP_ITEM_STRING)) + return -1; + return c->item->as.String.len; +} + +static int _close(void *cookie, bool blob) { + struct cookie *c = cookie; + free(c); + return 0; +} + +static ssize_t read_blob(void *cookie, char *buf, size_t size) { + return _read(cookie, buf, size, true); +} + +static int close_blob(void *cookie) { + return _close(cookie, true); +} + +static const cookie_io_functions_t func_blob = { + .read = read_blob, + .close = close_blob, +}; + + +static ssize_t read_string(void *cookie, char *buf, size_t size) { + return _read(cookie, buf, size, false); +} + +static int close_string(void *cookie) { + return _close(cookie, false); +} + +static const cookie_io_functions_t func_string = { + .read = read_string, + .close = close_string, +}; + +FILE *cp_unpack_fopen(cp_unpack_t unpack, struct cpitem *item) { + item->bufsiz = 0; + cp_unpack(unpack, item); + if (item->type != CP_ITEM_BLOB && item->type != CP_ITEM_STRING) + return NULL; + + struct cookie *cookie = malloc(sizeof *cookie); + *cookie = (struct cookie){ + .unpack = unpack, + .item = item, + }; + return fopencookie( + cookie, "r", item->type == CP_ITEM_STRING ? func_string : func_blob); +} diff --git a/libshvchainpack/cpcp_convert.c b/libshvchainpack/cpcp_convert.c deleted file mode 100644 index fbbaf9f..0000000 --- a/libshvchainpack/cpcp_convert.c +++ /dev/null @@ -1,282 +0,0 @@ -#include -#include - -#include - -void cpcp_convert(cpcp_unpack_context *in_ctx, enum cpcp_format in_format, - cpcp_pack_context *out_ctx, enum cpcp_format out_format) { - if (!in_ctx->container_stack) { - // cpcp_convert() cannot worj without input context container state set - in_ctx->err_no = CPCP_RC_LOGICAL_ERROR; - return; - } - bool o_cpon_input = (in_format == CPCP_Cpon); - bool o_chainpack_output = (out_format == CPCP_ChainPack); - bool meta_just_closed = false; - do { - if (o_cpon_input) - cpon_unpack_next(in_ctx); - else - chainpack_unpack_next(in_ctx); - if (in_ctx->err_no != CPCP_RC_OK) - break; - - cpcp_container_state *parent_state = - cpcp_unpack_context_parent_container_state(in_ctx); - if (o_chainpack_output) { - } else { - if (parent_state != NULL) { - bool is_string_concat = 0; - if (in_ctx->item.type == CPCP_ITEM_STRING) { - cpcp_string *it = &(in_ctx->item.as.String); - if (it->chunk_cnt > 1) { - // multichunk string - // this can happen, when parsed string is greater than - // unpack_context buffer or escape sequence is - // encountered concatenate it with previous chunk - is_string_concat = 1; - } - } - if (!is_string_concat && - in_ctx->item.type != CPCP_ITEM_CONTAINER_END) { - switch (parent_state->container_type) { - case CPCP_ITEM_LIST: - if (!meta_just_closed) - cpon_pack_field_delim(out_ctx, - parent_state->item_count == 1, false); - break; - case CPCP_ITEM_MAP: - case CPCP_ITEM_IMAP: - case CPCP_ITEM_META: { - bool is_key = (parent_state->item_count % 2); - if (is_key) { - if (!meta_just_closed) - cpon_pack_field_delim(out_ctx, - parent_state->item_count == 1, false); - } else { - // delimite value - if (!meta_just_closed) - cpon_pack_key_val_delim(out_ctx); - } - break; - } - default: - break; - } - } - } - } - meta_just_closed = false; - switch (in_ctx->item.type) { - case CPCP_ITEM_INVALID: { - // end of input - break; - } - case CPCP_ITEM_NULL: { - if (o_chainpack_output) - chainpack_pack_null(out_ctx); - else - cpon_pack_null(out_ctx); - break; - } - case CPCP_ITEM_LIST: { - if (o_chainpack_output) - chainpack_pack_list_begin(out_ctx); - else - cpon_pack_list_begin(out_ctx); - break; - } - case CPCP_ITEM_MAP: { - if (o_chainpack_output) - chainpack_pack_map_begin(out_ctx); - else - cpon_pack_map_begin(out_ctx); - break; - } - case CPCP_ITEM_IMAP: { - if (o_chainpack_output) - chainpack_pack_imap_begin(out_ctx); - else - cpon_pack_imap_begin(out_ctx); - break; - } - case CPCP_ITEM_META: { - if (o_chainpack_output) - chainpack_pack_meta_begin(out_ctx); - else - cpon_pack_meta_begin(out_ctx); - break; - } - case CPCP_ITEM_CONTAINER_END: { - cpcp_container_state *st = - cpcp_unpack_context_closed_container_state(in_ctx); - if (!st) { - in_ctx->err_no = CPCP_RC_CONTAINER_STACK_UNDERFLOW; - return; - } - meta_just_closed = (st->container_type == CPCP_ITEM_META); - - if (o_chainpack_output) { - chainpack_pack_container_end(out_ctx); - } else { - switch (st->container_type) { - case CPCP_ITEM_LIST: - cpon_pack_list_end(out_ctx, false); - break; - case CPCP_ITEM_MAP: - cpon_pack_map_end(out_ctx, false); - break; - case CPCP_ITEM_IMAP: - cpon_pack_imap_end(out_ctx, false); - break; - case CPCP_ITEM_META: - cpon_pack_meta_end(out_ctx, false); - break; - default: - // cannot finish Cpon container without container - // type info - in_ctx->err_no = CPCP_RC_LOGICAL_ERROR; - return; - } - } - break; - } - case CPCP_ITEM_BLOB: { - cpcp_string *it = &in_ctx->item.as.String; - if (o_chainpack_output) { - if (it->chunk_cnt == 1 && it->last_chunk) { - // one chunk string with known length is always packed - // as RAW - chainpack_pack_blob(out_ctx, (uint8_t *)it->chunk_start, - it->chunk_size); - } else if (it->string_size >= 0) { - if (it->chunk_cnt == 1) - chainpack_pack_blob_start(out_ctx, - (size_t)(it->string_size), - (uint8_t *)it->chunk_start, - (size_t)(it->string_size)); - else - chainpack_pack_blob_cont(out_ctx, - (uint8_t *)it->chunk_start, it->chunk_size); - } else { - // cstring - // not supported, there is nothing like CBlob - } - } else { - // Cpon - if (it->chunk_cnt == 1) - cpon_pack_blob_start(out_ctx, - (uint8_t *)it->chunk_start, it->chunk_size); - else - cpon_pack_blob_cont(out_ctx, (uint8_t *)it->chunk_start, - (unsigned)(it->chunk_size)); - if (it->last_chunk) - cpon_pack_blob_finish(out_ctx); - } - break; - } - case CPCP_ITEM_STRING: { - cpcp_string *it = &in_ctx->item.as.String; - if (o_chainpack_output) { - if (it->chunk_cnt == 1 && it->last_chunk) { - // one chunk string with known length is always packed - // as RAW - chainpack_pack_string( - out_ctx, it->chunk_start, it->chunk_size); - } else if (it->string_size >= 0) { - if (it->chunk_cnt == 1) - chainpack_pack_string_start(out_ctx, - (size_t)(it->string_size), it->chunk_start, - (size_t)(it->string_size)); - else - chainpack_pack_string_cont( - out_ctx, it->chunk_start, it->chunk_size); - } else { - // cstring - if (it->chunk_cnt == 1) - chainpack_pack_cstring_start( - out_ctx, it->chunk_start, it->chunk_size); - else - chainpack_pack_cstring_cont( - out_ctx, it->chunk_start, it->chunk_size); - if (it->last_chunk) - chainpack_pack_cstring_finish(out_ctx); - } - } else { - // Cpon - if (it->chunk_cnt == 1) - cpon_pack_string_start( - out_ctx, it->chunk_start, it->chunk_size); - else - cpon_pack_string_cont(out_ctx, it->chunk_start, - (unsigned)(it->chunk_size)); - if (it->last_chunk) - cpon_pack_string_finish(out_ctx); - } - break; - } - case CPCP_ITEM_BOOLEAN: { - if (o_chainpack_output) - chainpack_pack_boolean(out_ctx, in_ctx->item.as.Bool); - else - cpon_pack_boolean(out_ctx, in_ctx->item.as.Bool); - break; - } - case CPCP_ITEM_INT: { - if (o_chainpack_output) - chainpack_pack_int(out_ctx, in_ctx->item.as.Int); - else - cpon_pack_int(out_ctx, in_ctx->item.as.Int); - break; - } - case CPCP_ITEM_UINT: { - if (o_chainpack_output) - chainpack_pack_uint(out_ctx, in_ctx->item.as.UInt); - else - cpon_pack_uint(out_ctx, in_ctx->item.as.UInt); - break; - } - case CPCP_ITEM_DECIMAL: { - if (o_chainpack_output) - chainpack_pack_decimal(out_ctx, &in_ctx->item.as.Decimal); - else - cpon_pack_decimal(out_ctx, &in_ctx->item.as.Decimal); - break; - } - case CPCP_ITEM_DOUBLE: { - if (o_chainpack_output) - chainpack_pack_double(out_ctx, in_ctx->item.as.Double); - else - cpon_pack_double(out_ctx, in_ctx->item.as.Double); - break; - } - case CPCP_ITEM_DATE_TIME: { - cpcp_date_time *it = &in_ctx->item.as.DateTime; - if (o_chainpack_output) - chainpack_pack_date_time(out_ctx, it); - else - cpon_pack_date_time(out_ctx, it); - break; - } - } - { - cpcp_container_state *top_state = - cpcp_unpack_context_top_container_state(in_ctx); - // take just one object from stream - if (!top_state) { - if (((in_ctx->item.type == CPCP_ITEM_STRING || - in_ctx->item.type == CPCP_ITEM_BLOB) && - !in_ctx->item.as.String.last_chunk) || - meta_just_closed) { - // do not stop parsing in the middle of the string - // or after meta - } else { - break; - } - } - } - } while (in_ctx->err_no == CPCP_RC_OK && out_ctx->err_no == CPCP_RC_OK); - - if (out_ctx->handle_pack_overflow) - out_ctx->handle_pack_overflow(out_ctx, 0); -} diff --git a/libshvchainpack/cpdatetime.c b/libshvchainpack/cpdatetime.c new file mode 100644 index 0000000..c2fa8f9 --- /dev/null +++ b/libshvchainpack/cpdatetime.c @@ -0,0 +1,18 @@ +#include "time.h" +#include + +struct tm cpdttotm(struct cpdatetime v) { + struct tm res; + time_t t = v.msecs / 1000; + gmtime_r(&t, &res); + res.tm_gmtoff = v.offutc * 60; + return res; +} + +struct cpdatetime cptmtodt(struct tm v) { + int32_t utcoff = v.tm_gmtoff; + return (struct cpdatetime){ + .msecs = timegm(&v) * 1000, + .offutc = utcoff / 60, + }; +} diff --git a/libshvchainpack/cpdecimal.c b/libshvchainpack/cpdecimal.c new file mode 100644 index 0000000..7727ccd --- /dev/null +++ b/libshvchainpack/cpdecimal.c @@ -0,0 +1,27 @@ +#include +#include + +double cpdectod(struct cpdecimal v) { + double res = v.mantisa; + if (v.exponent >= 0) { + for (long i = 0; i < v.exponent; i++) + res *= 10; + } else { + for (long i = v.exponent; i < 0; i++) + res /= 10; + } + return res; +} + +#if 0 +struct cpdecimal cpdtodec(double v) { + // TODO + double i; + double d = modf(v, &i); + printf("That would be %f and %f\n", i, d); + int exp; + double s = frexp(v, &exp); + printf("frexp %d for %lf\n", exp, s); + return (struct cpdecimal) {.mantisa = i, .exponent = d}; +} +#endif diff --git a/libshvchainpack/cpon.h b/libshvchainpack/cpon.h deleted file mode 100644 index 4b0455b..0000000 --- a/libshvchainpack/cpon.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef CPON_H -#define CPON_H - -#include - -#define CPON_NULL "null" -#define CPON_TRUE "true" -#define CPON_FALSE "false" -#define CPON_LIST_BEGIN "[" -#define CPON_LIST_END "]" -#define CPON_FIELD_DELIM "," -#define CPON_MAP_BEGIN "{" -#define CPON_IMAP_BEGIN "i{" -#define CPON_MAP_END "}" -#define CPON_KEY_DELIM ":" -#define CPON_META_BEGIN "<" -#define CPON_META_END ">" -#define CPON_DATETIME_BEGIN "d\"" -#define CPON_UNSIGNED_END "u" -#define CPON_ELLIPSIS "..." - -#endif diff --git a/libshvchainpack/cpon_pack.c b/libshvchainpack/cpon_pack.c index ca5bddf..7b02868 100644 --- a/libshvchainpack/cpon_pack.c +++ b/libshvchainpack/cpon_pack.c @@ -1,8 +1,9 @@ -#include "cpon.h" +#include +#include #include #include #include - +#include #define PUTC(V) \ do { \ @@ -10,14 +11,16 @@ return -1; \ res++; \ } while (false) -#define WRITE(V, CNT, N) \ +#define PUTS(V) \ do { \ - ssize_t __cnt = f ? fwrite((V), (CNT), (N), f) : (CNT) * (N); \ - if (__cnt > 0) \ - return -1; \ - res += __cnt; \ + const char *__v = (V); \ + size_t __strlen = strlen(__v); \ + if (f) { \ + if (fwrite(__v, 1, __strlen, f) != __strlen) \ + return -1; \ + } \ + res += __strlen; \ } while (false) -#define PUTS(V) WRITE(V, strlen(V), 1) #define PRINTF(...) \ do { \ ssize_t __cnt; \ @@ -39,8 +42,9 @@ static ssize_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec); -static ssize_t cpon_pack_buf(FILE *f, const struct cpbuf *buf, bool string); -static bool ctxpush(struct cpon_state *state, enum cp_item_type tp); +static ssize_t cpon_pack_buf(FILE *f, const struct cpitem *item); +static ssize_t ctxpush( + FILE *f, struct cpon_state *state, enum cp_item_type tp, const char *str); static enum cp_item_type ctxpop(struct cpon_state *state); @@ -48,42 +52,45 @@ ssize_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) ssize_t res = 0; if (state->depth <= state->cnt) { - if (state->depth > 0) { + if (state->depth > 0 && item->type != CP_ITEM_CONTAINER_END) { struct cpon_state_ctx *ctx = &state->ctx[state->depth - 1]; - switch (ctx->tp) { - case CP_ITEM_LIST: - if (!ctx->first) - PUTS(CPON_FIELD_DELIM); - break; - case CP_ITEM_MAP: - case CP_ITEM_IMAP: - case CP_ITEM_META: - if (ctx->even) { + if (!ctx->meta) { + switch (ctx->tp) { + case CP_ITEM_LIST: if (!ctx->first) - PUTS(CPON_FIELD_DELIM); - } else - PUTS(CPON_KEY_DELIM); - default: - break; - } - ctx->first = false; - ctx->even = !ctx->even; + PUTC(','); + break; + case CP_ITEM_MAP: + case CP_ITEM_IMAP: + case CP_ITEM_META: + if (ctx->even) { + if (!ctx->first) + PUTC(','); + } else + PUTC(':'); + default: + break; + } + ctx->first = false; + ctx->even = !ctx->even; + } else + ctx->meta = false; } switch (item->type) { /* We pack invalid as NULL to ensure that we pack at least * somethuing */ case CP_ITEM_INVALID: case CP_ITEM_NULL: - PUTS(CPON_NULL); + PUTS("null"); break; case CP_ITEM_BOOL: - PUTS(item->as.Bool ? CPON_TRUE : CPON_FALSE); + PUTS(item->as.Bool ? "true" : "false"); break; case CP_ITEM_INT: - PRINTF("%" PRId64, item->as.Int); + PRINTF("%lld", item->as.Int); break; case CP_ITEM_UINT: - PRINTF("%" PRIu64 CPON_UNSIGNED_END, item->as.UInt); + PRINTF("%lluu", item->as.UInt); break; case CP_ITEM_DOUBLE: PRINTF("%G", item->as.Double); @@ -92,46 +99,52 @@ ssize_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) CALL(cpon_pack_decimal, &item->as.Decimal); break; case CP_ITEM_BLOB: - if (item->as.Blob.first) - PUTS("b\""); - CALL(cpon_pack_buf, &item->as.Blob, false); + if (item->as.Blob.flags & CPBI_F_FIRST) + PUTS(item->as.Blob.flags & CPBI_F_HEX ? "x\"" : "b\""); + CALL(cpon_pack_buf, item); break; case CP_ITEM_STRING: - if (item->as.Blob.first) + if (item->as.Blob.flags & CPBI_F_FIRST) PUTS("\""); - CALL(cpon_pack_buf, &item->as.Blob, true); + CALL(cpon_pack_buf, item); break; case CP_ITEM_DATETIME: - // TODO - PRINTF(CPON_DATETIME_BEGIN "\""); + struct tm tm = cpdttotm(item->as.Datetime); + PRINTF("d\"%d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d", tm.tm_year + 1900, + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, + (int)(item->as.Datetime.msecs % 1000)); + if (item->as.Datetime.offutc) + PRINTF("%s%.2d:%.2d\"", item->as.Datetime.offutc > 0 ? "+" : "", + item->as.Datetime.offutc / 60, + abs(item->as.Datetime.offutc) % 60); + else + PUTS("Z\""); break; case CP_ITEM_LIST: - if (ctxpush(state, CP_ITEM_LIST)) - PUTC(CPON_LIST_BEGIN); + CALL(ctxpush, state, CP_ITEM_LIST, "["); break; case CP_ITEM_MAP: - if (ctxpush(state, CP_ITEM_MAP)) - PUTS(CPON_MAP_BEGIN); + CALL(ctxpush, state, CP_ITEM_MAP, "{"); break; case CP_ITEM_IMAP: - if (ctxpush(state, CP_ITEM_IMAP)) - PUTS(CPON_IMAP_BEGIN); + CALL(ctxpush, state, CP_ITEM_IMAP, "i{"); break; case CP_ITEM_META: - if (ctxpush(state, CP_ITEM_META)) - PUTS(CPON_META_BEGIN); + CALL(ctxpush, state, CP_ITEM_META, "<"); break; case CP_ITEM_CONTAINER_END: switch (ctxpop(state)) { case CP_ITEM_LIST: - PUTS(CPON_LIST_END); + PUTC(']'); break; case CP_ITEM_MAP: case CP_ITEM_IMAP: - PUTS(CPON_MAP_END); + PUTC('}'); break; case CP_ITEM_META: - PUTS(CPON_META_END); + PUTC('>'); + if (state->depth > 0) + state->ctx[state->depth - 1].meta = true; break; default: break; @@ -139,62 +152,66 @@ ssize_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) break; } } else if (state->depth == state->cnt && state->ctx[state->depth - 1].first) { - PUTS(CPON_ELLIPSIS); + PUTS("..."); state->ctx[state->depth - 1].first = false; } return res; } -static ssize_t cpon_pack_buf(FILE *f, const struct cpbuf *buf, bool string) { +static ssize_t cpon_pack_buf(FILE *f, const struct cpitem *item) { ssize_t res = 0; - for (size_t i = 0; i < buf->len; i++) { - uint8_t b = buf->rbuf[i]; + for (size_t i = 0; i < item->as.Blob.len; i++) { + uint8_t b = item->rbuf[i]; + if (item->type == CP_ITEM_BLOB && item->as.Blob.flags & CPBI_F_HEX) + PRINTF("%.2X", b); + else { #define ESCAPE(V) \ do { \ PUTC('\\'); \ b = V; \ } while (false) - switch (b) { - case '\\': - ESCAPE('\\'); - break; - case '\0': - ESCAPE('0'); - break; - case '\a': - ESCAPE('a'); - break; - case '\b': - ESCAPE('b'); - break; - case '\t': - ESCAPE('t'); - break; - case '\n': - ESCAPE('n'); - break; - case '\v': - ESCAPE('v'); - break; - case '\f': - ESCAPE('f'); - break; - case '\r': - ESCAPE('r'); - break; - case '"': - ESCAPE('"'); - break; - } + switch (b) { + case '\\': + ESCAPE('\\'); + break; + case '\0': + ESCAPE('0'); + break; + case '\a': + ESCAPE('a'); + break; + case '\b': + ESCAPE('b'); + break; + case '\t': + ESCAPE('t'); + break; + case '\n': + ESCAPE('n'); + break; + case '\v': + ESCAPE('v'); + break; + case '\f': + ESCAPE('f'); + break; + case '\r': + ESCAPE('r'); + break; + case '"': + ESCAPE('"'); + break; + } #undef ESCAPE - if (!string && (b < 32 || b >= 127)) - PRINTF("\\%.2X", b); - else - PUTC(b); + if (item->type == CP_ITEM_BLOB && (b < 32 || b >= 127)) + PRINTF("\\%.2X", b); + else + PUTC(b); + } } - if (buf->last) + if (item->as.Blob.flags & CPBI_F_LAST) PUTC('\"'); return res; @@ -203,47 +220,54 @@ static ssize_t cpon_pack_buf(FILE *f, const struct cpbuf *buf, bool string) { static ssize_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec) { ssize_t res = 0; - bool neg = dec->mantisa < 0; - uint64_t mantisa = neg ? -dec->mantisa : dec->mantisa; - - /* The 64-bit number can ocuppy at most 20 characters plus zero byte */ - static const size_t strsiz; - char str[strsiz]; - ssize_t len = snprintf(str, strsiz, "%" PRIu64, mantisa); - assert(len < strsiz); - - // TODO sometimes we can use XXeYY format - - if (neg) - PUTC('-'); - int64_t i = len - 1; - for (; i >= 0 && i >= -dec->exponent; i--) - PUTC(str[i]); - for (int64_t z = dec->exponent; z >= 0; z--) - PUTC('0'); - if (len <= -dec->exponent) - PUTC('0'); - PUTC('.'); - for (int64_t z = -dec->exponent - 1; z >= 0; z--) - PUTC('0'); - for (; i >= 0; i--) - PUTC(str[i]); + if (dec->exponent <= 6 && dec->exponent >= -9) { + /* Pack in X.Y notation */ + bool neg = dec->mantisa < 0; + uint64_t mantisa = neg ? -dec->mantisa : dec->mantisa; + + /* The 64-bit number can ocuppy at most 20 characters plus zero byte */ + static const size_t strsiz = 21; + char str[strsiz]; + ssize_t len = snprintf(str, strsiz, "%" PRIu64, mantisa); + assert(len < strsiz); + + if (neg) + PUTC('-'); + int64_t i = len - 1; + for (; i >= 0 && i >= -dec->exponent; i--) + PUTC(str[len - i - 1]); + for (int64_t z = dec->exponent; z > 0; z--) + PUTC('0'); + if (len <= -dec->exponent) + PUTC('0'); + PUTC('.'); + for (int64_t z = -dec->exponent - len; z > 0; z--) + PUTC('0'); + for (; i >= 0; i--) + PUTC(str[len - i - 1]); + } else + /* Pack in XeY notation */ + PRINTF("%llde%d", dec->mantisa, dec->exponent); return res; } -static bool ctxpush(struct cpon_state *state, enum cp_item_type tp) { +static ssize_t ctxpush( + FILE *f, struct cpon_state *state, enum cp_item_type tp, const char *str) { + ssize_t res = 0; if (state->depth == state->cnt && state->realloc) state->realloc(state); - state->depth++; - if (state->depth <= state->cnt) { - state->ctx[state->depth - 1] = (struct cpon_state_ctx){ + if (state->depth < state->cnt) { + state->ctx[state->depth] = (struct cpon_state_ctx){ .tp = tp, .first = true, .even = true, }; - } - return state->depth <= state->cnt; + PUTS(str); + } else if (state->depth == state->cnt) + PUTS("..."); + state->depth++; + return res; } static enum cp_item_type ctxpop(struct cpon_state *state) { diff --git a/libshvchainpack/cpon_unpack.c b/libshvchainpack/cpon_unpack.c index f5484cd..8ddac4d 100644 --- a/libshvchainpack/cpon_unpack.c +++ b/libshvchainpack/cpon_unpack.c @@ -1,29 +1,41 @@ -#include "cpon.h" +#include #include #include #include -static size_t cpon_unpack_buf(FILE *f, struct cpbuf *buf, bool *ok); - - -size_t cpon_unpack(FILE *f, struct cpitem *item) { - size_t res = 0; #define GETC \ ({ \ int __v = getc(f); \ - if (__v == EOF) { \ - item->type = CP_ITEM_INVALID; \ - return res; \ - } \ res++; \ __v; \ }) +#define UNGETC(V) \ + do { \ + ungetc((V), f); \ + res--; \ + } while (false) +#define SCANF(FMT, ...) \ + ({ \ + size_t __off = ftell(f); \ + ssize_t __res = fscanf(f, FMT, __VA_ARGS__); \ + res += ftell(f) - __off; \ + __res; \ + }) + + +static size_t cpon_unpack_buf(FILE *f, struct cpitem *item, bool *ok); + + +// TODO use state +size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) { + size_t res = 0; #define EXPECT(V) \ do { \ char c = GETC; \ if (c != (V)) { \ - ungetc(c, f); \ + if (c != EOF) \ + ungetc(c, f); \ item->type = CP_ITEM_INVALID; \ return res; \ } \ @@ -33,36 +45,42 @@ size_t cpon_unpack(FILE *f, struct cpitem *item) { size_t __expects_len = strlen(V); \ for (size_t __expect_i = 0; __expect_i < __expects_len; __expect_i++) \ EXPECT((V)[__expect_i]); \ - } while(false) + } while (false) #define CALL(FUNC, ...) \ do { \ bool ok; \ res += FUNC(f, __VA_ARGS__, &ok); \ - if (!ok) { \ - item->type = CP_ITEM_INVALID; \ + if (!ok) \ return res; \ - } \ } while (false) /* Continue reading previous item */ - if ((item->type == CP_ITEM_STRING || item->type == CP_ITEM_BLOB) && - !item->as.String.last) { - CALL(cpon_unpack_buf, &item->as.String); + if ((item->type == CP_ITEM_BLOB || item->type == CP_ITEM_STRING) && + (item->as.Blob.eoff != 0 || !(item->as.Blob.flags & CPBI_F_LAST))) { + item->as.Blob.flags &= ~CPBI_F_FIRST; + CALL(cpon_unpack_buf, item); return res; } + item->type = CP_ITEM_INVALID; bool comment = false; char c; while (true) { c = GETC; + if (c == EOF) + return res; if (comment) { if (c == '*') { c = GETC; + if (c == EOF) + return res; comment = c != '/'; } continue; } - if (c == ' ' || c == '\t' || c == ':' || c == ',') + // TODO : and , should be accepted only in places we know they should be + // thanks to the cpon status. + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == ':' || c == ',') continue; if (c == '/') { EXPECT('*'); @@ -89,16 +107,82 @@ size_t cpon_unpack(FILE *f, struct cpitem *item) { break; case '-': case '0' ... '9': - // TODO + UNGETC(c); + if (SCANF("%lli", &item->as.Int) != 1) + return res; + item->type = CP_ITEM_INT; + bool has_sign = c == '-'; + c = GETC; + if (!has_sign && c == 'u') { + item->type = CP_ITEM_UINT; + } else if (c == 'e') { + item->type = CP_ITEM_DECIMAL; + item->as.Decimal.mantisa = item->as.Int; + SCANF("%i", &item->as.Decimal.exponent); + } else if (c == '.') { + item->type = CP_ITEM_DECIMAL; + item->as.Decimal.mantisa = item->as.Int; + unsigned long long dec; + ssize_t rres = res; + if (SCANF("%llu", &dec) == 1) { + item->as.Decimal.exponent = rres - res; + int64_t mult = 1; + for (int i = 0; i < -item->as.Decimal.exponent; i++) + mult *= 10; + // TODO this is not ideal because we might hit limit. We + // should add only up to the dec being zero and keep the + // rest of the exponent as it is. + item->as.Decimal.mantisa = + ((item->as.Decimal.mantisa < 0 ? -1 : 1) * dec) + + (item->as.Decimal.mantisa * mult); + } + if (item->as.Decimal.mantisa == 0) + break; + while (item->as.Decimal.mantisa % 10 == 0) { + item->as.Decimal.mantisa /= 10; + item->as.Decimal.exponent++; + } + } else + UNGETC(c); break; + case 'x': case 'b': EXPECT('"'); item->type = CP_ITEM_BLOB; - // TODO load some blob data + item->as.String.flags = CPBI_F_FIRST | CPBI_F_STREAM; + if (c == 'x') + item->as.String.flags |= CPBI_F_HEX; + item->as.String.eoff = 0; + CALL(cpon_unpack_buf, item); + break; + case 'd': + struct tm tm = (struct tm){}; + unsigned msecs; + if (SCANF("\"%u-%u-%uT%u:%u:%u.%u", &tm.tm_year, &tm.tm_mon, + &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &msecs) != 7) + return res; + tm.tm_year -= 1900; + tm.tm_mon -= 1; + c = GETC; + if (c == '+' || c == '-') { + unsigned h, m; + SCANF("%u:%u", &h, &m); + tm.tm_gmtoff = ((h * 60) + m) * 60; + tm.tm_gmtoff *= c == '-' ? -1 : 1; + } else if (c != 'Z') { + UNGETC(c); + return res; + } + EXPECT('"'); + item->type = CP_ITEM_DATETIME; + item->as.Datetime = cptmtodt(tm); + item->as.Datetime.msecs += msecs; break; case '"': item->type = CP_ITEM_STRING; - // TODO load some string + item->as.String.flags = CPBI_F_FIRST | CPBI_F_STREAM; + item->as.String.eoff = 0; + CALL(cpon_unpack_buf, item); break; case '[': item->type = CP_ITEM_LIST; @@ -119,9 +203,85 @@ size_t cpon_unpack(FILE *f, struct cpitem *item) { item->type = CP_ITEM_CONTAINER_END; break; default: - ungetc(c, f); - item->type = CP_ITEM_INVALID; + UNGETC(c); break; } return res; } + +static size_t cpon_unpack_buf(FILE *f, struct cpitem *item, bool *ok) { + if (ok) + *ok = true; + + if (item->bufsiz == 0) + return 0; /* Nowhere to place data so just inform user about type */ + + size_t i = 0; + size_t res = 0; + bool escape = false; + while (i < item->bufsiz) { + int c = getc(f); + if (c == EOF) { + if (ok) + *ok = false; + return i; + } + res++; + if (item->type == CP_ITEM_BLOB && item->as.Blob.flags & CPBI_F_HEX) { + if (c == '"') { + item->as.Blob.flags |= CPBI_F_LAST; + break; + } + UNGETC(c); + uint8_t byte; + if (SCANF("%2" SCNx8, &byte) != 1) { + if (ok) + *ok = false; + return i; + } + if (item->buf) + item->buf[i++] = byte; + } else if (escape) { + switch (c) { + case 'a': + c = '\a'; + break; + case 'b': + c = '\b'; + break; + case 't': + c = '\t'; + break; + case 'n': + c = '\n'; + break; + case 'v': + c = '\v'; + break; + case 'f': + c = '\f'; + break; + case 'r': + c = '\r'; + break; + } + if (item->type == CP_ITEM_BLOB) { + UNGETC(c); + SCANF("%X", (unsigned *)&c); + } + if (item->buf) + item->buf[i++] = c; + escape = false; + } else { + if (c == '\\') { + escape = true; + } else if (c == '"') { + item->as.Blob.flags |= CPBI_F_LAST; + break; + } else if (item->buf) + item->buf[i++] = c; + } + } + item->as.Blob.len = i; + return res; +} diff --git a/libshvchainpack/libshvchainpack.version b/libshvchainpack/libshvchainpack.version index bfdf18f..4399479 100644 --- a/libshvchainpack/libshvchainpack.version +++ b/libshvchainpack/libshvchainpack.version @@ -1,94 +1,32 @@ V0.0 { global: - # cpcp.h - cpcp_error_string; + # shv/cp.h + cp_item_type_str; + cpdectod; + cpdtodec; + cpdttotm; + cptmtodt; + chainpack_pack; + _chainpack_pack_uint; + chainpack_unpack; + _chainpack_unpack_uint; + cpon_pack; + cpon_unpack; - # cpcp_pack.h - cpcp_pack_context_init; - cpcp_pack_context_dry_run_init; - cpcp_pack_copy_byte; - cpcp_pack_copy_bytes; + # shv/cp_pack.h + cp_pack_chainpack_init; + cp_pack_cpon_init; + cp_pack_fopen; - # cpcp_unpack.h - cpcp_item_type_to_string; - cpcp_string_init; - cpcp_exponential_to_double; - cpcp_decimal_to_double; - cpcp_decimal_to_string; - cpcp_container_state_init; - cpcp_container_stack_init; - cpcp_unpack_context_init; - cpcp_unpack_context_push_container_state; - cpcp_unpack_context_top_container_state; - cpcp_unpack_context_parent_container_state; - cpcp_unpack_context_closed_container_state; - cpcp_unpack_context_pop_container_state; - cpcp_unpack_take_byte; - cpcp_unpack_peek_byte; + # shv/cp_unpack.h + cp_unpack_chainpack_init; + cp_unpack_cpon_init; + cp_unpack_skip; + cp_unpack_finish; + cp_unpack_strdup; + cp_unpack_memdup; + cp_unpack_fopen; - # chainpack.h - chainpack_packing_schema_name; - chainpack_pack_uint_data; - chainpack_pack_null; - chainpack_pack_boolean; - chainpack_pack_int; - chainpack_pack_uint; - chainpack_pack_double; - chainpack_pack_decimal; - chainpack_pack_date_time; - chainpack_pack_blob; - chainpack_pack_blob_start; - chainpack_pack_blob_cont; - chainpack_pack_string; - chainpack_pack_string_start; - chainpack_pack_string_cont; - chainpack_pack_cstring; - chainpack_pack_cstring_terminated; - chainpack_pack_cstring_start; - chainpack_pack_cstring_cont; - chainpack_pack_cstring_finish; - chainpack_pack_list_begin; - chainpack_pack_map_begin; - chainpack_pack_imap_begin; - chainpack_pack_meta_begin; - chainpack_pack_container_end; - chainpack_unpack_uint_data; - chainpack_unpack_uint_data2; - chainpack_unpack_next; - - # cpon.h - cpon_timegm; - cpon_gmtime; - cpon_pack_null; - cpon_pack_boolean; - cpon_pack_int; - cpon_pack_uint; - cpon_pack_double; - cpon_pack_decimal; - cpon_pack_date_time; - cpon_pack_blob; - cpon_pack_blob_start; - cpon_pack_blob_cont; - cpon_pack_blob_finish; - cpon_pack_string; - cpon_pack_string_terminated; - cpon_pack_string_start; - cpon_pack_string_cont; - cpon_pack_string_finish; - cpon_pack_list_begin; - cpon_pack_list_end; - cpon_pack_map_begin; - cpon_pack_map_end; - cpon_pack_imap_begin; - cpon_pack_imap_end; - cpon_pack_meta_begin; - cpon_pack_meta_end; - cpon_pack_copy_str; - cpon_pack_field_delim; - cpon_pack_key_val_delim; - cpon_unpack_skip_insignificant; - cpon_unpack_next; - cpon_unpack_date_time; local: *; }; diff --git a/libshvchainpack/meson.build b/libshvchainpack/meson.build index 4ed902c..4fec64c 100644 --- a/libshvchainpack/meson.build +++ b/libshvchainpack/meson.build @@ -1,16 +1,20 @@ libshvchainpack_sources = files( - 'chainpack.c', 'chainpack_pack.c', 'chainpack_unpack.c', - 'cpcp.c', - 'cpcp_convert.c', - 'cpon.c', + 'cp_pack.c', + 'cp_unpack.c', + 'cpdatetime.c', + 'cpdecimal.c', + 'cpon_pack.c', + 'cpon_unpack.c', ) +libshvchainpack_dependencies = [m] libshvchainpack = library( 'shvchainpack', libshvchainpack_sources, version: '0.0.0', + dependencies: libshvchainpack_dependencies, include_directories: includes, link_args: '-Wl,--version-script=' + join_paths( meson.current_source_dir(), @@ -21,6 +25,7 @@ libshvchainpack = library( install_headers(libshvchainpack_headers) libshvchainpack_dep = declare_dependency( + dependencies: libshvchainpack_dependencies, include_directories: includes, link_with: libshvchainpack, ) diff --git a/libshvrpc/libshvrpc.version b/libshvrpc/libshvrpc.version index 212ce9f..45832a2 100644 --- a/libshvrpc/libshvrpc.version +++ b/libshvrpc/libshvrpc.version @@ -1,5 +1,15 @@ V0.0 { global: + # shv/rpcmsg.h + rpcmsg_pack_request; + rpcmsg_pack_signal; + rpcmsg_access_str; + rpcmsg_access_extract; + rpcmsg_access_unpack; + rpcmsg_meta_unpack; + rpcmsg_pack_response; + + # shv/rpcurl.h rpcurl_parse; rpcurl_free; rpcurl_str; diff --git a/libshvrpc/meson.build b/libshvrpc/meson.build index 9300c44..a10d004 100644 --- a/libshvrpc/meson.build +++ b/libshvrpc/meson.build @@ -4,7 +4,9 @@ libshvrpc_sources = [ 'rpcclient_datagram.c', 'rpcclient_serial.c', 'rpcclient_stream.c', - 'rpcmsg.c', + 'rpcmsg_access.c', + 'rpcmsg_meta.c', + 'rpcmsg_pack.c', 'rpcurl.c', ), gperf.process('rpcmsg_access.gperf'), diff --git a/libshvrpc/rpcclient.c b/libshvrpc/rpcclient.c index 2499762..f15eebf 100644 --- a/libshvrpc/rpcclient.c +++ b/libshvrpc/rpcclient.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -8,72 +7,25 @@ #include -struct rpcclient_msg rpcclient_next_message(rpcclient_t client) { - struct rpcclient_msg res = { - .ctx = client->nextmsg(client), - }; - if (res.ctx) { - const char *t = cpcp_unpack_take_byte(res.ctx); - if (t) - res.format = *t; - } - return res; -} - -const cpcp_item *rpcclient_next_item(struct rpcclient_msg msg) { - static const cpcp_item invalid = {.type = CPCP_ITEM_INVALID}; - if (msg.ctx == NULL) - return &invalid; - switch (msg.format) { - case CPCP_ChainPack: - chainpack_unpack_next(msg.ctx); - break; - case CPCP_Cpon: - cpon_unpack_next(msg.ctx); - break; - default: - return &invalid; - } - if (msg.ctx->err_no != CPCP_RC_OK) - return &invalid; - return &msg.ctx->item; +void rpcclient_log_lock(struct rpcclient_logger *logger, bool in) { + if (logger == NULL) + return; + // TODO signal interrupt + sem_wait(&logger->semaphore); + fputs(in ? "<= " : "=> ", logger->f); } -bool rpcclient_skip_item(struct rpcclient_msg msg) { - unsigned depth = 0; - do { - const cpcp_item *i = rpcclient_next_item(msg); - switch (i->type) { - case CPCP_ITEM_STRING: - case CPCP_ITEM_BLOB: - while (!i->as.String.last_chunk) { - i = rpcclient_next_item(msg); - assert(i->type == CPCP_ITEM_STRING || - i->type == CPCP_ITEM_BLOB); - } - break; - case CPCP_ITEM_LIST: - case CPCP_ITEM_MAP: - case CPCP_ITEM_IMAP: - case CPCP_ITEM_META: - depth++; - break; - case CPCP_ITEM_CONTAINER_END: - depth--; - break; - case CPCP_ITEM_INVALID: - return false; - default: - break; - } - } while (depth); - return true; +void rpcclient_log_item(struct rpcclient_logger *logger, const struct cpitem *item) { + if (logger == NULL) + return; + cpon_pack(logger->f, &logger->cpon_state, item); } -void rpcclient_disconnect(rpcclient_t client) { - if (client == NULL || client->disconnect == NULL) +void rpcclient_log_unlock(struct rpcclient_logger *logger) { + if (logger == NULL) return; - client->disconnect(client); + fputc('\n', logger->f); + sem_post(&logger->semaphore); } struct rpcclient *rpcclient_connect(const struct rpcurl *url) { diff --git a/libshvrpc/rpcclient_stream.c b/libshvrpc/rpcclient_stream.c index ec683d7..f802ac3 100644 --- a/libshvrpc/rpcclient_stream.c +++ b/libshvrpc/rpcclient_stream.c @@ -1,3 +1,4 @@ +#include "shv/cp.h" #include #include #include @@ -14,103 +15,114 @@ struct rpcclient_stream { struct rpcclient c; - int rfd, wfd; - uint8_t unpackbuf[SHV_UNPACK_BUFSIZ]; - cpcp_unpack_context unpack; - size_t msglen; - uint8_t packbuf[SHV_PACK_BUFSIZ]; - cpcp_pack_context pack; + /* Read */ + int rfd; + FILE *rf; + ssize_t rmsgoff; + /* Write */ + int wfd; + FILE *fbuf; + char *buf; + size_t bufsiz; }; -static void overflow_handler(struct cpcp_pack_context *pack, size_t size_hint) { - struct rpcclient_stream *c = (struct rpcclient_stream *)(pack - - offsetof(struct rpcclient_stream, pack)); - - size_t to_send = pack->current - pack->start; - char *ptr_data = pack->start; - while (to_send > 0) { - ssize_t i = write(c->wfd, ptr_data, to_send); - if (i == -1) { - if (errno == EINTR) - continue; - printf("Overflow error: %s\n", strerror(errno)); - pack->current = pack->end; /* signal error */ - return; - } - ptr_data += i; - to_send -= i; - } - pack->current = pack->start; -} - -static size_t underflow_handler(struct cpcp_unpack_context *unpack) { - struct rpcclient_stream *c = (struct rpcclient_stream *)(unpack - - offsetof(struct rpcclient_stream, unpack)); - - c->msglen -= unpack->end - unpack->start; +ssize_t cookie_read(void *cookie, char *buf, size_t size) { + struct rpcclient_stream *c = (struct rpcclient_stream *)cookie; + if (c->rmsgoff < 0) + /* Read only byte by byte when receiving message length to not feed too + * much bytes to the FILE. + */ + size = 1; + else if (c->rmsgoff < size) + /* Read up to the end of the message and then signal EOF by reading 0. */ + size = c->rmsgoff; ssize_t i; do - i = read(c->rfd, (void *)unpack->start, - c->msglen >= 0 && c->msglen < SHV_UNPACK_BUFSIZ ? c->msglen - : SHV_UNPACK_BUFSIZ); + i = read(c->rfd, buf, size); while (i == -1 && errno == EINTR); - if (i < 0) { - printf("Underflow error: %s\n", strerror(errno)); - return 0; - } - unpack->current = unpack->start; - unpack->end = unpack->start + i; + c->rmsgoff -= i; return i; } -cpcp_unpack_context *nextmsg(struct rpcclient *client) { - struct rpcclient_stream *sclient = (struct rpcclient_stream *)client; - sclient->msglen = -1; - sclient->msglen = chainpack_unpack_uint_data(&sclient->unpack, NULL); - if (sclient->unpack.err_no != CPCP_RC_OK) - return NULL; // TODO error - return &sclient->unpack; +static size_t cp_unpack_stream(void *ptr, struct cpitem *item) { + struct rpcclient_stream *c = ptr - offsetof(struct rpcclient, unpack); + // TODO log + return chainpack_unpack(c->rf, item); } -cpcp_pack_context *packmsg(struct rpcclient *client, cpcp_pack_context *prev) { - struct rpcclient_stream *sclient = (struct rpcclient_stream *)client; - if (prev == NULL) { - cpcp_pack_context_dry_run_init(&sclient->pack); - return &sclient->pack; - } - if (prev->start == NULL) { - assert(prev == &sclient->pack); - size_t len = prev->bytes_written; - cpcp_pack_context_init(&sclient->pack, sclient->packbuf, - SHV_PACK_BUFSIZ, overflow_handler); - chainpack_pack_uint_data(&sclient->pack, len); - return &sclient->pack; +static bool nextmsg_stream(struct rpcclient *client) { + struct rpcclient_stream *c = (struct rpcclient_stream *)client; + c->rmsgoff = -1; + unsigned long long rmsgoff; + bool ok; + _chainpack_unpack_uint(c->rf, &rmsgoff, &ok); + /* Note: we use stack allocated variable here because cookie_read might be + * called multiple times and we always want to have -1 in rmsgoff; + */ + c->rmsgoff = rmsgoff; + if (ok && fgetc(c->rf) != CP_ChainPack) + ok = false; + // TODO log lock + return ok; +} + +static ssize_t cp_pack_stream(void *ptr, const struct cpitem *item) { + struct rpcclient_stream *c = ptr - offsetof(struct rpcclient, unpack); + // TODO log + return chainpack_pack(c->fbuf, item); +} + +static bool sendmsg_stream(struct rpcclient *client) { + struct rpcclient_stream *c = (struct rpcclient_stream *)client; + fflush(c->fbuf); + size_t len = ftell(c->fbuf); + _chainpack_pack_uint(c->rf, len); + ssize_t i = 0; + while (i < len) { + ssize_t r = write(c->wfd, c->buf, len); + if (r == -1) { + if (errno == EINTR) + continue; + return false; + } + i += r; } - return NULL; + fseek(c->fbuf, 0, SEEK_SET); + return true; } static void disconnect(rpcclient_t client) { - struct rpcclient_stream *sclient = (struct rpcclient_stream *)client; - close(sclient->rfd); - if (sclient->rfd != sclient->wfd) - close(sclient->wfd); - free(sclient); + struct rpcclient_stream *c = (struct rpcclient_stream *)client; + fclose(c->rf); + fclose(c->fbuf); + close(c->rfd); + if (c->rfd != c->wfd) + close(c->wfd); + free(c); } rpcclient_t rpcclient_stream_new(int readfd, int writefd) { + // TODO check for allocation and open failures struct rpcclient_stream *res = malloc(sizeof *res); - res->c = (struct rpcclient){ - .nextmsg = nextmsg, - .packmsg = packmsg, - .disconnect = disconnect, + FILE *f = fopencookie(res, "r", (cookie_io_functions_t){.read = cookie_read}); + *res = (struct rpcclient_stream){ + .c = + (struct rpcclient){ + .unpack = cp_unpack_stream, + .nextmsg = nextmsg_stream, + .pack = cp_pack_stream, + .sendmsg = sendmsg_stream, + .disconnect = disconnect, + .logger = NULL, + }, + .rf = f, + .rfd = readfd, + .rmsgoff = 0, + .wfd = writefd, + .bufsiz = 0, }; - res->unpackbuf[0] = 0; /* Note: just to shut up warning */ - cpcp_unpack_context_init( - &res->unpack, res->unpackbuf, 0, underflow_handler, NULL); - res->rfd = readfd; - res->wfd = writefd; - res->msglen = 0; + res->fbuf = open_memstream(&res->buf, &res->bufsiz); return &res->c; } diff --git a/libshvrpc/rpcmsg.c b/libshvrpc/rpcmsg.c index 800b34d..359d04a 100644 --- a/libshvrpc/rpcmsg.c +++ b/libshvrpc/rpcmsg.c @@ -1,59 +1,6 @@ -#include -#include -#include -#include "rpcmsg_access.gperf.h" -enum msgtags { - MSG_TAG_META_TYPE_ID = 1, - MSG_TAG_META_TYPE_NAMESPACE_ID, - MSG_TAG_REQUEST_ID = 8, - MSG_TAG_SHV_PATH, - MSG_TAG_METHOD, - MSG_TAG_CALLER_IDS, - MSG_TAG_REV_CALLER_IDS, - MSG_TAG_ACCESS_GRANT, - MSG_TAG_USER_ID, -}; -enum msgkeys { - MSG_KEY_PARAMS = 1, - MSG_KEY_RESULT, - MSG_KEY_ERROR, -}; - - -void rpcmsg_pack_request(cpcp_pack_context *pack, const char *path, - const char *method, int64_t rid) { - chainpack_pack_meta_begin(pack); - chainpack_pack_int(pack, MSG_TAG_META_TYPE_ID); - chainpack_pack_int(pack, 1); - chainpack_pack_int(pack, MSG_TAG_REQUEST_ID); - chainpack_pack_int(pack, rid); - chainpack_pack_int(pack, MSG_TAG_SHV_PATH); - chainpack_pack_cstring_terminated(pack, path); - chainpack_pack_int(pack, MSG_TAG_METHOD); - chainpack_pack_cstring_terminated(pack, method); - chainpack_pack_container_end(pack); - - chainpack_pack_imap_begin(pack); - chainpack_pack_int(pack, MSG_KEY_PARAMS); -} - -void rpcmsg_pack_signal( - cpcp_pack_context *pack, const char *path, const char *method) { - chainpack_pack_meta_begin(pack); - chainpack_pack_int(pack, MSG_TAG_META_TYPE_ID); - chainpack_pack_int(pack, 1); - chainpack_pack_int(pack, MSG_TAG_SHV_PATH); - chainpack_pack_cstring_terminated(pack, path); - chainpack_pack_int(pack, MSG_TAG_METHOD); - chainpack_pack_cstring_terminated(pack, method); - chainpack_pack_container_end(pack); - - chainpack_pack_imap_begin(pack); - chainpack_pack_int(pack, MSG_KEY_PARAMS); -} static size_t roundup2(size_t n) { n--; @@ -100,63 +47,7 @@ bool rpcmsg_str_truncated(const struct rpcmsg_str *str) { return true; } -bool rpcmsg_unpack_access(struct rpcclient_msg msg, enum rpcmsg_access *acc) { - const cpcp_item *i; - size_t off = 0; - char buf[4]; /* The maximum for known access levels is 4 characters */ -#define PARSE_ACCESS \ - do { \ - if (acc && off <= 4) { \ - const struct gperf_rpcmsg_access_match *match = \ - gperf_rpcmsg_access(buf, off); \ - if (match) \ - *acc = match->acc; \ - } \ - } while (false) - - do { - i = rpcclient_next_item(msg); - if (i->type != CPCP_ITEM_STRING) - return false; - for (size_t p = 0; p < i->as.String.chunk_size; p++) { - char c = i->as.String.chunk_start[p]; - if (c == ',') { - PARSE_ACCESS; - off = 0; - } else if (off < 4) - buf[off++] = c; - } - } while (!i->as.String.last_chunk); - PARSE_ACCESS; - return true; -#undef PARSE_ACCESS -} - -const char *rpcmsg_access_str(enum rpcmsg_access acc) { - switch (acc) { - case RPCMSG_ACC_BROWSE: - return "bws"; - case RPCMSG_ACC_READ: - return "rd"; - case RPCMSG_ACC_WRITE: - return "wr"; - case RPCMSG_ACC_COMMAND: - return "cmd"; - case RPCMSG_ACC_CONFIG: - return "cfg"; - case RPCMSG_ACC_SERVICE: - return "srv"; - case RPCMSG_ACC_SUPER_SERVICE: - return "ssrv"; - case RPCMSG_ACC_DEVEL: - return "dev"; - case RPCMSG_ACC_ADMIN: - return "su"; - default: - return ""; - } -} static void ensure_rinfo(struct rpcmsg_request_info **rinfo, bool increase) { size_t siz = (*rinfo)->cidscnt + (*rinfo)->rcidscnt + (increase ? 1 : 0); diff --git a/libshvrpc/rpcmsg_access.c b/libshvrpc/rpcmsg_access.c new file mode 100644 index 0000000..f918bae --- /dev/null +++ b/libshvrpc/rpcmsg_access.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#include "rpcmsg_access.gperf.h" +#include "shv/cp.h" + + +const char *rpcmsg_access_str(enum rpcmsg_access acc) { + switch (acc) { + case RPCMSG_ACC_BROWSE: + return "bws"; + case RPCMSG_ACC_READ: + return "rd"; + case RPCMSG_ACC_WRITE: + return "wr"; + case RPCMSG_ACC_COMMAND: + return "cmd"; + case RPCMSG_ACC_CONFIG: + return "cfg"; + case RPCMSG_ACC_SERVICE: + return "srv"; + case RPCMSG_ACC_SUPER_SERVICE: + return "ssrv"; + case RPCMSG_ACC_DEVEL: + return "dev"; + case RPCMSG_ACC_ADMIN: + return "su"; + default: + return ""; + } +} + + +enum rpcmsg_access rpcmsg_access_extract(const char *str) { + char *term; + do { + term = strchrnul(str, ','); + const struct gperf_rpcmsg_access_match *match = + gperf_rpcmsg_access(str, term - str); + if (match) + return match->acc; + str = term + 1; + } while (*term == ','); + return RPCMSG_ACC_INVALID; +} + +enum rpcmsg_access rpcmsg_access_unpack(cp_unpack_t unpack, struct cpitem *item) { + FILE *f = cp_unpack_fopen(unpack, item); + if (item->type != CP_ITEM_STRING) + goto error; + /* The longest access right is four characters. We load five to prevent + * match on same prefix and need one more for null byte. + */ + char str[6]; + while (true) { + if (fscanf(f, "%5[^,]", str) != 1) + goto error; + const struct gperf_rpcmsg_access_match *match = + gperf_rpcmsg_access(str, strlen(str)); + if (match) { + fclose(f); + if (!(item->as.String.flags & CPBI_F_LAST)) + cp_unpack_skip(unpack, item); + return match->acc; + } + int c; + do + c = fgetc(f); + while (c != EOF && c != ','); + } +error: + if (f) + fclose(f); + return RPCMSG_ACC_INVALID; +} diff --git a/libshvrpc/rpcmsg_meta.c b/libshvrpc/rpcmsg_meta.c new file mode 100644 index 0000000..a0a8228 --- /dev/null +++ b/libshvrpc/rpcmsg_meta.c @@ -0,0 +1,150 @@ +#include "shv/cp.h" +#include +#include +#include +#include + + +static bool unpack_string(cp_unpack_t unpack, struct cpitem *item, char **dest, + size_t limit, struct obstack *obstack) { + if (obstack) { + do { + /* We are using here fast growing in a reverse way. We first store + * data and only after that we grow object. We do this because we + * do not know number of needed bytes upfront. This way we do not + * have to use temporally buffer and copy data around. We copy it + * directly to the obstack's buffer. + */ + // TODO ensure that limit is considered + char *ptr = obstack_next_free(obstack); + int siz = obstack_room(obstack); + if (siz == 0) { + obstack_1grow(obstack, '\0'); + siz = obstack_room(obstack) + 1; + assert(siz > 0); + } + ssize_t res = cp_unpack_strncpy(unpack, item, ptr, siz); + if (res == -1) { + obstack_free(obstack, obstack_base(obstack)); + return false; + } + obstack_blank_fast(obstack, res); + if (res < siz) + break; + } while (true); + obstack_1grow(obstack, '\0'); + *dest = obstack_finish(obstack); + } else + cp_unpack_strncpy(unpack, item, *dest, limit); + return true; +} + +ssize_t obstack_write(void *cookie, const char *buf, size_t size) { + struct obstack *obstack = cookie; + obstack_grow(obstack, buf, size); + return size; +} + +static bool unpack_cids(cp_unpack_t unpack, struct cpitem *item, + struct rpcmsg_ptr *ptr, size_t limit, struct obstack *obstack) { + FILE *f; + if (obstack) { + // TODO respect limit + f = fopencookie( + obstack, "w", (cookie_io_functions_t){.write = obstack_write}); + setvbuf(f, NULL, _IONBF, 0); + } else + f = fmemopen(ptr->ptr, limit, "w"); + + bool res = true; + unsigned depth = 0; + do { + cp_unpack(unpack, item); + if (item->type == CP_ITEM_INVALID || chainpack_pack(f, item) == -1) { + /* Note: chainpack_pack Can fail only due to not enough space in the + * buffer. + * In case of cids we consider it as a meta parsing failure because + * without it we can't send response correctly anyway. + */ + res = false; + break; + } + switch (item->type) { + case CP_ITEM_LIST: + case CP_ITEM_MAP: + case CP_ITEM_IMAP: + case CP_ITEM_META: + depth++; + break; + case CP_ITEM_CONTAINER_END: + depth--; + break; + default: + break; + } + } while (depth > 0); + fclose(f); + if (obstack) { + ptr->siz = obstack_object_size(obstack); + ptr->ptr = obstack_finish(obstack); + } + return res; +} + +bool rpcmsg_meta_unpack(cp_unpack_t unpack, struct cpitem *item, + struct rpcmsg_meta *meta, struct rpcmsg_meta_limits *limits, + struct obstack *obstack) { + meta->type = RPCMSG_T_INVALID; + cp_unpack(unpack, item); + if (item->type != CP_ITEM_META) + return false; + + bool has_rid = false; + meta->request_id = INT64_MIN; + meta->access_grant = RPCMSG_ACC_INVALID; + + /* Go trough meta and parse what we want from it */ + while (true) { + cp_unpack(unpack, item); + if (item->type == CP_ITEM_CONTAINER_END) + break; + if (item->type != CP_ITEM_INT) + return false; + switch (item->as.Int) { + case RPCMSG_TAG_REQUEST_ID: + cp_unpack(unpack, item); + has_rid = true; + if (item->type == CP_ITEM_INT) + meta->request_id = item->as.Int; + else if (item->type == CP_ITEM_UINT) + meta->request_id = item->as.UInt; + else + return false; + break; + case RPCMSG_TAG_SHV_PATH: + if (!unpack_string(unpack, item, &meta->path, + limits ? limits->path : -1, obstack)) + return false; + break; + case RPCMSG_TAG_METHOD: + if (!unpack_string(unpack, item, &meta->method, + limits ? limits->method : -1, obstack)) + return false; + break; + case RPCMSG_TAG_CALLER_IDS: + if (!unpack_cids(unpack, item, &meta->cids, + limits ? limits->cids : -1, obstack)) + return false; + break; + case RPCMSG_TAG_ACCESS_GRANT: + meta->access_grant = rpcmsg_access_unpack(unpack, item); + break; + default: + cp_unpack_skip(unpack, item); + break; + } + } + meta->type = meta->method ? (has_rid ? RPCMSG_T_REQUEST : RPCMSG_T_SIGNAL) + : (has_rid ? RPCMSG_T_RESPONSE : RPCMSG_T_INVALID); + return true; +} diff --git a/libshvrpc/rpcmsg_pack.c b/libshvrpc/rpcmsg_pack.c new file mode 100644 index 0000000..c14aa32 --- /dev/null +++ b/libshvrpc/rpcmsg_pack.c @@ -0,0 +1,90 @@ +#include "shv/cp_pack.h" +#include +#include +#include +#include + + +void rpcmsg_pack_request( + cp_pack_t pack, const char *path, const char *method, int64_t rid) { + cp_pack_meta_begin(pack); + cp_pack_int(pack, RPCMSG_TAG_META_TYPE_ID); + cp_pack_int(pack, 1); + cp_pack_int(pack, RPCMSG_TAG_REQUEST_ID); + cp_pack_int(pack, rid); + cp_pack_int(pack, RPCMSG_TAG_SHV_PATH); + cp_pack_str(pack, path); + cp_pack_int(pack, RPCMSG_TAG_METHOD); + cp_pack_str(pack, method); + cp_pack_container_end(pack); + + cp_pack_imap_begin(pack); + cp_pack_int(pack, RPCMSG_KEY_PARAMS); +} + +void rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) { + cp_pack_meta_begin(pack); + cp_pack_int(pack, RPCMSG_TAG_META_TYPE_ID); + cp_pack_int(pack, 1); + cp_pack_int(pack, RPCMSG_TAG_SHV_PATH); + cp_pack_str(pack, path); + cp_pack_int(pack, RPCMSG_TAG_METHOD); + cp_pack_str(pack, method); + cp_pack_container_end(pack); + + cp_pack_imap_begin(pack); + cp_pack_int(pack, RPCMSG_KEY_PARAMS); +} + +static void _pack_response(cp_pack_t pack, struct rpcmsg_meta *meta) { + cp_pack_meta_begin(pack); + cp_pack_int(pack, RPCMSG_TAG_META_TYPE_ID); + cp_pack_int(pack, 1); + cp_pack_int(pack, RPCMSG_TAG_SHV_PATH); + cp_pack_str(pack, meta->path); + cp_pack_int(pack, RPCMSG_TAG_METHOD); + cp_pack_str(pack, meta->method); + // TODO copy over other parameters + cp_pack_container_end(pack); + + cp_pack_imap_begin(pack); +} + +void rpcmsg_pack_response(cp_pack_t pack, struct rpcmsg_meta *meta) { + _pack_response(pack, meta); + cp_pack_int(pack, RPCMSG_KEY_RESULT); +} + +void rpcmsg_pack_error(cp_pack_t pack, struct rpcmsg_meta *meta, + enum rpcmsg_error error, const char *msg) { + _pack_response(pack, meta); + cp_pack_int(pack, RPCMSG_KEY_ERROR); + cp_pack_imap_begin(pack); + cp_pack_int(pack, RPCMSG_E_KEY_CODE); + cp_pack_int(pack, error); + cp_pack_int(pack, RPCMSG_E_KEY_MESSAGE); + cp_pack_str(pack, msg); + cp_pack_container_end(pack); + cp_pack_container_end(pack); +} + +void rpcmsg_pack_ferror(cp_pack_t pack, struct rpcmsg_meta *meta, + enum rpcmsg_error error, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + rpcmsg_pack_vferror(pack, meta, error, fmt, args); + va_end(args); +} + +void rpcmsg_pack_vferror(cp_pack_t pack, struct rpcmsg_meta *meta, + enum rpcmsg_error error, const char *fmt, va_list args) { + va_list argsdup; + va_copy(argsdup, args); + size_t siz = vsnprintf(NULL, 0, fmt, argsdup); + va_end(argsdup); + + char msg[siz + 1]; + vsnprintf(msg, siz + 1, fmt, args); + + rpcmsg_pack_error(pack, meta, error, msg); +} diff --git a/meson.build b/meson.build index 61280da..e7e7bb6 100644 --- a/meson.build +++ b/meson.build @@ -11,6 +11,7 @@ isnuttx = ( cc.get_define('__NuttX__') != '' # Won't be defined outside NuttX ) fs = import('fs') +cmake = import('cmake') add_project_arguments('-D_GNU_SOURCE', language: 'c') @@ -19,14 +20,25 @@ add_project_arguments( '-DPROJECT_VERSION="@0@"'.format(meson.project_version()), language: 'c', ) -add_project_arguments( - '-DSHV_UNPACK_BUFSIZ=' + get_option('unpack_bufsiz').to_string(), - language: 'c', -) -add_project_arguments( - '-DSHV_PACK_BUFSIZ=' + get_option('pack_bufsiz').to_string(), - language: 'c', -) + + +if not isnuttx + m = cc.find_library('m', required : false) + argp = cc.has_function('argp_parse') ? declare_dependency() : cc.find_library('argp') + uriparser = dependency('liburiparser', version: '>= 0.9.0') +else + m = declare_dependency() + argp = declare_dependency() + uriparser_var = cmake.subproject_options() + uriparser_var.add_cmake_defines({ + 'URIPARSER_BUILD_TESTS': false, + 'URIPARSER_BUILD_DOCS': false, + 'URIPARSER_BUILD_TOOLS': false, + 'URIPARSER_BUILD_WCHAR_T': false, + }) + uriparser_proj = cmake.subproject('uriparser', options: uriparser_var) + uriparser = uriparser_proj.dependency('uriparser') +endif gperf = generator(find_program('gperf'), @@ -34,12 +46,20 @@ gperf = generator(find_program('gperf'), arguments: ['@EXTRA_ARGS@', '--output-file=@OUTPUT@', '@INPUT@'] ) -uriparser = dependency('liburiparser', version: '>= 0.9.0') - subdir('include') subdir('libshvchainpack') subdir('libshvrpc') +subdir('libshvbroker') + +cpconv_opt = get_option('cpconv') +if cpconv_opt + subdir('cpconv') +endif +shvbroker_opt = get_option('shvbroker') +if shvbroker_opt + subdir('shvbroker') +endif test_buildtypes = ['debug', 'debugoptimized'] diff --git a/meson_options.txt b/meson_options.txt index e486a2a..ca49ca7 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -5,20 +5,16 @@ option( description: 'Expect tests to be build and check for their dependencies', ) -# libshvchainpack - -# libshvrpc +# Applications option( - 'unpack_bufsiz', - type: 'integer', - min: 1, - value: 8, - description: 'Size of the buffer in bytes for the unpacker.', + 'cpconv', + type: 'boolean', + value: true, + description: 'Build cpconv tool that converts between Chainpack and Cpon.', ) option( - 'pack_bufsiz', - type: 'integer', - min: 1, - value: 8, - description: 'Size of the buffer in bytes for the packer.', + 'shvbroker', + type: 'boolean', + value: true, + description: 'Build SHV RPC Broker standalone application.', ) diff --git a/shvbroker/config.c b/shvbroker/config.c new file mode 100644 index 0000000..e037bd6 --- /dev/null +++ b/shvbroker/config.c @@ -0,0 +1,50 @@ +#include "config.h" +#include +#include +#include + + +static void print_usage(const char *argv0) { + fprintf(stderr, "%s [-Vh] [-c FILE]\n", argv0); +} + +static void print_help(const char *argv0) { + print_usage(argv0); + fprintf(stderr, "\n"); + fprintf(stderr, "Arguments:\n"); + fprintf(stderr, " -c FILE Configuration file\n"); + fprintf(stderr, " -V Print version and exit\n"); + fprintf(stderr, " -h Print this help text\n"); +} + +static void parse_opts(const char **confpath, int argc, char **argv) { + int c; + while ((c = getopt(argc, argv, "c:Vh")) != -1) { + switch (c) { + case 'c': + *confpath = argv[optind]; + break; + case 'V': + printf("%s " PROJECT_VERSION, argv[0]); + exit(0); + case 'h': + print_help(argv[0]); + exit(0); + default: + print_usage(argv[0]); + fprintf(stderr, "Invalid option: -%c\n", c); + exit(2); + } + } + if (optind < argc) { + fprintf(stderr, "Invalid argument: %s\n", argv[optind + 1]); + exit(2); + } +} + + +void load_config(int argc, char **argv) { + const char *confpath = NULL; + parse_opts(&confpath, argc, argv); + // TODO load config +} diff --git a/shvbroker/config.h b/shvbroker/config.h new file mode 100644 index 0000000..6f44de4 --- /dev/null +++ b/shvbroker/config.h @@ -0,0 +1,16 @@ +/***************************************************************************** + * Copyright (C) 2022 Elektroline Inc. All rights reserved. + * K Ladvi 1805/20 + * Prague, 184 00 + * info@elektroline.cz (+420 284 021 111) + *****************************************************************************/ +#ifndef _FOO_CONFIG_H_ +#define _FOO_CONFIG_H_ + + +/* Parse arguments and load configuration file + * Returned pointer is to statically allocated (do not call free on it). + */ +void load_config(int argc, char **argv) __attribute__((nonnull)); + +#endif diff --git a/shvbroker/main.c b/shvbroker/main.c new file mode 100644 index 0000000..0de1b67 --- /dev/null +++ b/shvbroker/main.c @@ -0,0 +1,12 @@ +#include +#include +#include +#include +#include "config.h" + + +int main(int argc, char **argv) { + load_config(argc, argv); + + return 0; +} diff --git a/shvbroker/meson.build b/shvbroker/meson.build new file mode 100644 index 0000000..e0c9cf4 --- /dev/null +++ b/shvbroker/meson.build @@ -0,0 +1,22 @@ +shvbroker_sources = files('config.c') +shvbroker_dependencies = [libshvbroker_dep, argp] + + +if isnuttx + shvbroker = static_library( + 'shvbroker', + shvbroker_sources + ['main.c'], + dependencies: shvbroker_dependencies, + install: not meson.is_subproject(), + c_args: '-Dmain=shvbroker_main', + ) +else + shvbroker = executable( + 'shvbroker', + shvbroker_sources + ['main.c'], + dependencies: shvbroker_dependencies, + install: true, + ) +endif + +shvbroker_internal_includes = include_directories('.') diff --git a/subprojects/uriparser.wrap b/subprojects/uriparser.wrap new file mode 100644 index 0000000..3a5c423 --- /dev/null +++ b/subprojects/uriparser.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://github.com/uriparser/uriparser.git +revision = uriparser-0.9.7 diff --git a/tests/unit/libshvchainpack/chainpack.c b/tests/unit/libshvchainpack/chainpack.c index 8476aed..0917a92 100644 --- a/tests/unit/libshvchainpack/chainpack.c +++ b/tests/unit/libshvchainpack/chainpack.c @@ -1,435 +1,89 @@ -#include -#include -#include -#include +#include #define SUITE "chainpack" #include -#include "packunpack.h" -#define UNPACK_NEXT \ - { \ - chainpack_unpack_next(&unpack); \ - ck_assert_int_eq(unpack.err_no, CPCP_RC_OK); \ - } \ - while (false) +#include "packstream.h" +#include "bdata.h" +#include "item.h" -TEST_CASE(pack, setup_pack, teardown_pack){}; -TEST_CASE(unpack){}; - - -static const uint8_t cpnull[] = {0x80}; -TEST(pack, pack_none) { - chainpack_pack_null(&pack); - ck_assert_stash(cpnull, sizeof(cpnull)); -} -END_TEST -TEST(unpack, unpack_null) { - cpcp_unpack_context_init(&unpack, cpnull, sizeof(cpnull), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_NULL); -} -END_TEST - -static const struct { - const bool v; - const uint8_t cp; -} cpbool_d[] = {{true, 0xfe}, {false, 0xfd}}; -ARRAY_TEST(pack, pack_bool, cpbool_d) { - chainpack_pack_boolean(&pack, _d.v); - ck_assert_uint_eq(pack.current - pack.start, 1); - ck_assert_int_eq(stashbuf[0], _d.cp); -} -END_TEST -ARRAY_TEST(unpack, unpack_bool, cpbool_d) { - cpcp_unpack_context_init(&unpack, &_d.cp, 1, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_BOOLEAN); - ck_assert(unpack.item.as.Bool == _d.v); -} -END_TEST - -static const struct { - const int64_t v; - const struct bdata cp; -} cpint_d[] = { - {0, B(0x40)}, - {-1, B(0x82, 0x41)}, - {-2, B(0x82, 0x42)}, - {7, B(0x47)}, - {42, B(0x6a)}, - {2147483647, B(0x82, 0xf0, 0x7f, 0xff, 0xff, 0xff)}, - {4294967295, B(0x82, 0xf1, 0x00, 0xff, 0xff, 0xff, 0xff)}, - {9007199254740991, B(0x82, 0xf3, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)}, - {-9007199254740991, B(0x82, 0xf3, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)}, -}; -ARRAY_TEST(pack, pack_int, cpint_d) { - chainpack_pack_int(&pack, _d.v); - ck_assert_stash(_d.cp.v, _d.cp.len); -} -END_TEST -ARRAY_TEST(unpack, unpack_int, cpint_d) { - cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, _d.v); -} -END_TEST - -static const struct { - const uint64_t v; - const struct bdata cp; -} cpuint_d[] = { - {0, B(0x00)}, - {1, B(0x01)}, - {2147483647, B(0x81, 0xf0, 0x7f, 0xff, 0xff, 0xff)}, - {4294967295, B(0x81, 0xf0, 0xff, 0xff, 0xff, 0xff)}, -}; -ARRAY_TEST(pack, pack_uint, cpuint_d) { - chainpack_pack_uint(&pack, _d.v); - ck_assert_stash(_d.cp.v, _d.cp.len); -} -END_TEST -ARRAY_TEST(unpack, unpack_uint, cpuint_d) { - cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_UINT); - ck_assert_uint_eq(unpack.item.as.UInt, _d.v); -} -END_TEST - -static const struct { - const double v; - const struct bdata cp; -} cpdouble_d[] = { - {223.0, B(0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x6b, 0x40)}, -}; -ARRAY_TEST(pack, pack_double, cpdouble_d) { - chainpack_pack_double(&pack, _d.v); - ck_assert_stash(_d.cp.v, _d.cp.len); -} -END_TEST -ARRAY_TEST(unpack, unpack_double, cpdouble_d) { - cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_DOUBLE); - ck_assert_double_eq(unpack.item.as.Double, _d.v); -} -END_TEST - -static const struct { - const cpcp_decimal v; - const struct bdata cp; -} cpdecimal_d[] = { - {.v = {.mantisa = 23, .exponent = -1}, B(0x8c, 0x17, 0x41)}, -}; -ARRAY_TEST(pack, pack_decimal, cpdecimal_d) { - chainpack_pack_decimal(&pack, &_d.v); - ck_assert_stash(_d.cp.v, _d.cp.len); -} -END_TEST -ARRAY_TEST(unpack, unpack_decimal, cpdecimal_d) { - cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_DECIMAL); - ck_assert_int_eq(unpack.item.as.Decimal.mantisa, _d.v.mantisa); - ck_assert_int_eq(unpack.item.as.Decimal.exponent, _d.v.exponent); -} -END_TEST -static const struct { - const char *const v; - const struct bdata cp; -} cpstring_d[] = { - {.v = "", B(0x86, 0x00)}, - {.v = "foo", B(0x86, 0x03, 'f', 'o', 'o')}, -}; -ARRAY_TEST(pack, pack_string, cpstring_d) { - chainpack_pack_string(&pack, _d.v, strlen(_d.v)); - ck_assert_stash(_d.cp.v, _d.cp.len); -} -END_TEST -ARRAY_TEST(unpack, unpack_string, cpstring_d) { - cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); - ck_assert_int_eq(unpack.item.as.String.chunk_size, strlen(_d.v)); - ck_assert_str_eq(unpack.item.as.String.chunk_start, _d.v); -} -END_TEST - -static const struct { - const char *const v; - const struct bdata cp; -} cpcstring_d[] = { - {.v = "", B(0x8e, 0x00)}, - {.v = "foo", B(0x8e, 'f', 'o', 'o', 0x00)}, -}; -ARRAY_TEST(pack, pack_cstring, cpcstring_d) { - chainpack_pack_cstring_terminated(&pack, _d.v); - ck_assert_stash(_d.cp.v, _d.cp.len); -} -END_TEST -ARRAY_TEST(unpack, unpack_cstring, cpcstring_d) { - cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); - ck_assert_int_eq(unpack.item.as.String.chunk_size, strlen(_d.v)); - ck_assert_str_eq(unpack.item.as.String.chunk_start, _d.v); -} -END_TEST +TEST_CASE(pack, setup_packstream, teardown_packstream){}; +TEST_CASE(unpack){}; -static const struct { - const struct bdata blob; - const struct bdata cp; -} cpblob_d[] = { - {B(0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a), - B(0x85, 0x06, 0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a)}, -}; -ARRAY_TEST(pack, pack_blob, cpblob_d) { - chainpack_pack_blob(&pack, _d.blob.v, _d.blob.len); - ck_assert_stash(_d.cp.v, _d.cp.len); -} -END_TEST -ARRAY_TEST(unpack, unpack_blob, cpblob_d) { - cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_BLOB); - ck_assert_int_eq(unpack.item.as.String.chunk_size, _d.blob.len); - ck_assert_mem_eq(unpack.item.as.String.chunk_start, _d.blob.v, _d.blob.len); -} -END_TEST static const struct { - const cpcp_date_time v; + struct cpitem item; const struct bdata cp; -} cpdatetime_d[] = { - {.v = {.msecs_since_epoch = 1517529600001, .minutes_from_utc = 0}, +} single_d[] = { + {{.type = CP_ITEM_NULL}, B(0x80)}, + {{.type = CP_ITEM_BOOL, .as.Bool = true}, B(0xfe)}, + {{.type = CP_ITEM_BOOL, .as.Bool = false}, B(0xfd)}, + {{.type = CP_ITEM_INT, .as.Int = 0}, B(0x40)}, + {{.type = CP_ITEM_INT, .as.Int = -1}, B(0x82, 0x41)}, + {{.type = CP_ITEM_INT, .as.Int = -2}, B(0x82, 0x42)}, + {{.type = CP_ITEM_INT, .as.Int = 7}, B(0x47)}, + {{.type = CP_ITEM_INT, .as.Int = 42}, B(0x6a)}, + {{.type = CP_ITEM_INT, .as.Int = 528}, B(0x82, 0x82, 0x10)}, + {{.type = CP_ITEM_INT, .as.Int = 2147483647}, + B(0x82, 0xf0, 0x7f, 0xff, 0xff, 0xff)}, + {{.type = CP_ITEM_INT, .as.Int = 4294967295}, + B(0x82, 0xf1, 0x00, 0xff, 0xff, 0xff, 0xff)}, + {{.type = CP_ITEM_INT, .as.Int = 9007199254740991}, + B(0x82, 0xf3, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)}, + {{.type = CP_ITEM_INT, .as.Int = -9007199254740991}, + B(0x82, 0xf3, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)}, + {{.type = CP_ITEM_UINT, .as.UInt = 0}, B(0x00)}, + {{.type = CP_ITEM_UINT, .as.UInt = 1}, B(0x01)}, + {{.type = CP_ITEM_UINT, .as.UInt = 126}, B(0x81, 0x7e)}, + {{.type = CP_ITEM_UINT, .as.UInt = 127}, B(0x81, 0x7f)}, + {{.type = CP_ITEM_UINT, .as.UInt = 2147483647}, + B(0x81, 0xf0, 0x7f, 0xff, 0xff, 0xff)}, + {{.type = CP_ITEM_UINT, .as.UInt = 4294967295}, + B(0x81, 0xf0, 0xff, 0xff, 0xff, 0xff)}, + {{.type = CP_ITEM_DOUBLE, .as.Double = 223.0}, + B(0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x6b, 0x40)}, + {{.type = CP_ITEM_DECIMAL, .as.Decimal = {.mantisa = 23, .exponent = -1}}, + B(0x8c, 0x17, 0x41)}, + {{.type = CP_ITEM_DATETIME, .as.Datetime = {.msecs = 1517529600001, .offutc = 0}}, B(0x8d, 0x04)}, - {.v = {.msecs_since_epoch = 1517529600001, .minutes_from_utc = 60}, + {{.type = CP_ITEM_DATETIME, + .as.Datetime = {.msecs = 1517529600001, .offutc = 60}}, B(0x8d, 0x82, 0x11)}, + {{.type = CP_ITEM_STRING, .rchr = "", .as.String = {.flags = CPBI_F_SINGLE}}, + B(0x86, 0x00)}, + {{.type = CP_ITEM_STRING, + .rchr = "foo", + .as.String = {.len = 3, .flags = CPBI_F_SINGLE}}, + B(0x86, 0x03, 'f', 'o', 'o')}, + {{.type = CP_ITEM_STRING, + .rchr = "", + .as.String = {.flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, + B(0x8e, 0x00)}, + {{.type = CP_ITEM_STRING, + .rchr = "foo", + .as.String = {.len = 3, .flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, + B(0x8e, 'f', 'o', 'o', 0x00)}, + {{.type = CP_ITEM_BLOB, + .rbuf = (const uint8_t[]){0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a}, + .as.Blob = {.len = 6, .flags = CPBI_F_SINGLE}}, + B(0x85, 0x06, 0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a)}, + {{.type = CP_ITEM_LIST}, B(0x88)}, + {{.type = CP_ITEM_MAP}, B(0x89)}, + {{.type = CP_ITEM_IMAP}, B(0x8a)}, + {{.type = CP_ITEM_META}, B(0x8b)}, + {{.type = CP_ITEM_CONTAINER_END}, B(0xff)}, }; -ARRAY_TEST(pack, pack_datetime, cpdatetime_d) { - chainpack_pack_date_time(&pack, &_d.v); - ck_assert_stash(_d.cp.v, _d.cp.len); -} -END_TEST -ARRAY_TEST(unpack, unpack_datetime, cpdatetime_d) { - cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_DATE_TIME); - ck_assert_int_eq( - unpack.item.as.DateTime.msecs_since_epoch, _d.v.msecs_since_epoch); - ck_assert_int_eq( - unpack.item.as.DateTime.minutes_from_utc, _d.v.minutes_from_utc); -} -END_TEST - -#define INTARRAY(...) \ - (int64_t[]){__VA_ARGS__}, sizeof((int64_t[]){__VA_ARGS__}) / sizeof(int64_t) -static const struct { - const int64_t *const v; - const size_t vcnt; - const struct bdata cp; -} cplist_ints_d[] = { - {INTARRAY(), B(0x88, 0xff)}, - {INTARRAY(1), B(0x88, 0x41, 0xff)}, - {INTARRAY(1, 2, 3), B(0x88, 0x41, 0x42, 0x43, 0xff)}, -}; -ARRAY_TEST(pack, pack_list_ints, cplist_ints_d) { - chainpack_pack_list_begin(&pack); - for (size_t i = 0; i < _d.vcnt; i++) - chainpack_pack_int(&pack, _d.v[i]); - chainpack_pack_container_end(&pack); - ck_assert_stash(_d.cp.v, _d.cp.len); -} -END_TEST -ARRAY_TEST(unpack, unpack_list_ints, cplist_ints_d) { - cpcp_unpack_context_init(&unpack, _d.cp.v, _d.cp.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); - for (size_t i = 0; i < _d.vcnt; i++) { - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, _d.v[i]); - } - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); -} -END_TEST - -const struct bdata list_in_list_d = B(0x88, 0x88, 0xff, 0xff); -TEST(pack, pack_list_in_list) { - chainpack_pack_list_begin(&pack); - chainpack_pack_list_begin(&pack); - chainpack_pack_container_end(&pack); - chainpack_pack_container_end(&pack); - ck_assert_stash(list_in_list_d.v, list_in_list_d.len); -} -END_TEST -TEST(unpack, unpack_list_in_list) { - cpcp_unpack_context_init( - &unpack, list_in_list_d.v, list_in_list_d.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); -} -END_TEST - -static const struct bdata map_d = - B(0x89, 0x86, 0x03, 'f', 'o', 'o', 0x86, 0x03, 'b', 'a', 'r', 0xff); -TEST(pack, pack_map) { - chainpack_pack_map_begin(&pack); - chainpack_pack_string(&pack, "foo", 3); - chainpack_pack_string(&pack, "bar", 3); - chainpack_pack_container_end(&pack); - ck_assert_stash(map_d.v, map_d.len); -} -END_TEST -TEST(unpack, unpack_map) { - cpcp_unpack_context_init(&unpack, map_d.v, map_d.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_MAP); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); - ck_assert_int_eq(unpack.item.as.String.chunk_size, 3); - ck_assert_mem_eq(unpack.item.as.String.chunk_start, "foo", 3); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); - ck_assert_int_eq(unpack.item.as.String.chunk_size, 3); - ck_assert_mem_eq(unpack.item.as.String.chunk_start, "bar", 3); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); -} -END_TEST - -static const struct bdata imap_d = B(0x8a, 0x41, 0x42, 0xff); -TEST(pack, pack_imap) { - chainpack_pack_imap_begin(&pack); - chainpack_pack_int(&pack, 1); - chainpack_pack_int(&pack, 2); - chainpack_pack_container_end(&pack); - ck_assert_stash(imap_d.v, imap_d.len); -} -END_TEST -TEST(unpack, unpack_imap) { - cpcp_unpack_context_init(&unpack, imap_d.v, imap_d.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_IMAP); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, 1); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, 2); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); -} -END_TEST - -static const struct bdata meta_d = B(0x8b, 0x41, 0x42, 0xff, 0x43); -TEST(pack, pack_meta) { - chainpack_pack_meta_begin(&pack); - chainpack_pack_int(&pack, 1); - chainpack_pack_int(&pack, 2); - chainpack_pack_container_end(&pack); - chainpack_pack_int(&pack, 3); - ck_assert_stash(meta_d.v, meta_d.len); -} -END_TEST -TEST(unpack, unpack_meta) { - cpcp_unpack_context_init(&unpack, meta_d.v, meta_d.len, NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_META); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, 1); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, 2); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, 3); -} -END_TEST - -/* Packing function should not generate any output when pack context is in error - */ -TEST(pack, pack_uint_error) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - chainpack_pack_uint(&pack, 0); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -TEST(pack, pack_int_error) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - chainpack_pack_int(&pack, 0); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -TEST(pack, pack_decimal_error) { - const static cpcp_decimal d = {0, 0}; - pack.err_no = CPCP_RC_LOGICAL_ERROR; - chainpack_pack_decimal(&pack, &d); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -TEST(pack, pack_double_error) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - chainpack_pack_double(&pack, 0); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -TEST(pack, pack_date_error) { - static const cpcp_date_time time_er = {0, 0}; - pack.err_no = CPCP_RC_LOGICAL_ERROR; - chainpack_pack_date_time(&pack, &time_er); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -static const void (*pack_error_simple_call_d[])(cpcp_pack_context *) = { - chainpack_pack_null, chainpack_pack_list_begin, chainpack_pack_map_begin, - chainpack_pack_imap_begin, chainpack_pack_meta_begin, - chainpack_pack_container_end}; -ARRAY_TEST(pack, pack_error_simple_call) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - _d(&pack); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -TEST(pack, pack_bool_error) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - chainpack_pack_boolean(&pack, 0); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -TEST(unpack, unpack_next_error) { - cpcp_unpack_context_init(&unpack, NULL, 0, NULL, NULL); - unpack.err_no = CPCP_RC_LOGICAL_ERROR; - chainpack_unpack_next(&unpack); - ck_assert_ptr_eq(unpack.start, unpack.current); -} -END_TEST -TEST(unpack, unnpack_next_string_error) { - cpcp_unpack_context_init(&unpack, NULL, 0, NULL, NULL); - unpack.item.type = CPCP_ITEM_STRING; - unpack.item.as.String.last_chunk = 0; - chainpack_unpack_next(&unpack); - ck_assert_ptr_eq(unpack.start, unpack.current); +ARRAY_TEST(pack, pack_single, single_d) { + ck_assert_int_eq(chainpack_pack(packstream, &_d.item), _d.cp.len); + ck_assert_packbuf(_d.cp.v, _d.cp.len); } END_TEST -TEST(unpack, unpack_next_blob_error) { - cpcp_unpack_context_init(&unpack, NULL, 0, NULL, NULL); - unpack.item.type = CPCP_ITEM_BLOB; - unpack.item.as.String.last_chunk = 0; - chainpack_unpack_next(&unpack); - ck_assert_ptr_eq(unpack.start, unpack.current); +ARRAY_TEST(unpack, unpack_single, single_d) { + FILE *f = fmemopen((void *)_d.cp.v, _d.cp.len, "r"); + uint8_t buf[BUFSIZ]; + struct cpitem item = {.buf = buf, .bufsiz = BUFSIZ}; + ck_assert_int_eq(chainpack_unpack(f, &item), _d.cp.len); + ck_assert_item(item, _d.item); } END_TEST diff --git a/tests/unit/libshvchainpack/chainpackh.c b/tests/unit/libshvchainpack/chainpackh.c new file mode 100644 index 0000000..5d9aa96 --- /dev/null +++ b/tests/unit/libshvchainpack/chainpackh.c @@ -0,0 +1,201 @@ +#include + +#define SUITE "chainpackh" +#include + + +TEST_CASE(read){}; +TEST_CASE(write){}; + + +static const struct { + uint8_t c; + bool sign; +} scheme_signed_d[] = { + {0x0, false}, + {0xff, true}, + {0x40, true}, + {0xbf, false}, +}; +ARRAY_TEST(read, scheme_signed) { + ck_assert(chainpack_scheme_signed(_d.c) == _d.sign); +} +END_TEST + +static const struct { + uint8_t c; + unsigned v; +} scheme_uint_d[] = { + {0x0, 0}, + {0x3f, 0x3f}, + {0xff, 0x3f}, +}; +ARRAY_TEST(read, scheme_uint) { + ck_assert_uint_eq(chainpack_scheme_uint(_d.c), _d.v); +} +END_TEST + +static const struct { + uint8_t c; + bool is; +} is_int_d[] = { + {0x00, false}, + {0xff, false}, + {0x7f, true}, + {0x3f, false}, + {CPS_Int, true}, +}; +ARRAY_TEST(read, is_int) { + ck_assert(chainpack_is_int(_d.c) == _d.is); +} +END_TEST + +static const struct { + uint8_t c; + bool is; +} is_uint_d[] = { + {0x00, true}, + {0xff, false}, + {0x3f, true}, + {0x7f, false}, + {CPS_Int, true}, +}; +ARRAY_TEST(read, is_uint) { + ck_assert(chainpack_is_uint(_d.c) == _d.is); +} +END_TEST + +static const struct { + uint8_t c; + unsigned bytes; +} int_bytes_d[] = { + {0x0f, 1}, + {0x7f, 1}, + {0xbf, 2}, + {0xdf, 3}, + {0xef, 4}, + {0xf0, 5}, + {0xf1, 6}, + {0xfd, 18}, +}; +ARRAY_TEST(read, int_bytes) { + ck_assert_uint_eq(chainpack_int_bytes(_d.c), _d.bytes); +} +END_TEST + +static const struct { + uint8_t c; + unsigned bytes; + unsigned v; +} uint_value1_d[] = { + {0x00, 1, 0}, + {0xff, 1, 0x7f}, + {0xff, 2, 0x3f}, + {0xff, 3, 0x1f}, + {0xff, 4, 0x0f}, + {0xff, 5, 0x00}, +}; +ARRAY_TEST(read, uint_value1) { + ck_assert_uint_eq(chainpack_uint_value1(_d.c, _d.bytes), _d.v); +} +END_TEST + +static const struct { + uint8_t c; + unsigned bytes; + int v; +} int_value1_d[] = { + {0x00, 1, 0}, + {0xff, 1, -0x3f}, + {0xbf, 1, 0x3f}, + {0xff, 2, -0x1f}, + {0xdf, 2, 0x1f}, + {0xff, 3, -0x0f}, + {0xef, 3, 0x0f}, + {0xff, 4, -0x07}, + {0xf7, 4, 0x07}, + {0xff, 5, 0x00}, +}; +ARRAY_TEST(read, int_value1) { + ck_assert_int_eq(chainpack_int_value1(_d.c, _d.bytes), _d.v); +} +END_TEST + +static const struct { + unsigned long long v; + unsigned bytes; +} w_uint_bytes_d[] = { + {0, 1}, + {1, 1}, + {0x7f, 1}, + {0x8f, 2}, + {0x3fff, 2}, + {0x4fff, 3}, + {0x1fffff, 3}, + {0x2fffff, 4}, + {0xfffffff, 4}, + {0x1fffffff, 5}, + {0xffffffff, 5}, + {0x1ffffffff, 6}, + {0xffffffffff, 6}, + {0x1ffffffffff, 7}, +}; +ARRAY_TEST(write, w_uint_bytes) { + ck_assert_uint_eq(chainpack_w_uint_bytes(_d.v), _d.bytes); +} +END_TEST + +static const struct { + long long v; + unsigned bytes; +} w_int_bytes_d[] = { + {0, 1}, + {1, 1}, + {-1, 1}, + {0x3f, 1}, + {-0x3f, 1}, + {0x4f, 2}, + {-0x4f, 2}, + {0x1fff, 2}, + {0x2fff, 3}, + {0x0fffff, 3}, + {0x7ffffff, 4}, + {0x8ffffff, 5}, + {0x7fffffff, 5}, + {0x8fffffff, 6}, + {0x7fffffffff, 6}, + {0x8fffffffff, 7}, +}; +ARRAY_TEST(write, w_int_bytes) { + ck_assert_uint_eq(chainpack_w_int_bytes(_d.v), _d.bytes); +} +END_TEST + +static const struct { + unsigned long long num; + unsigned bytes; + uint8_t byte; +} w_uint_value1_d[] = { + {0, 1, 0x00}, + {1, 1, 0x01}, + {0x7f, 1, 0x7f}, + {0x8e, 2, 0x80}, +}; +ARRAY_TEST(write, w_uint_value1) { + ck_assert_uint_eq(chainpack_w_uint_value1(_d.num, _d.bytes), _d.byte); +} +END_TEST + +static const struct { + long long num; + unsigned bytes; + uint8_t byte; +} w_int_value1_d[] = { + {0, 1, 0x00}, + {1, 1, 0x01}, + {-1, 1, 0x41}, +}; +ARRAY_TEST(write, w_int_value1) { + ck_assert_uint_eq(chainpack_w_int_value1(_d.num, _d.bytes), _d.byte); +} +END_TEST diff --git a/tests/unit/libshvchainpack/cp_pack.c b/tests/unit/libshvchainpack/cp_pack.c new file mode 100644 index 0000000..89c3f6c --- /dev/null +++ b/tests/unit/libshvchainpack/cp_pack.c @@ -0,0 +1,374 @@ +#include +#include + +#define SUITE "cp_pack" +#include +#include "packstream.h" +#include "bdata.h" + +struct cp_pack_chainpack pack_chainpack; +struct cp_pack_cpon pack_cpon; +cp_pack_t pack; + +static void setup_chainpack(void) { + setup_packstream(); + pack = cp_pack_chainpack_init(&pack_chainpack, packstream); +} + +static void setup_cpon(void) { + setup_packstream(); + pack = cp_pack_cpon_init(&pack_cpon, packstream, NULL); +} + + +TEST_CASE(chainpack, setup_chainpack, teardown_packstream){}; +TEST_CASE(cpon, setup_cpon, teardown_packstream){}; + + +TEST(chainpack, chainpack_null) { + cp_pack_null(pack); + struct bdata b = B(CPS_Null); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_null) { + cp_pack_null(pack); + ck_assert_packstr("null"); +} +END_TEST + +static void pack_bool_true(void) { + bool v = true; + cp_pack_value(pack, v); +} +TEST(chainpack, chainpack_bool_true) { + pack_bool_true(); + struct bdata b = B(CPS_TRUE); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_bool_true) { + pack_bool_true(); + ck_assert_packstr("true"); +} +END_TEST + +static void pack_bool_false(void) { + bool v = false; + cp_pack_value(pack, v); +} +TEST(chainpack, chainpack_bool_false) { + pack_bool_false(); + struct bdata b = B(CPS_FALSE); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_bool_false) { + pack_bool_false(); + ck_assert_packstr("false"); +} +END_TEST + +static void pack_int(void) { + cp_pack_value(pack, 42); +} +TEST(chainpack, chainpack_int) { + pack_int(); + struct bdata b = B(0x6a); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_int) { + pack_int(); + ck_assert_packstr("42"); +} +END_TEST + +static void pack_uint(void) { + unsigned v = 42; + cp_pack_value(pack, v); +} +TEST(chainpack, chainpack_uint) { + pack_uint(); + struct bdata b = B(0x2a); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_uint) { + pack_uint(); + ck_assert_packstr("42u"); +} +END_TEST + +static void pack_double(void) { + cp_pack_value(pack, 42.2); +} +TEST(chainpack, chainpack_double) { + pack_double(); + struct bdata b = B(0x83, 0x9a, 0x99, 0x99, 0x99, 0x99, 0x19, 0x45, 0x40); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_double) { + pack_double(); + ck_assert_packstr("42.2"); +} +END_TEST + +static void pack_decimal(void) { + struct cpdecimal v = (struct cpdecimal){.mantisa = 422, .exponent = -1}; + cp_pack_value(pack, v); +} +TEST(chainpack, chainpack_decimal) { + pack_decimal(); + struct bdata b = B(0x8c, 0x81, 0xa6, 0x41); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_decimal) { + pack_decimal(); + ck_assert_packstr("42.2"); +} +END_TEST + +static void pack_datetime(void) { + struct cpdatetime v = (struct cpdatetime){.msecs = 2346, .offutc = -60}; + cp_pack_value(pack, v); +} +TEST(chainpack, chainpack_datetime) { + pack_datetime(); + struct bdata b = B(0x8d, 0xf3, 0x82, 0xc2, 0xa7, 0xa0, 0x0d, 0xaa, 0x0f); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_datetime) { + pack_datetime(); + ck_assert_packstr("d\"1970-01-01T00:00:02.346-01:00\""); +} +END_TEST + +static void pack_blob(void) { + struct bdata v = B(0x42, 0x11); + cp_pack_value(pack, v.v, v.len); +} +TEST(chainpack, chainpack_blob) { + pack_blob(); + struct bdata b = B(0x85, 0x02, 0x42, 0x11); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_blob) { + pack_blob(); + ck_assert_packstr("b\"B\\11\""); +} +END_TEST + +static void pack_blob_seq(void) { + struct bdata v = B(0x42, 0x11); + struct cpitem item; + cp_pack_blob_size(pack, &item, v.len); + for (size_t i = 0; i < v.len; i++) + cp_pack_blob_data(pack, &item, &v.v[i], 1); +} +TEST(chainpack, chainpack_blob_seq) { + pack_blob_seq(); + struct bdata b = B(0x85, 0x02, 0x42, 0x11); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_blob_seq) { + pack_blob_seq(); + ck_assert_packstr("b\"B\\11\""); +} +END_TEST + +static void pack_str(void) { + const char *str = "foo"; + cp_pack_value(pack, str); +} +TEST(chainpack, chainpack_str) { + pack_str(); + struct bdata b = B(0x86, 0x03, 'f', 'o', 'o'); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_str) { + pack_str(); + ck_assert_packstr("\"foo\""); +} +END_TEST + +static void pack_string(void) { + const char *str = "foo"; + cp_pack_value(pack, str, 3); +} +TEST(chainpack, chainpack_string) { + pack_string(); + struct bdata b = B(0x86, 0x03, 'f', 'o', 'o'); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_string) { + pack_string(); + ck_assert_packstr("\"foo\""); +} +END_TEST + +static void pack_string_seq(void) { + const char *str = "foo"; + struct cpitem item; + cp_pack_string_size(pack, &item, 3); + for (int i = 0; i < 3; i++) + cp_pack_string_data(pack, &item, &str[i], 1); +} +TEST(chainpack, chainpack_string_seq) { + pack_string_seq(); + struct bdata b = B(0x86, 0x03, 'f', 'o', 'o'); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_string_seq) { + pack_string_seq(); + ck_assert_packstr("\"foo\""); +} +END_TEST + +static void pack_list(void) { + cp_pack_list_begin(pack); + cp_pack_list_begin(pack); + cp_pack_list_begin(pack); + cp_pack_container_end(pack); + cp_pack_container_end(pack); + cp_pack_container_end(pack); +} +TEST(chainpack, chainpack_list) { + pack_list(); + struct bdata b = B(0x88, 0x88, 0x88, 0xff, 0xff, 0xff); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_list) { + pack_list(); + ck_assert_packstr("[[[]]]"); +} +END_TEST + +static void pack_map(void) { + cp_pack_map_begin(pack); + cp_pack_map_begin(pack); + cp_pack_map_begin(pack); + cp_pack_container_end(pack); + cp_pack_container_end(pack); + cp_pack_container_end(pack); +} +TEST(chainpack, chainpack_map) { + pack_map(); + struct bdata b = B(0x89, 0x89, 0x89, 0xff, 0xff, 0xff); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_map) { + pack_map(); + ck_assert_packstr("{{{}}}"); +} +END_TEST + +static void pack_imap(void) { + cp_pack_imap_begin(pack); + cp_pack_imap_begin(pack); + cp_pack_imap_begin(pack); + cp_pack_container_end(pack); + cp_pack_container_end(pack); + cp_pack_container_end(pack); +} +TEST(chainpack, chainpack_imap) { + pack_imap(); + struct bdata b = B(0x8a, 0x8a, 0x8a, 0xff, 0xff, 0xff); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_imap) { + pack_imap(); + ck_assert_packstr("i{i{i{}}}"); +} +END_TEST + +static void pack_containers(void) { + cp_pack_list_begin(pack); + cp_pack_map_begin(pack); + cp_pack_imap_begin(pack); + cp_pack_container_end(pack); + cp_pack_container_end(pack); + cp_pack_container_end(pack); +} +TEST(chainpack, chainpack_containers) { + pack_containers(); + struct bdata b = B(0x88, 0x89, 0x8a, 0xff, 0xff, 0xff); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_containers) { + pack_containers(); + ck_assert_packstr("[{i{}}]"); +} +END_TEST + +static void pack_meta(void) { + cp_pack_meta_begin(pack); + cp_pack_int(pack, 0); + cp_pack_meta_begin(pack); + cp_pack_container_end(pack); + cp_pack_int(pack, 0); + cp_pack_container_end(pack); + cp_pack_null(pack); +} +TEST(chainpack, chainpack_meta) { + pack_meta(); + struct bdata b = B(0x8b, 0x40, 0x8b, 0xff, 0x40, 0xff, 0x80); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_meta) { + pack_meta(); + ck_assert_packstr("<0:<>0>null"); +} +END_TEST + + +static void pack_fopen_str(void) { + FILE *f = cp_pack_fopen(pack, true); + fputs("Num", f); + fprintf(f, ": %d", 42); + fclose(f); +} +TEST(chainpack, chainpack_fopen_str) { + pack_fopen_str(); + struct bdata b = B(0x8e, 'N', 'u', 'm', ':', ' ', '4', '2', 0x00); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_fopen_str) { + pack_fopen_str(); + ck_assert_packstr("\"Num: 42\""); +} +END_TEST + +static void pack_fopen_blob(void) { + FILE *f = cp_pack_fopen(pack, false); + setvbuf(f, NULL, _IONBF, 0); + struct bdata v = B(0x42, 0x11); + for (size_t i = 0; i < v.len; i++) + fputc(v.v[i], f); + fclose(f); +} +TEST(chainpack, chainpack_fopen_blob) { + pack_fopen_blob(); + struct bdata b = B(0x8f, 0x01, 0x42, 0x01, 0x11, 0x00); + ck_assert_packbuf(b.v, b.len); +} +END_TEST +TEST(cpon, cpon_fopen_blob) { + pack_fopen_blob(); + ck_assert_packstr("b\"B\\11\""); +} +END_TEST diff --git a/tests/unit/libshvchainpack/cp_unpack.c b/tests/unit/libshvchainpack/cp_unpack.c new file mode 100644 index 0000000..84ac598 --- /dev/null +++ b/tests/unit/libshvchainpack/cp_unpack.c @@ -0,0 +1,240 @@ +#include "check.h" +#include "shv/cp.h" +#include +#include +#include + +#define SUITE "cp_unpack" +#include +#include "unpack.h" +#include "item.h" + + +TEST_CASE(unpack){}; + + +TEST(unpack, chainpack_unpack_null) { + struct bdata b = B(CPS_Null); + cp_unpack_t unpack = unpack_chainpack(&b); + struct cpitem item = (struct cpitem){}; + ck_assert_uint_eq(cp_unpack(unpack, &item), 1); + unpack_free(unpack); +} +END_TEST +TEST(unpack, cpon_unpack_null) { + const char *str = "null"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + ck_assert_uint_eq(cp_unpack(unpack, &item), 4); + unpack_free(unpack); +} +END_TEST + +/* Test our internal ability to allocate more state contexts */ +TEST(unpack, cpon_unpack_ctx_realloc) { + const char *str = "[[[]]]"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + for (int i = 0; i < 3; i++) { + ck_assert_uint_eq(cp_unpack(unpack, &item), 1); + ck_assert_int_eq(item.type, CP_ITEM_LIST); + } + for (int i = 0; i < 3; i++) { + ck_assert_uint_eq(cp_unpack(unpack, &item), 1); + ck_assert_int_eq(item.type, CP_ITEM_CONTAINER_END); + } + + unpack_free(unpack); +} +END_TEST + +TEST(unpack, skip_int) { + const char *str = "[1,2]"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_LIST); + cp_unpack_skip(unpack, &item); + cp_unpack(unpack, &item); + ck_assert_item_int(item, 2); + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + + unpack_free(unpack); +} +END_TEST + +TEST(unpack, skip_string) { + const char *str = "[\"Some string we want to skip\"]"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_LIST); + cp_unpack_skip(unpack, &item); + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + + unpack_free(unpack); +} +END_TEST + +TEST(unpack, skip_string_fin) { + const char *str = "[\"Some string we want to skip\"]"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_LIST); + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_STRING); + cp_unpack_skip(unpack, &item); + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + + unpack_free(unpack); +} +END_TEST + +TEST(unpack, skip_containers) { + const char *str = "[{0:<>null}]"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_LIST); + cp_unpack_skip(unpack, &item); + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + + unpack_free(unpack); +} +END_TEST + +TEST(unpack, finish_containers) { + const char *str = "[{0:<>null}]"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_LIST); + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_MAP); + cp_unpack_finish(unpack, &item); + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + + unpack_free(unpack); +} +END_TEST + + +TEST(unpack, unpack_strdup) { + const char *str = "\"Some text\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + char *res = cp_unpack_strdup(unpack, &item); + ck_assert_str_eq(res, "Some text"); + free(res); + unpack_free(unpack); +} +END_TEST + +TEST(unpack, unpack_memdup) { + const char *str = "b\"123\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + uint8_t *res; + size_t siz; + cp_unpack_memdup(unpack, &item, &res, &siz); + struct bdata exp = B('1', '2', '3'); + ck_assert_int_eq(siz, exp.len); + ck_assert_mem_eq(res, exp.v, siz); + free(res); + + unpack_free(unpack); +} +END_TEST + +TEST(unpack, unpack_strdup_invalid) { + const char *str = "0"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + ck_assert_ptr_null(cp_unpack_strdup(unpack, &item)); + ck_assert_int_eq(item.type, CP_ITEM_INT); + unpack_free(unpack); +} +END_TEST + + +TEST(unpack, unpack_strncpy) { + const char *str = "\"Some text\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + char buf[6]; + ck_assert_int_eq(cp_unpack_strncpy(unpack, &item, buf, 5), 5); + buf[5] = '\0'; + ck_assert_str_eq(buf, "Some "); + ck_assert_int_eq(cp_unpack_strncpy(unpack, &item, buf, 5), 4); + ck_assert_str_eq(buf, "text"); + + unpack_free(unpack); +} +END_TEST + +TEST(unpack, unpack_memcpy) { + const char *str = "x\"010203040506070809\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + uint8_t buf[6]; + ck_assert_int_eq(cp_unpack_memcpy(unpack, &item, buf, 5), 5); + uint8_t exp1[] = {0x1, 0x2, 0x3, 0x4, 0x5}; + ck_assert_mem_eq(buf, exp1, 5); + ck_assert_int_eq(cp_unpack_memcpy(unpack, &item, buf, 5), 4); + uint8_t exp2[] = {0x6, 0x7, 0x8, 0x9}; + ck_assert_mem_eq(buf, exp2, 4); + + unpack_free(unpack); +} +END_TEST + + +TEST(unpack, unpack_fopen_string) { + const char *str = "\"Some: 42:string\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + FILE *f = cp_unpack_fopen(unpack, &item); + ck_assert_item_type(item, CP_ITEM_STRING); + int d; + char buf[10]; + ck_assert_int_eq(fscanf(f, "Some: %d:%9s", &d, buf), 2); + ck_assert_int_eq(d, 42); + ck_assert_str_eq(buf, "string"); + fclose(f); + + unpack_free(unpack); +} +END_TEST + +TEST(unpack, unpack_fopen_blob) { + const char *str = "x\"010203040506070809\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + FILE *f = cp_unpack_fopen(unpack, &item); + ck_assert_item_type(item, CP_ITEM_BLOB); + char buf[10]; + ck_assert_int_eq(fread(buf, 1, 10, f), 9); + uint8_t exp[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9}; + ck_assert_mem_eq(buf, exp, 9); + fclose(f); + + unpack_free(unpack); +} +END_TEST diff --git a/tests/unit/libshvchainpack/cpdatetime.c b/tests/unit/libshvchainpack/cpdatetime.c new file mode 100644 index 0000000..fbd8132 --- /dev/null +++ b/tests/unit/libshvchainpack/cpdatetime.c @@ -0,0 +1,48 @@ +#include + +#define SUITE "cpdatetime" +#include + + +TEST_CASE(all) {} + +static const struct { + struct cpdatetime dt; + struct tm tm; +} _d[] = { + {(struct cpdatetime){}, + (struct tm){ + .tm_mday = 1, + .tm_wday = 4, + .tm_year = 70, + }}, + {(struct cpdatetime){.offutc = 60}, + (struct tm){ + .tm_mday = 1, + .tm_wday = 4, + .tm_year = 70, + .tm_gmtoff = 3600, + }}, +}; + +ARRAY_TEST(all, dttotm, _d) { + struct tm tm = cpdttotm(_d.dt); + ck_assert_int_eq(tm.tm_sec, _d.tm.tm_sec); + ck_assert_int_eq(tm.tm_min, _d.tm.tm_min); + ck_assert_int_eq(tm.tm_hour, _d.tm.tm_hour); + ck_assert_int_eq(tm.tm_mday, _d.tm.tm_mday); + ck_assert_int_eq(tm.tm_mon, _d.tm.tm_mon); + ck_assert_int_eq(tm.tm_year, _d.tm.tm_year); + ck_assert_int_eq(tm.tm_wday, _d.tm.tm_wday); + ck_assert_int_eq(tm.tm_yday, _d.tm.tm_yday); + ck_assert_int_eq(tm.tm_isdst, _d.tm.tm_isdst); + ck_assert_int_eq(tm.tm_gmtoff, _d.tm.tm_gmtoff); +} +END_TEST + +ARRAY_TEST(all, tmtodt, _d) { + struct cpdatetime dt = cptmtodt(_d.tm); + ck_assert_int_eq(dt.msecs, _d.dt.msecs); + ck_assert_int_eq(dt.offutc, _d.dt.offutc); +} +END_TEST diff --git a/tests/unit/libshvchainpack/cpdecimal.c b/tests/unit/libshvchainpack/cpdecimal.c new file mode 100644 index 0000000..763014f --- /dev/null +++ b/tests/unit/libshvchainpack/cpdecimal.c @@ -0,0 +1,31 @@ +#include + +#define SUITE "cpdecimal" +#include + + +TEST_CASE(all) {} + +static const struct { + double f; + struct cpdecimal d; +} _d[] = { + {0., (struct cpdecimal){}}, + {1., (struct cpdecimal){.mantisa = 1}}, + {-1., (struct cpdecimal){.mantisa = -1}}, + {42.124, (struct cpdecimal){.mantisa = 42124, .exponent = -3}}, +}; + +ARRAY_TEST(all, dectod, _d) { + ck_assert_double_eq_tol(cpdectod(_d.d), _d.f, 0.0000001); +} +END_TEST + +#if 0 +ARRAY_TEST(all, dtodec, _d) { + struct cpdecimal d = cpdtodec(_d.f); + ck_assert_int_eq(d.mantisa, _d.d.mantisa); + ck_assert_int_eq(d.exponent, _d.d.exponent); +} +END_TEST +#endif diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index fc9e543..daf8154 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -1,443 +1,113 @@ -#include -#include -#include -#include +#include "check.h" +#include #define SUITE "cpon" #include -#include "packunpack.h" -#define UNPACK_NEXT \ - { \ - cpon_unpack_next(&unpack); \ - ck_assert_int_eq(unpack.err_no, CPCP_RC_OK); \ - } \ - while (false) +#include "packstream.h" +#include "item.h" -TEST_CASE(pack, setup_pack, teardown_pack){}; -TEST_CASE(unpack){}; - - -static const char *const cpnull = "null"; -TEST(pack, pack_none) { - cpon_pack_null(&pack); - ck_assert_stashstr(cpnull); -} -END_TEST -TEST(unpack, unpack_null) { - cpcp_unpack_context_init(&unpack, cpnull, strlen(cpnull), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_NULL); -} -END_TEST - -static const struct { - const bool v; - const char *const cp; -} cpbool_d[] = {{true, "true"}, {false, "false"}}; -ARRAY_TEST(pack, pack_bool, cpbool_d) { - cpon_pack_boolean(&pack, _d.v); - ck_assert_stashstr(_d.cp); -} -END_TEST -ARRAY_TEST(unpack, unpack_bool, cpbool_d) { - cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_BOOLEAN); - ck_assert(unpack.item.as.Bool == _d.v); -} -END_TEST - -static const struct { - const int64_t v; - const char *const cp; -} cpint_d[] = { - {0, "0"}, - {-1, "-1"}, - {-2, "-2"}, - {7, "7"}, - {42, "42"}, - {2147483647, "2147483647"}, - {4294967295, "4294967295"}, - {9007199254740991, "9007199254740991"}, - {-9007199254740991, "-9007199254740991"}, -}; -ARRAY_TEST(pack, pack_int, cpint_d) { - cpon_pack_int(&pack, _d.v); - ck_assert_stashstr(_d.cp); -} -END_TEST -ARRAY_TEST(unpack, unpack_int, cpint_d) { - cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, _d.v); -} -END_TEST - -static const struct { - const uint64_t v; - const char *const cp; -} cpuint_d[] = { - {0, "0u"}, - {1, "1u"}, - {2147483647, "2147483647u"}, - {4294967295, "4294967295u"}, -}; -ARRAY_TEST(pack, pack_uint, cpuint_d) { - cpon_pack_uint(&pack, _d.v); - ck_assert_stashstr(_d.cp); -} -END_TEST -ARRAY_TEST(unpack, unpack_uint, cpuint_d) { - cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_UINT); - ck_assert_int_eq(unpack.item.as.UInt, _d.v); -} -END_TEST -struct cpdecimal { - const cpcp_decimal v; - const char *const cp; -}; -static const struct cpdecimal cpdecimal_d[] = { - {.v = {}, "0."}, - {.v = {.mantisa = 223}, "223."}, - {.v = {.mantisa = 23, .exponent = -1}, "2.3"}, -}; -ARRAY_TEST(pack, pack_decimal, cpdecimal_d) { - cpon_pack_decimal(&pack, &_d.v); - ck_assert_stashstr(_d.cp); -} -END_TEST -static void test_unpack_decimal(struct cpdecimal _d) { - cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_DOUBLE); - ck_assert_int_eq(unpack.item.as.Decimal.mantisa, _d.v.mantisa); - ck_assert_int_eq(unpack.item.as.Decimal.exponent, _d.v.exponent); -} -ARRAY_TEST(unpack, unpack_decimal, cpdecimal_d) { - test_unpack_decimal(_d); -} -END_TEST -static const struct cpdecimal cpdecimal_unpack_d[] = { - // TODO it should be more like exponent 0 not -1 🤷 - {.v = {.exponent = -1}, "0.0"}, - {.v = {.mantisa = 2230, .exponent = -1}, "223.0"}, -}; -ARRAY_TEST(unpack, unpack_only_decimal, cpdecimal_unpack_d) { - test_unpack_decimal(_d); -} -END_TEST - -static const struct { - const char *const v; - const char *const cp; -} cpstring_d[] = { - {"", "\"\""}, - {"foo", "\"foo\""}, - {"dvaačtyřicet", "\"dvaačtyřicet\""}, - {"some\t\"tab\"", "\"some\\t\\\"tab\\\"\""}, -}; -ARRAY_TEST(pack, pack_string, cpstring_d) { - cpon_pack_string_terminated(&pack, _d.v); - ck_assert_stashstr(_d.cp); -} -END_TEST -ARRAY_TEST(unpack, unpack_string, cpstring_d) { - cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_UINT); - ck_assert_int_eq(unpack.item.as.String.chunk_size, strlen(_d.v)); - ck_assert_str_eq(unpack.item.as.String.chunk_start, _d.v); -} -END_TEST +TEST_CASE(pack, setup_packstream, teardown_packstream){}; +TEST_CASE(unpack){}; -static const struct { - const struct bdata blob; - const char *const cp; -} cpblob_d[] = { - {B(0x61, 0x62, 0xcd, '\t', '\r', '\n'), "b\"ab\\cd\\t\\r\\n\""}, -}; -ARRAY_TEST(pack, pack_blob, cpblob_d) { - cpon_pack_blob(&pack, _d.blob.v, _d.blob.len); - ck_assert_stashstr(_d.cp); -} -END_TEST -ARRAY_TEST(unpack, unpack_blob, cpblob_d) { - cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_BLOB); - ck_assert_int_eq(unpack.item.as.String.chunk_size, _d.blob.len); - ck_assert_mem_eq(unpack.item.as.String.chunk_start, _d.blob.v, _d.blob.len); -} -END_TEST static const struct { - const cpcp_date_time v; - const char *const cp; -} cpdatetime_d[] = { - {.v = {.msecs_since_epoch = 1517529600000, .minutes_from_utc = 0}, - "d\"2018-02-02T00:00:00Z\""}, - {.v = {.msecs_since_epoch = 1517529600001, .minutes_from_utc = 60}, - "d\"2018-02-02T01:00:00.001+01\""}, - {.v = {.msecs_since_epoch = 1809340212345, .minutes_from_utc = 60}, - "d\"2027-05-03T11:30:12.345+01\""}, + struct cpitem item; + const char *cp; +} single_d[] = { + {{.type = CP_ITEM_NULL}, "null"}, + {{.type = CP_ITEM_BOOL, .as.Bool = true}, "true"}, + {{.type = CP_ITEM_BOOL, .as.Bool = false}, "false"}, + {{.type = CP_ITEM_INT, .as.Int = 0}, "0"}, + {{.type = CP_ITEM_INT, .as.Int = -1}, "-1"}, + {{.type = CP_ITEM_INT, .as.Int = -2}, "-2"}, + {{.type = CP_ITEM_INT, .as.Int = 7}, "7"}, + {{.type = CP_ITEM_INT, .as.Int = 42}, "42"}, + {{.type = CP_ITEM_INT, .as.Int = 2147483647}, "2147483647"}, + {{.type = CP_ITEM_INT, .as.Int = 4294967295}, "4294967295"}, + {{.type = CP_ITEM_INT, .as.Int = 9007199254740991}, "9007199254740991"}, + {{.type = CP_ITEM_INT, .as.Int = -9007199254740991}, "-9007199254740991"}, + {{.type = CP_ITEM_UINT, .as.UInt = 0}, "0u"}, + {{.type = CP_ITEM_UINT, .as.UInt = 1}, "1u"}, + {{.type = CP_ITEM_UINT, .as.UInt = 127}, "127u"}, + {{.type = CP_ITEM_UINT, .as.UInt = 2147483647}, "2147483647u"}, + {{.type = CP_ITEM_UINT, .as.UInt = 4294967295}, "4294967295u"}, + {{.type = CP_ITEM_DECIMAL, .as.Decimal = (struct cpdecimal){}}, "0."}, + {{.type = CP_ITEM_DECIMAL, .as.Decimal = (struct cpdecimal){.mantisa = 223}}, + "223."}, + {{.type = CP_ITEM_DECIMAL, + .as.Decimal = (struct cpdecimal){.mantisa = 223, .exponent = -1}}, + "22.3"}, + {{.type = CP_ITEM_DECIMAL, + .as.Decimal = (struct cpdecimal){.mantisa = 223, .exponent = 3}}, + "223000."}, + {{.type = CP_ITEM_DECIMAL, + .as.Decimal = (struct cpdecimal){.mantisa = 223, .exponent = -5}}, + "0.00223"}, + {{.type = CP_ITEM_DECIMAL, + .as.Decimal = (struct cpdecimal){.mantisa = 223, .exponent = 7}}, + "223e7"}, + {{.type = CP_ITEM_DECIMAL, + .as.Decimal = (struct cpdecimal){.mantisa = 223, .exponent = -12}}, + "223e-12"}, + {{.type = CP_ITEM_DATETIME, .as.Datetime = (struct cpdatetime){}}, + "d\"1970-01-01T00:00:00.000Z\""}, + {{.type = CP_ITEM_DATETIME, .as.Datetime = {.msecs = 1517529600001, .offutc = 0}}, + "d\"2018-02-02T00:00:00.001Z\""}, + {{.type = CP_ITEM_DATETIME, + .as.Datetime = {.msecs = 1517529600001, .offutc = 60}}, + "d\"2018-02-02T00:00:00.001+01:00\""}, + {{.type = CP_ITEM_DATETIME, + .as.Datetime = {.msecs = 1692776567123, .offutc = -75}}, + "d\"2023-08-23T07:42:47.123-01:15\""}, + {{.type = CP_ITEM_STRING, + .rchr = "", + .as.String = {.flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, + "\"\""}, + {{.type = CP_ITEM_STRING, + .rchr = "foo", + .as.String = {.len = 3, .flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, + "\"foo\""}, + {{.type = CP_ITEM_STRING, + .rchr = "foo", + .as.String = {.len = 3, .flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, + "\"foo\""}, + {{.type = CP_ITEM_STRING, + .rchr = "\f\a\b\t", + .as.String = {.len = 4, .flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, + "\"\\f\\a\\b\\t\""}, + {{.type = CP_ITEM_BLOB, + .rbuf = (const uint8_t[]){}, + .as.String = {.flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, + "b\"\""}, + {{.type = CP_ITEM_BLOB, + .rbuf = (const uint8_t[]){0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a}, + .as.String = {.len = 6, .flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, + "b\"ab\\CD\\v\\r\\n\""}, + {{.type = CP_ITEM_BLOB, + .rbuf = (const uint8_t[]){}, + .as.String = {.flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, + "b\"\""}, + {{.type = CP_ITEM_BLOB, + .rbuf = (const uint8_t[]){0xfe, 0x00, 0x42}, + .as.String = {.len = 3, .flags = CPBI_F_SINGLE | CPBI_F_STREAM | CPBI_F_HEX}}, + "x\"FE0042\""}, }; -ARRAY_TEST(pack, pack_datetime, cpdatetime_d) { - cpon_pack_date_time(&pack, &_d.v); - ck_assert_stashstr(_d.cp); +ARRAY_TEST(pack, pack_single, single_d) { + struct cpon_state st = {}; + /*ck_assert_int_eq(cpon_pack(packstream, &st, &_d.item), strlen(_d.cp));*/ + cpon_pack(packstream, &st, &_d.item); + ck_assert_packstr(_d.cp); } END_TEST -ARRAY_TEST(unpack, unpack_datetime, cpdatetime_d) { - cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_DATE_TIME); - ck_assert_int_eq( - unpack.item.as.DateTime.msecs_since_epoch, _d.v.msecs_since_epoch); - ck_assert_int_eq( - unpack.item.as.DateTime.minutes_from_utc, _d.v.minutes_from_utc); +ARRAY_TEST(unpack, unpack_single, single_d) { + size_t len = strlen(_d.cp); + FILE *f = fmemopen((void *)_d.cp, len, "r"); + struct cpon_state st = {}; + uint8_t buf[BUFSIZ]; + struct cpitem item = {.buf = buf, .bufsiz = BUFSIZ}; + ck_assert_int_eq(cpon_unpack(f, &st, &item), len); + ck_assert_item(item, _d.item); } END_TEST - -#define INTARRAY(...) \ - (int64_t[]){__VA_ARGS__}, sizeof((int64_t[]){__VA_ARGS__}) / sizeof(int64_t) -static const struct { - const int64_t *const v; - const size_t vcnt; - const char *const cp; -} cplist_ints_d[] = { - {INTARRAY(), "[]"}, - {INTARRAY(1), "[1]"}, - {INTARRAY(1, 2, 3), "[1,2,3]"}, -}; -ARRAY_TEST(pack, pack_list_ints, cplist_ints_d) { - cpon_pack_list_begin(&pack); - for (size_t i = 0; i < _d.vcnt; i++) { - cpon_pack_field_delim(&pack, i == 0, true); - cpon_pack_int(&pack, _d.v[i]); - } - cpon_pack_list_end(&pack, true); - ck_assert_stashstr(_d.cp); -} -END_TEST -ARRAY_TEST(unpack, unpack_list_ints, cplist_ints_d) { - cpcp_unpack_context_init(&unpack, _d.cp, strlen(_d.cp), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); - for (size_t i = 0; i < _d.vcnt; i++) { - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, _d.v[i]); - } - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); -} -END_TEST - -static const char *const list_in_list_d = "[[]]"; -TEST(pack, pack_list_in_list) { - cpon_pack_list_begin(&pack); - cpon_pack_list_begin(&pack); - cpon_pack_list_end(&pack, true); - cpon_pack_list_end(&pack, true); - ck_assert_stashstr(list_in_list_d); -} -END_TEST -TEST(unpack, unpack_list_in_list) { - cpcp_unpack_context_init( - &unpack, list_in_list_d, strlen(list_in_list_d), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_LIST); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); -} -END_TEST - -static const char *const map_d = "{\"foo\":\"bar\"}"; -TEST(pack, pack_map) { - cpon_pack_map_begin(&pack); - cpon_pack_string(&pack, "foo", 3); - cpon_pack_key_val_delim(&pack); - cpon_pack_string(&pack, "bar", 3); - cpon_pack_map_end(&pack, true); - ck_assert_stashstr(map_d); -} -END_TEST -TEST(unpack, unpack_map) { - cpcp_unpack_context_init(&unpack, map_d, strlen(map_d), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_MAP); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); - ck_assert_int_eq(unpack.item.as.String.chunk_size, 3); - ck_assert_mem_eq(unpack.item.as.String.chunk_start, "foo", 3); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_STRING); - ck_assert_int_eq(unpack.item.as.String.chunk_size, 3); - ck_assert_mem_eq(unpack.item.as.String.chunk_start, "bar", 3); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); -} -END_TEST - -static const char *const imap_d = "i{1:2}"; -TEST(pack, pack_imap) { - cpon_pack_imap_begin(&pack); - cpon_pack_int(&pack, 1); - cpon_pack_key_val_delim(&pack); - cpon_pack_int(&pack, 2); - cpon_pack_imap_end(&pack, true); - ck_assert_stashstr(imap_d); -} -END_TEST -TEST(unpack, unpack_imap) { - cpcp_unpack_context_init(&unpack, imap_d, strlen(imap_d), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_IMAP); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, 1); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, 2); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); -} -END_TEST - -static const char *const meta_d = "<1:2>3"; -TEST(pack, pack_meta) { - cpon_pack_meta_begin(&pack); - cpon_pack_int(&pack, 1); - cpon_pack_key_val_delim(&pack); - cpon_pack_int(&pack, 2); - cpon_pack_meta_end(&pack, true); - cpon_pack_int(&pack, 3); - ck_assert_stashstr(meta_d); -} -END_TEST -TEST(unpack, unpack_meta) { - cpcp_unpack_context_init(&unpack, meta_d, strlen(meta_d), NULL, NULL); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_META); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, 1); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, 2); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_CONTAINER_END); - UNPACK_NEXT - ck_assert_int_eq(unpack.item.type, CPCP_ITEM_INT); - ck_assert_int_eq(unpack.item.as.Int, 3); -} -END_TEST - -/* Packing function should not generate any output when pack context is in error - */ -TEST(pack, pack_uint_error) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - cpon_pack_uint(&pack, 0); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -TEST(pack, pack_int_error) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - cpon_pack_int(&pack, 0); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -TEST(pack, pack_double_error) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - cpon_pack_double(&pack, 0); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -static void (*const pack_error_simple_call_d[])(struct cpcp_pack_context *) = { - cpon_pack_null, cpon_pack_list_begin, cpon_pack_map_begin, - cpon_pack_imap_begin, cpon_pack_meta_begin}; -ARRAY_TEST(pack, pack_error_simple_call) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - _d(&pack); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -static void (*const pack_error_bool_call_d[])(cpcp_pack_context *, bool) = { - cpon_pack_boolean, cpon_pack_list_end, cpon_pack_map_end, - cpon_pack_imap_end, cpon_pack_meta_end}; -ARRAY_TEST(pack, pack_error_bool_call) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - _d(&pack, 0); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -TEST(pack, pack_copy_test) { - char *text = "Testing function cpon_pack_copy_str."; - cpon_pack_copy_str(&pack, text); - ck_assert_stashstr("Testing function cpon_pack_copy_str."); -} -END_TEST -static const struct { - const double num; - const char *const str_num; -} cpon_double_d[] = {{0., "0."}, {10000.5, "10000.5"}, {-10000.5, "-10000.5"}, - {0.9, "0.9"}, {10000000, "1e7"}, {0.08, "8e-2"}}; -ARRAY_TEST(pack, pack_double_d, cpon_double_d) { - cpon_pack_double(&pack, _d.num); - ck_assert_stashstr(_d.str_num); -} -END_TEST -TEST(pack, pack_string_start_err) { - pack.err_no = CPCP_RC_LOGICAL_ERROR; - cpon_pack_string_start(&pack, "error", 5); - ck_assert_ptr_eq(pack.start, pack.current); -} -END_TEST -TEST(pack, pack_string_start_test) { - cpon_pack_string_start(&pack, "test: \0\n\t\b\r\\\"", 13); - ck_assert_stashstr("\"test: \\0\\n\\t\\b\\r\\\\\\\""); -} -END_TEST -TEST(pack, pack_string_cont_test) { - cpon_pack_string_cont(&pack, "test: \0\n\t\b\r\\\"", 13); - ck_assert_stashstr("test: \\0\\n\\t\\b\\r\\\\\\\""); -} -END_TEST -TEST(pack, pack_string_finish_test) { - cpon_pack_string_finish(&pack); - ck_assert_stashstr("\""); -} -END_TEST -TEST(unpack, unpack_insig_error) { - cpcp_unpack_context_init(&unpack, NULL, 0, NULL, NULL); - cpon_unpack_skip_insignificant(&unpack); - ck_assert_ptr_eq(unpack.start, unpack.current); -} -END_TEST -TEST(unpack, unpack_next_error) { - cpcp_unpack_context_init(&unpack, NULL, 0, NULL, NULL); - unpack.err_no = CPCP_RC_LOGICAL_ERROR; - cpon_unpack_next(&unpack); - ck_assert_ptr_eq(unpack.start, unpack.current); -} -END_TEST -TEST(unpack, unpack_skip_zero_test) { - cpcp_unpack_context_init(&unpack, "", 0, NULL, NULL); - cpon_unpack_skip_insignificant(&unpack); - ck_assert_ptr_eq(unpack.start, unpack.current); -} -END_TEST -TEST(unpack, unpack_skip_neg_test) { - cpcp_unpack_context_init(&unpack, "žttest", 6, NULL, NULL); - cpon_unpack_skip_insignificant(&unpack); - ck_assert_str_eq("test", unpack.current); -} -END_TEST \ No newline at end of file diff --git a/tests/unit/libshvchainpack/meson.build b/tests/unit/libshvchainpack/meson.build index 707495a..4642eb2 100644 --- a/tests/unit/libshvchainpack/meson.build +++ b/tests/unit/libshvchainpack/meson.build @@ -1,12 +1,17 @@ unittest_libshvchainpack = executable( 'unittest-libshvchainpack', [ - 'packunpack.c', 'chainpack.c', + 'chainpackh.c', + 'cp_pack.c', + 'cp_unpack.c', + 'cpdatetime.c', + 'cpdecimal.c', 'cpon.c', + unittest_utils_src, ], dependencies: [libshvrpc_dep, check_suite, obstack], - include_directories: includes, + include_directories: [includes, unittest_utils_includes], ) test( 'unittest-libshvchainpack', @@ -15,4 +20,5 @@ test( env: unittests_env, protocol: 'tap', depends: unittest_libshvchainpack, + timeout: 120, ) diff --git a/tests/unit/libshvchainpack/packunpack.c b/tests/unit/libshvchainpack/packunpack.c deleted file mode 100644 index 010ea85..0000000 --- a/tests/unit/libshvchainpack/packunpack.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "packunpack.h" - -struct cpcp_pack_context pack; -struct cpcp_unpack_context unpack; - -uint8_t *stashbuf; -size_t stashsiz; - -static void pack_overflow_handler(struct cpcp_pack_context *pack, size_t size_hint) { - size_t off = pack->current - pack->start; - stashsiz *= 2; - uint8_t *buf = realloc(stashbuf, stashsiz); - ck_assert_ptr_nonnull(buf); - stashbuf = buf; - pack->start = (char *)buf; - pack->current = (char *)buf + off; - pack->end = (char *)buf + stashsiz; -} - -void setup_pack(void) { - stashsiz = 2; - stashbuf = malloc(stashsiz); - cpcp_pack_context_init(&pack, stashbuf, stashsiz, pack_overflow_handler); -} - -void teardown_pack(void) { - free(stashbuf); -} diff --git a/tests/unit/libshvchainpack/packunpack.h b/tests/unit/libshvchainpack/packunpack.h deleted file mode 100644 index b7efdfd..0000000 --- a/tests/unit/libshvchainpack/packunpack.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef _PACKUNPACK_H_ -#define _PACKUNPACK_H_ -#include -#include -#include -#include -#include - -/* Unitility macro that provides array of bytes with its size */ -struct bdata { - const uint8_t *const v; - const size_t len; -}; -#define B(...) \ - (struct bdata) { \ - .v = (const uint8_t[]){__VA_ARGS__}, \ - .len = sizeof((const uint8_t[]){__VA_ARGS__}), \ - } - -extern struct cpcp_pack_context pack; -extern struct cpcp_unpack_context unpack; - -extern uint8_t *stashbuf; -extern size_t stashsiz; - -#define ck_assert_stash(v, len) \ - do { \ - ck_assert_uint_eq(pack.current - pack.start, len); \ - ck_assert_mem_eq(stashbuf, v, len); \ - } while (false) - -#define ck_assert_stash(v, len) \ - do { \ - ck_assert_uint_eq(pack.current - pack.start, len); \ - ck_assert_mem_eq(stashbuf, v, len); \ - } while (false) - -#define ck_assert_stashstr(v) \ - do { \ - cpcp_pack_copy_byte(&pack, '\0'); \ - ck_assert_str_eq((char *)stashbuf, v); \ - } while (false) - -void setup_pack(void); -void teardown_pack(void); - -#endif diff --git a/tests/unit/libshvrpc/meson.build b/tests/unit/libshvrpc/meson.build index 96fc410..49f6400 100644 --- a/tests/unit/libshvrpc/meson.build +++ b/tests/unit/libshvrpc/meson.build @@ -1,10 +1,14 @@ unittest_libshvrpc = executable( 'unittest-libshvrpc', [ + 'rpcmsg_meta.c', + 'rpcmsg_pack.c', + 'rpcmsg_access.c', 'rpcurl.c', + unittest_utils_src, ], dependencies: [libshvrpc_dep, check_suite, obstack], - include_directories: includes, + include_directories: [includes, unittest_utils_includes], ) test( 'unittest-libshvrpc', diff --git a/tests/unit/libshvrpc/rpcmsg_access.c b/tests/unit/libshvrpc/rpcmsg_access.c new file mode 100644 index 0000000..ed9015b --- /dev/null +++ b/tests/unit/libshvrpc/rpcmsg_access.c @@ -0,0 +1,95 @@ +#include "shv/cp.h" +#include +#include + +#define SUITE "rpcmsg_access" +#include +#include "unpack.h" +#include "item.h" + + +TEST_CASE(all) {} + + +static const struct { + enum rpcmsg_access acc; + const char *str; +} acc_str_d[] = { + {RPCMSG_ACC_BROWSE, "bws"}, + {RPCMSG_ACC_READ, "rd"}, + {RPCMSG_ACC_WRITE, "wr"}, + {RPCMSG_ACC_COMMAND, "cmd"}, + {RPCMSG_ACC_CONFIG, "cfg"}, + {RPCMSG_ACC_SERVICE, "srv"}, + {RPCMSG_ACC_SUPER_SERVICE, "ssrv"}, + {RPCMSG_ACC_DEVEL, "dev"}, + {RPCMSG_ACC_ADMIN, "su"}, + {RPCMSG_ACC_INVALID, ""}, +}; +ARRAY_TEST(all, acc_str) { + ck_assert_str_eq(rpcmsg_access_str(_d.acc), _d.str); +} +END_TEST + +static const struct { + const char *str; + enum rpcmsg_access acc; +} acc_extract_d[] = { + {"bws", RPCMSG_ACC_BROWSE}, + {"rd", RPCMSG_ACC_READ}, + {"wr", RPCMSG_ACC_WRITE}, + {"cmd", RPCMSG_ACC_COMMAND}, + {"cfg", RPCMSG_ACC_CONFIG}, + {"srv", RPCMSG_ACC_SERVICE}, + {"ssrv", RPCMSG_ACC_SUPER_SERVICE}, + {"dev", RPCMSG_ACC_DEVEL}, + {"su", RPCMSG_ACC_ADMIN}, + {"wr,other", RPCMSG_ACC_WRITE}, + {"Some,write,rd", RPCMSG_ACC_READ}, + {"", RPCMSG_ACC_INVALID}, + {"some,other", RPCMSG_ACC_INVALID}, +}; +ARRAY_TEST(all, acc_extract) { + ck_assert_int_eq(rpcmsg_access_extract(_d.str), _d.acc); +} +END_TEST + +ARRAY_TEST(all, acc_unpack, acc_extract_d) { + char *str; + ck_assert_int_ne(asprintf(&str, "\"%s\"", _d.str), -1); + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + ck_assert_int_eq(rpcmsg_access_unpack(unpack, &item), _d.acc); + unpack_free(unpack); + free(str); +} +END_TEST + +TEST(all, acc_unpack_invalid) { + const char *str = "42"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + ck_assert_int_eq(rpcmsg_access_unpack(unpack, &item), RPCMSG_ACC_INVALID); + unpack_free(unpack); +} + +TEST(all, acc_unpack_invalid_blob) { + const char *str = "x\"00\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + ck_assert_int_eq(rpcmsg_access_unpack(unpack, &item), RPCMSG_ACC_INVALID); + unpack_free(unpack); +} + +/* It should always unpack the whole string */ +TEST(all, acc_unpack_fully) { + const char *str = "[\"bws,and,some,other\"]"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_LIST); + ck_assert_int_eq(rpcmsg_access_unpack(unpack, &item), RPCMSG_ACC_BROWSE); + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + unpack_free(unpack); +} diff --git a/tests/unit/libshvrpc/rpcmsg_meta.c b/tests/unit/libshvrpc/rpcmsg_meta.c new file mode 100644 index 0000000..878b2c3 --- /dev/null +++ b/tests/unit/libshvrpc/rpcmsg_meta.c @@ -0,0 +1,57 @@ +#include +#include +#include +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +#define SUITE "rpcmsg_meta" +#include +#include "unpack.h" +#include "item.h" + + +TEST_CASE(all) {} + +static const struct { + const char *str; + const struct rpcmsg_meta meta; +} unpack_obstack_d[] = { + {"<>null", (struct rpcmsg_meta){.request_id = INT64_MIN}}, + {"<8:42,9:\".app\",10:\"ping\",13:\"some,dev,acc\">null", + (struct rpcmsg_meta){ + .type = RPCMSG_T_REQUEST, + .request_id = 42, + .path = ".app", + .method = "ping", + .access_grant = RPCMSG_ACC_DEVEL, + }}, + {"<1:1,8:24u,11:[0,3]>null", + (struct rpcmsg_meta){ + .type = RPCMSG_T_RESPONSE, + .request_id = 24, + .cids = {.ptr = (void *)(const uint8_t[]){0x88, 0x40, 0x43, 0xff}, + .siz = 4}, + }}, +}; +ARRAY_TEST(all, unpack_obstack) { + cp_unpack_t unpack = unpack_cpon(_d.str); + struct cpitem item = (struct cpitem){}; + struct rpcmsg_meta meta = (struct rpcmsg_meta){}; + struct obstack obstack; + obstack_init(&obstack); + + ck_assert(rpcmsg_meta_unpack(unpack, &item, &meta, NULL, &obstack)); + ck_assert_int_eq(meta.type, _d.meta.type); + ck_assert_int_eq(meta.request_id, _d.meta.request_id); + ck_assert_pstr_eq(meta.path, _d.meta.path); + ck_assert_pstr_eq(meta.method, _d.meta.method); + ck_assert_int_eq(meta.access_grant, _d.meta.access_grant); + ck_assert_int_eq(meta.cids.siz, _d.meta.cids.siz); + ck_assert_mem_eq(meta.cids.ptr, _d.meta.cids.ptr, _d.meta.cids.siz); + cp_unpack(unpack, &item); + ck_assert_item_type(item, CP_ITEM_NULL); + + obstack_free(&obstack, NULL); + unpack_free(unpack); +} +END_TEST diff --git a/tests/unit/libshvrpc/rpcmsg_pack.c b/tests/unit/libshvrpc/rpcmsg_pack.c new file mode 100644 index 0000000..f583873 --- /dev/null +++ b/tests/unit/libshvrpc/rpcmsg_pack.c @@ -0,0 +1,28 @@ +#include + +#define SUITE "rpcmsg_pack" +#include +#include "packstream.h" + + +TEST_CASE(all, setup_packstream_pack_cpon, teardown_packstream_pack) {} + +TEST(all, request) { + rpcmsg_pack_request(packstream_pack, ".app", "ping", 42); + ck_assert_packstr("<1:1,8:42,9:\".app\",10:\"ping\">i{1"); +} +END_TEST + +TEST(all, signal) { + rpcmsg_pack_signal(packstream_pack, "value", "qchng"); + ck_assert_packstr("<1:1,9:\"value\",10:\"qchng\">i{1"); +} +END_TEST + +TEST(all, chng) { + rpcmsg_pack_chng(packstream_pack, "value"); + ck_assert_packstr("<1:1,9:\"value\",10:\"chng\">i{1"); +} +END_TEST + +// TODO test pack response and error diff --git a/tests/unit/libshvrpc/rpcurl.c b/tests/unit/libshvrpc/rpcurl.c index 388acf2..03368bf 100644 --- a/tests/unit/libshvrpc/rpcurl.c +++ b/tests/unit/libshvrpc/rpcurl.c @@ -1,7 +1,8 @@ +#include + #define SUITE "rpcurl" #include -#include TEST_CASE(parse){}; diff --git a/tests/unit/meson.build b/tests/unit/meson.build index e9d2486..7c2e4cd 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -10,5 +10,6 @@ unittests_env = [ 'CK_VERBOSITY=silent', ] +subdir('utils') subdir('libshvchainpack') subdir('libshvrpc') diff --git a/tests/unit/utils/bdata.h b/tests/unit/utils/bdata.h new file mode 100644 index 0000000..5f0576a --- /dev/null +++ b/tests/unit/utils/bdata.h @@ -0,0 +1,17 @@ +#ifndef BDATA_H +#define BDATA_H +#include +#include + +/* Unitility macro that provides array of bytes with its size */ +struct bdata { + const uint8_t *const v; + const size_t len; +}; +#define B(...) \ + (struct bdata) { \ + .v = (const uint8_t[]){__VA_ARGS__}, \ + .len = sizeof((const uint8_t[]){__VA_ARGS__}), \ + } + +#endif diff --git a/tests/unit/utils/item.h b/tests/unit/utils/item.h new file mode 100644 index 0000000..61efa6c --- /dev/null +++ b/tests/unit/utils/item.h @@ -0,0 +1,92 @@ +#ifndef ITEM_H +#define ITEM_H +#include + + +#define ck_assert_blob(a, b) \ + do { \ + ck_assert_int_eq(a.as.Blob.len, b.as.Blob.len); \ + ck_assert_mem_eq(a.buf, b.buf, a.as.Blob.len); \ + ck_assert_int_eq(a.as.Blob.eoff, b.as.Blob.eoff); \ + ck_assert_uint_eq(a.as.Blob.flags, b.as.Blob.flags); \ + } while (false) + +#define ck_assert_item(a, b) \ + do { \ + ck_assert_int_eq(a.type, b.type); \ + switch (a.type) { \ + case CP_ITEM_INVALID: \ + case CP_ITEM_NULL: \ + break; \ + case CP_ITEM_BOOL: \ + ck_assert(a.as.Bool == b.as.Bool); \ + break; \ + case CP_ITEM_INT: \ + ck_assert_int_eq(a.as.Int, b.as.Int); \ + break; \ + case CP_ITEM_UINT: \ + ck_assert_uint_eq(a.as.UInt, b.as.UInt); \ + break; \ + case CP_ITEM_DOUBLE: \ + ck_assert_double_eq(a.as.Double, b.as.Double); \ + break; \ + case CP_ITEM_DECIMAL: \ + ck_assert_int_eq(a.as.Decimal.mantisa, b.as.Decimal.mantisa); \ + ck_assert_int_eq(a.as.Decimal.exponent, b.as.Decimal.exponent); \ + break; \ + case CP_ITEM_BLOB: \ + case CP_ITEM_STRING: \ + ck_assert_blob(a, b); \ + break; \ + case CP_ITEM_DATETIME: \ + ck_assert_int_eq(a.as.Datetime.msecs, b.as.Datetime.msecs); \ + ck_assert_int_eq(a.as.Datetime.offutc, b.as.Datetime.offutc); \ + break; \ + case CP_ITEM_LIST: \ + case CP_ITEM_MAP: \ + case CP_ITEM_IMAP: \ + case CP_ITEM_META: \ + case CP_ITEM_CONTAINER_END: \ + break; \ + default: \ + abort(); \ + }; \ + } while (false) + + +#define ck_assert_item_type(i, tp) ck_assert_int_eq(i.type, tp) + +#define ck_assert_item_bool(i, v) \ + do { \ + ck_assert_int_eq(i.type, CP_ITEM_BOOL); \ + ck_assert_int_eq(i.as.Bool, v); \ + } while (false) +#define ck_assert_item_true(i) ck_assert_item_bool(i, true) +#define ck_assert_item_false(i) ck_assert_item_bool(i, false) + +#define ck_assert_item_int(i, v) \ + do { \ + ck_assert_int_eq(i.type, CP_ITEM_INT); \ + ck_assert_int_eq(i.as.Int, v); \ + } while (false) + +#define ck_assert_item_uint(i, v) \ + do { \ + ck_assert_int_eq(i.type, CP_ITEM_UINT); \ + ck_assert_uint_eq(i.as.Int, v); \ + } while (false) + +#define ck_assert_item_double(i, v) \ + do { \ + ck_assert_int_eq(i.type, CP_ITEM_DOUBLE); \ + ck_assert_double_eq(i.as.Double, v); \ + } while (false) + +#define ck_assert_item_decimal(i, v) \ + do { \ + ck_assert_int_eq(i.type, CP_ITEM_DECIMAL); \ + ck_assert_int_eq(i.as.Decimal.mantisa, v.mantisa); \ + ck_assert_int_eq(i.as.Decimal.exponent, v.exponent); \ + } while (false) + +#endif diff --git a/tests/unit/utils/meson.build b/tests/unit/utils/meson.build new file mode 100644 index 0000000..e155092 --- /dev/null +++ b/tests/unit/utils/meson.build @@ -0,0 +1,5 @@ +unittest_utils_src = files( + 'packstream.c', + 'unpack.c', +) +unittest_utils_includes = include_directories('.') diff --git a/tests/unit/utils/packstream.c b/tests/unit/utils/packstream.c new file mode 100644 index 0000000..8bfe715 --- /dev/null +++ b/tests/unit/utils/packstream.c @@ -0,0 +1,40 @@ +#include "packstream.h" +#include "shv/cp_pack.h" +#include + +char *packbuf; +size_t packbufsiz; +FILE *packstream; + + +void setup_packstream(void) { + packbufsiz = 0; + packstream = open_memstream(&packbuf, &packbufsiz); + ck_assert_ptr_nonnull(packstream); +} + +void teardown_packstream(void) { + fclose(packstream); + free(packbuf); +} + + +static struct cp_pack_chainpack pack_chainpack; +static struct cp_pack_cpon pack_cpon; +cp_pack_t packstream_pack; + +void setup_packstream_pack_chainpack(void) { + setup_packstream(); + packstream_pack = cp_pack_chainpack_init(&pack_chainpack, packstream); +} + +void setup_packstream_pack_cpon(void) { + setup_packstream(); + packstream_pack = cp_pack_cpon_init(&pack_cpon, packstream, ""); +} + +void teardown_packstream_pack(void) { + if (packstream_pack == &pack_cpon.func) + free(pack_cpon.state.ctx); + teardown_packstream(); +} diff --git a/tests/unit/utils/packstream.h b/tests/unit/utils/packstream.h new file mode 100644 index 0000000..81bf9f9 --- /dev/null +++ b/tests/unit/utils/packstream.h @@ -0,0 +1,38 @@ +#ifndef PACKSTREAM_H +#define PACKSTREAM_H +#include +#include +#include +#include + + +extern char *packbuf; +extern size_t packbufsiz; +extern FILE *packstream; + +#define ck_assert_packbuf(v, len) \ + do { \ + fflush(packstream); \ + ck_assert_uint_eq(packbufsiz, len); \ + ck_assert_mem_eq(packbuf, v, len); \ + } while (false) + +#define ck_assert_packstr(v) \ + do { \ + fputc('\0', packstream); \ + fflush(packstream); \ + ck_assert_str_eq((char *)packbuf, v); \ + } while (false) + +void setup_packstream(void); +void teardown_packstream(void); + + +extern cp_pack_t packstream_pack; + +void setup_packstream_pack_chainpack(void); +void setup_packstream_pack_cpon(void); +void teardown_packstream_pack(void); + + +#endif diff --git a/tests/unit/utils/unpack.c b/tests/unit/utils/unpack.c new file mode 100644 index 0000000..282078d --- /dev/null +++ b/tests/unit/utils/unpack.c @@ -0,0 +1,35 @@ +#include "unpack.h" +#include +#include +#include + +struct unpack { + union { + struct cp_unpack_chainpack chainpack; + struct cp_unpack_cpon cpon; + }; + bool is_cpon; + FILE *f; +}; + +cp_unpack_t unpack_chainpack(struct bdata *b) { + struct unpack *u = malloc(sizeof *u); + u->f = fmemopen((void *)b->v, b->len, "r"); + u->is_cpon = false; + return cp_unpack_chainpack_init(&u->chainpack, u->f); +} + +cp_unpack_t unpack_cpon(const char *str) { + struct unpack *u = malloc(sizeof *u); + u->f = fmemopen((void *)str, strlen(str), "r"); + u->is_cpon = true; + return cp_unpack_cpon_init(&u->cpon, u->f); +} + +void unpack_free(cp_unpack_t unpack) { + struct unpack *u = (struct unpack *)unpack; + if (u->is_cpon) + free(u->cpon.state.ctx); + fclose(u->f); + free(u); +} diff --git a/tests/unit/utils/unpack.h b/tests/unit/utils/unpack.h new file mode 100644 index 0000000..60ceaa6 --- /dev/null +++ b/tests/unit/utils/unpack.h @@ -0,0 +1,13 @@ +#ifndef UNPACK_H +#define UNPACK_H +#include +#include "bdata.h" + + +cp_unpack_t unpack_chainpack(struct bdata *); + +cp_unpack_t unpack_cpon(const char *str); + +void unpack_free(cp_unpack_t); + +#endif From 6348edda032fa5a716b6b200f646688c2b956b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Fri, 15 Sep 2023 10:48:28 +0200 Subject: [PATCH 20/44] meson.build: declare GNU_SOURCE only if we are not on NuttX So far this is only confusing because NuttX does not handle _GNU_SOURCE in any way. --- meson.build | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 5ba2622..1bc20f4 100644 --- a/meson.build +++ b/meson.build @@ -10,8 +10,9 @@ cc = meson.get_compiler('c') isnuttx = cc.get_define('__NuttX__') != '' # Won't be defined outside NuttX fs = import('fs') - -add_project_arguments('-D_GNU_SOURCE', language: 'c') +if not isnuttx + add_project_arguments('-D_GNU_SOURCE', language: 'c') +endif add_project_arguments('-DPROJECT_VERSION="@0@"'.format(meson.project_version()), language: 'c') From 857db5a187cb8ae2364fc0e02eb9670fb12c1c55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Fri, 15 Sep 2023 10:49:28 +0200 Subject: [PATCH 21/44] gitlab-ci: rework with gitlab CI cache and improve style The CI now uses docker that provides various caching for Nix. The new stage devenv was added to ensure that we build development environment only once and reuse it from cache down the line. nix develop is now quiet everywhere because it should be served from cache and thus is no longer interesting. --- .gitlab-ci.yml | 96 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aa37b10..67526fa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,5 @@ stages: + - devenv - build - test - lint @@ -12,20 +13,38 @@ workflow: variables: GIT_DEPTH: 0 - GIT_SUBMODULE_STRATEGY: recursive - GIT_SUBMODULE_UPDATE_FLAGS: --jobs 2 .nix: + image: registry.gitlab.com/cynerd/gitlab-ci-nix tags: - - nix + - docker + cache: + key: "nix" + paths: + - ".nix-cache" + before_script: + - gitlab-ci-nix-cache-before + after_script: + - gitlab-ci-nix-cache-after + +## Development environment ##################################################### +devenv: + stage: devenv + extends: .nix + script: + - nix develop -c true ## Build stage ################################################################# -build: +.build: stage: build extends: .nix + needs: ["devenv"] + +build: + extends: .build script: - - nix develop -c meson setup build -Doptimization=plain $SETUP_ARGS - - nix develop -c meson compile -C build + - nix develop --quiet -c meson setup build -Doptimization=plain $SETUP_ARGS + - nix develop --quiet -c meson compile -C build artifacts: when: always paths: @@ -41,21 +60,24 @@ build-werror: SETUP_ARGS: --werror nix: - stage: build - extends: .nix + extends: .build script: - nix build .?submodules=1 ## Test stage ################################################################## -tests: +.test: stage: test extends: .nix + needs: ["devenv"] + +tests: + extends: .test needs: - job: build artifacts: false script: - - "nix develop -c meson setup build -Doptimization=plain -Db_coverage=true" - - "nix develop -c meson test -C build" + - nix develop --quiet -c meson setup build -Doptimization=plain -Db_coverage=true + - nix develop --quiet -c meson test -C build artifacts: when: always paths: @@ -79,8 +101,7 @@ valgrind:drd: VALGRIND: drd nix-check: - stage: test - extends: .nix + extends: .test needs: - job: nix artifacts: false @@ -88,21 +109,20 @@ nix-check: - nix flake check .?submodules=1 include: - - template: Security/SAST.gitlab-ci.yml - template: Security/Secret-Detection.gitlab-ci.yml ## Linters ##################################################################### .linter: stage: lint extends: .nix - needs: [] + needs: ["devenv"] allow_failure: true .mesonlinter: extends: .linter script: - - 'nix develop -c meson setup build -Doptimization=plain' - - 'nix develop -c meson compile -C build $COMPILE_ARG' + - 'nix develop --quiet -c meson setup build -Doptimization=plain' + - 'nix develop --quiet -c meson compile -C build $COMPILE_ARG' cppcheck: extends: .mesonlinter @@ -117,49 +137,49 @@ flawfinder: shellcheck: extends: .linter script: - - "git ls-files '**.sh' | xargs nix develop -c shellcheck" + - git ls-files '**.sh' | xargs nix develop --quiet -c shellcheck ## Style stage ################################################################# .style: stage: style extends: .nix - needs: [] + needs: ["devenv"] allow_failure: true muon: extends: .style script: - - "git ls-files '**/meson.build' meson_options.txt | nix develop -c xargs -n 1 muon fmt -c .muon_fmt.ini -i" - - "git diff --exit-code" + - git ls-files '**/meson.build' meson_options.txt | nix develop --quiet -c xargs -n 1 muon fmt -c .muon_fmt.ini -i + - git diff --exit-code clang-format: extends: .style script: - - "git ls-files '**.[ch]' | xargs nix develop -c clang-format -i" - - "git diff --exit-code" + - git ls-files '**.[ch]' | xargs nix develop --quiet -c clang-format -i + - git diff --exit-code shell-format: extends: .style script: - - "git ls-files '**.sh' '**.bats' | xargs nix develop -c shfmt --binary-next-line -w" - - "git diff --exit-code" + - git ls-files '**.sh' '**.bats' | xargs nix develop --quiet -c shfmt --binary-next-line -w + - git diff --exit-code nixfmt: extends: .style script: - - "nix fmt" - - "git diff --exit-code" + - nix fmt + - git diff --exit-code editorconfig-checker: extends: .style script: - - "nix develop -c editorconfig-checker" + - nix develop --quiet -c editorconfig-checker -exclude '.nix-cache/.*' gitlint: extends: .style script: - - "git fetch" - - "nix develop -c gitlint --commits origin/master..$CI_COMMIT_SHA" + - git fetch + - nix develop --quiet -c gitlint --commits origin/master..$CI_COMMIT_SHA ## Code Coverage stage ######################################################### coverage: @@ -169,8 +189,8 @@ coverage: - job: tests artifacts: true script: - - "nix develop -c ninja -j1 -C build coverage-html coverage-xml" - - "mv build/meson-logs/coveragereport ./" + - nix develop --quiet -c ninja -j1 -C build coverage-html coverage-xml + - mv build/meson-logs/coveragereport ./ coverage: '/lines\.\.\.\.\.\.: (\d+.\d+%)/' artifacts: expire_in: 1 month @@ -190,9 +210,9 @@ dist: - job: tests artifacts: false script: - - "nix develop -c meson setup build" - - "nix develop -c meson dist -C build --formats xztar,gztar,zip" - - "mv build/meson-dist/* ./" + - nix develop --quiet -c meson setup build + - nix develop --quiet -c meson dist -C build --formats xztar,gztar,zip + - mv build/meson-dist/* ./ artifacts: expire_in: 1 month paths: @@ -209,7 +229,7 @@ release: - job: dist artifacts: true before_script: - - "apk update" - - "apk add bash curl yq" + - apk update + - apk add bash curl yq script: - - "bash .release.sh" + - ./.release.sh From 0a79fef4afa7d07f859d05795c3dee6cb5f773ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Wed, 13 Sep 2023 16:53:26 +0200 Subject: [PATCH 22/44] Initial implementation of RPC handler --- cpconv/main.c | 2 +- cpconv/meson.build | 2 +- demo/device_handler.c | 33 ++ demo/device_handler.h | 14 + demo/main.c | 89 ++++++ demo/meson.build | 25 ++ demo/opts.c | 65 ++++ demo/opts.h | 13 + demo/path.gperf | 37 +++ flake.lock | 91 +++++- flake.nix | 16 +- include/meson.build | 4 +- include/shv/cp.h | 309 ++++++++++++++---- include/shv/cp_pack.h | 95 +++--- include/shv/cp_unpack.h | 258 ++++++++++++++- include/shv/rpcclient.h | 50 ++- include/shv/rpcclient_impl.h | 30 +- include/shv/rpchandler.h | 53 ++- include/shv/rpclogin.h | 8 + include/shv/rpcmsg.h | 167 ++++------ include/shv/rpcnode.h | 42 +++ include/shv/rpcping.h | 9 + include/shv/rpcrespond.h | 33 ++ include/shv/rpctoplevel.h | 20 ++ include/shv/rpcurl.h | 29 +- libshvchainpack/chainpack_pack.c | 64 ++-- libshvchainpack/chainpack_unpack.c | 152 ++++----- libshvchainpack/common.c | 49 +++ libshvchainpack/common.h | 18 ++ libshvchainpack/cp_pack.c | 19 +- libshvchainpack/cp_unpack.c | 174 +++++++--- libshvchainpack/cpcp.c | 407 ------------------------ libshvchainpack/cpitem.c | 23 ++ libshvchainpack/cpon_pack.c | 129 ++++---- libshvchainpack/cpon_unpack.c | 110 ++++--- libshvchainpack/libshvchainpack.version | 9 +- libshvchainpack/meson.build | 2 + libshvrpc/libshvrpc.version | 41 ++- libshvrpc/meson.build | 11 +- libshvrpc/rpcclient.c | 59 ---- libshvrpc/rpcclient_connect.c | 18 ++ libshvrpc/rpcclient_log.c | 95 ++++++ libshvrpc/rpcclient_login.c | 139 ++++++++ libshvrpc/rpcclient_stream.c | 191 ++++++++--- libshvrpc/rpchandler.c | 176 ++++++++++ libshvrpc/rpcmsg.c | 24 +- libshvrpc/rpcmsg_access.c | 2 +- libshvrpc/rpcmsg_head.c | 183 +++++++++++ libshvrpc/rpcmsg_meta.c | 150 --------- libshvrpc/rpcmsg_pack.c | 150 +++++---- libshvrpc/rpcnode.c | 45 +++ libshvrpc/rpcrespond.c | 102 ++++++ libshvrpc/rpctoplevel.c | 80 +++++ libshvrpc/rpcurl.c | 14 +- libshvrpc/rpcurl_query.gperf | 5 +- meson.build | 21 +- meson_options.txt | 14 +- shvc/main.c | 58 ++++ shvc/meson.build | 22 ++ shvc/opts.c | 72 +++++ shvc/opts.h | 16 + {shvbroker => shvcbroker}/config.c | 0 {shvbroker => shvcbroker}/config.h | 0 {shvbroker => shvcbroker}/main.c | 0 {shvbroker => shvcbroker}/meson.build | 2 +- subprojects/uriparser.wrap | 3 - tests/meson.build | 3 + tests/run/__init__.py | 1 + tests/run/conftest.py | 28 ++ tests/run/meson.build | 49 +-- tests/run/test_cpconv.py | 41 +++ tests/test-driver.sh | 1 + tests/unit/libshvchainpack/chainpack.c | 113 +++++-- tests/unit/libshvchainpack/cp_pack.c | 2 + tests/unit/libshvchainpack/cp_unpack.c | 153 +++++++-- tests/unit/libshvchainpack/cpon.c | 104 +++--- tests/unit/libshvchainpack/meson.build | 1 + tests/unit/libshvrpc/meson.build | 9 +- tests/unit/libshvrpc/rpcclient_links.c | 44 +++ tests/unit/libshvrpc/rpcclient_login.c | 57 ++++ tests/unit/libshvrpc/rpcclient_ping.c | 23 ++ tests/unit/libshvrpc/rpcclient_ping.h | 7 + tests/unit/libshvrpc/rpcmsg_access.c | 4 +- tests/unit/libshvrpc/rpcmsg_head.c | 165 ++++++++++ tests/unit/libshvrpc/rpcmsg_meta.c | 57 ---- tests/unit/libshvrpc/rpcmsg_pack.c | 35 +- tests/unit/libshvrpc/rpcurl.c | 34 +- tests/unit/utils/bdata.h | 8 + tests/unit/utils/broker.c | 86 +++++ tests/unit/utils/broker.h | 12 + tests/unit/utils/item.h | 40 +-- tests/unit/utils/meson.build | 5 + tests/unit/utils/tmpdir.c | 35 ++ tests/unit/utils/tmpdir.h | 13 + 94 files changed, 3950 insertions(+), 1523 deletions(-) create mode 100644 demo/device_handler.c create mode 100644 demo/device_handler.h create mode 100644 demo/main.c create mode 100644 demo/meson.build create mode 100644 demo/opts.c create mode 100644 demo/opts.h create mode 100644 demo/path.gperf create mode 100644 include/shv/rpclogin.h create mode 100644 include/shv/rpcnode.h create mode 100644 include/shv/rpcping.h create mode 100644 include/shv/rpcrespond.h create mode 100644 include/shv/rpctoplevel.h create mode 100644 libshvchainpack/common.c create mode 100644 libshvchainpack/common.h delete mode 100644 libshvchainpack/cpcp.c create mode 100644 libshvchainpack/cpitem.c delete mode 100644 libshvrpc/rpcclient.c create mode 100644 libshvrpc/rpcclient_connect.c create mode 100644 libshvrpc/rpcclient_log.c create mode 100644 libshvrpc/rpcclient_login.c create mode 100644 libshvrpc/rpchandler.c create mode 100644 libshvrpc/rpcmsg_head.c delete mode 100644 libshvrpc/rpcmsg_meta.c create mode 100644 libshvrpc/rpcnode.c create mode 100644 libshvrpc/rpcrespond.c create mode 100644 libshvrpc/rpctoplevel.c create mode 100644 shvc/main.c create mode 100644 shvc/meson.build create mode 100644 shvc/opts.c create mode 100644 shvc/opts.h rename {shvbroker => shvcbroker}/config.c (100%) rename {shvbroker => shvcbroker}/config.h (100%) rename {shvbroker => shvcbroker}/main.c (100%) rename {shvbroker => shvcbroker}/meson.build (90%) delete mode 100644 subprojects/uriparser.wrap create mode 100644 tests/run/__init__.py create mode 100644 tests/run/conftest.py create mode 100644 tests/run/test_cpconv.py create mode 100644 tests/unit/libshvrpc/rpcclient_links.c create mode 100644 tests/unit/libshvrpc/rpcclient_login.c create mode 100644 tests/unit/libshvrpc/rpcclient_ping.c create mode 100644 tests/unit/libshvrpc/rpcclient_ping.h create mode 100644 tests/unit/libshvrpc/rpcmsg_head.c delete mode 100644 tests/unit/libshvrpc/rpcmsg_meta.c create mode 100644 tests/unit/utils/broker.c create mode 100644 tests/unit/utils/broker.h create mode 100644 tests/unit/utils/tmpdir.c create mode 100644 tests/unit/utils/tmpdir.h diff --git a/cpconv/main.c b/cpconv/main.c index c719086..827c0e6 100644 --- a/cpconv/main.c +++ b/cpconv/main.c @@ -69,7 +69,7 @@ int main(int argc, char **argv) { res = chainpack_unpack(in, &item); res > 0; })) { - if (item.type == CP_ITEM_INVALID) + if (item.type == CPITEM_INVALID) break; ssize_t res; if (conf.op == OP_PACK) diff --git a/cpconv/meson.build b/cpconv/meson.build index 1c5a078..f715569 100644 --- a/cpconv/meson.build +++ b/cpconv/meson.build @@ -1,5 +1,5 @@ cpconv_sources = files('opts.c') -cpconv_dependencies = [libshvchainpack_dep, argp] +cpconv_dependencies = [libshvchainpack_dep] if isnuttx diff --git a/demo/device_handler.c b/demo/device_handler.c new file mode 100644 index 0000000..02e4fc2 --- /dev/null +++ b/demo/device_handler.c @@ -0,0 +1,33 @@ +#include "device_handler.h" + +static enum rpchandler_func_res track(struct demo_handler *h, rpcreceive_t receive, + cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta) { + return false; +} + + +static const char *pthcmp(const char *pth, const char *name) { + size_t len = strlen(name); + if (!strncmp(pth, name, len) && (pth[len] == '\0' || pth[len] == '/')) + return &pth[len]; + return NULL; +} + +enum rpchandler_func_res demo_device_handler_func(void *ptr, rpcreceive_t receive, + cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta) { + struct demo_handler *h = ptr; + if (meta->path == NULL || *meta->path == '\0') { + /* We won't be called for anything else because top level is handled by + * rpctoplevel. + */ + assert(!strcmp(meta->method, "ls")); + } + const char *pth1, *pth2; + if ((pth1 = pthcmp(meta->path, "track"))) { + if (*pth1 == '\0') { + return track(h, receive, unpack, item, meta); + } + } + + return false; +} diff --git a/demo/device_handler.h b/demo/device_handler.h new file mode 100644 index 0000000..03cb635 --- /dev/null +++ b/demo/device_handler.h @@ -0,0 +1,14 @@ +#ifndef _DEVICE_HANDLER_H +#define _DEVICE_HANDLER_H +#include + + +struct demo_handler { + rpchandler_func func; +}; + + +enum rpchandler_func_res demo_device_handler_func(void *ptr, rpcreceive_t receive, + cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta); + +#endif diff --git a/demo/main.c b/demo/main.c new file mode 100644 index 0000000..9268c2c --- /dev/null +++ b/demo/main.c @@ -0,0 +1,89 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "opts.h" +#include "device_handler.h" + + +int main(int argc, char **argv) { + struct conf conf; + parse_opts(argc, argv, &conf); + + const char *errpos; + struct rpcurl *rpcurl = rpcurl_parse(conf.url, &errpos); + if (rpcurl == NULL) { + fprintf(stderr, "Invalid URL: %s\n", conf.url); + fprintf(stderr, "%*.s^\n", 13 + (int)(errpos - conf.url), ""); + return 1; + } + rpcclient_t client = rpcclient_connect(rpcurl); + if (client == NULL) { + fprintf(stderr, "Failed to connect to the: %s\n", rpcurl->location); + fprintf(stderr, "Please check your connection to the network\n"); + rpcurl_free(rpcurl); + return 1; + } + rpcclient_logger_t logger = NULL; + if (conf.verbose > 0) { + logger = rpcclient_logger_new(stderr, conf.verbose); + client->logger = logger; + } + switch (rpcclient_login(client, &rpcurl->login)) { + case RPCCLIENT_LOGIN_OK: + break; + case RPCCLIENT_LOGIN_INVALID: + fprintf(stderr, "Invalid login for connecting to the: %s\n", conf.url); + rpcclient_destroy(client); + rpcurl_free(rpcurl); + return 1; + case RPCCLIENT_LOGIN_ERROR: + fprintf(stderr, "Communication error with server\n"); + return 2; + } + rpcurl_free(rpcurl); + + rpctoplevel_t toplevel = rpctoplevel_new("demo-device", PROJECT_VERSION, true); + + struct demo_handler h = {.func = demo_device_handler_func}; + const rpchandler_func *funcs[] = { + rpctoplevel_func(toplevel), + &h.func, + NULL, + }; + rpchandler_t handler = rpchandler_new(client, funcs, NULL); + assert(handler); + + int res = 0; + struct pollfd pfd = {.fd = client->rfd, .events = POLLIN | POLLHUP}; + while (true) { + int pr = poll( + &pfd, 1, rpcclient_maxsleep(client, RPC_DEFAULT_IDLE_TIME) * 1000); + if (pr == 0) { + cp_pack_t pack = rpchandler_msg_new(handler); + rpcmsg_pack_request_void(pack, ".broker/app", "ping", 0); + rpchandler_msg_send(handler); + } else if (pr == -1 || pfd.revents & POLLERR) { + fprintf(stderr, "Poll error: %s\n", strerror(errno)); + res = 1; + break; + } else if (pfd.revents & POLLIN) { + rpchandler_next(handler); + } else if (pfd.revents & POLLHUP) { + fprintf(stderr, "Disconnected from the broker\n"); + break; + } + } + + rpchandler_destroy(handler); + rpctoplevel_destroy(toplevel); + rpcclient_destroy(client); + rpcclient_logger_destroy(logger); + return res; +} diff --git a/demo/meson.build b/demo/meson.build new file mode 100644 index 0000000..f80a5bd --- /dev/null +++ b/demo/meson.build @@ -0,0 +1,25 @@ +demo_device_sources = files( + 'device_handler.c', + 'opts.c', +) +demo_device_dependencies = [libshvrpc_dep] + + +if isnuttx + demo_device = static_library( + 'demo-device', + demo_device_sources + ['main.c'], + dependencies: demo_device_dependencies, + install: not meson.is_subproject(), + c_args: '-Dmain=demo_device_main', + ) +else + demo_device = executable( + 'demo-device', + demo_device_sources + ['main.c'], + dependencies: demo_device_dependencies, + install: true, + ) +endif + +demo_internal_includes = include_directories('.') diff --git a/demo/opts.c b/demo/opts.c new file mode 100644 index 0000000..4fe67d1 --- /dev/null +++ b/demo/opts.c @@ -0,0 +1,65 @@ +#include "opts.h" +#include +#include +#include +#include + +static const char *default_url = + "tcp://test@localhost?password=test&devmount=test/device"; + + +static void print_usage(const char *argv0) { + fprintf(stderr, "%s [-v] [URL]\n", argv0); +} + +static void print_help(const char *argv0) { + print_usage(argv0); + fprintf(stderr, "Example SHV RPC device implemented using SHVC.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Arguments:\n"); + fprintf(stderr, " -v Increase logging level of the communication\n"); + fprintf(stderr, " -q Decrease logging level of the communication\n"); + fprintf(stderr, " -d Set maximul logging level of the communication\n"); + fprintf(stderr, " -V Print SHVC version and exit\n"); + fprintf(stderr, " -h Print this help text\n"); +} + +void parse_opts(int argc, char **argv, struct conf *conf) { + *conf = (struct conf){ + .url = default_url, + .verbose = 0, + }; + + int c; + while ((c = getopt(argc, argv, "vqdVh")) != -1) { + switch (c) { + case 'v': + if (conf->verbose < UINT_MAX) + conf->verbose++; + break; + case 'q': + if (conf->verbose > 0) + conf->verbose--; + break; + case 'd': + conf->verbose = UINT_MAX; + break; + case 'V': + printf("%s " PROJECT_VERSION "\n", argv[0]); + exit(0); + case 'h': + print_help(argv[0]); + exit(0); + default: + print_usage(argv[0]); + fprintf(stderr, "Invalid option: -%c\n", c); + exit(2); + } + } + if (optind < argc) + conf->url = argv[optind++]; + if (optind < argc) { + fprintf(stderr, "Invalid argument: %s\n", argv[optind]); + exit(2); + } +} diff --git a/demo/opts.h b/demo/opts.h new file mode 100644 index 0000000..734a3d2 --- /dev/null +++ b/demo/opts.h @@ -0,0 +1,13 @@ +#ifndef _DEMO_OPTS_H_ +#define _DEMO_OPTS_H_ +#include + +struct conf { + const char *url; + unsigned verbose; +}; + +/* Parse arguments. */ +void parse_opts(int argc, char **argv, struct conf *conf) __attribute__((nonnull)); + +#endif diff --git a/demo/path.gperf b/demo/path.gperf new file mode 100644 index 0000000..1a2e412 --- /dev/null +++ b/demo/path.gperf @@ -0,0 +1,37 @@ +%compare-lengths +%compare-strncmp + +%pic +%enum +%readonly-tables +%switch=1 +%language=ANSI-C + +%define hash-function-name gperf_demo_path_hash +%define lookup-function-name gperf_demo_path +%define string-pool-name gperf_demo_path_string +%define constants-prefix GPERF_DEMO_PATH_ +%define slot-name offset + +%struct-type +%{ +enum gperf_demo_path { + GPERF_DEMO_PATH_ROOT, + GPERF_DEMO_PATH_PASSWORD, + GPERF_DEMO_PATH_DEVID, + GPERF_DEMO_PATH_DEVMOUNT, +}; +struct gperf_demo_path_match { + int offset; + enum gperf_demo_path query; +}; +%} + +struct gperf_demo_path_match; + +%% +shapass, GPERF_DEMO_PATH_SHAPASS +password, GPERF_DEMO_PATH_PASSWORD +devid, GPERF_DEMO_PATH_DEVID +devmount, GPERF_DEMO_PATH_DEVMOUNT +%% diff --git a/flake.lock b/flake.lock index c9c2eee..30920ce 100644 --- a/flake.lock +++ b/flake.lock @@ -40,6 +40,23 @@ "inputs": { "systems": "systems_2" }, + "locked": { + "lastModified": 1692799911, + "narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44", + "type": "github" + }, + "original": { + "id": "flake-utils", + "type": "indirect" + } + }, + "flake-utils_3": { + "inputs": { + "systems": "systems_3" + }, "locked": { "lastModified": 1689068808, "narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", @@ -53,9 +70,9 @@ "type": "indirect" } }, - "flake-utils_3": { + "flake-utils_4": { "inputs": { - "systems": "systems_3" + "systems": "systems_4" }, "locked": { "lastModified": 1681202837, @@ -86,11 +103,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1689752456, - "narHash": "sha256-VOChdECcEI8ixz8QY+YC4JaNEFwQd1V8bA0G4B28Ki0=", + "lastModified": 1692808169, + "narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "7f256d7da238cb627ef189d56ed590739f42f13b", + "rev": "9201b5ff357e781bf014d0330d18555695df7ba8", "type": "github" }, "original": { @@ -99,6 +116,20 @@ } }, "nixpkgs_3": { + "locked": { + "lastModified": 1692128808, + "narHash": "sha256-Di1Zm/P042NuwThMiZNrtmaAjd4Tm2qBOKHX7xUOfMk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4ed9856be002a730234a1a1ed9dcd9dd10cbdb40", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_4": { "locked": { "lastModified": 1682109806, "narHash": "sha256-d9g7RKNShMLboTWwukM+RObDWWpHKaqTYXB48clBWXI=", @@ -112,25 +143,46 @@ "type": "indirect" } }, + "pyshv": { + "inputs": { + "flake-utils": "flake-utils_3", + "nixpkgs": "nixpkgs_3", + "shvapp": "shvapp" + }, + "locked": { + "lastModified": 1692968432, + "narHash": "sha256-WLpSNxzbH5W2eqHz+wdljFJERM+b5M49b5/e+o85agM=", + "ref": "urltweak", + "rev": "d208f1a53090fc4e46cb229ba61ede5a0dea12dd", + "revCount": 95, + "type": "git", + "url": "https://gitlab.com/elektroline-predator/pyshv.git" + }, + "original": { + "ref": "urltweak", + "type": "git", + "url": "https://gitlab.com/elektroline-predator/pyshv.git" + } + }, "root": { "inputs": { "check-suite": "check-suite", "flake-utils": "flake-utils_2", "nixpkgs": "nixpkgs_2", - "shvapp": "shvapp" + "pyshv": "pyshv" } }, "shvapp": { "inputs": { - "flake-utils": "flake-utils_3", - "nixpkgs": "nixpkgs_3" + "flake-utils": "flake-utils_4", + "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1682590530, - "narHash": "sha256-Agpswh52I3qOZeaAmw3V/Kzw1zb6yyQ5mqaZOBzCE5k=", + "lastModified": 1691081147, + "narHash": "sha256-K19rIrLxuB5xVy/ZEbNHeHnPapyyeMvH9bOv3pGuvn8=", "ref": "refs/heads/master", - "rev": "fefca3fc2ef80ae8486ba892edf79fbc5ce4d7e8", - "revCount": 2063, + "rev": "e3154552a55d59be7d5c2426b92cd758e088444e", + "revCount": 2096, "submodules": true, "type": "git", "url": "https://github.com/silicon-heaven/shvapp.git" @@ -185,6 +237,21 @@ "repo": "default", "type": "github" } + }, + "systems_4": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 490ab07..c5e0d4b 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,7 @@ inputs = { check-suite.url = "github:cynerd/check-suite"; - shvapp.url = "git+https://github.com/silicon-heaven/shvapp.git?submodules=1"; + pyshv.url = "git+https://gitlab.com/elektroline-predator/pyshv.git?ref=urltweak"; }; outputs = { @@ -11,7 +11,7 @@ flake-utils, nixpkgs, check-suite, - shvapp, + pyshv, }: with builtins; with flake-utils.lib; @@ -28,7 +28,9 @@ outputs = ["out" "doc"]; sphinxRoot = "../docs"; buildInputs = [ + inih uriparser + openssl ]; nativeBuildInputs = [ # Build tools @@ -54,10 +56,16 @@ pkgs.check-suite ]; nativeCheckInputs = [ - pkgs.shvapp # Run tests bash bats + # Run tests + (python3.withPackages (pypkgs: + with pypkgs; [ + pytest + pytest-tap + pypkgs.pyshv + ])) ]; }; }; @@ -67,7 +75,7 @@ shvc = final: prev: packages (id prev); default = composeManyExtensions [ check-suite.overlays.default - shvapp.overlays.default + pyshv.overlays.default self.overlays.shvc ]; }; diff --git a/include/meson.build b/include/meson.build index 97b302c..d3e2bce 100644 --- a/include/meson.build +++ b/include/meson.build @@ -13,6 +13,4 @@ libshvrpc_headers = files( 'shv/rpcmsg.h', 'shv/rpcurl.h', ) -libshvbroker_headers = files( - 'shv/rpcbroker.h' -) +libshvbroker_headers = files('shv/rpcbroker.h') diff --git a/include/shv/cp.h b/include/shv/cp.h index 152df8c..e7b8f03 100644 --- a/include/shv/cp.h +++ b/include/shv/cp.h @@ -9,32 +9,130 @@ #include +/*! Definition of CP formats. */ enum cp_format { + /*! Binary format used in SHV communication. */ CP_ChainPack = 1, + /*! Human readable format based on JSON used in SHV communication logging + * but can be also used for data input and other such use cases. + */ CP_Cpon, + /*! Obsolete and no longer used format in SHV. It is mentioned only for + * completeness. + */ CP_Json, }; -enum cp_item_type { - CP_ITEM_INVALID = 0, - CP_ITEM_NULL, - CP_ITEM_BOOL, - CP_ITEM_INT, - CP_ITEM_UINT, - CP_ITEM_DOUBLE, - CP_ITEM_DECIMAL, - CP_ITEM_BLOB, - CP_ITEM_STRING, - CP_ITEM_DATETIME, - CP_ITEM_LIST, - CP_ITEM_MAP, - CP_ITEM_IMAP, - CP_ITEM_META, - CP_ITEM_CONTAINER_END, +/*! Enum identifying different items in SHV CP and few utility ones. */ +enum cpitem_type { + /*! Invalid or unknown type. Packers ignore this item and unpackers set it + * when error is encountered. + */ + CPITEM_INVALID = 0, + /*! NULL type used as dummy value in SHV. */ + CPITEM_NULL, + /*! Boolean value stored in `cpitem.as.Bool` (@ref cpitem.cpitem_as.Bool). */ + CPITEM_BOOL, + /*! Integer value stored in `cpitem.as.Int` (@ref cpitem.cpitem_as.Int). */ + CPITEM_INT, + /*! Unsigned integer value stored in `cpitem.as.UInt` (@ref + * cpitem.cpitem_as.UInt) */ + CPITEM_UINT, + /*! Double precision floating number value stored in `cpitem.as.Double` + * (@ref cpitem.cpitem_as.Double). */ + CPITEM_DOUBLE, + /*! Decimal number stored in `cpitem.as.Decimal` (@ref + * cpitem.cpitem_as.Decimal). */ + CPITEM_DECIMAL, + /*! Date and time with UTC offset stored in `cpitem.as.Datetime` (@ref + * cpitem.cpitem_as.Datetime). */ + CPITEM_DATETIME, + /*! Blob is sequence of bytes. + * + * To receive them you need to provide @ref cpitem.cpitem_buf.buf. Unpacker + * copies up to the @ref cpitem.bufsiz bytes to it and stores info about + * that in @ref cpbufinfo "cpitem.as.Blob". You might need to call + * unpacker multiple times to receive all bytes before you can unpack next + * item. + * + * Packer does the opposite. When you are packing you need to point @ref + * cpitem.cpitem_buf.rbuf to your data and set @ref cpbufinfo + * "cpitem.as.Blob" appropriately. Do not forget to correctly manage @ref + * cpbufinfo.flags and especially the @ref CPBI_F_FIRST and @ref CPBI_F_LAST + * flags. + */ + CPITEM_BLOB, + /*! Sequence of bytes that you should represent as a string. + * + * To receive them you need to provide @ref cpitem.cpitem_buf.chr. Unpacker + * copies up to the @ref cpitem.bufsiz bytes to it and stores info about + * that in @ref cpbufinfo "cpitem.as.String". You might need to call + * unpacker multiple times to receive all bytes before you can unpack next + * item. + * + * Packer does the opposite. When you are packing you need to point @ref + * cpitem.cpitem_buf.rchr to your data and set @ref cpbufinfo + * "cpitem.as.String" appropriately. + */ + CPITEM_STRING, + /*! Start of the list container. The followup items are part of this list up + * to the container end item. + */ + CPITEM_LIST, + /*! Start of the map container. The followup items need to be pairs of + * string and any other value. Map needs to be terminated with container + * end item. + * + * Containers from start to their respective end, strings and blobs are + * considered as a single items even when you need to invoke packer or + * unpacker multiple times. Meta container is always considered to be part + * of the following item and thus is not an item on its own. + */ + CPITEM_MAP, + /*! Start of the integer map container. The followup items need to be pairs + * of integer and any single value. Integer map needs to be terminated with + * container end item. + * + * Containers from start to their respective end, strings and blobs are + * considered as a single items even when you need to invoke packer or + * unpacker multiple times. Meta container is always considered to be part + * of the following item and thus is not an item on its own. + */ + CPITEM_IMAP, + /*! Special container that can precede any other item. Primarily it is used + * in SHV RPC protocol to store message info. The following items need to be + * pairs of either string or integer and any single value. Meta needs to be + * terminated with container end item. + */ + CPITEM_META, + /*! Container end item used to terminate lists, maps and metas. + */ + CPITEM_CONTAINER_END, + /*! The special item type that signals raw operation on the data. Sometimes + * it is required to bypass the parser (because there are binary data + * inserted in the data) or it is just more efficient to copy data without + * interpreting them. For that this special type is available. + * + * For unpacker you need to provide pointer to the writable buffer in + * @ref cpitem.cpitem_buf.buf and unpacker will store up to @ref + * cpitem.bufsiz bytes in it. Number of valid bytes will be stored in + * @ref cpbufinfo.len "cpitem.as.Blob.len". + * + * For packer you need to provide pointer to the data in @ref + * cpitem.cpitem_buf.rbuf and packer will write @ref cpbufinfo.len + * "cpitem.as.Blob.len" bytes without interpreting them. + */ + CPITEM_RAW = 256, }; -const char *cp_item_type_str(enum cp_item_type); +/*! Receive string name for the item type. + * + * @param tp: Item type the string should be provided for. + * @returns pointer to the string with name of the type. This is constant + * string: can't be freed or modified. + */ +const char *cpitem_type_str(enum cpitem_type tp); /*! Representation of decimal number. @@ -72,14 +170,14 @@ struct cpdatetime { int32_t offutc; }; -/*! Convert CP date and time representation to the C's time. +/*! Convert CP date and time representation to the POSIX time. * * @param v: Date and time to be converted. * @returns structure tm with date and time set. */ struct tm cpdttotm(struct cpdatetime v); -/*! Convert C's time to CP date and time representation. +/*! Convert POSIX time to CP date and time representation. * * @param v: Date and time to be converted. * @returns structure cpdatetime with date and time set. @@ -87,23 +185,60 @@ struct tm cpdttotm(struct cpdatetime v); struct cpdatetime cptmtodt(struct tm v); -/*! Signaling of first and last block. You should set both to `true` if this - * is the only block to be packed. If you plan to append additional chunks - * you should set on first iteration only `first` and to terminate you need - * to set `last`. +/*! Errors reported by unpack functions. + * + * Pack functions can encounter only @ref CPERR_IO because they expect sane + * input and writable output. The result is that unpack functions do not use + * these errors. */ +enum cperror { + /*! There is no error (item is invalid because nothing was stored into it) + */ + CPERR_NONE = 0, + /*! Stream reported end of file. */ + CPERR_EOF, + /*! Stream reported error. + * + * Investigate `errno` for the error code. + */ + CPERR_IO, + /*! Data in stream do not have valid format */ + CPERR_INVALID, + /*! Data in stream had size outside of the value supported by SHVC. + * + * This applies to numeric as well as too long strings or blobs. + */ + CPERR_OVERFLOW, +}; +/*! Flag used in @ref cpbufinfo.flags to inform about first block of Blob or + * String. This is always set when new string or blob is encountered. + */ #define CPBI_F_FIRST (1 << 0) +/*! Flag used in @ref cpbufinfo.flags to inform about last block of Blob or + * String. This tells you that no more bytes can be unpacked for the string. + */ #define CPBI_F_LAST (1 << 1) +/*! Flag used in @ref cpbufinfo.flags to signal string or blob with unknown + * size upfront. + */ #define CPBI_F_STREAM (1 << 2) +/*! Flag used in @ref cpbufinfo.flags for CPON blobs. There are two ways to + * pack blobs in CPON, regular one that uses escape sequences and sequence of + * hexadecimal numbers. This flag tells packer that it should use hex + * representation. Unpacker sets this flag to remember that it should interpret + * data as hex. + */ #define CPBI_F_HEX (1 << 3) +/*! Combination of @ref CPBI_F_FIRST and CPBI_F_LAST. It is pretty common to use + * these two together when you are packing short strings or blobs and thus they + * are provided in this macro. + */ #define CPBI_F_SINGLE (CPBI_F_FIRST | CPBI_F_LAST) -/* - */ +/*! Info about data stored in the @ref cpitem buffer. */ struct cpbufinfo { - // TODO we need to allow documented data skip without providing buffer - /*! Number of valid bytes in `buf` (`rbuf`, `chr` and `rchr`). + /*! Number of valid bytes in @ref cpitem.cpitem_buf. * * The special case is when you invoke packer just to get packed size (that * is `FILE` is `NULL`). In such case buffer is not accessed and thus it can @@ -117,33 +252,50 @@ struct cpbufinfo { * `eoff`. * * Unpacking: Set and modified by unpacker. Do not modify it when unpacking. - * In case `CPBI_F_STREAM` is not set in `flags` then you can use it to get - * a full size on first unpack (`flags` contains `CPBI_F_FIRST`). + * In case @ref CPBI_F_STREAM is not set in @ref cpbufinfo.flags then you + * can use it to get a full size on first unpack (@ref cpbufinfo.flags + * contains @ref CPBI_F_FIRST). * * The size is chosen to be at minimum 32 bits and this we should be able to - * for sure handle almost strings with 4G size. We are talking here about a + * for sure handle strings with almost 4G size. We are talking here about a * single message. Messages with such a huge size should be broker to the - * separate ones and this we do not have to support longer strings. + * separate ones and thus we do not have to support longer strings. */ unsigned long eoff; - /*! Bitwise combination of `VPBI_F_*` flags. */ + /*! Bitwise combination of `CPBI_F_*` flags. */ uint8_t flags; }; +/*! Representation of a single item (or even part of a single item) used for + * both packing and unpacking. + */ struct cpitem { - enum cp_item_type type; - /*! foo + /*! Type of the item. */ + enum cpitem_type type; + /*! TODO */ union cpitem_as { - struct cpbufinfo String; - struct cpbufinfo Blob; - struct cpdatetime Datetime; - struct cpdecimal Decimal; - unsigned long long UInt; + /*! Used to store value for @ref CPITEM_BOOL. */ + bool Bool; + /*! Used to store value for @ref CPITEM_INT. */ long long Int; + /*! Used to store value for @ref CPITEM_UINT. */ + unsigned long long UInt; + /*! Used to store value for @ref CPITEM_DOUBLE. */ double Double; - bool Bool; - } as; // TODO possibly just use anonymous union + /*! Used to store value for @ref CPITEM_DECIMAL. */ + struct cpdecimal Decimal; + /*! Used to store value for @ref CPITEM_DATETIME. */ + struct cpdatetime Datetime; + /*! Info about binary data for @ref CPITEM_BLOB. */ + struct cpbufinfo Blob; + /*! Info about string data for @ref CPITEM_STRING. */ + struct cpbufinfo String; + /*! Info about type of unpack error for @ref CPITEM_INVALID. */ + enum cperror Error; + } + /*! Wtf */ + as; // TODO possibly just use anonymous union /*! Pointer to the buffer with data chunk. * * It is defined both as `const` as well as modifiable. The pack functions @@ -154,36 +306,74 @@ struct cpitem { * The `chr` and `rchr` are additionally provided just to allow easy * assignment of the strings without changing type (which would be required * due to change of the sign). + * + * The special case is when you set buffer to `NULL` while `bufsiz != 0`, + * this discards up to `bufsiz` bytes instead of copying. This is used to + * skip data. */ union cpitem_buf { + /*! Buffer used to store binary data when unpacking. */ uint8_t *buf; + /*! Buffer used to receive binary data when packing. */ const uint8_t *rbuf; + /*! Buffer used to store string data when unpacking. */ char *chr; + /*! Buffer used to receive string data when packing. */ const char *rchr; }; - /*! Size of the `buf` (`rbuf`, `chr` and `rchr`). This is used only by - * unpacking functions to know the limit of the buffer. + /*! Size of the @ref cpitem_buf.buf (@ref cpitem_buf.rbuf, @ref + * cpitem_buf.chr and @ref cpitem_buf.rchr). This is used only by unpacking + * functions to know the limit of the buffer. + * + * It is common to set this to zero to receive type of the item and only + * after that you would set the pointer to the buffer and its size. */ size_t bufsiz; }; - -/*! Unpack next item from Chainpack. +/*! Minimal initialization of @ref cpitem for unpacking. + * + * The structure items are designed to be zero in default to allow + * initialization with `struct cpitem foo = {}` but in reality only item type, + * error type and size of the buffer to the zero. Type needs to be set because + * unpack could get confused if type would be string or blob. Error type needs + * to be set to signal no previous error. The buffer size needs to be zero to + * prevent writing to the buffer. + * + * @param item: Pointer to the item to be initialized. */ -ssize_t chainpack_pack(FILE *, const struct cpitem *) __attribute__((nonnull(2))); +static inline void cpitem_unpack_init(struct cpitem *item) { + item->type = CPITEM_INVALID; + item->as.Error = CPERR_NONE; + item->buf = NULL; + item->bufsiz = 0; +} -ssize_t _chainpack_pack_uint(FILE *, unsigned long long v); -size_t chainpack_unpack(FILE *, struct cpitem *) __attribute__((nonnull)); +/*! Unpack item from ChainPack data format. + * + * @returns Number of bytes read from @ref f. + */ +size_t chainpack_unpack(FILE *f, struct cpitem *item) __attribute__((nonnull)); -size_t _chainpack_unpack_uint(FILE *, unsigned long long *v, bool *ok); +/*! Pack next item to ChainPack data format. + * + * @returns Number of bytes written to @ref f. On error it returns value of + * zero. Note that some bytes might have been successfully written before error + * was detected. Number of written bytes in case of an error is irrelevant + * because packed data is invalid anyway. Be aware that some inputs might + * inevitably produce no output (strings and blobs that are not first or last + * and have zero length produce no output). + */ +size_t chainpack_pack(FILE *f, const struct cpitem *item) + __attribute__((nonnull(2))); struct cpon_state { const char *indent; size_t depth; struct cpon_state_ctx { - enum cp_item_type tp; + enum cpitem_type tp; bool first; bool even; bool meta; @@ -192,11 +382,22 @@ struct cpon_state { void (*realloc)(struct cpon_state *state); }; -ssize_t cpon_pack(FILE *, struct cpon_state *, const struct cpitem *) - __attribute__((nonnull(2, 3))); - -size_t cpon_unpack(FILE *, struct cpon_state *, struct cpitem *) +/*! Pack next item to CPON data format. + */ +size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) __attribute__((nonnull)); +/*! Unpack next item from CPON data format. + * + * @returns Number of bytes written to @ref f. On error it returns value of + * zero. Note that some bytes might have been successfully written before error + * was detected. Number of written bytes in case of an error is irrelevant + * because packed data is invalid anyway. Be aware that some inputs might + * inevitably produce no output (strings and blobs that are not first or last + * and have zero length produce no output). + */ +size_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) + __attribute__((nonnull(2, 3))); + #endif diff --git a/include/shv/cp_pack.h b/include/shv/cp_pack.h index 17f2346..f7fed4f 100644 --- a/include/shv/cp_pack.h +++ b/include/shv/cp_pack.h @@ -2,10 +2,11 @@ #define SHV_CP_PACK_H #include +#include #include #include -typedef ssize_t (*cp_pack_func_t)(void *ptr, const struct cpitem *item); +typedef size_t (*cp_pack_func_t)(void *ptr, const struct cpitem *item); typedef cp_pack_func_t *cp_pack_t; @@ -34,57 +35,57 @@ cp_pack_t cp_pack_cpon_init(struct cp_pack_cpon *pack, FILE *f, }) -static inline ssize_t cp_pack_null(cp_pack_t pack) { +static inline size_t cp_pack_null(cp_pack_t pack) { struct cpitem i; - i.type = CP_ITEM_NULL; + i.type = CPITEM_NULL; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_bool(cp_pack_t pack, bool v) { +static inline size_t cp_pack_bool(cp_pack_t pack, bool v) { struct cpitem i; - i.type = CP_ITEM_BOOL; + i.type = CPITEM_BOOL; i.as.Bool = v; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_int(cp_pack_t pack, int64_t v) { +static inline size_t cp_pack_int(cp_pack_t pack, long long v) { struct cpitem i; - i.type = CP_ITEM_INT; + i.type = CPITEM_INT; i.as.Int = v; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_uint(cp_pack_t pack, uint64_t v) { +static inline size_t cp_pack_uint(cp_pack_t pack, unsigned long long v) { struct cpitem i; - i.type = CP_ITEM_UINT; + i.type = CPITEM_UINT; i.as.UInt = v; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_double(cp_pack_t pack, double v) { +static inline size_t cp_pack_double(cp_pack_t pack, double v) { struct cpitem i; - i.type = CP_ITEM_DOUBLE; + i.type = CPITEM_DOUBLE; i.as.Double = v; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_decimal(cp_pack_t pack, struct cpdecimal v) { +static inline size_t cp_pack_decimal(cp_pack_t pack, struct cpdecimal v) { struct cpitem i; - i.type = CP_ITEM_DECIMAL; + i.type = CPITEM_DECIMAL; i.as.Decimal = v; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_datetime(cp_pack_t pack, struct cpdatetime v) { +static inline size_t cp_pack_datetime(cp_pack_t pack, struct cpdatetime v) { struct cpitem i; - i.type = CP_ITEM_DATETIME; + i.type = CPITEM_DATETIME; i.as.Datetime = v; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_blob(cp_pack_t pack, const uint8_t *buf, size_t len) { +static inline size_t cp_pack_blob(cp_pack_t pack, const uint8_t *buf, size_t len) { struct cpitem i; - i.type = CP_ITEM_BLOB; + i.type = CPITEM_BLOB; i.rbuf = buf; i.as.Blob = (struct cpbufinfo){ .len = len, @@ -94,9 +95,9 @@ static inline ssize_t cp_pack_blob(cp_pack_t pack, const uint8_t *buf, size_t le return cp_pack(pack, &i); } -static inline ssize_t cp_pack_blob_size( +static inline size_t cp_pack_blob_size( cp_pack_t pack, struct cpitem *item, size_t siz) { - item->type = CP_ITEM_BLOB; + item->type = CPITEM_BLOB; item->as.Blob = (struct cpbufinfo){ .len = 0, .eoff = siz, @@ -105,9 +106,9 @@ static inline ssize_t cp_pack_blob_size( return cp_pack(pack, item); } -static inline ssize_t cp_pack_blob_data( +static inline size_t cp_pack_blob_data( cp_pack_t pack, struct cpitem *item, const uint8_t *buf, size_t siz) { - assert(item->type == CP_ITEM_BLOB); + assert(item->type == CPITEM_BLOB); assert(item->as.Blob.eoff >= siz); item->rbuf = buf; item->as.Blob.len = siz; @@ -118,9 +119,9 @@ static inline ssize_t cp_pack_blob_data( return cp_pack(pack, item); } -static inline ssize_t cp_pack_string(cp_pack_t pack, const char *buf, size_t len) { +static inline size_t cp_pack_string(cp_pack_t pack, const char *buf, size_t len) { struct cpitem i; - i.type = CP_ITEM_STRING; + i.type = CPITEM_STRING; i.rchr = buf; i.as.String = (struct cpbufinfo){ .len = len, @@ -130,13 +131,31 @@ static inline ssize_t cp_pack_string(cp_pack_t pack, const char *buf, size_t len return cp_pack(pack, &i); } -static inline ssize_t cp_pack_str(cp_pack_t pack, const char *str) { +static inline size_t cp_pack_str(cp_pack_t pack, const char *str) { return cp_pack_string(pack, str, strlen(str)); } -static inline ssize_t cp_pack_string_size( +static inline size_t cp_pack_vfstr(cp_pack_t pack, const char *fmt, va_list args) { + va_list cargs; + va_copy(cargs, args); + int siz = vsnprintf(NULL, 0, fmt, cargs); + va_end(cargs); + char str[siz]; + assert(vsnprintf(str, siz, fmt, args) == siz); + return cp_pack_string(pack, str, siz - 1); +} + +static inline size_t cp_pack_fstr(cp_pack_t pack, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + size_t res = cp_pack_vfstr(pack, fmt, args); + va_end(args); + return res; +} + +static inline size_t cp_pack_string_size( cp_pack_t pack, struct cpitem *item, size_t siz) { - item->type = CP_ITEM_STRING; + item->type = CPITEM_STRING; item->as.String = (struct cpbufinfo){ .len = 0, .eoff = siz, @@ -145,9 +164,9 @@ static inline ssize_t cp_pack_string_size( return cp_pack(pack, item); } -static inline ssize_t cp_pack_string_data( +static inline size_t cp_pack_string_data( cp_pack_t pack, struct cpitem *item, const char *buf, size_t siz) { - assert(item->type == CP_ITEM_STRING); + assert(item->type == CPITEM_STRING); assert(item->as.Blob.eoff >= siz); item->rchr = buf; item->as.String.len = siz; @@ -158,33 +177,33 @@ static inline ssize_t cp_pack_string_data( return cp_pack(pack, item); } -static inline ssize_t cp_pack_list_begin(cp_pack_t pack) { +static inline size_t cp_pack_list_begin(cp_pack_t pack) { struct cpitem i; - i.type = CP_ITEM_LIST; + i.type = CPITEM_LIST; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_map_begin(cp_pack_t pack) { +static inline size_t cp_pack_map_begin(cp_pack_t pack) { struct cpitem i; - i.type = CP_ITEM_MAP; + i.type = CPITEM_MAP; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_imap_begin(cp_pack_t pack) { +static inline size_t cp_pack_imap_begin(cp_pack_t pack) { struct cpitem i; - i.type = CP_ITEM_IMAP; + i.type = CPITEM_IMAP; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_meta_begin(cp_pack_t pack) { +static inline size_t cp_pack_meta_begin(cp_pack_t pack) { struct cpitem i; - i.type = CP_ITEM_META; + i.type = CPITEM_META; return cp_pack(pack, &i); } -static inline ssize_t cp_pack_container_end(cp_pack_t pack) { +static inline size_t cp_pack_container_end(cp_pack_t pack) { struct cpitem i; - i.type = CP_ITEM_CONTAINER_END; + i.type = CPITEM_CONTAINER_END; return cp_pack(pack, &i); } diff --git a/include/shv/cp_unpack.h b/include/shv/cp_unpack.h index a362ac7..a1fa336 100644 --- a/include/shv/cp_unpack.h +++ b/include/shv/cp_unpack.h @@ -2,6 +2,7 @@ #define SHV_CP_UNPACK_H #include +#include typedef size_t (*cp_unpack_func_t)(void *ptr, struct cpitem *item); typedef cp_unpack_func_t *cp_unpack_t; @@ -31,16 +32,37 @@ cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *pack, FILE *f) (*__unpack)(__unpack, (ITEM)); \ }) +#define cp_unpack_expect_type(UNPACK, ITEM, TYPE) \ + ({ \ + struct cpitem *__item = ITEM; \ + cp_unpack(UNPACK, __item); \ + __item->type == TYPE; \ + }) + +/*! Instead of getting next item this drops the any unread blocks from current + * one. The effect is that next unpack will unpack a new item instead of next + * block of the current one. + * + * This is here for strings and blobs and only skips all unread bytes from them, + * it won't continue to the next item. This allows you to use this function to + * just skip any bytes of data you do not care about and go to the next item. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @returns number of read bytes. + */ +size_t cp_unpack_drop(cp_unpack_t unpack, struct cpitem *item) + __attribute__((nonnull)); + /*! Instead of getting next item this skips it. * * The difference between getting and not using the item is that this skips the * whole item. That means the whole list or map as well as whole string or blob. * This includes multiple calls to the `cp_unpack`. * - * Note that for strings and blobs this only skips all unread bytes from them, - * it won't continue to the next item unless all bytes were already received. - * This allows you to use this function to just skip any bytes of data you do - * not care about and go to the next item. + * This also fist drop any unfinished string or blob with `cp_unpack_drop` to + * for sure finish the previous item. * * @param unpack: Unpack handle. * @param item: Item used for the last `cp_unpack` call. This is required @@ -52,23 +74,88 @@ size_t cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) /*! Read until the currently opened container is finished. * - * This is same function such as `cp_unpack_skip` with difference that it stops - * not after end of the next item, but on container end for which there was no + * This is same function as `cp_unpack_skip` with difference that it stops not + * after end of the next item, but on container end for which there was no * container open. The effect is that opened container is skipped. * * @param unpack: Unpack handle. * @param item: Item used for the last `cp_unpack` call. This is required * because there might be unfinished string or blob we need to know about. + * @param depth: How many containers we should finish. To finish one pass `1`. + * To close two containers pass `2` and so on. The `0` provides the same + * functionality as `cp_unpack_skip`. * @returns number of read bytes. */ -size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item) +size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item, unsigned depth) __attribute__((nonnull)); +#define cp_extract_int(ITEM, DEST) \ + ({ \ + struct cpitem *__item = ITEM; \ + bool __valid = false; \ + if (__item->type == CPITEM_INT) { \ + if (sizeof(DEST) < sizeof(long long)) { \ + long long __lim = 1LL << ((sizeof(DEST) * 8) - 1); \ + __valid = __item->as.Int >= -__lim && __item->as.Int < __lim; \ + } else \ + __valid = true; \ + (DEST) = __item->as.Int; \ + } \ + __valid; \ + }) + +#define cp_extact_uint(ITEM, DEST) \ + ({ \ + struct cpitem *__item = ITEM; \ + bool __valid = false; \ + if (__item->type == CPITEM_UINT) { \ + if (sizeof(DEST) < sizeof(unsigned long long)) { \ + unsigned long long __lim = 1LL << ((sizeof(DEST) * 8) - 1); \ + __valid = __item->as.Int < __lim; \ + } else \ + __valid = true; \ + (DEST) = __item->as.Int; \ + } \ + __valid; \ + }) + +#define cp_unpack_int(UNPACK, ITEM, DEST) \ + ({ \ + struct cpitem *__uitem = ITEM; \ + ssize_t res = cp_unpack(UNPACK, __uitem); \ + cp_extract_int(__uitem, DEST) ? res : -res; \ + }) + +#define cp_unpack_uint(UNPACK, ITEM, DEST) \ + ({ \ + struct cpitem *__uitem = ITEM; \ + ssize_t res = cp_unpack(UNPACK, __uitem); \ + cp_extract_uint(__uitem, DEST) ? res : -res; \ + }) + +/*! Unpack a single by from string. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @returns Unpacked byte or `-1` in case of an unpack error. + */ +__attribute__((nonnull)) static inline int cp_unpack_getc( + cp_unpack_t unpack, struct cpitem *item) { + uint8_t res = 0; + item->buf = &res; + item->bufsiz = 1; + cp_unpack(unpack, item); + if (item->type != CPITEM_STRING) + return -1; + return res; +} + /*! Copy string to malloc allocated buffer and return it. * * This unpacks the whole string and returns it. * - * The error can be detected by checking: `item->type == CP_ITEM_STRING`. + * The error can be detected by checking: `item->type == CPITEM_STRING`. * * @param unpack: Unpack handle. * @param item: Item used for the `cp_unpack` calls and was used in the last @@ -78,6 +165,54 @@ size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item) char *cp_unpack_strdup(cp_unpack_t unpack, struct cpitem *item) __attribute__((nonnull)); +/*! Copy string of at most given length to malloc allocated buffer and return + * it. + * + * This unpacks the string up to the given length and returns it. It always + * appends '\0'. + * + * The error can be detected by checking: `item->type == CPITEM_STRING`. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @param len: Maximum number of bytes to be copied. + * @returns malloc allocated null terminated string. + */ +char *cp_unpack_strndup(cp_unpack_t unpack, struct cpitem *item, size_t len) + __attribute__((nonnull)); + +/*! Copy string to obstack allocated buffer and return it. + * + * This is same as @ref cp_unpack_strdup except it uses obstack to allocated the + * required space. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @param obstack: Obstack used to allocate the space needed for the string + * object. + * @returns pointer to the allocated string object. + */ +char *cp_unpack_strdupo(cp_unpack_t unpack, struct cpitem *item, + struct obstack *obstack) __attribute__((nonnull)); + +/*! Copy string up to given length to obstack allocated buffer and return it. + * + * This is same as @ref cp_unpack_strndup except it uses obstack to allocated + * the required space. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @param obstack: Obstack used to allocate the space needed for the string + * object. + * @param len: Maximum number of bytes to be copied. + * @returns pointer to the allocated string object. + */ +char *cp_unpack_strndupo(cp_unpack_t unpack, struct cpitem *item, size_t len, + struct obstack *obstack) __attribute__((nonnull)); + /*! Copy string to the provided buffer. * * Be aware that compared to the `strncpy` this has a different return! @@ -97,19 +232,39 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_strncpy( item->chr = dest; item->bufsiz = n; cp_unpack(unpack, item); - if (item->type != CP_ITEM_STRING) + if (item->type != CPITEM_STRING) { + item->bufsiz = 0; return -1; + } if (item->as.String.len < n) dest[item->as.String.len] = '\0'; + item->bufsiz = 0; return item->as.String.len; } +/*! Expect one of the strings to be present and provide index to say which. + * + * Parsing of maps is a common operation. Regularly keys can be in any order and + * thus we need to expect multiple different strings to be present. This handles + * that for you. + * + * Note that the implementation is based on decision tree that is generated in + * runtime. If you know the strings upfront you can get a better performance + * if you fetch bytes and use some hash searching algorithm instead. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + */ +int cp_unpack_expect_str(cp_unpack_t unpack, struct cpitem *item, + const char **strings) __attribute__((nonnull)); + /*! Copy blob to malloc allocated buffer and provide it. * * This unpacks the whole blob and returns it. * - * The error can be detected by checking: `item->type == CP_ITEM_BLOB`. + * The error can be detected by checking: `item->type == CPITEM_BLOB`. * * @param unpack: Unpack handle. * @param item: Item used for the `cp_unpack` calls and was used in the last @@ -120,6 +275,57 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_strncpy( void cp_unpack_memdup(cp_unpack_t unpack, struct cpitem *item, uint8_t **data, size_t *siz) __attribute__((nonnull)); +/*! Copy blob up to given number of bytes to malloc allocated buffer and provide + * it. + * + * This unpacks the blob up to the given size and returns it. + * + * The error can be detected by checking: `item->type == CPITEM_BLOB`. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @param data: Pointer where pointer to the data would be placed. + * @param siz: Pointer to maximum number of bytes to be copied that is updated + * with number of valid bytes actually copied over. + */ +void cp_unpack_memndup(cp_unpack_t unpack, struct cpitem *item, uint8_t **data, + size_t *siz) __attribute__((nonnull)); + +/*! Copy blob to obstack allocated buffer and return it. + * + * This is same as @ref cp_unpack_memdup except it uses obstack to allocated the + * required space. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @param data: Pointer where pointer to the data would be placed. + * @param siz: Pointer where number of valid data bytes were unpacked. + * @param obstack: Obstack used to allocate the space needed for the blob + * object. + */ +void cp_unpack_memdupo(cp_unpack_t unpack, struct cpitem *item, uint8_t **buf, + size_t *siz, struct obstack *obstack) __attribute__((nonnull)); + +/*! Copy blob up to given number of bytes to obstack allocated buffer and return + * it. + * + * This is same as @ref cp_unpack_memndup except it uses obstack to allocated + * the required space. + * + * @param unpack: Unpack handle. + * @param item: Item used for the `cp_unpack` calls and was used in the last + * one. + * @param data: Pointer where pointer to the data would be placed. + * @param siz: Pointer to maximum number of bytes to be copied that is updated + * with number of valid bytes actually copied over. + * @param obstack: Obstack used to allocate the space needed for the blob + * object. + */ +void cp_unpack_memndupo(cp_unpack_t unpack, struct cpitem *item, uint8_t **buf, + size_t *siz, struct obstack *obstack) __attribute__((nonnull)); + /*! Copy blob to the provided buffer. * * Be aware that compared to the `memcpy` this has a different return! @@ -136,11 +342,41 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_memcpy( item->buf = dest; item->bufsiz = siz; cp_unpack(unpack, item); - if (item->type != CP_ITEM_BLOB) + if (item->type != CPITEM_BLOB) return -1; return item->as.Blob.len; } +#define for_cp_unpack_list(UNPACK, ITEM) \ + while (({ \ + struct cpitem *__item = ITEM; \ + cp_unpack(UNPACK, __item); \ + __item->type != CPITEM_INVALID && __item->type != CPITEM_CONTAINER_END; \ + })) + +#define for_cp_unpack_ilist(UNPACK, ITEM, CNT) \ + for (unsigned CNT = 0; ({ \ + struct cpitem *__item = ITEM; \ + cp_unpack(UNPACK, __item); \ + __item->type != CPITEM_INVALID && \ + __item->type != CPITEM_CONTAINER_END; \ + }); \ + CNT++) + +#define for_cp_unpack_map(UNPACK, ITEM) \ + while (({ \ + struct cpitem *__item = ITEM; \ + cp_unpack(UNPACK, __item); \ + __item->type == CPITEM_STRING; \ + })) + +#define for_cp_unpack_imap(UNPACK, ITEM) \ + while (({ \ + struct cpitem *__item = ITEM; \ + cp_unpack(UNPACK, __item); \ + __item->type == CPITEM_INT; \ + })) + /*! Open string or blob for reading using stream. * * This allows you to use `fscanf` and other functions to deal with strings and diff --git a/include/shv/rpcclient.h b/include/shv/rpcclient.h index 98e7879..1b5d63f 100644 --- a/include/shv/rpcclient.h +++ b/include/shv/rpcclient.h @@ -4,11 +4,24 @@ #include #include +/*! Number of seconds that is default idle time before brokers disconnect + * clients for inactivity. + */ +#define RPC_DEFAULT_IDLE_TIME 180 + typedef struct rpcclient *rpcclient_t; rpcclient_t rpcclient_connect(const struct rpcurl *url); +enum rpcclient_login_res { + RPCCLIENT_LOGIN_OK, + RPCCLIENT_LOGIN_INVALID, + RPCCLIENT_LOGIN_ERROR, +} rpcclient_login(rpcclient_t client, const struct rpclogin_options *opts) + __attribute__((nonnull)); + + rpcclient_t rpcclient_stream_new(int readfd, int writefd); rpcclient_t rpcclient_stream_tcp_connect(const char *location, int port); rpcclient_t rpcclient_stream_unix_connect(const char *location); @@ -19,22 +32,47 @@ rpcclient_t rpcclient_datagram_udp_connect(const char *location, int port); rpcclient_t rpcclient_serial_new(int fd); rpcclient_t rpcclient_serial_connect(const char *path); -#define rpcclient_disconnect(CLIENT) ((CLIENT)->disconnect(CLIENT)) -bool rpcclient_login(rpcclient_t, const char *username, const char *password, - enum rpc_login_type type) __attribute__((nonnull(1))); +#define rpcclient_destroy(CLIENT) ((CLIENT)->disconnect(CLIENT)) +#define rpcclient_nextmsg(CLIENT) ((CLIENT)->msgfetch(CLIENT, true)) - -#define rpcclient_nextmsg(CLIENT) (&(CLIENT)->nextmsg(CLIENT)) +#define rpcclient_validmsg(CLIENT) ((CLIENT)->msgfetch(CLIENT, false)) #define rpcclient_unpack(CLIENT) (&(CLIENT)->unpack) #define rpcclient_pack(CLIENT) (&(CLIENT)->pack) -#define rpcclient_send(CLIENT) ((CLIENT)->send(CLIENT_PTR)) +#define rpcclient_sendmsg(CLIENT) ((CLIENT)->msgflush(CLIENT, true)) + +#define rpcclient_dropmsg(CLIENT) ((CLIENT)->msgflush(CLIENT, false)) #define rpcclient_logger(CLIENT) ((CLIENT)->logger) +typedef struct rpcclient_logger *rpcclient_logger_t; + +void rpcclient_logger_destroy(rpcclient_logger_t logger); + +rpcclient_logger_t rpcclient_logger_new(FILE *f, unsigned maxdepth) + __attribute__((nonnull, malloc, malloc(rpcclient_logger_destroy))); + +/*! Calculate maximum sleep before some message needs to be sent. + * + * This time is the number of seconds until the half of the disconnect delay + * seconds is reached. + * + * @param client: Client handle. + * @param idle_time: Number of seconds before + * @returns number of seconds before you should send some message. + */ +static inline int rpcclient_maxsleep(rpcclient_t client, int idle_time) { + struct timespec t; + assert(clock_gettime(CLOCK_MONOTONIC, &t) == 0); + int period = idle_time / 2; + if (t.tv_sec > client->last_send.tv_sec + period) + return 0; + return period - t.tv_sec + client->last_send.tv_sec; +} + #endif diff --git a/include/shv/rpcclient_impl.h b/include/shv/rpcclient_impl.h index 9fa6b3f..4fd0a62 100644 --- a/include/shv/rpcclient_impl.h +++ b/include/shv/rpcclient_impl.h @@ -2,27 +2,25 @@ #define SHV_RPCCLIENT_IMPL_H #include #include +#include +#include #include #include -#include // TODO possibly hide this and just provide function to allocate it -struct rpcclient_logger { - FILE *f; - struct cpon_state cpon_state; - sem_t semaphore; -}; struct rpcclient { - /* Receave */ + int rfd; cp_unpack_func_t unpack; - bool (*nextmsg)(struct rpcclient *); - /* Send */ + bool (*msgfetch)(struct rpcclient *, bool newmsg); + struct timespec last_receive; + cp_pack_func_t pack; - bool (*sendmsg)(struct rpcclient *); - /* Disconnect */ + bool (*msgflush)(struct rpcclient *, bool send); + struct timespec last_send; + void (*disconnect)(struct rpcclient *); - /* Logging */ + struct rpcclient_logger *logger; }; @@ -35,4 +33,12 @@ void rpcclient_log_item(struct rpcclient_logger *, const struct cpitem *) void rpcclient_log_unlock(struct rpcclient_logger *); +static inline void rpcclient_last_receive_update(struct rpcclient *client) { + clock_gettime(CLOCK_MONOTONIC, &client->last_receive); +} + +static inline void rpcclient_last_send_update(struct rpcclient *client) { + clock_gettime(CLOCK_MONOTONIC, &client->last_send); +} + #endif diff --git a/include/shv/rpchandler.h b/include/shv/rpchandler.h index 90ce771..107e23e 100644 --- a/include/shv/rpchandler.h +++ b/include/shv/rpchandler.h @@ -1,35 +1,56 @@ #ifndef SHV_RPCHANDLER_H #define SHV_RPCHANDLER_H -#include "shv/rpcmsg.h" #include +#include #include +#include -#define RPCH_F_KEEP_META +typedef struct rpchandler *rpchandler_t; +typedef struct rpcreceive *rpcreceive_t; -struct rpchandler { - int flags; - void *(*init)(rpcclient_t); - void (*deinit)(void *cookie, rpcclient_t); - bool (*handle_request)(void *cookie, rpcclient_t, const char *path, - const char *method, const struct rpcmsg_request_info *info); - bool (*handle_response)(void *cookie, rpcclient_t, int64_t rid, bool error); - bool (*handle_signal)( - void *cookie, rpcclient_t, const char *path, const char *method); +enum rpchandler_func_res { + RPCHFR_UNHANDLED, + RPCHFR_HANDLED, + RPCHFR_RECV_ERR, + RPCHFR_SEND_ERR, }; +typedef enum rpchandler_func_res (*rpchandler_func)(void *ptr, rpcreceive_t receive, + cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta); -bool rpchandler_loop(rpcclient_t, struct rpchandler *); -typedef struct rpchandler_state *rpchandler_state_t; +void rpchandler_destroy(rpchandler_t rpchandler); -rpchandler_state_t rpchandler_new(rpcclient_t, struct rpchandler *); +rpchandler_t rpchandler_new(rpcclient_t client, const rpchandler_func **funcs, + const struct rpcmsg_meta_limits *limits) + __attribute__((nonnull(1), malloc, malloc(rpchandler_destroy, 1))); -bool rpchandler_next(rpchandler_state_t); -void rpchandler_destroy(rpchandler_state_t); +bool rpchandler_next(rpchandler_t rpchandler) __attribute__((nonnull)); +int rpchandler_spawn_thread(rpchandler_t rpchandler, pthread_t *restrict thread, + const pthread_attr_t *restrict attr) __attribute__((nonnull(1, 2))); + + +int rpchandler_next_request_id(rpchandler_t rpchandler) __attribute__((nonnull)); + + +cp_pack_t rpchandler_msg_new(rpchandler_t rpchandler) __attribute__((nonnull)); + +bool rpchandler_msg_send(rpchandler_t rpchandler) __attribute__((nonnull)); + +bool rpchandler_msg_drop(rpchandler_t rpchandler) __attribute__((nonnull)); + + +bool rpcreceive_validmsg(rpcreceive_t receive) __attribute__((nonnull)); + +cp_pack_t rpcreceive_response_new(rpcreceive_t receive) __attribute__((nonnull)); + +bool rpcreceive_response_send(rpcreceive_t receive) __attribute__((nonnull)); + +bool rpcreceive_response_drop(rpcreceive_t receive) __attribute__((nonnull)); #endif diff --git a/include/shv/rpclogin.h b/include/shv/rpclogin.h new file mode 100644 index 0000000..6d13ab5 --- /dev/null +++ b/include/shv/rpclogin.h @@ -0,0 +1,8 @@ +#ifndef SHV_RPCLOGIN_H +#define SHV_RPCLOGIN_H +#include +#include +#include + + +#endif diff --git a/include/shv/rpcmsg.h b/include/shv/rpcmsg.h index b1a4281..e7f798f 100644 --- a/include/shv/rpcmsg.h +++ b/include/shv/rpcmsg.h @@ -27,7 +27,10 @@ enum rpcmsg_keys { RPCMSG_KEY_ERROR, }; -enum rpcmsg_error_key { RPCMSG_E_KEY_CODE = 1, RPCMSG_E_KEY_MESSAGE }; +enum rpcmsg_error_key { + RPCMSG_ERR_KEY_CODE = 1, + RPCMSG_ERR_KEY_MESSAGE, +}; /*! Pack request message meta and open imap. The followup packed data are * parameters provided to the method call request. The message needs to be @@ -40,8 +43,37 @@ enum rpcmsg_error_key { RPCMSG_E_KEY_CODE = 1, RPCMSG_E_KEY_MESSAGE }; * @param rid: request identifier. Thanks to this number you can associate * response with requests. */ -void rpcmsg_pack_request(cp_pack_t, const char *path, const char *method, - int64_t rid) __attribute__((nonnull)); +size_t rpcmsg_pack_request(cp_pack_t pack, const char *path, const char *method, + int rid) __attribute__((nonnull(1, 3))); + +/*! Pack request message with no parameters. + * + * This provides an easy way to just pack message without arguments. It calls + * `rpcmsg_pack_request` and packs additional `NULL` and `CONTAINER_END` to + * complete the message. Such message can be immediately sent. + * + * @param pack: pack context the meta should be written to. + * @param path: SHV path to the node the method we want to request is associated + * with. + * @param method: name of the method we request to call. + * @param rid: request identifier. Thanks to this number you can associate + * response with requests. + */ +__attribute__((nonnull(1, 3))) static inline size_t rpcmsg_pack_request_void( + cp_pack_t pack, const char *path, const char *method, int rid) { + size_t res = 0; +#define RES(V) \ + ({ \ + size_t __res = V; \ + res += __res; \ + __res; \ + }) + if (RES(rpcmsg_pack_request(pack, path, method, rid)) && + RES(cp_pack_null(pack)) && RES(cp_pack_container_end(pack))) + return res; +#undef RES + return 0; +} /*! Pack signal message meta and open imap. The followup packed data are * signaled values. The message needs to be terminated with container end @@ -51,8 +83,8 @@ void rpcmsg_pack_request(cp_pack_t, const char *path, const char *method, * @param path: SHV path to the node method is associated with. * @param method: name of the method signal is raised for. */ -void rpcmsg_pack_signal(cp_pack_t, const char *path, const char *method) - __attribute__((nonnull)); +size_t rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) + __attribute__((nonnull(1, 3))); /*! Pack value change signal message meta and open imap. The followup packed * data are signaled values. The message needs to be terminated with container @@ -64,8 +96,9 @@ void rpcmsg_pack_signal(cp_pack_t, const char *path, const char *method) * @param pack: pack context the meta should be written to. * @param path: SHV path the value change signal is associated with. */ -static inline void rpcmsg_pack_chng(cp_pack_t pack, const char *path) { - rpcmsg_pack_signal(pack, path, "chng"); +__attribute__((nonnull(1))) static inline size_t rpcmsg_pack_chng( + cp_pack_t pack, const char *path) { + return rpcmsg_pack_signal(pack, path, "chng"); } @@ -118,6 +151,8 @@ enum rpcmsg_type { RPCMSG_T_REQUEST, /*! Message is response (`request_id` is valid). */ RPCMSG_T_RESPONSE, + /*! Message is response with error attached (`request_id` is valid). */ + RPCMSG_T_ERROR, /*! Message is signal (`method` is valid). */ RPCMSG_T_SIGNAL, }; @@ -145,6 +180,12 @@ struct rpcmsg_meta { enum rpcmsg_access access_grant; /*! Client IDs in Chainpack if message is request. */ struct rpcmsg_ptr cids; + + struct rpcmsg_meta_extra { + int key; + struct rpcmsg_ptr ptr; + struct rpcmsg_meta_extra *next; + } * extra; }; /*! Limits imposed on `struct rpcmsg_meta` fields. @@ -172,12 +213,12 @@ struct rpcmsg_meta_limits { * supported path. It is to detect that this is too long path (because that * won't be signaled to you in any other way). */ - size_t path; + ssize_t path; /*! Limit on `method` size. * * The same suggestion applies here as for `path`. */ - size_t method; + ssize_t method; /*! Limit on `cids.ptr` buffer. * * It is highly suggested to set this to `-1` or some high number if you use @@ -187,10 +228,17 @@ struct rpcmsg_meta_limits { * In case you do not use obstack then set buffer size of `cids.ptr` as * usual. */ - size_t cids; + ssize_t cids; + /*! If extra parameters should be preserved or no. + * + * The default is that they are dropped (`false`). In most cases that is + * what they are not needed. This is only essential for Broker that should + * preserve all parameters not just those he knows. + */ + bool extra; }; -/*! Unpack meta of the RPC message. +/*! Unpack meta and openning of iMap of the RPC message. * * The unpacking can be performed in two different modes. You can either provide * obstack and in such case data would be pushed to it (highly suggested). But @@ -218,13 +266,15 @@ struct rpcmsg_meta_limits { * @returns `false` in case unpack reports error, if meta contains invalid value * for supported key, or when there is not enough space to store caller IDs. In * other cases `true` is returned. In short this return primarily signals if it - * is possible to generate response to this message for request messages. + * is possible to generate response to this message for request messages and if + * it is possible to parameters, response or error for other message types. */ -bool rpcmsg_meta_unpack(cp_unpack_t unpack, struct cpitem *item, +bool rpcmsg_head_unpack(cp_unpack_t unpack, struct cpitem *item, struct rpcmsg_meta *meta, struct rpcmsg_meta_limits *limits, struct obstack *obstack); -void rpcmsg_pack_response(cp_pack_t, struct rpcmsg_meta *meta) + +size_t rpcmsg_pack_response(cp_pack_t, const struct rpcmsg_meta *meta) __attribute__((nonnull)); enum rpcmsg_error { @@ -241,97 +291,14 @@ enum rpcmsg_error { RPCMSG_E_USER_CODE = 32, }; -void rpcmsg_pack_error(cp_pack_t, struct rpcmsg_meta *meta, +size_t rpcmsg_pack_error(cp_pack_t, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *msg) __attribute__((nonnull(1, 2))); -void rpcmsg_pack_ferror(cp_pack_t, struct rpcmsg_meta *meta, +size_t rpcmsg_pack_ferror(cp_pack_t, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, ...) __attribute__((nonnull(1, 2))); -void rpcmsg_pack_vferror(cp_pack_t, struct rpcmsg_meta *meta, +size_t rpcmsg_pack_vferror(cp_pack_t, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, va_list args) __attribute__((nonnull(1, 2))); - -#if 0 - -/*! String handle that can be increased in size and reused for the future - * strings. It is used by `rpcmsg_unpack_str` and by other functions that - * internally call that function. It provides a growable string with known size - * limitation. - * - * You should preferably allocate this using `malloc` because - * `rpcmsg_unpack_str` will call `realloc` on it (with exception that is - * described in the `siz` field description). - */ -struct rpcclient_str { - /*! Size of the `str` in bytes. You can set this to a negative number to - * prevent `rpcmsg_unpack_str` from reallocating. This provides you either - * ceiling functionality or a way to pass non-malloc allocated memory. - */ - ssize_t siz; - /*! Actual characters of the string. */ - char str[]; -}; - -/*! Unpack string to `rpcmsg_str`. - * - */ -bool rpcclient_unpack_str(rpcclient_t, struct rpcclient_str **str); - -/*! Check if `rpcmsg_str` has truncated content. - * - * `rpcmsg_unpack_str` might not be able to store the whole string if you set - * `siz` to some negative number as that prevents reallocation. In such case you - * can end up with truncated string and this method detects it. You should - * always use it before you call C string methods (that expect `NULL` - * termination) to check validity. - * - * @param str: pointer to the `rpcmsg_str` that was previously passed to - * `rpcmsg_unpack_str`. - * @returns: `true` if string is truncated or otherwise invalid and `false` - * otherwise. - */ -bool rpcclient_str_truncated(const struct rpcclient_str *str) - __attribute__((nonnull)); - - - -bool rpcmsg_unpack_access(rpcclient_t, enum rpcmsg_access *access); - -const char *rpcmsg_access_str(enum rpcmsg_access); - -struct rpcmsg_request_info { - /* Access level assigned by SHV Broker */ - enum rpcmsg_access acc_grant; - /* Request ID */ - int64_t rid; - /* Client IDs */ - size_t cidscnt, rcidscnt; - // TODO ssize_t and allow no realloc trough it? - size_t cidssiz; - int64_t cids[]; -}; - -/*! Unpack message meta and open imap. The followup data depend on type returned - * by this function. - * - * @param unpack: unpack context the meta should be read from. - * @param path: pointer to the variable where SHV path is stored. - * @param method: name of the method signal is raised for. - */ -enum rpcmsg_type { - /*! The message's meta is invalid or unpack is in error state. This is an - * error state and you most likely want to perform reconnect or connection - * reset. - */ - RPCMSG_T_INVALID, - RPCMSG_T_REQUEST, - RPCMSG_T_RESPONSE, - RPCMSG_T_ERROR, - RPCMSG_T_SIGNAL, -} rpcmsg_unpack(rpcclient_t, struct rpcmsg_str **path, - struct rpcmsg_str **method, struct rpcmsg_request_info **rinfo); - - -#endif #endif diff --git a/include/shv/rpcnode.h b/include/shv/rpcnode.h new file mode 100644 index 0000000..fcb0e0b --- /dev/null +++ b/include/shv/rpcnode.h @@ -0,0 +1,42 @@ +#ifndef SHV_RPCNODE_H +#define SHV_RPCNODE_H + +#include +#include + +#define RPCNODE_DIR_ + +struct rpcnode_dir_request { + const char *name; + bool list_of_maps; +}; + +enum rpcnode_dir_signature { + RPCNODE_DIR_VOID_VOID, + RPCNODE_DIR_VOID_PARAM, + RPCNODE_DIR_RET_VOID, + RPCNODE_DIR_RET_PARAM, +}; + +#define RPCNODE_DIR_F_SIGNAL (1 << 0) +#define RPCNODE_DIR_F_GETTER (1 << 1) +#define RPCNODE_DIR_F_SETTER (1 << 2) +#define RPCNODE_DIR_F_LARGE_RESULT_HINT (1 << 3) + +bool rpcnode_handle_dir(cp_unpack_t unpack, struct cpitem *item, + struct rpcnode_dir_request *dirr, struct obstack *obstack) + __attribute__((nonnull)); + +bool rpcnode_pack_dir(cp_pack_t pack, bool list_of_maps, const char *name, + enum rpcnode_dir_signature signature, int flags, enum rpcmsg_access acc, + const char *description) __attribute__((nonnull(1, 3))); + + +typedef int rpcnode_ls_request; + +rpcnode_ls_request rpcnode_unpack_ls(cp_unpack_t unpack, struct cpitem *item); + +void rpcnode_pack_ls(cp_pack_t pack); + + +#endif diff --git a/include/shv/rpcping.h b/include/shv/rpcping.h new file mode 100644 index 0000000..77bb343 --- /dev/null +++ b/include/shv/rpcping.h @@ -0,0 +1,9 @@ +#ifndef SHV_RPCPING_H +#define SHV_RPCPING_H +#include + +static inline size_t rpcping_pack(cp_pack_t pack, int rid) { + return rpcmsg_pack_request_void(pack, "broker/app", "ping", rid); +} + +#endif diff --git a/include/shv/rpcrespond.h b/include/shv/rpcrespond.h new file mode 100644 index 0000000..29b2f49 --- /dev/null +++ b/include/shv/rpcrespond.h @@ -0,0 +1,33 @@ +#ifndef SHV_RPCRESPOND_H +#define SHV_RPCRESPOND_H + +#include +#include +#include + + +typedef struct rpcresponder *rpcresponder_t; + +void rpcresponder_destroy(rpcresponder_t responder); + +rpcresponder_t rpcresponder_new(void) + __attribute__((malloc, malloc(rpcresponder_destroy, 1))); + +rpchandler_func *rpcresponder_func(rpcresponder_t responder) + __attribute__((nonnull, returns_nonnull)); + +typedef struct rpcrespond *rpcrespond_t; + +rpcrespond_t rpcrespond_expect(rpcresponder_t responder, int request_id) + __attribute__((nonnull)); + +bool rpcrespond_waitfor(rpcrespond_t respond, cp_unpack_t *unpack, + struct cpitem **item, int timeout) __attribute__((nonnull)); + +const struct rpcmsg_meta *rpcrespond_meta(rpcrespond_t respond) + __attribute__((nonnull)); + +bool rpcrespond_validmsg(rpcrespond_t respond) __attribute__((nonnull)); + + +#endif diff --git a/include/shv/rpctoplevel.h b/include/shv/rpctoplevel.h new file mode 100644 index 0000000..6aedd67 --- /dev/null +++ b/include/shv/rpctoplevel.h @@ -0,0 +1,20 @@ +#ifndef SHV_RPCRESPOND_H +#define SHV_RPCRESPOND_H + +#include +#include +#include + + +typedef struct rpctoplevel *rpctoplevel_t; + +void rpctoplevel_destroy(rpctoplevel_t toplevel); + +rpctoplevel_t rpctoplevel_new(const char *app_name, const char *app_version, + bool device) __attribute__((malloc, malloc(rpctoplevel_destroy, 1))); + +rpchandler_func *rpctoplevel_func(rpctoplevel_t toplevel) + __attribute__((nonnull, returns_nonnull)); + + +#endif diff --git a/include/shv/rpcurl.h b/include/shv/rpcurl.h index 4b6070e..5edc2d5 100644 --- a/include/shv/rpcurl.h +++ b/include/shv/rpcurl.h @@ -1,5 +1,5 @@ -#ifndef _SHV_RPCURL_H -#define _SHV_RPCURL_H +#ifndef SHV_RPCURL_H +#define SHV_RPCURL_H /*! Protocol used to connect client to the server or to listen for connection on */ @@ -21,33 +21,38 @@ enum rpc_protocol { }; /*! The format of the password passed to the login process. */ -enum rpc_login_type { +enum rpclogin_type { /*! The password is in plain text */ RPC_LOGIN_PLAIN, /*! Password is hashed with SHA1 password */ RPC_LOGIN_SHA1, }; -/*! SHV RPC URL representation */ -struct rpcurl { - /*! Protocol used to connect to the server */ - enum rpc_protocol protocol; - /*! Location where client should connect to or server listen on */ - const char *location; - /*! Port number used for TCP and UDP connections */ - int port; +struct rpclogin_options { /*! User name used to login client connection to the server */ const char *username; /*! Password used to login client connection to the server */ const char *password; /*! The format of the password */ - enum rpc_login_type login_type; + enum rpclogin_type login_type; /*! Device ID sent as part of login information to the server */ const char *device_id; /*! Device's requested mount point on server */ const char *device_mountpoint; }; +/*! SHV RPC URL representation */ +struct rpcurl { + /*! Protocol used to connect to the server */ + enum rpc_protocol protocol; + /*! Location where client should connect to or server listen on */ + const char *location; + /*! Port number used for TCP and UDP connections */ + int port; + /*! Login options */ + struct rpclogin_options login; +}; + /*! Parse string URL. * diff --git a/libshvchainpack/chainpack_pack.c b/libshvchainpack/chainpack_pack.c index 53cbee8..227f252 100644 --- a/libshvchainpack/chainpack_pack.c +++ b/libshvchainpack/chainpack_pack.c @@ -1,5 +1,7 @@ #include #include +#include +#include "common.h" #ifndef __BYTE_ORDER__ #error We use __BYTE_ORDER__ macro and we need it to be defined @@ -10,7 +12,7 @@ do { \ uint8_t __v = (V); \ if (f && fputc(__v, f) != __v) \ - return -1; \ + return 0; \ res++; \ } while (false) #define WRITE(V, SIZ) \ @@ -18,35 +20,36 @@ size_t __siz = SIZ; \ if (f) { \ if (fwrite((V), 1, __siz, f) != __siz) \ - return -1; \ + return 0; \ } \ res += __siz; \ } while (false) #define CALL(FUNC, ...) \ do { \ ssize_t __cnt = FUNC(f, __VA_ARGS__); \ - if (__cnt < 0) \ - return -1; \ + if (__cnt == 0) \ + return 0; \ res += __cnt; \ } while (false) -static ssize_t chainpack_pack_int(FILE *f, int64_t v); +static size_t chainpack_pack_int(FILE *f, long long v); +static size_t chainpack_pack_uint(FILE *f, unsigned long long v); -ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { - ssize_t res = 0; +size_t chainpack_pack(FILE *f, const struct cpitem *item) { + size_t res = 0; + if (common_pack(&res, f, item)) + return res; switch (item->type) { - /* We pack invalid as NULL to ensure that we pack at least somethuing */ - case CP_ITEM_INVALID: - case CP_ITEM_NULL: + case CPITEM_NULL: PUTC(CPS_Null); break; - case CP_ITEM_BOOL: + case CPITEM_BOOL: PUTC(item->as.Bool ? CPS_TRUE : CPS_FALSE); break; - case CP_ITEM_INT: + case CPITEM_INT: if (item->as.Int >= 0 && item->as.Int < 64) PUTC((item->as.Int % 64) + 64); else { @@ -54,15 +57,15 @@ ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { CALL(chainpack_pack_int, item->as.Int); } break; - case CP_ITEM_UINT: + case CPITEM_UINT: if (item->as.UInt < 64) PUTC(item->as.Int % 64); else { PUTC(CPS_UInt); - CALL(_chainpack_pack_uint, item->as.UInt); + CALL(chainpack_pack_uint, item->as.UInt); } break; - case CP_ITEM_DOUBLE: + case CPITEM_DOUBLE: PUTC(CPS_Double); #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ const uint8_t *_b = (const uint8_t *)&item->as.Double; @@ -72,37 +75,37 @@ ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { WRITE((uint8_t *)&item->as.Double, sizeof(double)); #endif break; - case CP_ITEM_DECIMAL: + case CPITEM_DECIMAL: PUTC(CPS_Decimal); CALL(chainpack_pack_int, item->as.Decimal.mantisa); CALL(chainpack_pack_int, item->as.Decimal.exponent); break; - case CP_ITEM_BLOB: + case CPITEM_BLOB: if (item->as.Blob.flags & CPBI_F_FIRST) { if (item->as.Blob.flags & CPBI_F_STREAM) { PUTC(CPS_BlobChain); } else { PUTC(CPS_Blob); - CALL(_chainpack_pack_uint, + CALL(chainpack_pack_uint, item->as.Blob.len + item->as.Blob.eoff); } } if (item->as.Blob.len) { if (item->as.Blob.flags & CPBI_F_STREAM) - CALL(_chainpack_pack_uint, item->as.Blob.len); + CALL(chainpack_pack_uint, item->as.Blob.len); WRITE(item->rbuf, item->as.Blob.len); } if (item->as.Blob.flags & CPBI_F_STREAM && item->as.Blob.flags & CPBI_F_LAST) PUTC(0); break; - case CP_ITEM_STRING: + case CPITEM_STRING: if (item->as.String.flags & CPBI_F_FIRST) { if (item->as.String.flags & CPBI_F_STREAM) PUTC(CPS_CString); else { PUTC(CPS_String); - CALL(_chainpack_pack_uint, + CALL(chainpack_pack_uint, (int64_t)item->as.String.len + item->as.String.eoff); } } @@ -111,7 +114,7 @@ ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { item->as.Blob.flags & CPBI_F_LAST) PUTC('\0'); break; - case CP_ITEM_DATETIME: + case CPITEM_DATETIME: PUTC(CPS_DateTime); /* Some arguable optimizations when msec == 0 or TZ_offset == a this * can save byte in packed date-time, but packing scheme is more @@ -133,26 +136,29 @@ ssize_t chainpack_pack(FILE *f, const struct cpitem *item) { msecs |= 2; CALL(chainpack_pack_int, msecs); break; - case CP_ITEM_LIST: + case CPITEM_LIST: PUTC(CPS_List); break; - case CP_ITEM_MAP: + case CPITEM_MAP: PUTC(CPS_Map); break; - case CP_ITEM_IMAP: + case CPITEM_IMAP: PUTC(CPS_IMap); break; - case CP_ITEM_META: + case CPITEM_META: PUTC(CPS_MetaMap); break; - case CP_ITEM_CONTAINER_END: + case CPITEM_CONTAINER_END: PUTC(CPS_TERM); break; + default: + abort(); /* anything else should be handled in common_pack */ + break; } return res; } -ssize_t _chainpack_pack_uint(FILE *f, unsigned long long v) { +static size_t chainpack_pack_uint(FILE *f, unsigned long long v) { ssize_t res = 0; unsigned bytes = chainpack_w_uint_bytes(v); uint8_t buf[bytes]; @@ -163,7 +169,7 @@ ssize_t _chainpack_pack_uint(FILE *f, unsigned long long v) { return res; } -static ssize_t chainpack_pack_int(FILE *f, int64_t v) { +static size_t chainpack_pack_int(FILE *f, long long v) { ssize_t res = 0; unsigned bytes = chainpack_w_int_bytes(v); uint8_t buf[bytes]; diff --git a/libshvchainpack/chainpack_unpack.c b/libshvchainpack/chainpack_unpack.c index e679e8f..db95da8 100644 --- a/libshvchainpack/chainpack_unpack.c +++ b/libshvchainpack/chainpack_unpack.c @@ -1,18 +1,25 @@ #include #include +#include +#include "common.h" -static size_t chainpack_unpack_int(FILE *f, long long *v, bool *ok); -static size_t chainpack_unpack_buf(FILE *f, struct cpitem *item, bool *ok); +static size_t chainpack_unpack_uint( + FILE *f, unsigned long long *v, enum cperror *err); +static size_t chainpack_unpack_int(FILE *f, long long *v, enum cperror *err); +static size_t chainpack_unpack_buf(FILE *f, struct cpitem *item, enum cperror *err); size_t chainpack_unpack(FILE *f, struct cpitem *item) { size_t res = 0; + if (common_unpack(&res, f, item)) + return res; #define GETC \ ({ \ int __v = getc(f); \ if (__v == EOF) { \ - item->type = CP_ITEM_INVALID; \ + item->type = CPITEM_INVALID; \ + item->as.Error = feof(f) ? CPERR_EOF : CPERR_IO; \ return res; \ } \ res++; \ @@ -21,27 +28,30 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { #define READ(PTR, SIZ) \ do { \ size_t __siz = SIZ; \ - ssize_t __v = fread((PTR), 1, __siz, f); \ - if (__v != __siz) { \ - item->type = CP_ITEM_INVALID; \ - if (__v > 0) \ - res += __v; \ + ssize_t __v = fread((PTR), __siz, 1, f); \ + if (__v != 1) { \ + item->type = CPITEM_INVALID; \ + item->as.Error = feof(f) ? CPERR_EOF : CPERR_IO; \ return res; \ } \ res += __siz; \ } while (false) #define CALL(FUNC, ...) \ do { \ - bool ok; \ - res += FUNC(f, __VA_ARGS__, &ok); \ - if (!ok) { \ - item->type = CP_ITEM_INVALID; \ + enum cperror err = CPERR_NONE; \ + res += FUNC(f, __VA_ARGS__, &err); \ + if (err != CPERR_NONE) { \ + item->type = CPITEM_INVALID; \ + if (err == CPERR_EOF && !feof(f)) \ + item->as.Error = CPERR_IO; \ + else \ + item->as.Error = err; \ return res; \ } \ } while (false) /* Continue reading previous item */ - if ((item->type == CP_ITEM_BLOB || item->type == CP_ITEM_STRING) && + if ((item->type == CPITEM_BLOB || item->type == CPITEM_STRING) && (item->as.Blob.eoff != 0 || !(item->as.Blob.flags & CPBI_F_LAST))) { /* There is no difference between string and blob in data type and thus * we can write this code to serve them both. @@ -54,36 +64,36 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { uint8_t scheme = GETC; if (scheme < CPS_Null) { if (chainpack_scheme_signed(scheme)) { - item->type = CP_ITEM_INT; + item->type = CPITEM_INT; item->as.Int = chainpack_scheme_uint(scheme); } else { - item->type = CP_ITEM_UINT; + item->type = CPITEM_UINT; item->as.UInt = chainpack_scheme_uint(scheme); } } else { unsigned long long ull; switch (scheme) { case CPS_Null: - item->type = CP_ITEM_NULL; + item->type = CPITEM_NULL; break; case CPS_TRUE: - item->type = CP_ITEM_BOOL; + item->type = CPITEM_BOOL; item->as.Bool = true; break; case CPS_FALSE: - item->type = CP_ITEM_BOOL; + item->type = CPITEM_BOOL; item->as.Bool = false; break; case CPS_Int: - item->type = CP_ITEM_INT; + item->type = CPITEM_INT; CALL(chainpack_unpack_int, &item->as.Int); break; case CPS_UInt: - item->type = CP_ITEM_UINT; - CALL(_chainpack_unpack_uint, &item->as.UInt); + item->type = CPITEM_UINT; + CALL(chainpack_unpack_uint, &item->as.UInt); break; case CPS_Double: - item->type = CP_ITEM_DOUBLE; + item->type = CPITEM_DOUBLE; #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ uint8_t *b = (uint8_t *)&(item->as.Double); for (ssize_t i = sizeof(double) - 1; i >= 0; i--) @@ -93,7 +103,7 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { #endif break; case CPS_Decimal: - item->type = CP_ITEM_DECIMAL; + item->type = CPITEM_DECIMAL; CALL(chainpack_unpack_int, &item->as.Decimal.mantisa); long long exp; CALL(chainpack_unpack_int, &exp); @@ -101,8 +111,8 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { break; case CPS_Blob: case CPS_BlobChain: - item->type = CP_ITEM_BLOB; - CALL(_chainpack_unpack_uint, &ull); + item->type = CPITEM_BLOB; + CALL(chainpack_unpack_uint, &ull); // TODO check that we do not crop the value item->as.String.eoff = ull; item->as.Blob.flags = CPBI_F_FIRST; @@ -112,20 +122,20 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { break; case CPS_String: case CPS_CString: - item->type = CP_ITEM_STRING; + item->type = CPITEM_STRING; item->as.String.flags = CPBI_F_FIRST; if (scheme == CPS_CString) { item->as.String.eoff = 0; item->as.String.flags |= CPBI_F_STREAM; } else { - CALL(_chainpack_unpack_uint, &ull); + CALL(chainpack_unpack_uint, &ull); // TODO check that we do not crop the value item->as.String.eoff = ull; } CALL(chainpack_unpack_buf, item); break; case CPS_DateTime: - item->type = CP_ITEM_DATETIME; + item->type = CPITEM_DATETIME; long long d; CALL(chainpack_unpack_int, &d); int32_t offset = 0; @@ -145,23 +155,23 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { (struct cpdatetime){.msecs = d, .offutc = offset * 15}; break; case CPS_MetaMap: - item->type = CP_ITEM_META; + item->type = CPITEM_META; break; case CPS_Map: - item->type = CP_ITEM_MAP; + item->type = CPITEM_MAP; break; case CPS_IMap: - item->type = CP_ITEM_IMAP; + item->type = CPITEM_IMAP; break; case CPS_List: - item->type = CP_ITEM_LIST; + item->type = CPITEM_LIST; break; case CPS_TERM: - item->type = CP_ITEM_CONTAINER_END; + item->type = CPITEM_CONTAINER_END; break; default: ungetc(scheme, f); - item->type = CP_ITEM_INVALID; + item->type = CPITEM_INVALID; } } @@ -171,13 +181,12 @@ size_t chainpack_unpack(FILE *f, struct cpitem *item) { #undef CALL } -size_t _chainpack_unpack_uint(FILE *f, unsigned long long *v, bool *ok) { +size_t chainpack_unpack_uint(FILE *f, unsigned long long *v, enum cperror *err) { ssize_t res = 0; int head = getc(f); if (head == EOF) { - if (ok) - *ok = false; + *err = CPERR_EOF; return res; } res++; @@ -188,26 +197,20 @@ size_t _chainpack_unpack_uint(FILE *f, unsigned long long *v, bool *ok) { for (unsigned i = 1; i < bytes; i++) { int r = getc(f); if (r == EOF) { - if (ok) - *ok = false; + *err = CPERR_EOF; return res; } res++; *v = (*v << 8) | r; } - if (ok) - *ok = true; return res; } -static size_t chainpack_unpack_int(FILE *f, long long *v, bool *ok) { - bool ourok; - if (ok == NULL) - ok = &ourok; - size_t res = _chainpack_unpack_uint(f, (unsigned long long *)v, ok); +static size_t chainpack_unpack_int(FILE *f, long long *v, enum cperror *err) { + size_t res = chainpack_unpack_uint(f, (unsigned long long *)v, err); - if (*ok) { + if (*err == CPERR_NONE) { /* This is kind of magic that requires some explanation. * We need to calculate where is sign bit. It is always the most * significant bit in the number but the location depends on number of @@ -229,24 +232,19 @@ static size_t chainpack_unpack_int(FILE *f, long long *v, bool *ok) { } -static size_t chainpack_unpack_buf(FILE *f, struct cpitem *item, bool *ok) { - bool ourok = true; - if (ok) - *ok = true; - else - ok = &ourok; - - if (item->bufsiz == 0) +static size_t chainpack_unpack_buf(FILE *f, struct cpitem *item, enum cperror *err) { + if (item->bufsiz == 0) { + item->as.Blob.len = 0; return 0; /* Nowhere to place data so just inform user about type */ + } - if (item->as.Blob.flags & CPBI_F_STREAM && item->type == CP_ITEM_STRING) { + if (item->as.Blob.flags & CPBI_F_STREAM && item->type == CPITEM_STRING) { /* Handle C string */ size_t i; for (i = 0; i < item->bufsiz; i++) { int c = getc(f); if (c == EOF) { - if (ok) - *ok = false; + *err = CPERR_EOF; return i; } if (c == '\0') { @@ -265,26 +263,34 @@ static size_t chainpack_unpack_buf(FILE *f, struct cpitem *item, bool *ok) { size_t res = 0; item->as.Blob.len = 0; while (item->as.Blob.len < item->bufsiz) { - size_t toread = item->as.Blob.eoff > item->bufsiz ? item->bufsiz - : item->as.Blob.eoff; - ssize_t rres = toread; - if (item->buf) - rres = fread(item->buf + item->as.Blob.len, 1, toread, f); - if (rres < 0) { - *ok = false; - break; + size_t toread = MIN(item->as.Blob.eoff, item->bufsiz - item->as.Blob.len); + if (item->buf) { + if (toread > 0 && + fread(item->buf + item->as.Blob.len, toread, 1, f) != 1) { + *err = CPERR_EOF; + break; + } + } else { + for (size_t siz = toread; siz > 0;) { + size_t bufsiz = MIN(siz, BUFSIZ); + uint8_t buf[bufsiz]; + if (fread(buf, bufsiz, 1, f) != 1) { + *err = CPERR_EOF; + break; + } + siz -= bufsiz; + } } - // TODO what if we failed to read enough data due to EOF? - res += rres; - item->as.Blob.len = rres; - item->as.Blob.eoff -= rres; + res += toread; + item->as.Blob.len = toread; + item->as.Blob.eoff -= toread; if (item->as.Blob.flags & CPBI_F_STREAM && item->as.Blob.eoff == 0) { unsigned long long ull; - res += _chainpack_unpack_uint(f, &ull, ok); + res += chainpack_unpack_uint(f, &ull, err); + if (*err != CPERR_NONE) + break; // TODO check that we do not crop the value item->as.String.eoff = ull; - if (!*ok) - break; } if (item->as.Blob.eoff == 0) { item->as.Blob.flags |= CPBI_F_LAST; diff --git a/libshvchainpack/common.c b/libshvchainpack/common.c new file mode 100644 index 0000000..225ad1a --- /dev/null +++ b/libshvchainpack/common.c @@ -0,0 +1,49 @@ +#include "common.h" +#include + +bool common_unpack(size_t *res, FILE *f, struct cpitem *item) { + switch (item->type) { + case CPITEM_INVALID: + /* Do nothing in case previous item reported an error */ + return item->as.Error != CPERR_NONE; + case CPITEM_RAW: + break; + default: + return false; + } + + /* Handle raw read */ + if (item->buf) { + item->as.Blob.len = fread(item->buf, 1, item->bufsiz, f); + *res += item->as.Blob.len; + } else { + size_t siz = item->bufsiz; + uint8_t buf[MIN(BUFSIZ, item->bufsiz)]; + size_t i; + do { + i = fread(buf, 1, MIN(BUFSIZ, siz), f); + siz -= i; + *res += i; + } while (i != 0); + } + return true; +} + +bool common_pack(size_t *res, FILE *f, const struct cpitem *item) { + switch (item->type) { + case CPITEM_INVALID: + /* Do nothing for invalid item */ + return true; + case CPITEM_RAW: + break; + default: + return false; + } + + /* Handle raw write */ + if (item->as.Blob.len == 0 || fwrite(item->buf, item->as.Blob.len, 1, f) == 1) + *res = item->as.Blob.len; + else + *res = -1; + return true; +} diff --git a/libshvchainpack/common.h b/libshvchainpack/common.h new file mode 100644 index 0000000..2584693 --- /dev/null +++ b/libshvchainpack/common.h @@ -0,0 +1,18 @@ +#ifndef _SHVCHAINPACK_COMMON_H +#define _SHVCHAINPACK_COMMON_H + +#include + +/* Common handling of the item for unpack functions. + * + * This covers RAW access as well as initial sanity checks. + */ +bool common_unpack(size_t *res, FILE *f, struct cpitem *item); + +/* Common handling of the item for pack functions. + * + * This covers RAW access and invalid item. + */ +bool common_pack(size_t *res, FILE *f, const struct cpitem *item); + +#endif diff --git a/libshvchainpack/cp_pack.c b/libshvchainpack/cp_pack.c index 594f61d..f7b1a9c 100644 --- a/libshvchainpack/cp_pack.c +++ b/libshvchainpack/cp_pack.c @@ -40,31 +40,29 @@ cp_pack_t cp_pack_cpon_init(struct cp_pack_cpon *pack, FILE *f, const char *inde } -// TODO test if ftell works or if we need to implement seek as well - static ssize_t _write(void *cookie, const char *buf, size_t size, bool blob) { cp_pack_t pack = cookie; struct cpitem item; - item.type = blob ? CP_ITEM_BLOB : CP_ITEM_STRING; + item.type = blob ? CPITEM_BLOB : CPITEM_STRING; item.rchr = buf; item.as.Blob = (struct cpbufinfo){ .len = size, .flags = CPBI_F_STREAM, }; - cp_pack(pack, &item); - return size; + ssize_t res = cp_pack(pack, &item); + return res >= 0 ? size : 0; } static int _close(void *cookie, bool blob) { cp_pack_t pack = cookie; struct cpitem item; - item.type = blob ? CP_ITEM_BLOB : CP_ITEM_STRING; + item.type = blob ? CPITEM_BLOB : CPITEM_STRING; item.as.Blob = (struct cpbufinfo){ .len = 0, .flags = CPBI_F_STREAM | CPBI_F_LAST, }; - cp_pack(pack, &item); - return 0; + ssize_t res = cp_pack(pack, &item); + return res >= 0 ? 0 : EOF; } static ssize_t write_blob(void *cookie, const char *buf, size_t size) { @@ -96,11 +94,12 @@ static const cookie_io_functions_t func_string = { FILE *cp_pack_fopen(cp_pack_t pack, bool str) { struct cpitem item; - item.type = str ? CP_ITEM_STRING : CP_ITEM_BLOB; + item.type = str ? CPITEM_STRING : CPITEM_BLOB; item.as.Blob = (struct cpbufinfo){ .len = 0, .flags = CPBI_F_FIRST | CPBI_F_STREAM, }; - cp_pack(pack, &item); + if (cp_pack(pack, &item) == 0) + return NULL; return fopencookie(pack, "a", str ? func_string : func_blob); } diff --git a/libshvchainpack/cp_unpack.c b/libshvchainpack/cp_unpack.c index 151aaee..71ebb04 100644 --- a/libshvchainpack/cp_unpack.c +++ b/libshvchainpack/cp_unpack.c @@ -2,6 +2,7 @@ #include #include #include +#include static size_t cp_unpack_chainpack_func(void *ptr, struct cpitem *item) { struct cp_unpack_chainpack *p = ptr; @@ -40,90 +41,161 @@ cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *unpack, FILE *f) { return &unpack->func; } -static size_t _cp_unpack_skip( - cp_unpack_t unpack, struct cpitem *item, unsigned depth) { +size_t cp_unpack_drop(cp_unpack_t unpack, struct cpitem *item) { size_t res = 0; item->buf = NULL; item->bufsiz = SIZE_MAX; - if ((item->type == CP_ITEM_STRING || item->type == CP_ITEM_BLOB) && - !(item->as.Blob.flags & CPBI_F_LAST)) { - depth++; - } + while ((item->type == CPITEM_STRING || item->type == CPITEM_BLOB) && + !(item->as.Blob.flags & CPBI_F_LAST)) + res += cp_unpack(unpack, item); + item->bufsiz = 0; + return res; +} + +size_t cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) { + return cp_unpack_finish(unpack, item, 0); +} + +size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item, unsigned depth) { + size_t res = 0; + cp_unpack_drop(unpack, item); + item->bufsiz = SIZE_MAX; do { res += cp_unpack(unpack, item); switch (item->type) { - case CP_ITEM_BLOB: - case CP_ITEM_STRING: + case CPITEM_BLOB: + case CPITEM_STRING: if (item->as.Blob.flags & CPBI_F_FIRST) depth++; if (item->as.Blob.flags & CPBI_F_LAST) depth--; break; - case CP_ITEM_LIST: - case CP_ITEM_MAP: - case CP_ITEM_IMAP: - case CP_ITEM_META: + case CPITEM_LIST: + case CPITEM_MAP: + case CPITEM_IMAP: + case CPITEM_META: depth++; break; - case CP_ITEM_CONTAINER_END: + case CPITEM_CONTAINER_END: depth--; break; - case CP_ITEM_INVALID: + case CPITEM_INVALID: return false; default: break; } } while (depth); + item->bufsiz = 0; return res; } -size_t cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) { - return _cp_unpack_skip(unpack, item, 0); -} - -size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item) { - return _cp_unpack_skip(unpack, item, 1); -} - -static void *cp_unpack_dup(cp_unpack_t unpack, struct cpitem *item, size_t *size) { - item->bufsiz = 2; - uint8_t *res = malloc(item->bufsiz); +static void *cp_unpack_dup( + cp_unpack_t unpack, struct cpitem *item, size_t *size, size_t len) { + uint8_t *res = NULL; size_t off = 0; - item->buf = res; - while (true) { + do { + item->bufsiz = MAX(off, 4); + if (len < (item->bufsiz + off)) + item->bufsiz = len - off; + res = realloc(res, off + item->bufsiz); + assert(res); + item->buf = res + off; cp_unpack(unpack, item); - if (item->type != (size ? CP_ITEM_BLOB : CP_ITEM_STRING)) { + if (item->type != (size ? CPITEM_BLOB : CPITEM_STRING)) { free(res); return NULL; } - if (item->as.Blob.flags & CPBI_F_LAST) { - off += item->as.Blob.len; - if (size) { - *size = off; - return realloc(res, off); - } else { - res = realloc(res, off + 1); - res[off] = '\0'; - return res; - } - } - /* We expect here that all available bytes are used */ - assert(item->as.Blob.len == item->bufsiz); - off += item->bufsiz; - item->bufsiz = off; - res = realloc(res, off * 2); - item->buf = res + off; + off += item->as.Blob.len; + } while (off < len && !(item->as.Blob.flags & CPBI_F_LAST)); + if (size) { + *size = off; + return realloc(res, off); + } else { + res = realloc(res, off + 1); + res[off] = '\0'; + return res; } - return res; } char *cp_unpack_strdup(cp_unpack_t unpack, struct cpitem *item) { - return cp_unpack_dup(unpack, item, NULL); + return cp_unpack_dup(unpack, item, NULL, SIZE_MAX); +} + +char *cp_unpack_strndup(cp_unpack_t unpack, struct cpitem *item, size_t len) { + return cp_unpack_dup(unpack, item, NULL, len); } void cp_unpack_memdup( cp_unpack_t unpack, struct cpitem *item, uint8_t **buf, size_t *siz) { - *buf = cp_unpack_dup(unpack, item, siz); + *buf = cp_unpack_dup(unpack, item, siz, SIZE_MAX); +} + +void cp_unpack_memndup( + cp_unpack_t unpack, struct cpitem *item, uint8_t **buf, size_t *siz) { + *buf = cp_unpack_dup(unpack, item, siz, *siz); +} + +static void *cp_unpack_dupo(cp_unpack_t unpack, struct cpitem *item, + size_t *size, size_t len, struct obstack *obstack) { + int zeropad = 0; + do { + /* We are using here fast growing in a reverse way. We first store + * data and only after that we grow object. We do this because we + * do not know number of needed bytes upfront. This way we do not + * have to use temporally buffer and copy data around. We copy it + * directly to the obstack's buffer. + */ + if (obstack_room(obstack) == 0) { + obstack_1grow(obstack, '\0'); /* Force allocation of more room */ + zeropad = 1; + } + item->chr = obstack_next_free(obstack); + size_t room = obstack_room(obstack) + zeropad; + size_t limit = len - obstack_object_size(obstack); + item->bufsiz = MIN(room, limit); + cp_unpack(unpack, item); + if (item->type != (size ? CPITEM_BLOB : CPITEM_STRING)) { + obstack_free(obstack, obstack_base(obstack)); + item->bufsiz = 0; + return NULL; + } + if (item->as.Blob.len > 0) { + obstack_blank_fast(obstack, item->as.Blob.len - zeropad); + zeropad = 0; + } + } while (obstack_object_size(obstack) < len && + !(item->as.Blob.flags & CPBI_F_LAST)); + if (size) + *size = obstack_object_size(obstack) - zeropad; + else if (zeropad == 0) + obstack_1grow(obstack, '\0'); + return obstack_finish(obstack); +} + +char *cp_unpack_strdupo( + cp_unpack_t unpack, struct cpitem *item, struct obstack *obstack) { + return cp_unpack_dupo(unpack, item, NULL, SIZE_MAX, obstack); +} + +char *cp_unpack_strndupo(cp_unpack_t unpack, struct cpitem *item, size_t len, + struct obstack *obstack) { + return cp_unpack_dupo(unpack, item, NULL, len, obstack); +} + +void cp_unpack_memdupo(cp_unpack_t unpack, struct cpitem *item, uint8_t **buf, + size_t *siz, struct obstack *obstack) { + *buf = cp_unpack_dupo(unpack, item, siz, SIZE_MAX, obstack); +} + +void cp_unpack_memndupo(cp_unpack_t unpack, struct cpitem *item, uint8_t **buf, + size_t *siz, struct obstack *obstack) { + *buf = cp_unpack_dupo(unpack, item, siz, *siz, obstack); +} + +int cp_unpack_expect_str( + cp_unpack_t unpack, struct cpitem *item, const char **strings) { + // TODO + return -2; } @@ -139,7 +211,7 @@ static ssize_t _read(void *cookie, char *buf, size_t size, bool blob) { c->item->chr = buf; c->item->bufsiz = size; cp_unpack(c->unpack, c->item); - if (c->item->type != (blob ? CP_ITEM_BLOB : CP_ITEM_STRING)) + if (c->item->type != (blob ? CPITEM_BLOB : CPITEM_STRING)) return -1; return c->item->as.String.len; } @@ -180,7 +252,7 @@ static const cookie_io_functions_t func_string = { FILE *cp_unpack_fopen(cp_unpack_t unpack, struct cpitem *item) { item->bufsiz = 0; cp_unpack(unpack, item); - if (item->type != CP_ITEM_BLOB && item->type != CP_ITEM_STRING) + if (item->type != CPITEM_BLOB && item->type != CPITEM_STRING) return NULL; struct cookie *cookie = malloc(sizeof *cookie); @@ -189,5 +261,5 @@ FILE *cp_unpack_fopen(cp_unpack_t unpack, struct cpitem *item) { .item = item, }; return fopencookie( - cookie, "r", item->type == CP_ITEM_STRING ? func_string : func_blob); + cookie, "r", item->type == CPITEM_STRING ? func_string : func_blob); } diff --git a/libshvchainpack/cpcp.c b/libshvchainpack/cpcp.c deleted file mode 100644 index 73f378f..0000000 --- a/libshvchainpack/cpcp.c +++ /dev/null @@ -1,407 +0,0 @@ -#include -#include - - -const char *cpcp_error_string(enum cpcp_error errno) { - switch (errno) { - case CPCP_RC_OK: - return ""; - case CPCP_RC_MALLOC_ERROR: - return "MALLOC_ERROR"; - case CPCP_RC_BUFFER_OVERFLOW: - return "BUFFER_OVERFLOW"; - case CPCP_RC_BUFFER_UNDERFLOW: - return "BUFFER_UNDERFLOW"; - case CPCP_RC_MALFORMED_INPUT: - return "MALFORMED_INPUT"; - case CPCP_RC_LOGICAL_ERROR: - return "LOGICAL_ERROR"; - case CPCP_RC_CONTAINER_STACK_OVERFLOW: - return "CONTAINER_STACK_OVERFLOW"; - case CPCP_RC_CONTAINER_STACK_UNDERFLOW: - return "CONTAINER_STACK_UNDERFLOW"; - default: - return "UNKNOWN"; - } -} - -//=========================== PACK ============================ -void cpcp_pack_context_init(cpcp_pack_context *pack_context, void *data, - size_t length, cpcp_pack_overflow_handler poh) { - pack_context->start = pack_context->current = (char *)data; - pack_context->end = pack_context->start; - if (length) { - pack_context->end += length; - } - pack_context->err_no = 0; - pack_context->handle_pack_overflow = poh; - pack_context->err_no = CPCP_RC_OK; - pack_context->cpon_options.indent = NULL; - pack_context->cpon_options.json_output = 0; - pack_context->nest_count = 0; - pack_context->bytes_written = 0; -} - -void cpcp_pack_context_dry_run_init(cpcp_pack_context *pack_context) { - cpcp_pack_context_init(pack_context, NULL, 0, NULL); -} - -static bool is_dry_run(cpcp_pack_context *pack_context) { - return pack_context->handle_pack_overflow == NULL && - pack_context->start == NULL && pack_context->end == NULL && - pack_context->current == NULL; -} - -// try to make size_hint bytes space in pack_context -// returns number of bytes available in pack_context buffer, can be < size_hint, -// but always > 0 returns 0 if fails -static size_t cpcp_pack_make_space( - cpcp_pack_context *pack_context, size_t size_hint) { - if (pack_context->err_no != CPCP_RC_OK) - return 0; - size_t free_space = (size_t)(pack_context->end - pack_context->current); - if (free_space < size_hint) { - if (!pack_context->handle_pack_overflow) { - pack_context->err_no = CPCP_RC_BUFFER_OVERFLOW; - return 0; - } - pack_context->handle_pack_overflow(pack_context, size_hint); - free_space = (size_t)(pack_context->end - pack_context->current); - if (free_space < 1) { - pack_context->err_no = CPCP_RC_BUFFER_OVERFLOW; - return 0; - } - } - return free_space; -} - -// alocate more bytes, move current after allocated bytec -static char *cpcp_pack_reserve_space(cpcp_pack_context *pack_context, size_t more) { - if (pack_context->err_no != CPCP_RC_OK) - return NULL; - size_t free_space = cpcp_pack_make_space(pack_context, more); - if (free_space < more) { - pack_context->err_no = CPCP_RC_BUFFER_OVERFLOW; - return NULL; - } - char *p = pack_context->current; - pack_context->current = p + more; - return p; -} - -size_t cpcp_pack_copy_byte(cpcp_pack_context *pack_context, uint8_t b) { - pack_context->bytes_written += 1; - if (is_dry_run(pack_context)) - return 1; - - char *p = cpcp_pack_reserve_space(pack_context, 1); - if (!p) - return 0; - *p = (char)b; - return 1; -} - -size_t cpcp_pack_copy_bytes( - cpcp_pack_context *pack_context, const void *str, size_t len) { - pack_context->bytes_written += len; - if (is_dry_run(pack_context)) - return len; - - size_t copied = 0; - while (pack_context->err_no == CPCP_RC_OK && copied < len) { - size_t buff_size = cpcp_pack_make_space(pack_context, len); - if (buff_size == 0) { - pack_context->err_no = CPCP_RC_BUFFER_OVERFLOW; - return 0; - } - size_t rest = len - copied; - if (rest > buff_size) - rest = buff_size; - memcpy(pack_context->current, ((const char *)str) + copied, rest); - copied += rest; - pack_context->current += rest; - } - return copied; -} - -//================================ UNPACK ================================ -const char *cpcp_item_type_to_string(cpcp_item_types t) { - switch (t) { - case CPCP_ITEM_INVALID: - break; - case CPCP_ITEM_NULL: - return "NULL"; - case CPCP_ITEM_BOOLEAN: - return "BOOLEAN"; - case CPCP_ITEM_INT: - return "INT"; - case CPCP_ITEM_UINT: - return "UINT"; - case CPCP_ITEM_DOUBLE: - return "DOUBLE"; - case CPCP_ITEM_DECIMAL: - return "DECIMAL"; - case CPCP_ITEM_BLOB: - return "BLOB"; - case CPCP_ITEM_STRING: - return "STRING"; - case CPCP_ITEM_DATE_TIME: - return "DATE_TIME"; - case CPCP_ITEM_LIST: - return "LIST"; - case CPCP_ITEM_MAP: - return "MAP"; - case CPCP_ITEM_IMAP: - return "IMAP"; - case CPCP_ITEM_META: - return "META"; - case CPCP_ITEM_CONTAINER_END: - return "CONTAINER_END"; - } - return "INVALID"; -} - -void cpcp_container_state_init( - cpcp_container_state *self, cpcp_item_types cont_type) { - self->container_type = cont_type; - self->current_item_type = CPCP_ITEM_INVALID; - self->item_count = 0; -} - -void cpcp_container_stack_init(cpcp_container_stack *self, - cpcp_container_state *states, size_t capacity, - cpcp_container_stack_overflow_handler hnd) { - self->container_states = states; - self->capacity = capacity; - self->length = 0; - self->overflow_handler = hnd; -} - -void cpcp_unpack_context_init(cpcp_unpack_context *self, const void *data, - size_t length, cpcp_unpack_underflow_handler huu, cpcp_container_stack *stack) { - self->item.type = CPCP_ITEM_INVALID; - self->start = self->current = (const char *)data; - self->end = self->start + length; - self->err_no = CPCP_RC_OK; - self->parser_line_no = 1; - self->err_msg = ""; - self->handle_unpack_underflow = huu; - self->container_stack = stack; - self->string_chunk_buff = self->default_string_chunk_buff; - self->string_chunk_buff_len = sizeof(self->default_string_chunk_buff); -} - -cpcp_container_state *cpcp_unpack_context_push_container_state( - cpcp_unpack_context *self, cpcp_item_types container_type) { - if (!self->container_stack) { - // C++ implementation does not require container states stack - return NULL; - } - if (self->container_stack->length == self->container_stack->capacity) { - if (!self->container_stack->overflow_handler) { - self->err_no = CPCP_RC_CONTAINER_STACK_OVERFLOW; - return NULL; - } - int rc = self->container_stack->overflow_handler(self->container_stack); - if (rc < 0) { - self->err_no = CPCP_RC_CONTAINER_STACK_OVERFLOW; - return NULL; - } - } - if (self->container_stack->length < self->container_stack->capacity - 1) { - cpcp_container_state *state = self->container_stack->container_states + - self->container_stack->length; - cpcp_container_state_init(state, container_type); - self->container_stack->length++; - return state; - } - self->err_no = CPCP_RC_CONTAINER_STACK_OVERFLOW; - return NULL; -} - -cpcp_container_state *cpcp_unpack_context_top_container_state( - cpcp_unpack_context *self) { - if (self->container_stack && self->container_stack->length > 0) { - return self->container_stack->container_states + - self->container_stack->length - 1; - } - return NULL; -} - -cpcp_container_state *cpcp_unpack_context_parent_container_state( - cpcp_unpack_context *self) { - if (self->container_stack && self->container_stack->length > 0) { - cpcp_container_state *top_st = self->container_stack->container_states + - self->container_stack->length - 1; - if (top_st && top_st->item_count == 0) { - if (self->container_stack->length > 1) - return self->container_stack->container_states + - self->container_stack->length - 2; - - return NULL; - } - return top_st; - } - return NULL; -} - -cpcp_container_state *cpcp_unpack_context_closed_container_state( - cpcp_unpack_context *self) { - if (self->container_stack && self->item.type == CPCP_ITEM_CONTAINER_END) { - cpcp_container_state *st = self->container_stack->container_states + - self->container_stack->length; - return st; - } - return NULL; -} - -void cpcp_unpack_context_pop_container_state(cpcp_unpack_context *self) { - if (self->container_stack && self->container_stack->length > 0) { - self->container_stack->length--; - } -} - -void cpcp_string_init(cpcp_string *self, cpcp_unpack_context *unpack_context) { - self->chunk_size = 0; - self->chunk_cnt = 0; - self->last_chunk = 0; - self->string_size = -1; - self->size_to_load = -1; - self->chunk_start = unpack_context->string_chunk_buff; - self->chunk_buff_len = unpack_context->string_chunk_buff_len; - self->blob_hex = 0; -} - -const char *cpcp_unpack_take_byte(cpcp_unpack_context *unpack_context) { - const char *p = cpcp_unpack_peek_byte(unpack_context); - if (p) - unpack_context->current++; - return p; -} - -const char *cpcp_unpack_peek_byte(cpcp_unpack_context *unpack_context) { - static const size_t more = 1; - if (unpack_context->current >= unpack_context->end) { - if (!unpack_context->handle_unpack_underflow) { - unpack_context->err_no = CPCP_RC_BUFFER_UNDERFLOW; - return NULL; - } - size_t sz = unpack_context->handle_unpack_underflow(unpack_context); - if (sz < more) { - unpack_context->err_no = CPCP_RC_BUFFER_UNDERFLOW; - return NULL; - } - unpack_context->current = unpack_context->start; - } - const char *p = unpack_context->current; - return p; -} - -double cpcp_exponentional_to_double( - int64_t const mantisa, const int exponent, const int base) { - double d = (double)mantisa; - int i; - for (i = 0; i < exponent; ++i) - d *= base; - for (i = exponent; i < 0; ++i) - d /= base; - return d; -} - -double cpcp_decimal_to_double(const int64_t mantisa, const int exponent) { - return cpcp_exponentional_to_double(mantisa, exponent, 10); -} - -static size_t int_to_str(char *buff, size_t buff_len, int64_t val) { - size_t n = 0; - bool neg = false; - char *str = buff; - size_t i; - if (val < 0) { - neg = true; - val = -val; - str = buff + 1; - buff_len--; - } - if (val == 0) { - if (n == buff_len) - return 0; - str[n++] = '0'; - } else - while (val != 0) { - int d = (int)(val % 10); - val /= 10; - if (n == buff_len) - return 0; - str[n++] = '0' + (char)d; - } - for (i = 0; i < n / 2; ++i) { - char c = str[i]; - str[i] = str[n - i - 1]; - str[n - i - 1] = c; - } - if (neg) { - buff[0] = '-'; - n++; - } - return n; -} - -size_t cpcp_decimal_to_string( - char *buff, size_t buff_len, int64_t mantisa, int exponent) { - bool neg = false; - if (mantisa < 0) { - mantisa = -mantisa; - neg = true; - } - - // at least 21 characters for 64-bit types. - char *str = buff; - if (neg) { - str++; - buff_len--; - } - size_t mantisa_str_len = int_to_str(str, buff_len, mantisa); - if (mantisa_str_len == 0) { - return mantisa_str_len; - } - - size_t dec_places = (exponent < 0) ? (size_t)(-exponent) : 0; - if (dec_places > 0 && dec_places < mantisa_str_len) { - size_t dot_ix = mantisa_str_len - dec_places; - for (size_t i = dot_ix; i < mantisa_str_len; ++i) - str[mantisa_str_len + dot_ix - i] = - str[mantisa_str_len + dot_ix - i - 1]; - str[dot_ix] = '.'; - mantisa_str_len++; - } else if (dec_places > 0 && dec_places <= 3) { - size_t extra_0_cnt = dec_places - mantisa_str_len; - for (size_t i = 0; i < mantisa_str_len; ++i) - str[mantisa_str_len - i - 1 + extra_0_cnt + 2] = - str[mantisa_str_len - i - 1]; - str[0] = '0'; - str[1] = '.'; - for (size_t i = 0; i < extra_0_cnt; ++i) - str[2 + i] = '0'; - mantisa_str_len += extra_0_cnt + 2; - } else if (exponent > 0 && mantisa_str_len + (unsigned)exponent <= 9) { - for (size_t i = 0; i < (unsigned)exponent; ++i) - str[mantisa_str_len++] = '0'; - str[mantisa_str_len++] = '.'; - } else if (exponent == 0) { - str[mantisa_str_len++] = '.'; - } else { - str[mantisa_str_len++] = 'e'; - size_t n2 = int_to_str( - str + mantisa_str_len, buff_len - mantisa_str_len, exponent); - if (n2 == 0) { - return n2; - } - mantisa_str_len += n2; - } - if (neg) { - buff[0] = '-'; - mantisa_str_len++; - } - return mantisa_str_len; -} diff --git a/libshvchainpack/cpitem.c b/libshvchainpack/cpitem.c new file mode 100644 index 0000000..eaf61a6 --- /dev/null +++ b/libshvchainpack/cpitem.c @@ -0,0 +1,23 @@ +#include + +static const char *const typenames[] = { + [CPITEM_INVALID] = "INVALID", + [CPITEM_NULL] = "NULL", + [CPITEM_BOOL] = "BOOL", + [CPITEM_INT] = "INT", + [CPITEM_UINT] = "UINT", + [CPITEM_DOUBLE] = "DOUBLE", + [CPITEM_DECIMAL] = "DECIMAL", + [CPITEM_BLOB] = "BLOB", + [CPITEM_STRING] = "STRING", + [CPITEM_DATETIME] = "DATETIME", + [CPITEM_LIST] = "LIST", + [CPITEM_MAP] = "MAP", + [CPITEM_IMAP] = "IMAP", + [CPITEM_META] = "META", + [CPITEM_CONTAINER_END] = "CONTAINER_END", +}; + +const char *cpitem_type_str(enum cpitem_type tp) { + return typenames[tp <= CPITEM_CONTAINER_END ? tp : 0]; +} diff --git a/libshvchainpack/cpon_pack.c b/libshvchainpack/cpon_pack.c index 7b02868..90eb045 100644 --- a/libshvchainpack/cpon_pack.c +++ b/libshvchainpack/cpon_pack.c @@ -4,65 +4,66 @@ #include #include #include +#include "common.h" #define PUTC(V) \ do { \ if (f && fputc((V), f) != (V)) \ - return -1; \ + return 0; \ res++; \ } while (false) #define PUTS(V) \ do { \ const char *__v = (V); \ size_t __strlen = strlen(__v); \ - if (f) { \ - if (fwrite(__v, 1, __strlen, f) != __strlen) \ - return -1; \ - } \ + if (f && fwrite(__v, 1, __strlen, f) != __strlen) \ + return 0; \ res += __strlen; \ } while (false) #define PRINTF(...) \ do { \ - ssize_t __cnt; \ + size_t __cnt; \ if (f == NULL) \ __cnt = snprintf(NULL, 0, __VA_ARGS__); \ else \ __cnt = fprintf(f, __VA_ARGS__); \ if (__cnt < 0) \ - return -1; \ + return 0; \ res += __cnt; \ } while (false) #define CALL(FUNC, ...) \ do { \ - ssize_t __cnt = FUNC(f, __VA_ARGS__); \ - if (__cnt < 0) \ - return -1; \ + size_t __cnt = FUNC(f, __VA_ARGS__); \ + if (__cnt == 0) \ + return 0; \ res += __cnt; \ } while (false) -static ssize_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec); -static ssize_t cpon_pack_buf(FILE *f, const struct cpitem *item); -static ssize_t ctxpush( - FILE *f, struct cpon_state *state, enum cp_item_type tp, const char *str); -static enum cp_item_type ctxpop(struct cpon_state *state); +static size_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec); +static size_t cpon_pack_buf(FILE *f, const struct cpitem *item); +static size_t ctxpush( + FILE *f, struct cpon_state *state, enum cpitem_type tp, const char *str); +static enum cpitem_type ctxpop(struct cpon_state *state); -ssize_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) { - ssize_t res = 0; +size_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) { + size_t res = 0; + if (common_pack(&res, f, item)) + return res; if (state->depth <= state->cnt) { - if (state->depth > 0 && item->type != CP_ITEM_CONTAINER_END) { + if (state->depth > 0 && item->type != CPITEM_CONTAINER_END) { struct cpon_state_ctx *ctx = &state->ctx[state->depth - 1]; if (!ctx->meta) { switch (ctx->tp) { - case CP_ITEM_LIST: + case CPITEM_LIST: if (!ctx->first) PUTC(','); break; - case CP_ITEM_MAP: - case CP_ITEM_IMAP: - case CP_ITEM_META: + case CPITEM_MAP: + case CPITEM_IMAP: + case CPITEM_META: if (ctx->even) { if (!ctx->first) PUTC(','); @@ -77,38 +78,41 @@ ssize_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) ctx->meta = false; } switch (item->type) { - /* We pack invalid as NULL to ensure that we pack at least - * somethuing */ - case CP_ITEM_INVALID: - case CP_ITEM_NULL: + case CPITEM_NULL: PUTS("null"); break; - case CP_ITEM_BOOL: + case CPITEM_BOOL: PUTS(item->as.Bool ? "true" : "false"); break; - case CP_ITEM_INT: + case CPITEM_INT: PRINTF("%lld", item->as.Int); break; - case CP_ITEM_UINT: + case CPITEM_UINT: PRINTF("%lluu", item->as.UInt); break; - case CP_ITEM_DOUBLE: + case CPITEM_DOUBLE: PRINTF("%G", item->as.Double); break; - case CP_ITEM_DECIMAL: + case CPITEM_DECIMAL: CALL(cpon_pack_decimal, &item->as.Decimal); break; - case CP_ITEM_BLOB: + case CPITEM_BLOB: if (item->as.Blob.flags & CPBI_F_FIRST) PUTS(item->as.Blob.flags & CPBI_F_HEX ? "x\"" : "b\""); - CALL(cpon_pack_buf, item); + if (item->as.Blob.len > 0) + CALL(cpon_pack_buf, item); + if (item->as.Blob.flags & CPBI_F_LAST) + PUTC('\"'); break; - case CP_ITEM_STRING: + case CPITEM_STRING: if (item->as.Blob.flags & CPBI_F_FIRST) PUTS("\""); - CALL(cpon_pack_buf, item); + if (item->as.String.len > 0) + CALL(cpon_pack_buf, item); + if (item->as.Blob.flags & CPBI_F_LAST) + PUTC('\"'); break; - case CP_ITEM_DATETIME: + case CPITEM_DATETIME: struct tm tm = cpdttotm(item->as.Datetime); PRINTF("d\"%d-%.2d-%.2dT%.2d:%.2d:%.2d.%.3d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, @@ -120,28 +124,28 @@ ssize_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) else PUTS("Z\""); break; - case CP_ITEM_LIST: - CALL(ctxpush, state, CP_ITEM_LIST, "["); + case CPITEM_LIST: + CALL(ctxpush, state, CPITEM_LIST, "["); break; - case CP_ITEM_MAP: - CALL(ctxpush, state, CP_ITEM_MAP, "{"); + case CPITEM_MAP: + CALL(ctxpush, state, CPITEM_MAP, "{"); break; - case CP_ITEM_IMAP: - CALL(ctxpush, state, CP_ITEM_IMAP, "i{"); + case CPITEM_IMAP: + CALL(ctxpush, state, CPITEM_IMAP, "i{"); break; - case CP_ITEM_META: - CALL(ctxpush, state, CP_ITEM_META, "<"); + case CPITEM_META: + CALL(ctxpush, state, CPITEM_META, "<"); break; - case CP_ITEM_CONTAINER_END: + case CPITEM_CONTAINER_END: switch (ctxpop(state)) { - case CP_ITEM_LIST: + case CPITEM_LIST: PUTC(']'); break; - case CP_ITEM_MAP: - case CP_ITEM_IMAP: + case CPITEM_MAP: + case CPITEM_IMAP: PUTC('}'); break; - case CP_ITEM_META: + case CPITEM_META: PUTC('>'); if (state->depth > 0) state->ctx[state->depth - 1].meta = true; @@ -150,6 +154,9 @@ ssize_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) break; } break; + default: + abort(); /* anything else should be handled in common_pack */ + break; } } else if (state->depth == state->cnt && state->ctx[state->depth - 1].first) { PUTS("..."); @@ -159,12 +166,12 @@ ssize_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) return res; } -static ssize_t cpon_pack_buf(FILE *f, const struct cpitem *item) { - ssize_t res = 0; +static size_t cpon_pack_buf(FILE *f, const struct cpitem *item) { + size_t res = 0; for (size_t i = 0; i < item->as.Blob.len; i++) { uint8_t b = item->rbuf[i]; - if (item->type == CP_ITEM_BLOB && item->as.Blob.flags & CPBI_F_HEX) + if (item->type == CPITEM_BLOB && item->as.Blob.flags & CPBI_F_HEX) PRINTF("%.2X", b); else { #define ESCAPE(V) \ @@ -205,20 +212,18 @@ static ssize_t cpon_pack_buf(FILE *f, const struct cpitem *item) { break; } #undef ESCAPE - if (item->type == CP_ITEM_BLOB && (b < 32 || b >= 127)) + if (item->type == CPITEM_BLOB && (b < 32 || b >= 127)) PRINTF("\\%.2X", b); else PUTC(b); } } - if (item->as.Blob.flags & CPBI_F_LAST) - PUTC('\"'); return res; } -static ssize_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec) { - ssize_t res = 0; +static size_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec) { + size_t res = 0; if (dec->exponent <= 6 && dec->exponent >= -9) { /* Pack in X.Y notation */ @@ -252,9 +257,9 @@ static ssize_t cpon_pack_decimal(FILE *f, const struct cpdecimal *dec) { return res; } -static ssize_t ctxpush( - FILE *f, struct cpon_state *state, enum cp_item_type tp, const char *str) { - ssize_t res = 0; +static size_t ctxpush( + FILE *f, struct cpon_state *state, enum cpitem_type tp, const char *str) { + size_t res = 0; if (state->depth == state->cnt && state->realloc) state->realloc(state); if (state->depth < state->cnt) { @@ -270,10 +275,10 @@ static ssize_t ctxpush( return res; } -static enum cp_item_type ctxpop(struct cpon_state *state) { +static enum cpitem_type ctxpop(struct cpon_state *state) { assert(state->depth > 0); state->depth--; if (state->depth < state->cnt) return state->ctx[state->depth].tp; - return CP_ITEM_INVALID; + return CPITEM_INVALID; } diff --git a/libshvchainpack/cpon_unpack.c b/libshvchainpack/cpon_unpack.c index 8ddac4d..1602d40 100644 --- a/libshvchainpack/cpon_unpack.c +++ b/libshvchainpack/cpon_unpack.c @@ -1,7 +1,9 @@ #include +#include #include #include #include +#include "common.h" #define GETC \ @@ -24,20 +26,34 @@ }) -static size_t cpon_unpack_buf(FILE *f, struct cpitem *item, bool *ok); +static size_t cpon_unpack_buf(FILE *f, struct cpitem *item, enum cperror *err); // TODO use state size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) { size_t res = 0; + if (common_unpack(&res, f, item)) + return res; +#define ERR_IO \ + do { \ + item->type = CPITEM_INVALID; \ + item->as.Error = feof(f) ? CPERR_EOF : CPERR_IO; \ + return res; \ + } while (false) +#define ERR_INVALID \ + do { \ + item->type = CPITEM_INVALID; \ + item->as.Error = CPERR_INVALID; \ + return res; \ + } while (false) #define EXPECT(V) \ do { \ char c = GETC; \ + if (c == EOF) \ + ERR_IO; \ if (c != (V)) { \ - if (c != EOF) \ - ungetc(c, f); \ - item->type = CP_ITEM_INVALID; \ - return res; \ + UNGETC(V); \ + ERR_INVALID; \ } \ } while (false) #define EXPECTS(V) \ @@ -48,32 +64,34 @@ size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) { } while (false) #define CALL(FUNC, ...) \ do { \ - bool ok; \ - res += FUNC(f, __VA_ARGS__, &ok); \ - if (!ok) \ - return res; \ + enum cperror err = CPERR_NONE; \ + res += FUNC(f, __VA_ARGS__, &err); \ + if (err == CPERR_EOF) \ + ERR_IO; \ + else if (err == CPERR_INVALID) \ + ERR_INVALID; \ } while (false) /* Continue reading previous item */ - if ((item->type == CP_ITEM_BLOB || item->type == CP_ITEM_STRING) && + if ((item->type == CPITEM_BLOB || item->type == CPITEM_STRING) && (item->as.Blob.eoff != 0 || !(item->as.Blob.flags & CPBI_F_LAST))) { item->as.Blob.flags &= ~CPBI_F_FIRST; CALL(cpon_unpack_buf, item); return res; } - item->type = CP_ITEM_INVALID; + item->type = CPITEM_INVALID; bool comment = false; char c; while (true) { c = GETC; if (c == EOF) - return res; + ERR_IO; if (comment) { if (c == '*') { c = GETC; if (c == EOF) - return res; + ERR_IO; comment = c != '/'; } continue; @@ -93,34 +111,35 @@ size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) { switch (c) { case 'n': EXPECTS("ull"); - item->type = CP_ITEM_NULL; + item->type = CPITEM_NULL; break; case 't': EXPECTS("rue"); - item->type = CP_ITEM_BOOL; + item->type = CPITEM_BOOL; item->as.Bool = true; break; case 'f': EXPECTS("alse"); - item->type = CP_ITEM_BOOL; + item->type = CPITEM_BOOL; item->as.Bool = false; break; case '-': case '0' ... '9': UNGETC(c); if (SCANF("%lli", &item->as.Int) != 1) - return res; - item->type = CP_ITEM_INT; + ERR_IO; + item->type = CPITEM_INT; bool has_sign = c == '-'; c = GETC; if (!has_sign && c == 'u') { - item->type = CP_ITEM_UINT; + item->type = CPITEM_UINT; } else if (c == 'e') { - item->type = CP_ITEM_DECIMAL; + item->type = CPITEM_DECIMAL; item->as.Decimal.mantisa = item->as.Int; + item->as.Decimal.exponent = 0; SCANF("%i", &item->as.Decimal.exponent); } else if (c == '.') { - item->type = CP_ITEM_DECIMAL; + item->type = CPITEM_DECIMAL; item->as.Decimal.mantisa = item->as.Int; unsigned long long dec; ssize_t rres = res; @@ -148,7 +167,7 @@ size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) { case 'x': case 'b': EXPECT('"'); - item->type = CP_ITEM_BLOB; + item->type = CPITEM_BLOB; item->as.String.flags = CPBI_F_FIRST | CPBI_F_STREAM; if (c == 'x') item->as.String.flags |= CPBI_F_HEX; @@ -158,9 +177,12 @@ size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) { case 'd': struct tm tm = (struct tm){}; unsigned msecs; - if (SCANF("\"%u-%u-%uT%u:%u:%u.%u", &tm.tm_year, &tm.tm_mon, - &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &msecs) != 7) - return res; + int sres = SCANF("\"%u-%u-%uT%u:%u:%u.%u", &tm.tm_year, &tm.tm_mon, + &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &msecs); + if (sres == -1) + ERR_IO; + if (sres != 7) + ERR_INVALID; tm.tm_year -= 1900; tm.tm_mon -= 1; c = GETC; @@ -171,36 +193,36 @@ size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) { tm.tm_gmtoff *= c == '-' ? -1 : 1; } else if (c != 'Z') { UNGETC(c); - return res; + ERR_INVALID; } EXPECT('"'); - item->type = CP_ITEM_DATETIME; + item->type = CPITEM_DATETIME; item->as.Datetime = cptmtodt(tm); item->as.Datetime.msecs += msecs; break; case '"': - item->type = CP_ITEM_STRING; + item->type = CPITEM_STRING; item->as.String.flags = CPBI_F_FIRST | CPBI_F_STREAM; item->as.String.eoff = 0; CALL(cpon_unpack_buf, item); break; case '[': - item->type = CP_ITEM_LIST; + item->type = CPITEM_LIST; break; case '{': - item->type = CP_ITEM_MAP; + item->type = CPITEM_MAP; break; case 'i': EXPECT('{'); - item->type = CP_ITEM_IMAP; + item->type = CPITEM_IMAP; break; case '<': - item->type = CP_ITEM_META; + item->type = CPITEM_META; break; case '>': case '}': case ']': - item->type = CP_ITEM_CONTAINER_END; + item->type = CPITEM_CONTAINER_END; break; default: UNGETC(c); @@ -209,12 +231,11 @@ size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) { return res; } -static size_t cpon_unpack_buf(FILE *f, struct cpitem *item, bool *ok) { - if (ok) - *ok = true; - - if (item->bufsiz == 0) +static size_t cpon_unpack_buf(FILE *f, struct cpitem *item, enum cperror *err) { + if (item->bufsiz == 0) { + item->as.Blob.len = 0; return 0; /* Nowhere to place data so just inform user about type */ + } size_t i = 0; size_t res = 0; @@ -222,21 +243,20 @@ static size_t cpon_unpack_buf(FILE *f, struct cpitem *item, bool *ok) { while (i < item->bufsiz) { int c = getc(f); if (c == EOF) { - if (ok) - *ok = false; + *err = CPERR_EOF; return i; } res++; - if (item->type == CP_ITEM_BLOB && item->as.Blob.flags & CPBI_F_HEX) { + if (item->type == CPITEM_BLOB && item->as.Blob.flags & CPBI_F_HEX) { if (c == '"') { item->as.Blob.flags |= CPBI_F_LAST; break; } UNGETC(c); uint8_t byte; - if (SCANF("%2" SCNx8, &byte) != 1) { - if (ok) - *ok = false; + int sres = SCANF("%2" SCNx8, &byte); + if (sres != 1) { + *err = sres == -1 ? CPERR_EOF : CPERR_INVALID; return i; } if (item->buf) @@ -265,7 +285,7 @@ static size_t cpon_unpack_buf(FILE *f, struct cpitem *item, bool *ok) { c = '\r'; break; } - if (item->type == CP_ITEM_BLOB) { + if (item->type == CPITEM_BLOB) { UNGETC(c); SCANF("%X", (unsigned *)&c); } diff --git a/libshvchainpack/libshvchainpack.version b/libshvchainpack/libshvchainpack.version index 4399479..4ca73ba 100644 --- a/libshvchainpack/libshvchainpack.version +++ b/libshvchainpack/libshvchainpack.version @@ -1,7 +1,7 @@ V0.0 { global: # shv/cp.h - cp_item_type_str; + cpitem_type_str; cpdectod; cpdtodec; cpdttotm; @@ -21,10 +21,17 @@ V0.0 { # shv/cp_unpack.h cp_unpack_chainpack_init; cp_unpack_cpon_init; + cp_unpack_drop; cp_unpack_skip; cp_unpack_finish; cp_unpack_strdup; + cp_unpack_strndup; + cp_unpack_strdupo; + cp_unpack_strndupo; cp_unpack_memdup; + cp_unpack_memndup; + cp_unpack_memdupo; + cp_unpack_memndupo; cp_unpack_fopen; diff --git a/libshvchainpack/meson.build b/libshvchainpack/meson.build index 4fec64c..4d22aaa 100644 --- a/libshvchainpack/meson.build +++ b/libshvchainpack/meson.build @@ -1,10 +1,12 @@ libshvchainpack_sources = files( 'chainpack_pack.c', 'chainpack_unpack.c', + 'common.c', 'cp_pack.c', 'cp_unpack.c', 'cpdatetime.c', 'cpdecimal.c', + 'cpitem.c', 'cpon_pack.c', 'cpon_unpack.c', ) diff --git a/libshvrpc/libshvrpc.version b/libshvrpc/libshvrpc.version index 45832a2..19ec927 100644 --- a/libshvrpc/libshvrpc.version +++ b/libshvrpc/libshvrpc.version @@ -6,23 +6,48 @@ V0.0 { rpcmsg_access_str; rpcmsg_access_extract; rpcmsg_access_unpack; - rpcmsg_meta_unpack; + rpcmsg_head_unpack; rpcmsg_pack_response; + rpcmsg_pack_error; + rpcmsg_pack_ferror; + rpcmsg_pack_vferror; # shv/rpcurl.h rpcurl_parse; rpcurl_free; rpcurl_str; + # shv/rpcclient.h rpcclient_connect; - rpcclient_new_stream; - rpcclient_new_datagram; - rpcclient_new_serial; - rpcclient_disconnect; + rpcclient_login; + rpcclient_stream_new; + rpcclient_stream_tcp_connect; + rpcclient_stream_unix_connect; + rpcclient_datagram_new; + rpcclient_datagram_udp_connect; + rpcclient_serial_new; + rpcclient_serial_connect; + rpcclient_logger_new; + rpcclient_logger_destroy; - rpcclient_next_message; - rpcclient_next_item; - rpcclient_pack_context; + # shv/rpchandler.h + rpchandler_destroy; + rpchandler_new; + rpchandler_next; + rpchandler_spawn_thread; + rpchandler_next_request_id; + rpchandler_msg_new; + rpchandler_msg_send; + rpchandler_msg_drop; + rpcreceive_validmsg; + rpcreceive_response_new; + rpcreceive_response_send; + rpcreceive_response_drop; + + # shv/rpctoplevel.h + rpctoplevel_new; + rpctoplevel_destroy; + rpctoplevel_func; local: *; }; diff --git a/libshvrpc/meson.build b/libshvrpc/meson.build index a10d004..f9da07e 100644 --- a/libshvrpc/meson.build +++ b/libshvrpc/meson.build @@ -1,19 +1,24 @@ libshvrpc_sources = [ files( - 'rpcclient.c', + 'rpcclient_connect.c', 'rpcclient_datagram.c', + 'rpcclient_log.c', + 'rpcclient_login.c', 'rpcclient_serial.c', 'rpcclient_stream.c', + 'rpchandler.c', 'rpcmsg_access.c', - 'rpcmsg_meta.c', + 'rpcmsg_head.c', 'rpcmsg_pack.c', + 'rpcrespond.c', + 'rpctoplevel.c', 'rpcurl.c', ), gperf.process('rpcmsg_access.gperf'), gperf.process('rpcurl_query.gperf'), gperf.process('rpcurl_scheme.gperf'), ] -libshvrpc_dependencies = [libshvchainpack_dep, uriparser] +libshvrpc_dependencies = [libshvchainpack_dep, openssl, uriparser] libshvrpc = library( 'shvrpc', diff --git a/libshvrpc/rpcclient.c b/libshvrpc/rpcclient.c deleted file mode 100644 index f15eebf..0000000 --- a/libshvrpc/rpcclient.c +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - - -void rpcclient_log_lock(struct rpcclient_logger *logger, bool in) { - if (logger == NULL) - return; - // TODO signal interrupt - sem_wait(&logger->semaphore); - fputs(in ? "<= " : "=> ", logger->f); -} - -void rpcclient_log_item(struct rpcclient_logger *logger, const struct cpitem *item) { - if (logger == NULL) - return; - cpon_pack(logger->f, &logger->cpon_state, item); -} - -void rpcclient_log_unlock(struct rpcclient_logger *logger) { - if (logger == NULL) - return; - fputc('\n', logger->f); - sem_post(&logger->semaphore); -} - -struct rpcclient *rpcclient_connect(const struct rpcurl *url) { - struct rpcclient *res; - switch (url->protocol) { - case RPC_PROTOCOL_TCP: - res = rpcclient_stream_tcp_connect(url->location, url->port); - break; - case RPC_PROTOCOL_LOCAL_SOCKET: - res = rpcclient_stream_unix_connect(url->location); - break; - case RPC_PROTOCOL_UDP: - res = rpcclient_datagram_udp_connect(url->location, url->port); - break; - case RPC_PROTOCOL_SERIAL_PORT: - res = rpcclient_serial_connect(url->location); - break; - default: - abort(); - }; - if (rpcclient_login(res, url->username, url->password, url->login_type)) - return res; - rpcclient_disconnect(res); - return NULL; -} - -bool rpcclient_login(rpcclient_t client, const char *username, - const char *password, enum rpc_login_type type) { - // TODO - return false; -} diff --git a/libshvrpc/rpcclient_connect.c b/libshvrpc/rpcclient_connect.c new file mode 100644 index 0000000..75a96e0 --- /dev/null +++ b/libshvrpc/rpcclient_connect.c @@ -0,0 +1,18 @@ +#include +#include + + +struct rpcclient *rpcclient_connect(const struct rpcurl *url) { + switch (url->protocol) { + case RPC_PROTOCOL_TCP: + return rpcclient_stream_tcp_connect(url->location, url->port); + case RPC_PROTOCOL_LOCAL_SOCKET: + return rpcclient_stream_unix_connect(url->location); + case RPC_PROTOCOL_UDP: + return rpcclient_datagram_udp_connect(url->location, url->port); + case RPC_PROTOCOL_SERIAL_PORT: + return rpcclient_serial_connect(url->location); + default: + abort(); + }; +} diff --git a/libshvrpc/rpcclient_log.c b/libshvrpc/rpcclient_log.c new file mode 100644 index 0000000..fd44d86 --- /dev/null +++ b/libshvrpc/rpcclient_log.c @@ -0,0 +1,95 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +struct rpcclient_logger { + FILE *f; + bool ellipsis; + struct cpon_state cpon_state; + unsigned maxdepth; + sem_t semaphore; +}; + + +void rpcclient_log_lock(struct rpcclient_logger *logger, bool in) { + if (logger == NULL) + return; + // TODO signal interrupt + sem_wait(&logger->semaphore); + fputs(in ? "<= " : "=> ", logger->f); +} + +void rpcclient_log_item(struct rpcclient_logger *logger, const struct cpitem *item) { + if (logger == NULL) + return; + int semval; + assert(sem_getvalue(&logger->semaphore, &semval) == 0); + if (semval > 0) + return; /* Semaphore is not taken so do not log */ + + if (item->type == CPITEM_RAW) { + /* We do not want to print raw data and thus just replace it with dots */ + if (!logger->ellipsis) + fputs("...", logger->f); + logger->ellipsis = true; + } else if ((item->type == CPITEM_BLOB || item->type == CPITEM_STRING) && + item->buf == NULL) { + struct cpitem nitem = *item; + nitem.as.Blob.flags &= ~CPBI_F_HEX; + nitem.rchr = "..."; + nitem.as.Blob.len = (nitem.as.Blob.flags & CPBI_F_FIRST) ? 3 : 0; + cpon_pack(logger->f, &logger->cpon_state, &nitem); + logger->ellipsis = false; + } else { + cpon_pack(logger->f, &logger->cpon_state, item); + logger->ellipsis = false; + } +} + +void rpcclient_log_unlock(struct rpcclient_logger *logger) { + if (logger == NULL) + return; + if (logger->cpon_state.depth > 0) { + /* if we skip rest of the message */ + logger->cpon_state.depth = 0; + if (!logger->ellipsis) + fputs("...", logger->f); + logger->ellipsis = false; + } + fputc('\n', logger->f); + sem_post(&logger->semaphore); +} + + +static void cpon_state_realloc(struct cpon_state *state) { + const struct rpcclient_logger *logger = + (void *)state - offsetof(struct rpcclient_logger, cpon_state); + size_t newcnt = state->cnt ? state->cnt * 2 : 1; + if (newcnt > logger->maxdepth) + newcnt = logger->maxdepth; + if (newcnt > state->cnt) { + state->cnt = newcnt; + state->ctx = realloc(state->ctx, state->cnt * sizeof *state->ctx); + } +} + +rpcclient_logger_t rpcclient_logger_new(FILE *f, unsigned maxdepth) { + struct rpcclient_logger *res = malloc(sizeof *res); + res->f = f; + res->ellipsis = false; + res->cpon_state = (struct cpon_state){.realloc = cpon_state_realloc}; + res->maxdepth = maxdepth; + sem_init(&res->semaphore, false, 1); + return res; +} + +void rpcclient_logger_destroy(rpcclient_logger_t logger) { + free(logger->cpon_state.ctx); + sem_destroy(&logger->semaphore); + free(logger); +} diff --git a/libshvrpc/rpcclient_login.c b/libshvrpc/rpcclient_login.c new file mode 100644 index 0000000..6ccb0be --- /dev/null +++ b/libshvrpc/rpcclient_login.c @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free +#include +#include + + +#define SHA1_SIZ 40 + +static void sha1_password(const char *nonce, const char *password, char *res) { + const EVP_MD *md = EVP_sha1(); + EVP_MD_CTX *ctx = EVP_MD_CTX_new(); + assert(EVP_DigestInit(ctx, md)); + assert(EVP_DigestUpdate(ctx, nonce, strlen(nonce))); + assert(EVP_DigestUpdate(ctx, password, strlen(password))); + unsigned siz; + unsigned char digest[20]; + assert(EVP_DigestFinal_ex(ctx, digest, &siz)); + assert(siz == 20); + static const char *const hex = "0123456789abcdef"; + for (unsigned i = 0; i < 20; i++) { + *res++ = hex[(digest[i] >> 4) & 0xf]; + *res++ = hex[digest[i] & 0xf]; + } + EVP_MD_CTX_free(ctx); +} + + +enum rpcclient_login_res rpcclient_login( + rpcclient_t client, const struct rpclogin_options *opts) { + enum rpcclient_login_res res = RPCCLIENT_LOGIN_ERROR; + struct obstack obs; + obstack_init(&obs); + void *obs_base = obstack_base(&obs); + struct cpitem item; + cpitem_unpack_init(&item); + struct rpcmsg_meta meta; + char *nonce = NULL; + + /* Hello */ + rpcmsg_pack_request(rpcclient_pack(client), NULL, "hello", 1); + cp_pack_null(rpcclient_pack(client)); + cp_pack_container_end(rpcclient_pack(client)); + if (!rpcclient_sendmsg(client)) + goto err; + + /* Response to hello */ + if (!(rpcclient_nextmsg(client) && + rpcmsg_head_unpack(rpcclient_unpack(client), &item, &meta, NULL, &obs) && + meta.request_id == 1)) + goto err; + obstack_free(&obs, obs_base); + // TODO unpack message beginning + if (!cp_unpack_expect_type(rpcclient_unpack(client), &item, CPITEM_MAP)) + goto err; + for_cp_unpack_map(rpcclient_unpack(client), &item) { + char str[7]; + if (cp_unpack_strncpy(rpcclient_unpack(client), &item, str, 7) < 7 && + !strcmp(str, "nonce")) { + nonce = cp_unpack_strdup(rpcclient_unpack(client), &item); + continue; + } + cp_unpack_skip(rpcclient_unpack(client), &item); + } + if (!rpcclient_validmsg(client)) + goto err; + + /* Login */ + rpcmsg_pack_request(rpcclient_pack(client), NULL, "login", 2); + cp_pack_map_begin(rpcclient_pack(client)); + + cp_pack_str(rpcclient_pack(client), "login"); + cp_pack_map_begin(rpcclient_pack(client)); + cp_pack_str(rpcclient_pack(client), "password"); + if (opts->login_type == RPC_LOGIN_SHA1) { + char sha1[SHA1_SIZ]; + sha1_password(nonce, opts->password, sha1); + cp_pack_string(rpcclient_pack(client), sha1, SHA1_SIZ); + } else + cp_pack_str(rpcclient_pack(client), opts->password); + cp_pack_str(rpcclient_pack(client), "user"); + cp_pack_str(rpcclient_pack(client), opts->username); + cp_pack_str(rpcclient_pack(client), "type"); + switch (opts->login_type) { + case RPC_LOGIN_PLAIN: + cp_pack_str(rpcclient_pack(client), "PLAIN"); + break; + case RPC_LOGIN_SHA1: + cp_pack_str(rpcclient_pack(client), "SHA1"); + break; + } + cp_pack_container_end(rpcclient_pack(client)); + + if (opts->device_id || opts->device_mountpoint) { + cp_pack_str(rpcclient_pack(client), "options"); + cp_pack_map_begin(rpcclient_pack(client)); + cp_pack_str(rpcclient_pack(client), "device"); + cp_pack_map_begin(rpcclient_pack(client)); + if (opts->device_id) { + cp_pack_str(rpcclient_pack(client), "deviceId"); + cp_pack_str(rpcclient_pack(client), opts->device_id); + } + if (opts->device_mountpoint) { + cp_pack_str(rpcclient_pack(client), "mountPoint"); + cp_pack_str(rpcclient_pack(client), opts->device_mountpoint); + } + cp_pack_container_end(rpcclient_pack(client)); + cp_pack_container_end(rpcclient_pack(client)); + } + + cp_pack_container_end(rpcclient_pack(client)); + cp_pack_container_end(rpcclient_pack(client)); + if (!rpcclient_sendmsg(client)) + goto err; + + /* Response to login */ + if (!(rpcclient_nextmsg(client) && + rpcmsg_head_unpack(rpcclient_unpack(client), &item, &meta, NULL, &obs) && + meta.request_id == 2 && rpcclient_validmsg(client))) + goto err; + switch (meta.type) { + case RPCMSG_T_RESPONSE: + res = RPCCLIENT_LOGIN_OK; + break; + case RPCMSG_T_ERROR: + res = RPCCLIENT_LOGIN_INVALID; + break; + default: + break; + } +err: + free(nonce); + obstack_free(&obs, NULL); + return res; +} diff --git a/libshvrpc/rpcclient_stream.c b/libshvrpc/rpcclient_stream.c index f802ac3..2245f94 100644 --- a/libshvrpc/rpcclient_stream.c +++ b/libshvrpc/rpcclient_stream.c @@ -1,5 +1,5 @@ -#include "shv/cp.h" #include +#include #include #include #include @@ -11,14 +11,14 @@ #include #include #include +#include struct rpcclient_stream { struct rpcclient c; /* Read */ - int rfd; FILE *rf; - ssize_t rmsgoff; + size_t rmsgoff; /* Write */ int wfd; FILE *fbuf; @@ -27,66 +27,127 @@ struct rpcclient_stream { }; -ssize_t cookie_read(void *cookie, char *buf, size_t size) { - struct rpcclient_stream *c = (struct rpcclient_stream *)cookie; - if (c->rmsgoff < 0) - /* Read only byte by byte when receiving message length to not feed too - * much bytes to the FILE. - */ - size = 1; - else if (c->rmsgoff < size) - /* Read up to the end of the message and then signal EOF by reading 0. */ - size = c->rmsgoff; +static ssize_t xread(int fd, char *buf, size_t siz) { ssize_t i; do - i = read(c->rfd, buf, size); + i = read(fd, buf, siz); while (i == -1 && errno == EINTR); - c->rmsgoff -= i; + return i; +} + +static int readc(int fd) { + uint8_t v; + if (xread(fd, (void *)&v, 1) < 0) + return -1; + return v; +} + +static ssize_t cookie_read(void *cookie, char *buf, size_t size) { + struct rpcclient_stream *c = (struct rpcclient_stream *)cookie; + if (c->rmsgoff < size) + /* Read up to the end of the message and then signal EOF by reading 0. */ + size = c->rmsgoff; + ssize_t i = xread(c->c.rfd, buf, size); + if (i > 0) + c->rmsgoff -= i; return i; } static size_t cp_unpack_stream(void *ptr, struct cpitem *item) { - struct rpcclient_stream *c = ptr - offsetof(struct rpcclient, unpack); - // TODO log - return chainpack_unpack(c->rf, item); + struct rpcclient_stream *c = ptr - offsetof(struct rpcclient_stream, c.unpack); + size_t res = chainpack_unpack(c->rf, item); + rpcclient_log_item(c->c.logger, item); + return res; +} + +static ssize_t read_size(int fd) { + int c; + if ((c = readc(fd)) == -1) + return -1; + unsigned bytes = chainpack_int_bytes(c); + size_t res = chainpack_uint_value1(c, bytes); + for (unsigned i = 1; i < bytes; i++) { + if ((c = readc(fd)) == -1) + return -1; + res = (res << 8) | c; + } + return res; } -static bool nextmsg_stream(struct rpcclient *client) { +static bool msgfetch_stream(struct rpcclient *client, bool newmsg) { struct rpcclient_stream *c = (struct rpcclient_stream *)client; - c->rmsgoff = -1; - unsigned long long rmsgoff; - bool ok; - _chainpack_unpack_uint(c->rf, &rmsgoff, &ok); - /* Note: we use stack allocated variable here because cookie_read might be - * called multiple times and we always want to have -1 in rmsgoff; - */ - c->rmsgoff = rmsgoff; - if (ok && fgetc(c->rf) != CP_ChainPack) - ok = false; - // TODO log lock + bool ok = true; + if (newmsg) { + ssize_t msgsiz = read_size(c->c.rfd); + if (msgsiz == -1) + return false; + c->rmsgoff = msgsiz; + clearerr(c->rf); + if (ok) { + if (fgetc(c->rf) != CP_ChainPack) + ok = false; + else + rpcclient_log_lock(c->c.logger, true); + } + rpcclient_last_receive_update(client); + } else { + rpcclient_last_receive_update(client); + rpcclient_log_unlock(c->c.logger); + /* Flush the rest of the message */ + char buf[BUFSIZ]; + while (fread(buf, 1, BUFSIZ, c->rf) > 0) + ; + } return ok; } -static ssize_t cp_pack_stream(void *ptr, const struct cpitem *item) { - struct rpcclient_stream *c = ptr - offsetof(struct rpcclient, unpack); - // TODO log +static size_t cp_pack_stream(void *ptr, const struct cpitem *item) { + struct rpcclient_stream *c = ptr - offsetof(struct rpcclient_stream, c.pack); + if (ftell(c->fbuf) == 0) + rpcclient_log_lock(c->c.logger, false); + rpcclient_log_item(c->c.logger, item); return chainpack_pack(c->fbuf, item); } -static bool sendmsg_stream(struct rpcclient *client) { +static bool write_size(int fd, size_t len) { + unsigned bytes = chainpack_w_uint_bytes(len); + for (unsigned i = 0; i < bytes; i++) { + uint8_t c = i == 0 ? chainpack_w_uint_value1(len, bytes) + : 0xff & (len >> (8 * (bytes - i - 1))); + int res; + do + res = write(fd, &c, 1); + while (res == -1 && errno == EINTR); + if (res == -1) + return false; + } + return true; +} + +static bool msgflush_stream(struct rpcclient *client, bool send) { struct rpcclient_stream *c = (struct rpcclient_stream *)client; + rpcclient_log_unlock(c->c.logger); fflush(c->fbuf); - size_t len = ftell(c->fbuf); - _chainpack_pack_uint(c->rf, len); - ssize_t i = 0; - while (i < len) { - ssize_t r = write(c->wfd, c->buf, len); - if (r == -1) { - if (errno == EINTR) - continue; + if (send) { + size_t len = ftell(c->fbuf); + if (!write_size(c->wfd, len + 1)) return false; + int res; + uint8_t cpf = CP_ChainPack; + do + res = write(c->wfd, &cpf, 1); + while (res == -1 && errno == EINTR); + ssize_t i = 0; + while (i < len) { + ssize_t r = write(c->wfd, c->buf, len); + if (r == -1) { + if (errno == EINTR) + continue; + return false; + } + i += r; } - i += r; + rpcclient_last_send_update(client); } fseek(c->fbuf, 0, SEEK_SET); return true; @@ -96,40 +157,66 @@ static void disconnect(rpcclient_t client) { struct rpcclient_stream *c = (struct rpcclient_stream *)client; fclose(c->rf); fclose(c->fbuf); - close(c->rfd); - if (c->rfd != c->wfd) + free(c->buf); + close(c->c.rfd); + if (c->c.rfd != c->wfd) close(c->wfd); free(c); } rpcclient_t rpcclient_stream_new(int readfd, int writefd) { - // TODO check for allocation and open failures struct rpcclient_stream *res = malloc(sizeof *res); + assert(res); FILE *f = fopencookie(res, "r", (cookie_io_functions_t){.read = cookie_read}); + assert(f); *res = (struct rpcclient_stream){ .c = (struct rpcclient){ + .rfd = readfd, .unpack = cp_unpack_stream, - .nextmsg = nextmsg_stream, + .msgfetch = msgfetch_stream, .pack = cp_pack_stream, - .sendmsg = sendmsg_stream, + .msgflush = msgflush_stream, .disconnect = disconnect, .logger = NULL, }, .rf = f, - .rfd = readfd, .rmsgoff = 0, .wfd = writefd, .bufsiz = 0, }; res->fbuf = open_memstream(&res->buf, &res->bufsiz); + assert(res->fbuf); return &res->c; } rpcclient_t rpcclient_stream_tcp_connect(const char *location, int port) { - int fd = socket(AF_INET6, SOCK_STREAM, 0); - // TODO connect - return rpcclient_stream_new(fd, fd); + struct addrinfo hints; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + struct addrinfo *addrs; + char *p; + assert(asprintf(&p, "%d", port) != -1); + int res = getaddrinfo(location, p, &hints, &addrs); + free(p); + if (res != 0) + return NULL; + + struct addrinfo *addr; + int sock; + for (addr = addrs; addr != NULL; addr = addr->ai_next) { + sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (sock == -1) + continue; + if (connect(sock, addr->ai_addr, addr->ai_addrlen) != -1) + break; + close(sock); + } + + freeaddrinfo(addrs); + return addr ? rpcclient_stream_new(sock, sock) : NULL; } rpcclient_t rpcclient_stream_unix_connect(const char *location) { diff --git a/libshvrpc/rpchandler.c b/libshvrpc/rpchandler.c new file mode 100644 index 0000000..e3c59c1 --- /dev/null +++ b/libshvrpc/rpchandler.c @@ -0,0 +1,176 @@ +#include +#include +#include +#include +#include +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +struct rpchandler { + const rpchandler_func **funcs; + const struct rpcmsg_meta_limits *meta_limits; + + struct rpcreceive { + rpcclient_t client; + /* Prevent from sending multiple messages. + * Sending in the receive thread can very easily result in deadlock if + * two SHVC handlers are put against each other. On the other hand it is + * very convenient to immediately respond to the request and thus we do + * a compromise, we allow sending one message after request and nothing + * more. + */ + bool can_respond; + /* We use two mutexes to implement priority locking. The thread + * receiving messages needs to have priority over others and thus it has + * a dedicated lock. Regular locks need to check if they can lock the + * prioprity lock and just release the regular lock if they can't. The + * priority thread takes priority lock to force others to yield the + * primary lock. + */ + pthread_mutex_t mutex, pmutex; + } recv; + + int nextrid; + struct obstack obs; +}; + +struct rpchandler_response { + rpcclient_t client; + pthread_mutex_t *mutex, *pmutex; +}; + +rpchandler_t rpchandler_new(rpcclient_t client, const rpchandler_func **funcs, + const struct rpcmsg_meta_limits *limits) { + struct rpchandler *res = malloc(sizeof *res); + res->funcs = funcs; + res->meta_limits = limits; + + res->recv.client = client; + pthread_mutex_init(&res->recv.mutex, NULL); + pthread_mutex_init(&res->recv.pmutex, NULL); + + /* There can be login in front of this which uses 1-3 as request ID. + * Technically we could use here even 1, because those requests are already + * handled and thus no collision can happen, but let's not. + */ + res->nextrid = 4; + obstack_init(&res->obs); + return res; +} + +void rpchandler_destroy(rpchandler_t rpchandler) { + if (rpchandler == NULL) + return; + obstack_free(&rpchandler->obs, NULL); + pthread_mutex_destroy(&rpchandler->recv.mutex); + pthread_mutex_destroy(&rpchandler->recv.pmutex); + free(rpchandler); +} + +bool rpchandler_next(struct rpchandler *rpchandler) { + if (!(rpcclient_nextmsg(rpchandler->recv.client))) + return false; + + bool res = false; + void *obs_base = obstack_base(&rpchandler->obs); + struct cpitem item; + cpitem_unpack_init(&item); + struct rpcmsg_meta meta; + if (!rpcmsg_head_unpack(rpcclient_unpack(rpchandler->recv.client), &item, + &meta, NULL, &rpchandler->obs)) + goto done; + + rpchandler->recv.can_respond = meta.type == RPCMSG_T_REQUEST; + enum rpchandler_func_res fres = RPCHFR_UNHANDLED; + for (const rpchandler_func **h = rpchandler->funcs; + *h != NULL && fres != RPCHFR_UNHANDLED; h++) { + fres = (***h)((void *)*h, &rpchandler->recv, + rpcclient_unpack(rpchandler->recv.client), &item, &meta); + } + + if (fres == RPCHFR_UNHANDLED) { + /* Default handler for requests, other types are dropped. */ + if (!rpcclient_validmsg(rpchandler->recv.client)) + goto done; + if (meta.type == RPCMSG_T_REQUEST) { + cp_pack_t pack = rpcreceive_response_new(&rpchandler->recv); + rpcmsg_pack_ferror(pack, &meta, RPCMSG_E_METHOD_NOT_FOUND, + "No such method '%s' on path '%s'", meta.method, meta.path); + rpcreceive_response_send(&rpchandler->recv); + } + } + // TODO handle other fres + + res = true; +done: + obstack_free(&rpchandler->obs, obs_base); + return res; +} + +static void *thread_loop(void *ctx) { + rpchandler_t rpchandler = ctx; + while (rpchandler_next(rpchandler)) {} + return NULL; +} + +int rpchandler_spawn_thread(rpchandler_t rpchandler, pthread_t *restrict thread, + const pthread_attr_t *restrict attr) { + return pthread_create(thread, attr, thread_loop, rpchandler); +} + +int rpchandler_next_request_id(rpchandler_t rpchandler) { + return rpchandler->nextrid++; +} + + +cp_pack_t rpchandler_msg_new(rpchandler_t rpchandler) { + while (true) { + pthread_mutex_lock(&rpchandler->recv.mutex); + if (pthread_mutex_trylock(&rpchandler->recv.pmutex) == 0) { + pthread_mutex_unlock(&rpchandler->recv.pmutex); + break; + } else + pthread_mutex_unlock(&rpchandler->recv.mutex); + } + return rpcclient_pack(rpchandler->recv.client); +} + +bool rpchandler_msg_send(rpchandler_t rpchandler) { + bool res = rpcclient_sendmsg(rpchandler->recv.client); + pthread_mutex_unlock(&rpchandler->recv.mutex); + return res; +} + +bool rpchandler_msg_drop(rpchandler_t rpchandler) { + bool res = rpcclient_dropmsg(rpchandler->recv.client); + pthread_mutex_unlock(&rpchandler->recv.mutex); + return res; +} + + +bool rpcreceive_validmsg(rpcreceive_t receive) { + return rpcclient_validmsg(receive->client); +} + +cp_pack_t rpcreceive_response_new(rpcreceive_t receive) { + assert(receive->can_respond); + pthread_mutex_lock(&receive->pmutex); + pthread_mutex_lock(&receive->mutex); + pthread_mutex_unlock(&receive->pmutex); + return rpcclient_pack(receive->client); +} + +bool rpcreceive_response_send(rpcreceive_t receive) { + assert(receive->can_respond); + bool res = rpcclient_sendmsg(receive->client); + receive->can_respond = false; + pthread_mutex_unlock(&receive->mutex); + return res; +} + +bool rpcreceive_response_drop(rpcreceive_t receive) { + assert(receive->can_respond); + bool res = rpcclient_dropmsg(receive->client); + pthread_mutex_unlock(&receive->mutex); + return res; +} diff --git a/libshvrpc/rpcmsg.c b/libshvrpc/rpcmsg.c index 359d04a..5025ade 100644 --- a/libshvrpc/rpcmsg.c +++ b/libshvrpc/rpcmsg.c @@ -18,7 +18,7 @@ bool rpcmsg_unpack_str(struct rpcclient_msg msg, struct rpcmsg_str **str) { size_t off = 0; do { i = rpcclient_next_item(msg); - if (i->type != CPCP_ITEM_STRING) + if (i->type != CPCPITEM_STRING) return false; if (str) { size_t chunk = i->as.String.chunk_size; @@ -61,14 +61,14 @@ enum rpcmsg_type rpcmsg_unpack(struct rpcclient_msg msg, struct rpcmsg_str **pat /* Unpack meta */ const cpcp_item *i; i = rpcclient_next_item(msg); - if (i->type != CPCP_ITEM_META) + if (i->type != CPCPITEM_META) return RPCMSG_T_INVALID; bool has_rid = false, has_method = false; do { i = rpcclient_next_item(msg); - if (i->type == CPCP_ITEM_CONTAINER_END) + if (i->type == CPCPITEM_CONTAINER_END) break; - if (i->type != CPCP_ITEM_INT) + if (i->type != CPCPITEM_INT) return RPCMSG_T_INVALID; switch (i->as.Int) { /* Note: we intentionally do not check MSG_TAG_META_TYPE_ID right @@ -77,7 +77,7 @@ enum rpcmsg_type rpcmsg_unpack(struct rpcclient_msg msg, struct rpcmsg_str **pat case MSG_TAG_REQUEST_ID: has_rid = true; i = rpcclient_next_item(msg); - if (i->type != CPCP_ITEM_INT) + if (i->type != CPCPITEM_INT) return RPCMSG_T_INVALID; if (rinfo) { ensure_rinfo(rinfo, false); @@ -86,18 +86,18 @@ enum rpcmsg_type rpcmsg_unpack(struct rpcclient_msg msg, struct rpcmsg_str **pat break; case MSG_TAG_SHV_PATH: i = rpcclient_next_item(msg); - if (i->type != CPCP_ITEM_STRING || !rpcmsg_unpack_str(msg, path)) + if (i->type != CPCPITEM_STRING || !rpcmsg_unpack_str(msg, path)) return RPCMSG_T_INVALID; break; case MSG_TAG_METHOD: has_method = true; i = rpcclient_next_item(msg); - if (i->type != CPCP_ITEM_STRING || !rpcmsg_unpack_str(msg, method)) + if (i->type != CPCPITEM_STRING || !rpcmsg_unpack_str(msg, method)) return RPCMSG_T_INVALID; break; case MSG_TAG_CALLER_IDS: i = rpcclient_next_item(msg); - if (i->type != CPCP_ITEM_INT) + if (i->type != CPCPITEM_INT) return RPCMSG_T_INVALID; if (rinfo) { ensure_rinfo(rinfo, true); @@ -109,7 +109,7 @@ enum rpcmsg_type rpcmsg_unpack(struct rpcclient_msg msg, struct rpcmsg_str **pat break; case MSG_TAG_REV_CALLER_IDS: i = rpcclient_next_item(msg); - if (i->type != CPCP_ITEM_INT) + if (i->type != CPCPITEM_INT) return RPCMSG_T_INVALID; if (rinfo) { ensure_rinfo(rinfo, true); @@ -135,11 +135,11 @@ enum rpcmsg_type rpcmsg_unpack(struct rpcclient_msg msg, struct rpcmsg_str **pat /* Start unpacking imap */ i = rpcclient_next_item(msg); - if (i->type != CPCP_ITEM_IMAP) + if (i->type != CPCPITEM_IMAP) return RPCMSG_T_INVALID; i = rpcclient_next_item(msg); - if (i->type != CPCP_ITEM_INT) - return i->type == CPCP_ITEM_CONTAINER_END ? res : RPCMSG_T_INVALID; + if (i->type != CPCPITEM_INT) + return i->type == CPCPITEM_CONTAINER_END ? res : RPCMSG_T_INVALID; switch (i->as.Int) { case MSG_KEY_PARAMS: return res != RPCMSG_T_RESPONSE ? res : RPCMSG_T_INVALID; diff --git a/libshvrpc/rpcmsg_access.c b/libshvrpc/rpcmsg_access.c index f918bae..284860a 100644 --- a/libshvrpc/rpcmsg_access.c +++ b/libshvrpc/rpcmsg_access.c @@ -48,7 +48,7 @@ enum rpcmsg_access rpcmsg_access_extract(const char *str) { enum rpcmsg_access rpcmsg_access_unpack(cp_unpack_t unpack, struct cpitem *item) { FILE *f = cp_unpack_fopen(unpack, item); - if (item->type != CP_ITEM_STRING) + if (item->type != CPITEM_STRING) goto error; /* The longest access right is four characters. We load five to prevent * match on same prefix and need one more for null byte. diff --git a/libshvrpc/rpcmsg_head.c b/libshvrpc/rpcmsg_head.c new file mode 100644 index 0000000..5b2e9d2 --- /dev/null +++ b/libshvrpc/rpcmsg_head.c @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include + + +static bool unpack_string(cp_unpack_t unpack, struct cpitem *item, char **dest, + ssize_t limit, struct obstack *obstack) { + if (obstack) { + *dest = cp_unpack_strndupo( + unpack, item, limit >= 0 ? limit : SIZE_MAX, obstack); + return *dest != NULL; + } + cp_unpack_strncpy(unpack, item, *dest, limit); + return true; +} + +struct obscookie { + struct obstack *obstack; + ssize_t limit; +}; + +static ssize_t obstack_write(void *cookie, const char *buf, size_t size) { + struct obscookie *c = cookie; + // TODO limit + obstack_grow(c->obstack, buf, size); + return size; +} + +static bool repack(cp_unpack_t unpack, struct cpitem *item, + struct rpcmsg_ptr *ptr, ssize_t limit, struct obstack *obstack) { + struct obscookie cookie; + FILE *f; + if (obstack) { + cookie = (struct obscookie){.obstack = obstack, .limit = limit}; + f = fopencookie( + &cookie, "w", (cookie_io_functions_t){.write = obstack_write}); + setvbuf(f, NULL, _IONBF, 0); + } else + f = fmemopen(ptr->ptr, limit, "w"); + + uint8_t buf[BUFSIZ]; + item->buf = buf; + item->bufsiz = BUFSIZ; + bool res = true; + unsigned depth = 0; + do { + cp_unpack(unpack, item); + if (item->type == CPITEM_INVALID || chainpack_pack(f, item) == -1) { + /* Note: chainpack_pack Can fail only due to not enough space in the + * buffer. + */ + res = false; + break; + } + switch (item->type) { + case CPITEM_LIST: + case CPITEM_MAP: + case CPITEM_IMAP: + case CPITEM_META: + depth++; + break; + case CPITEM_CONTAINER_END: + depth--; + break; + default: + break; + } + } while (depth > 0); + item->bufsiz = 0; + fclose(f); + if (obstack) { + ptr->siz = obstack_object_size(obstack); + ptr->ptr = obstack_finish(obstack); + } + return res; +} + +static bool unpack_cids(cp_unpack_t unpack, struct cpitem *item, + struct rpcmsg_ptr *ptr, ssize_t limit, struct obstack *obstack) { + return repack(unpack, item, ptr, limit, obstack); +} + +static bool unpack_extra(cp_unpack_t unpack, struct cpitem *item, + struct rpcmsg_meta_extra **extra, struct obstack *obstack) { + while (*extra != NULL) + extra = &(*extra)->next; + *extra = malloc(sizeof **extra); + (*extra)->key = item->as.Int; + (*extra)->next = NULL; + return repack(unpack, item, &(*extra)->ptr, -1, obstack); +} + +bool rpcmsg_head_unpack(cp_unpack_t unpack, struct cpitem *item, + struct rpcmsg_meta *meta, struct rpcmsg_meta_limits *limits, + struct obstack *obstack) { + meta->type = RPCMSG_T_INVALID; + cp_unpack(unpack, item); + if (item->type != CPITEM_META) + return false; + + meta->request_id = INT64_MIN; + meta->access_grant = RPCMSG_ACC_INVALID; + meta->cids.siz = 0; + if (obstack) { + meta->path = NULL; + meta->method = NULL; + } else { + meta->path[0] = '\0'; + meta->method[0] = '\0'; + } + + /* Go through meta and parse what we want from it */ + bool has_rid = false; + bool valid_path = true; + bool valid_method = false; + bool valid_cids = true; + while (true) { + cp_unpack(unpack, item); + if (item->type == CPITEM_CONTAINER_END) + break; + if (item->type != CPITEM_INT) + return false; + switch (item->as.Int) { + case RPCMSG_TAG_META_TYPE_ID: + case RPCMSG_TAG_META_TYPE_NAMESPACE_ID: + break; + case RPCMSG_TAG_REQUEST_ID: + has_rid = true; + if (cp_unpack_int(unpack, item, meta->request_id) <= 0) + return false; + break; + case RPCMSG_TAG_SHV_PATH: + valid_path = unpack_string(unpack, item, &meta->path, + limits ? limits->path : -1, obstack); + break; + case RPCMSG_TAG_METHOD: + valid_method = unpack_string(unpack, item, &meta->method, + limits ? limits->method : -1, obstack); + break; + case RPCMSG_TAG_CALLER_IDS: + valid_cids = unpack_cids(unpack, item, &meta->cids, + limits ? limits->cids : -1, obstack); + break; + case RPCMSG_TAG_ACCESS_GRANT: + meta->access_grant = rpcmsg_access_unpack(unpack, item); + break; + default: + if (limits && limits->extra) + unpack_extra(unpack, item, &meta->extra, obstack); + else + cp_unpack_skip(unpack, item); + break; + } + } + if (!cp_unpack_expect_type(unpack, item, CPITEM_IMAP)) + return false; + int key = -1; + cp_unpack(unpack, item); + if (item->type != CPITEM_CONTAINER_END && !cp_extract_int(item, key)) + return false; + if (has_rid) { + if (valid_method) { + meta->type = RPCMSG_T_REQUEST; + return valid_path && valid_cids && + (key == RPCMSG_KEY_PARAMS || key == -1); + } + if (key == RPCMSG_KEY_RESULT || key == -1) + meta->type = RPCMSG_T_RESPONSE; + else if (key == RPCMSG_KEY_ERROR) + meta->type = RPCMSG_T_ERROR; + else + return false; + return true; + } else { + if (valid_method) { + meta->type = RPCMSG_T_SIGNAL; + return valid_path && (key == RPCMSG_KEY_PARAMS || key == -1); + } + return false; + } +} diff --git a/libshvrpc/rpcmsg_meta.c b/libshvrpc/rpcmsg_meta.c deleted file mode 100644 index a0a8228..0000000 --- a/libshvrpc/rpcmsg_meta.c +++ /dev/null @@ -1,150 +0,0 @@ -#include "shv/cp.h" -#include -#include -#include -#include - - -static bool unpack_string(cp_unpack_t unpack, struct cpitem *item, char **dest, - size_t limit, struct obstack *obstack) { - if (obstack) { - do { - /* We are using here fast growing in a reverse way. We first store - * data and only after that we grow object. We do this because we - * do not know number of needed bytes upfront. This way we do not - * have to use temporally buffer and copy data around. We copy it - * directly to the obstack's buffer. - */ - // TODO ensure that limit is considered - char *ptr = obstack_next_free(obstack); - int siz = obstack_room(obstack); - if (siz == 0) { - obstack_1grow(obstack, '\0'); - siz = obstack_room(obstack) + 1; - assert(siz > 0); - } - ssize_t res = cp_unpack_strncpy(unpack, item, ptr, siz); - if (res == -1) { - obstack_free(obstack, obstack_base(obstack)); - return false; - } - obstack_blank_fast(obstack, res); - if (res < siz) - break; - } while (true); - obstack_1grow(obstack, '\0'); - *dest = obstack_finish(obstack); - } else - cp_unpack_strncpy(unpack, item, *dest, limit); - return true; -} - -ssize_t obstack_write(void *cookie, const char *buf, size_t size) { - struct obstack *obstack = cookie; - obstack_grow(obstack, buf, size); - return size; -} - -static bool unpack_cids(cp_unpack_t unpack, struct cpitem *item, - struct rpcmsg_ptr *ptr, size_t limit, struct obstack *obstack) { - FILE *f; - if (obstack) { - // TODO respect limit - f = fopencookie( - obstack, "w", (cookie_io_functions_t){.write = obstack_write}); - setvbuf(f, NULL, _IONBF, 0); - } else - f = fmemopen(ptr->ptr, limit, "w"); - - bool res = true; - unsigned depth = 0; - do { - cp_unpack(unpack, item); - if (item->type == CP_ITEM_INVALID || chainpack_pack(f, item) == -1) { - /* Note: chainpack_pack Can fail only due to not enough space in the - * buffer. - * In case of cids we consider it as a meta parsing failure because - * without it we can't send response correctly anyway. - */ - res = false; - break; - } - switch (item->type) { - case CP_ITEM_LIST: - case CP_ITEM_MAP: - case CP_ITEM_IMAP: - case CP_ITEM_META: - depth++; - break; - case CP_ITEM_CONTAINER_END: - depth--; - break; - default: - break; - } - } while (depth > 0); - fclose(f); - if (obstack) { - ptr->siz = obstack_object_size(obstack); - ptr->ptr = obstack_finish(obstack); - } - return res; -} - -bool rpcmsg_meta_unpack(cp_unpack_t unpack, struct cpitem *item, - struct rpcmsg_meta *meta, struct rpcmsg_meta_limits *limits, - struct obstack *obstack) { - meta->type = RPCMSG_T_INVALID; - cp_unpack(unpack, item); - if (item->type != CP_ITEM_META) - return false; - - bool has_rid = false; - meta->request_id = INT64_MIN; - meta->access_grant = RPCMSG_ACC_INVALID; - - /* Go trough meta and parse what we want from it */ - while (true) { - cp_unpack(unpack, item); - if (item->type == CP_ITEM_CONTAINER_END) - break; - if (item->type != CP_ITEM_INT) - return false; - switch (item->as.Int) { - case RPCMSG_TAG_REQUEST_ID: - cp_unpack(unpack, item); - has_rid = true; - if (item->type == CP_ITEM_INT) - meta->request_id = item->as.Int; - else if (item->type == CP_ITEM_UINT) - meta->request_id = item->as.UInt; - else - return false; - break; - case RPCMSG_TAG_SHV_PATH: - if (!unpack_string(unpack, item, &meta->path, - limits ? limits->path : -1, obstack)) - return false; - break; - case RPCMSG_TAG_METHOD: - if (!unpack_string(unpack, item, &meta->method, - limits ? limits->method : -1, obstack)) - return false; - break; - case RPCMSG_TAG_CALLER_IDS: - if (!unpack_cids(unpack, item, &meta->cids, - limits ? limits->cids : -1, obstack)) - return false; - break; - case RPCMSG_TAG_ACCESS_GRANT: - meta->access_grant = rpcmsg_access_unpack(unpack, item); - break; - default: - cp_unpack_skip(unpack, item); - break; - } - } - meta->type = meta->method ? (has_rid ? RPCMSG_T_REQUEST : RPCMSG_T_SIGNAL) - : (has_rid ? RPCMSG_T_RESPONSE : RPCMSG_T_INVALID); - return true; -} diff --git a/libshvrpc/rpcmsg_pack.c b/libshvrpc/rpcmsg_pack.c index c14aa32..49ea3ff 100644 --- a/libshvrpc/rpcmsg_pack.c +++ b/libshvrpc/rpcmsg_pack.c @@ -1,82 +1,118 @@ -#include "shv/cp_pack.h" #include #include #include #include +#define RES(V) \ + do { \ + size_t __res = V; \ + if (__res == 0) \ + return 0; \ + res += __res; \ + } while (false) -void rpcmsg_pack_request( - cp_pack_t pack, const char *path, const char *method, int64_t rid) { - cp_pack_meta_begin(pack); - cp_pack_int(pack, RPCMSG_TAG_META_TYPE_ID); - cp_pack_int(pack, 1); - cp_pack_int(pack, RPCMSG_TAG_REQUEST_ID); - cp_pack_int(pack, rid); - cp_pack_int(pack, RPCMSG_TAG_SHV_PATH); - cp_pack_str(pack, path); - cp_pack_int(pack, RPCMSG_TAG_METHOD); - cp_pack_str(pack, method); - cp_pack_container_end(pack); - - cp_pack_imap_begin(pack); - cp_pack_int(pack, RPCMSG_KEY_PARAMS); + +static inline size_t meta_begin(cp_pack_t pack) { + size_t res = 0; + RES(cp_pack_meta_begin(pack)); + RES(cp_pack_int(pack, RPCMSG_TAG_META_TYPE_ID)); + RES(cp_pack_int(pack, 1)); + return res; } -void rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) { - cp_pack_meta_begin(pack); - cp_pack_int(pack, RPCMSG_TAG_META_TYPE_ID); - cp_pack_int(pack, 1); - cp_pack_int(pack, RPCMSG_TAG_SHV_PATH); - cp_pack_str(pack, path); - cp_pack_int(pack, RPCMSG_TAG_METHOD); - cp_pack_str(pack, method); - cp_pack_container_end(pack); - - cp_pack_imap_begin(pack); - cp_pack_int(pack, RPCMSG_KEY_PARAMS); + +size_t rpcmsg_pack_request( + cp_pack_t pack, const char *path, const char *method, int rid) { + size_t res = 0; + RES(meta_begin(pack)); + RES(cp_pack_int(pack, RPCMSG_TAG_REQUEST_ID)); + RES(cp_pack_int(pack, rid)); + if (path) { + RES(cp_pack_int(pack, RPCMSG_TAG_SHV_PATH)); + RES(cp_pack_str(pack, path)); + } + RES(cp_pack_int(pack, RPCMSG_TAG_METHOD)); + RES(cp_pack_str(pack, method)); + RES(cp_pack_container_end(pack)); + + RES(cp_pack_imap_begin(pack)); + RES(cp_pack_int(pack, RPCMSG_KEY_PARAMS)); + return res; } -static void _pack_response(cp_pack_t pack, struct rpcmsg_meta *meta) { - cp_pack_meta_begin(pack); - cp_pack_int(pack, RPCMSG_TAG_META_TYPE_ID); - cp_pack_int(pack, 1); - cp_pack_int(pack, RPCMSG_TAG_SHV_PATH); - cp_pack_str(pack, meta->path); - cp_pack_int(pack, RPCMSG_TAG_METHOD); - cp_pack_str(pack, meta->method); - // TODO copy over other parameters - cp_pack_container_end(pack); - - cp_pack_imap_begin(pack); +size_t rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) { + size_t res = 0; + RES(meta_begin(pack)); + RES(cp_pack_int(pack, RPCMSG_TAG_SHV_PATH)); + if (path) { + RES(cp_pack_str(pack, path)); + RES(cp_pack_int(pack, RPCMSG_TAG_METHOD)); + } + RES(cp_pack_str(pack, method)); + RES(cp_pack_container_end(pack)); + + RES(cp_pack_imap_begin(pack)); + RES(cp_pack_int(pack, RPCMSG_KEY_PARAMS)); + return res; +} + +static size_t _pack_response(cp_pack_t pack, const struct rpcmsg_meta *meta) { + size_t res = 0; + RES(meta_begin(pack)); + RES(cp_pack_int(pack, RPCMSG_TAG_REQUEST_ID)); + RES(cp_pack_int(pack, meta->request_id)); + if (meta->cids.siz) { + RES(cp_pack_int(pack, RPCMSG_TAG_CALLER_IDS)); + FILE *f = fmemopen(meta->cids.ptr, meta->cids.siz, "r"); + uint8_t buf[BUFSIZ]; + struct cpitem item = (struct cpitem){.buf = buf, .bufsiz = BUFSIZ}; + while (true) { + /* This has to be a valid chainpack! */ + assert(chainpack_unpack(f, &item) != -1); + if (item.type == CPITEM_INVALID) + break; + RES(cp_pack(pack, &item)); + } + fclose(f); + } + RES(cp_pack_container_end(pack)); + + RES(cp_pack_imap_begin(pack)); + return res; } -void rpcmsg_pack_response(cp_pack_t pack, struct rpcmsg_meta *meta) { - _pack_response(pack, meta); - cp_pack_int(pack, RPCMSG_KEY_RESULT); +size_t rpcmsg_pack_response(cp_pack_t pack, const struct rpcmsg_meta *meta) { + size_t res = 0; + RES(_pack_response(pack, meta)); + RES(cp_pack_int(pack, RPCMSG_KEY_RESULT)); + return res; } -void rpcmsg_pack_error(cp_pack_t pack, struct rpcmsg_meta *meta, +size_t rpcmsg_pack_error(cp_pack_t pack, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *msg) { - _pack_response(pack, meta); - cp_pack_int(pack, RPCMSG_KEY_ERROR); - cp_pack_imap_begin(pack); - cp_pack_int(pack, RPCMSG_E_KEY_CODE); - cp_pack_int(pack, error); - cp_pack_int(pack, RPCMSG_E_KEY_MESSAGE); - cp_pack_str(pack, msg); - cp_pack_container_end(pack); - cp_pack_container_end(pack); + size_t res = 0; + RES(_pack_response(pack, meta)); + RES(cp_pack_int(pack, RPCMSG_KEY_ERROR)); + RES(cp_pack_imap_begin(pack)); + RES(cp_pack_int(pack, RPCMSG_ERR_KEY_CODE)); + RES(cp_pack_int(pack, error)); + RES(cp_pack_int(pack, RPCMSG_ERR_KEY_MESSAGE)); + RES(cp_pack_str(pack, msg)); + RES(cp_pack_container_end(pack)); + RES(cp_pack_container_end(pack)); + return res; } -void rpcmsg_pack_ferror(cp_pack_t pack, struct rpcmsg_meta *meta, +size_t rpcmsg_pack_ferror(cp_pack_t pack, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, ...) { va_list args; va_start(args, fmt); - rpcmsg_pack_vferror(pack, meta, error, fmt, args); + size_t res = rpcmsg_pack_vferror(pack, meta, error, fmt, args); va_end(args); + return res; } -void rpcmsg_pack_vferror(cp_pack_t pack, struct rpcmsg_meta *meta, +size_t rpcmsg_pack_vferror(cp_pack_t pack, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, va_list args) { va_list argsdup; va_copy(argsdup, args); @@ -86,5 +122,5 @@ void rpcmsg_pack_vferror(cp_pack_t pack, struct rpcmsg_meta *meta, char msg[siz + 1]; vsnprintf(msg, siz + 1, fmt, args); - rpcmsg_pack_error(pack, meta, error, msg); + return rpcmsg_pack_error(pack, meta, error, msg); } diff --git a/libshvrpc/rpcnode.c b/libshvrpc/rpcnode.c new file mode 100644 index 0000000..d288993 --- /dev/null +++ b/libshvrpc/rpcnode.c @@ -0,0 +1,45 @@ +#include + + +bool rpcnode_handle_dir(cp_unpack_t unpack, struct cpitem *item, + struct rpcnode_dir_request *dirr, struct obstack *obstack) { + dirr->name = NULL; + dirr->list_of_maps = true; + if (item->type == CPITEM_CONTAINER_END) + return true; + + bool list = false; + cp_unpack(unpack, item); + if (item->type == CPITEM_NULL) + return true; + if (item->type == CPITEM_LIST) { + cp_unpack(unpack, item); + list = true; + } + if (item->type == CPITEM_STRING) { + dirr->name = cp_unpack_strdupo(unpack, item, obstack); + cp_unpack(unpack, item); + if (!list) + return true; + } else + return item->type == CPITEM_CONTAINER_END; + cp_unpack(unpack, item); + if (item->type == CPITEM_INT) { + dirr->list_of_maps = item->as.Int == 127; + return true; + } else if (item->type == CPITEM_CONTAINER_END) + return true; + return false; +} + +bool rpcnode_pack_dir(cp_pack_t pack, bool list_of_maps, const char *name, + enum rpcnode_dir_signature signature, int flags, enum rpcmsg_access acc, + const char *description) { + if (!list_of_maps) + return cp_pack_str(pack, name); + return cp_pack_map_begin(pack) && cp_pack_str(pack, "name") && + cp_pack_str(pack, name) && cp_pack_str(pack, "signature") && + cp_pack_int(pack, signature) && cp_pack_str(pack, "flags") && + cp_pack_int(pack, flags) && cp_pack_str(pack, "accessGrant") && + cp_pack_str(pack, rpcmsg_access_str(acc)) && cp_pack_container_end(pack); +} diff --git a/libshvrpc/rpcrespond.c b/libshvrpc/rpcrespond.c new file mode 100644 index 0000000..13c5ad5 --- /dev/null +++ b/libshvrpc/rpcrespond.c @@ -0,0 +1,102 @@ +#include +#include + +struct rpcresponder { + rpchandler_func func; + struct rpcrespond { + int request_id; + rpcreceive_t receive; + cp_unpack_t unpack; + struct cpitem *item; + const struct rpcmsg_meta *meta; + sem_t sem, sem_complete; + struct rpcrespond *next; + } * resp; + pthread_mutex_t lock; +}; + +static enum rpchandler_func_res func(void *ptr, rpcreceive_t receive, + cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta) { + struct rpcresponder *resp = ptr; + if (meta->type != RPCMSG_T_RESPONSE && meta->type != RPCMSG_T_ERROR) + return RPCHFR_UNHANDLED; + enum rpchandler_func_res res = RPCHFR_HANDLED; + pthread_mutex_lock(&resp->lock); + rpcrespond_t pr = NULL; + rpcrespond_t r = resp->resp; + while (r) { + if (r->request_id == meta->request_id) { + r->receive = receive; + r->unpack = unpack; + r->item = item; + r->meta = meta; + sem_post(&r->sem); + sem_wait(&r->sem_complete); + sem_destroy(&r->sem); + sem_destroy(&r->sem_complete); + if (pr) + pr->next = r->next; + else + resp->resp = r->next; + free(r); + break; + } + pr = r; + r = r->next; + }; + pthread_mutex_unlock(&resp->lock); + return res; +} + +rpcresponder_t rpcresponder_new(void) { + struct rpcresponder *res = malloc(sizeof *res); + res->func = func; + res->resp = NULL; + pthread_mutex_init(&res->lock, NULL); + return res; +} + +void rpcresponder_destroy(rpcresponder_t resp) { + rpcrespond_t r = resp->resp; + while (r) { + rpcrespond_t pr = r; + r = r->next; + free(pr); + }; + pthread_mutex_destroy(&resp->lock); + free(resp); +} + +rpchandler_func *rpcresponder_func(rpcresponder_t responder) { + return &responder->func; +} + + +rpcrespond_t rpcrespond_expect(rpcresponder_t responder, int request_id) { + rpcrespond_t res = malloc(sizeof *res); + res->request_id = request_id; + res->receive = NULL; + sem_init(&res->sem, false, 0); + sem_init(&res->sem_complete, false, 0); + + pthread_mutex_lock(&responder->lock); + rpcrespond_t *r = &responder->resp; + while (*r) + r = &(*r)->next; + *r = res; + pthread_mutex_unlock(&responder->lock); + + return res; +} + +bool rpcrespond_waitfor(rpcrespond_t respond, cp_unpack_t *unpack, + struct cpitem **item, int timeout) { + sem_wait(&respond->sem); + return 1; +} + +bool rpcrespond_validmsg(rpcrespond_t respond) { + bool res = rpcreceive_validmsg(respond->receive); + sem_post(&respond->sem_complete); + return res; +} diff --git a/libshvrpc/rpctoplevel.c b/libshvrpc/rpctoplevel.c new file mode 100644 index 0000000..32bc0a9 --- /dev/null +++ b/libshvrpc/rpctoplevel.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +struct rpctoplevel { + rpchandler_func func; + const char *app_name; + const char *app_version; + bool device; +}; + +static enum rpchandler_func_res func(void *ptr, rpcreceive_t receive, + cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta) { + struct rpctoplevel *h = ptr; + if (meta->type != RPCMSG_T_REQUEST || + (meta->path != NULL && *meta->path != '\0')) + return false; + cp_pack_t pack = rpcreceive_response_new(receive); + + if (!strcmp(meta->method, "appName")) { + if (!rpcreceive_validmsg(receive)) + return RPCHFR_RECV_ERR; + cp_pack_str(pack, h->app_name); + cp_pack_container_end(pack); + + } else if (!strcmp(meta->method, "appVersion")) { + if (!rpcreceive_validmsg(receive)) + return RPCHFR_RECV_ERR; + cp_pack_str(pack, h->app_version); + cp_pack_container_end(pack); + + } else { + struct obstack obstack; + obstack_init(&obstack); + + if (!strcmp(meta->method, "echo")) { + // TODO + // + + /*} else if (!strcmp(meta->method, "dir")) {*/ + /*struct rpcnode_dir_request req;*/ + /*if (!rpcnode_unpack_dir(unpack, item, &req, &obstack)) {*/ + /*obstack_free(&obstack, NULL);*/ + /*[>return;<]*/ + /*}*/ + + } else { + rpcmsg_pack_ferror(pack, meta, RPCMSG_E_METHOD_NOT_FOUND, + "No such path '%s' or method '%s' or insufficient access rights.", + meta->path, meta->method); + } + } + + if (!rpcreceive_response_send(receive)) + return RPCHFR_SEND_ERR; + return RPCHFR_HANDLED; +} + +rpctoplevel_t rpctoplevel_new( + const char *app_name, const char *app_version, bool device) { + struct rpctoplevel *res = malloc(sizeof *res); + *res = (struct rpctoplevel){ + .func = func, + .app_name = app_name, + .app_version = app_version, + .device = device, + }; + return res; +} + +void rpctoplevel_destroy(rpctoplevel_t toplevel) { + free(toplevel); +} + +rpchandler_func *rpctoplevel_func(rpctoplevel_t toplevel) { + return &toplevel->func; +} diff --git a/libshvrpc/rpcurl.c b/libshvrpc/rpcurl.c index da1b369..1509ad1 100644 --- a/libshvrpc/rpcurl.c +++ b/libshvrpc/rpcurl.c @@ -51,7 +51,7 @@ struct rpcurl *rpcurl_parse(const char *url, const char **error_pos) { if (uri.userInfo.first != uri.userInfo.afterLast) { res->username = strndup( uri.userInfo.first, uri.userInfo.afterLast - uri.userInfo.first); - res->rpcurl.username = res->username; + res->rpcurl.login.username = res->username; } if (uri.hostText.first != uri.hostText.afterLast) { @@ -109,25 +109,25 @@ struct rpcurl *rpcurl_parse(const char *url, const char **error_pos) { switch (match->query) { case GPERF_RPCURL_QUERY_SHAPASS: free(res->password); - res->rpcurl.login_type = RPC_LOGIN_SHA1; + res->rpcurl.login.login_type = RPC_LOGIN_SHA1; res->password = strdup(q->value); - res->rpcurl.password = res->password; + res->rpcurl.login.password = res->password; break; case GPERF_RPCURL_QUERY_PASSWORD: free(res->password); - res->rpcurl.login_type = RPC_LOGIN_PLAIN; + res->rpcurl.login.login_type = RPC_LOGIN_PLAIN; res->password = strdup(q->value); - res->rpcurl.password = res->password; + res->rpcurl.login.password = res->password; break; case GPERF_RPCURL_QUERY_DEVID: free(res->device_id); res->device_id = strdup(q->value); - res->rpcurl.device_id = res->device_id; + res->rpcurl.login.device_id = res->device_id; break; case GPERF_RPCURL_QUERY_DEVMOUNT: free(res->device_mountpoint); res->device_mountpoint = strdup(q->value); - res->rpcurl.device_mountpoint = res->device_mountpoint; + res->rpcurl.login.device_mountpoint = res->device_mountpoint; break; } q = q->next; diff --git a/libshvrpc/rpcurl_query.gperf b/libshvrpc/rpcurl_query.gperf index 2c7f607..7940952 100644 --- a/libshvrpc/rpcurl_query.gperf +++ b/libshvrpc/rpcurl_query.gperf @@ -1,6 +1,3 @@ -%compare-lengths -%compare-strncmp - %pic %enum %readonly-tables @@ -22,7 +19,7 @@ enum gperf_rpcurl_query { GPERF_RPCURL_QUERY_DEVMOUNT, }; struct gperf_rpcurl_query_match { - int offset; + int offset; enum gperf_rpcurl_query query; }; %} diff --git a/meson.build b/meson.build index e7e7bb6..2aa2eb5 100644 --- a/meson.build +++ b/meson.build @@ -3,7 +3,7 @@ project( 'c', version: files('version'), license: 'GPL-3.0-or-later', - default_options: 'c_std=c17', + default_options: 'c_std=gnu17', meson_version: '>=0.57.0', ) cc = meson.get_compiler('c') @@ -12,6 +12,7 @@ isnuttx = ( ) fs = import('fs') cmake = import('cmake') +python = import('python') add_project_arguments('-D_GNU_SOURCE', language: 'c') @@ -24,11 +25,13 @@ add_project_arguments( if not isnuttx m = cc.find_library('m', required : false) - argp = cc.has_function('argp_parse') ? declare_dependency() : cc.find_library('argp') + inih = dependency('inih') + openssl = dependency('openssl', version: '>=3.0.0') uriparser = dependency('liburiparser', version: '>= 0.9.0') else m = declare_dependency() - argp = declare_dependency() + inih = declare_dependency() + openssl = declare_dependency() uriparser_var = cmake.subproject_options() uriparser_var.add_cmake_defines({ 'URIPARSER_BUILD_TESTS': false, @@ -56,9 +59,17 @@ cpconv_opt = get_option('cpconv') if cpconv_opt subdir('cpconv') endif -shvbroker_opt = get_option('shvbroker') +shvc_opt = get_option('shvc') +if shvc_opt + subdir('shvc') +endif +shvbroker_opt = get_option('shvcbroker') if shvbroker_opt - subdir('shvbroker') + subdir('shvcbroker') +endif +demo_opt = get_option('demo') +if demo_opt + subdir('demo') endif diff --git a/meson_options.txt b/meson_options.txt index ca49ca7..b141ffb 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -13,8 +13,20 @@ option( description: 'Build cpconv tool that converts between Chainpack and Cpon.', ) option( - 'shvbroker', + 'shvc', + type: 'boolean', + value: true, + description: 'Build shvc tool that can call methods over SHV RPC protocol.', +) +option( + 'shvcbroker', type: 'boolean', value: true, description: 'Build SHV RPC Broker standalone application.', ) +option( + 'demo', + type: 'boolean', + value: true, + description: 'Build SHV RPC demo applications.', +) diff --git a/shvc/main.c b/shvc/main.c new file mode 100644 index 0000000..5ddbd12 --- /dev/null +++ b/shvc/main.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +#include +#include +#include "opts.h" + + +int main(int argc, char **argv) { + struct conf conf; + parse_opts(argc, argv, &conf); + + const char *errpos; + struct rpcurl *rpcurl = rpcurl_parse(conf.url, &errpos); + if (rpcurl == NULL) { + fprintf(stderr, "Invalid URL: %s\n", conf.url); + fprintf(stderr, "%*.s^\n", 13 + (int)(errpos - conf.url), ""); + return 1; + } + rpcclient_t client = rpcclient_connect(rpcurl); + if (client == NULL) { + fprintf(stderr, "Failed to connect to the: %s\n", rpcurl->location); + fprintf(stderr, "Please check your connection to the network\n"); + rpcurl_free(rpcurl); + return 1; + } + rpcclient_logger_t logger = NULL; + if (conf.verbose > 0) { + logger = rpcclient_logger_new(stderr, conf.verbose); + client->logger = logger; + } + switch (rpcclient_login(client, &rpcurl->login)) { + case RPCCLIENT_LOGIN_OK: + break; + case RPCCLIENT_LOGIN_INVALID: + fprintf(stderr, "Invalid login for connecting to the: %s\n", conf.url); + rpcclient_destroy(client); + rpcurl_free(rpcurl); + return 1; + case RPCCLIENT_LOGIN_ERROR: + fprintf(stderr, "Communication error with server\n"); + return 2; + } + rpcurl_free(rpcurl); + + // TODO error handling + rpcmsg_pack_request(rpcclient_pack(client), conf.path, conf.method, 3); + // TODO pack params + rpcclient_sendmsg(client); + + // TODO read response + + rpcclient_destroy(client); + rpcclient_logger_destroy(logger); + return 0; +} diff --git a/shvc/meson.build b/shvc/meson.build new file mode 100644 index 0000000..232f808 --- /dev/null +++ b/shvc/meson.build @@ -0,0 +1,22 @@ +shvc_sources = files('opts.c') +shvc_dependencies = [libshvrpc_dep] + + +if isnuttx + shvc = static_library( + 'shvc', + shvc_sources + ['main.c'], + dependencies: shvc_dependencies, + install: not meson.is_subproject(), + c_args: '-Dmain=shvc_main', + ) +else + shvc = executable( + 'shvc', + shvc_sources + ['main.c'], + dependencies: shvc_dependencies, + install: true, + ) +endif + +shvc_internal_includes = include_directories('.') diff --git a/shvc/opts.c b/shvc/opts.c new file mode 100644 index 0000000..3ca5992 --- /dev/null +++ b/shvc/opts.c @@ -0,0 +1,72 @@ +#include "opts.h" +#include +#include +#include +#include + + +static void print_usage(const char *argv0) { + fprintf(stderr, "%s [-vqd] [URL] [PATH] [METHOD] [PARAM]\n", argv0); +} + +static void print_help(const char *argv0) { + print_usage(argv0); + fprintf(stderr, "Convert between Chainpack and human readable Cpon.\n"); + fprintf(stderr, + "The conversion direction is can be autodetected but it is highly " + "advised to specify the direction if possible.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Arguments:\n"); + fprintf(stderr, " -v Increase logging level of the communication\n"); + fprintf(stderr, " -q Decrease logging level of the communication\n"); + fprintf(stderr, " -d Set maximul logging level of the communication\n"); + fprintf(stderr, " -V Print SHVC version and exit\n"); + fprintf(stderr, " -h Print this help text\n"); +} + +void parse_opts(int argc, char **argv, struct conf *conf) { + *conf = (struct conf){ + .url = "tcp://localhost", + .path = NULL, + .method = "dir", + .param = "null", + .verbose = 0, + }; + + int c; + while ((c = getopt(argc, argv, "vqdVh")) != -1) { + switch (c) { + case 'v': + if (conf->verbose < UINT_MAX) + conf->verbose++; + break; + case 'q': + if (conf->verbose > 0) + conf->verbose--; + break; + case 'd': + conf->verbose = UINT_MAX; + break; + case 'V': + printf("%s " PROJECT_VERSION "\n", argv[0]); + exit(0); + case 'h': + print_help(argv[0]); + exit(0); + default: + print_usage(argv[0]); + fprintf(stderr, "Invalid option: -%c\n", c); + exit(2); + } + } + if (optind < argc) + conf->url = argv[optind++]; + if (optind < argc) + conf->path = argv[optind++]; + if (optind < argc) + conf->method = argv[optind++]; + if (optind < argc) { + fprintf(stderr, "Invalid argument: %s\n", argv[optind]); + exit(2); + } +} diff --git a/shvc/opts.h b/shvc/opts.h new file mode 100644 index 0000000..226fba2 --- /dev/null +++ b/shvc/opts.h @@ -0,0 +1,16 @@ +#ifndef _SHVC_OPTS_H_ +#define _SHVC_OPTS_H_ +#include + +struct conf { + const char *url; + const char *path; + const char *method; + const char *param; + unsigned verbose; +}; + +/* Parse arguments. */ +void parse_opts(int argc, char **argv, struct conf *conf) __attribute__((nonnull)); + +#endif diff --git a/shvbroker/config.c b/shvcbroker/config.c similarity index 100% rename from shvbroker/config.c rename to shvcbroker/config.c diff --git a/shvbroker/config.h b/shvcbroker/config.h similarity index 100% rename from shvbroker/config.h rename to shvcbroker/config.h diff --git a/shvbroker/main.c b/shvcbroker/main.c similarity index 100% rename from shvbroker/main.c rename to shvcbroker/main.c diff --git a/shvbroker/meson.build b/shvcbroker/meson.build similarity index 90% rename from shvbroker/meson.build rename to shvcbroker/meson.build index e0c9cf4..fc9b533 100644 --- a/shvbroker/meson.build +++ b/shvcbroker/meson.build @@ -1,5 +1,5 @@ shvbroker_sources = files('config.c') -shvbroker_dependencies = [libshvbroker_dep, argp] +shvbroker_dependencies = [libshvbroker_dep, inih] if isnuttx diff --git a/subprojects/uriparser.wrap b/subprojects/uriparser.wrap deleted file mode 100644 index 3a5c423..0000000 --- a/subprojects/uriparser.wrap +++ /dev/null @@ -1,3 +0,0 @@ -[wrap-git] -url = https://github.com/uriparser/uriparser.git -revision = uriparser-0.9.7 diff --git a/tests/meson.build b/tests/meson.build index f17f10e..9c4c172 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,5 +1,8 @@ bash = find_program('bash') test_driver = meson.current_source_dir() + '/test-driver.sh' +pyshvbroker = find_program('pyshvbroker') + + subdir('unit') subdir('run') diff --git a/tests/run/__init__.py b/tests/run/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tests/run/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/run/conftest.py b/tests/run/conftest.py new file mode 100644 index 0000000..fb220d7 --- /dev/null +++ b/tests/run/conftest.py @@ -0,0 +1,28 @@ +import os +import shlex +import subprocess +import sys +import time + +import pytest + + +def pytest_addoption(parser): + parser.addoption( + "--cpconv", + help="Path to cpconv device binary", + metavar="PATH", + nargs=1, + ) + + +def pytest_configure(config): + cpconv = config.getoption("--cpconv") + setattr(config, "cpconv", cpconv[0] if cpconv else None) + + +@pytest.fixture(name="valgrind_exec", scope="session") +def fixture_valgrind_exec(): + """Parse and provide valgrind executer as passed by environment variable.""" + env = os.getenv("VALGRIND") + yield shlex.split(env) if env else [] diff --git a/tests/run/meson.build b/tests/run/meson.build index f13ea3f..3cc43fe 100644 --- a/tests/run/meson.build +++ b/tests/run/meson.build @@ -1,20 +1,33 @@ -bats = find_program('bats') +python_pytest = python.find_installation( + 'python', + modules: [ + 'pytest', + 'pytest_tap', + 'shv', + ], +) -bats_tests = [] +pytest_args = [ + test_driver, + '-n', + python_pytest.path(), + '-m', + 'pytest', + '--tap-stream', +] +pytest_depends = [] +if cpconv_opt + pytest_args += ['--cpconv=' + cpconv.full_path()] + pytest_depends += [cpconv] +endif +# TODO demo device and demo client and broker and so on +pytest_args += [meson.current_source_dir()] -foreach bats_test : bats_tests - test( - 'runtest-' + bats_test, - bash, - args: [ - test_driver, - '-n', - bats.full_path(), - '--tap', - join_paths(meson.current_source_dir(), bats_test + '.bats'), - ], - env: [], - protocol: 'tap', - depends: foo, - ) -endforeach +test( + 'run-pytest', + bash, + args: pytest_args, + protocol: 'tap', + timeout: 240, + depends: pytest_depends, +) diff --git a/tests/run/test_cpconv.py b/tests/run/test_cpconv.py new file mode 100644 index 0000000..a77d91b --- /dev/null +++ b/tests/run/test_cpconv.py @@ -0,0 +1,41 @@ +"""Check that cpconv works correctly.""" +import subprocess + +import pytest + + +@pytest.fixture(name="cpconv", scope="function") +def fixture_shvcall(request, valgrind_exec): + def cpconv(*args: str, check: bool = True) -> subprocess.CompletedProcess: + """Run cpconv with given arguments.""" + res = subprocess.run( + (*valgrind_exec, request.config.cpconv, *args), + stdout=subprocess.PIPE, + check=check, + ) + return res + + if request.config.cpconv is None: + pytest.skip("cpconv not provided") + yield cpconv + + +def test_help(cpconv): + """Just simple check that help works.""" + cpconv("-h") + +def test_version(cpconv): + """Just simple check that version works.""" + cpconv("-V") + +def test_both_op(cpconv): + """Usage of both -p and -u is not allowed.""" + assert cpconv("-p", "-u", check=False).returncode == 2 + +def test_invalid_opt(cpconv): + """Just some invalid option to cover that code.""" + assert cpconv("-x", check=False).returncode == 2 + +def test_invalid_arg(cpconv): + """Just some invalid argument to cover that code.""" + assert cpconv("input", "output", "invalid", check=False).returncode == 2 diff --git a/tests/test-driver.sh b/tests/test-driver.sh index 78d057d..88aad3e 100755 --- a/tests/test-driver.sh +++ b/tests/test-driver.sh @@ -15,6 +15,7 @@ default_valgrind_args+=('--show-error-list=yes') default_valgrind_args+=('--track-fds=yes') default_valgrind_args+=('--trace-children=yes') default_valgrind_args+=('--child-silent-after-fork=no') +default_valgrind_args+=('--trace-children-skip=*/pyshvbroker') case "${VALGRIND:-}" in memcheck) diff --git a/tests/unit/libshvchainpack/chainpack.c b/tests/unit/libshvchainpack/chainpack.c index 0917a92..7e61e6e 100644 --- a/tests/unit/libshvchainpack/chainpack.c +++ b/tests/unit/libshvchainpack/chainpack.c @@ -16,63 +16,62 @@ static const struct { struct cpitem item; const struct bdata cp; } single_d[] = { - {{.type = CP_ITEM_NULL}, B(0x80)}, - {{.type = CP_ITEM_BOOL, .as.Bool = true}, B(0xfe)}, - {{.type = CP_ITEM_BOOL, .as.Bool = false}, B(0xfd)}, - {{.type = CP_ITEM_INT, .as.Int = 0}, B(0x40)}, - {{.type = CP_ITEM_INT, .as.Int = -1}, B(0x82, 0x41)}, - {{.type = CP_ITEM_INT, .as.Int = -2}, B(0x82, 0x42)}, - {{.type = CP_ITEM_INT, .as.Int = 7}, B(0x47)}, - {{.type = CP_ITEM_INT, .as.Int = 42}, B(0x6a)}, - {{.type = CP_ITEM_INT, .as.Int = 528}, B(0x82, 0x82, 0x10)}, - {{.type = CP_ITEM_INT, .as.Int = 2147483647}, + {{.type = CPITEM_NULL}, B(0x80)}, + {{.type = CPITEM_BOOL, .as.Bool = true}, B(0xfe)}, + {{.type = CPITEM_BOOL, .as.Bool = false}, B(0xfd)}, + {{.type = CPITEM_INT, .as.Int = 0}, B(0x40)}, + {{.type = CPITEM_INT, .as.Int = -1}, B(0x82, 0x41)}, + {{.type = CPITEM_INT, .as.Int = -2}, B(0x82, 0x42)}, + {{.type = CPITEM_INT, .as.Int = 7}, B(0x47)}, + {{.type = CPITEM_INT, .as.Int = 42}, B(0x6a)}, + {{.type = CPITEM_INT, .as.Int = 528}, B(0x82, 0x82, 0x10)}, + {{.type = CPITEM_INT, .as.Int = 2147483647}, B(0x82, 0xf0, 0x7f, 0xff, 0xff, 0xff)}, - {{.type = CP_ITEM_INT, .as.Int = 4294967295}, + {{.type = CPITEM_INT, .as.Int = 4294967295}, B(0x82, 0xf1, 0x00, 0xff, 0xff, 0xff, 0xff)}, - {{.type = CP_ITEM_INT, .as.Int = 9007199254740991}, + {{.type = CPITEM_INT, .as.Int = 9007199254740991}, B(0x82, 0xf3, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)}, - {{.type = CP_ITEM_INT, .as.Int = -9007199254740991}, + {{.type = CPITEM_INT, .as.Int = -9007199254740991}, B(0x82, 0xf3, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff)}, - {{.type = CP_ITEM_UINT, .as.UInt = 0}, B(0x00)}, - {{.type = CP_ITEM_UINT, .as.UInt = 1}, B(0x01)}, - {{.type = CP_ITEM_UINT, .as.UInt = 126}, B(0x81, 0x7e)}, - {{.type = CP_ITEM_UINT, .as.UInt = 127}, B(0x81, 0x7f)}, - {{.type = CP_ITEM_UINT, .as.UInt = 2147483647}, + {{.type = CPITEM_UINT, .as.UInt = 0}, B(0x00)}, + {{.type = CPITEM_UINT, .as.UInt = 1}, B(0x01)}, + {{.type = CPITEM_UINT, .as.UInt = 126}, B(0x81, 0x7e)}, + {{.type = CPITEM_UINT, .as.UInt = 127}, B(0x81, 0x7f)}, + {{.type = CPITEM_UINT, .as.UInt = 2147483647}, B(0x81, 0xf0, 0x7f, 0xff, 0xff, 0xff)}, - {{.type = CP_ITEM_UINT, .as.UInt = 4294967295}, + {{.type = CPITEM_UINT, .as.UInt = 4294967295}, B(0x81, 0xf0, 0xff, 0xff, 0xff, 0xff)}, - {{.type = CP_ITEM_DOUBLE, .as.Double = 223.0}, + {{.type = CPITEM_DOUBLE, .as.Double = 223.0}, B(0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x6b, 0x40)}, - {{.type = CP_ITEM_DECIMAL, .as.Decimal = {.mantisa = 23, .exponent = -1}}, + {{.type = CPITEM_DECIMAL, .as.Decimal = {.mantisa = 23, .exponent = -1}}, B(0x8c, 0x17, 0x41)}, - {{.type = CP_ITEM_DATETIME, .as.Datetime = {.msecs = 1517529600001, .offutc = 0}}, + {{.type = CPITEM_DATETIME, .as.Datetime = {.msecs = 1517529600001, .offutc = 0}}, B(0x8d, 0x04)}, - {{.type = CP_ITEM_DATETIME, - .as.Datetime = {.msecs = 1517529600001, .offutc = 60}}, + {{.type = CPITEM_DATETIME, .as.Datetime = {.msecs = 1517529600001, .offutc = 60}}, B(0x8d, 0x82, 0x11)}, - {{.type = CP_ITEM_STRING, .rchr = "", .as.String = {.flags = CPBI_F_SINGLE}}, + {{.type = CPITEM_STRING, .rchr = "", .as.String = {.flags = CPBI_F_SINGLE}}, B(0x86, 0x00)}, - {{.type = CP_ITEM_STRING, + {{.type = CPITEM_STRING, .rchr = "foo", .as.String = {.len = 3, .flags = CPBI_F_SINGLE}}, B(0x86, 0x03, 'f', 'o', 'o')}, - {{.type = CP_ITEM_STRING, + {{.type = CPITEM_STRING, .rchr = "", .as.String = {.flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, B(0x8e, 0x00)}, - {{.type = CP_ITEM_STRING, + {{.type = CPITEM_STRING, .rchr = "foo", .as.String = {.len = 3, .flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, B(0x8e, 'f', 'o', 'o', 0x00)}, - {{.type = CP_ITEM_BLOB, + {{.type = CPITEM_BLOB, .rbuf = (const uint8_t[]){0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a}, .as.Blob = {.len = 6, .flags = CPBI_F_SINGLE}}, B(0x85, 0x06, 0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a)}, - {{.type = CP_ITEM_LIST}, B(0x88)}, - {{.type = CP_ITEM_MAP}, B(0x89)}, - {{.type = CP_ITEM_IMAP}, B(0x8a)}, - {{.type = CP_ITEM_META}, B(0x8b)}, - {{.type = CP_ITEM_CONTAINER_END}, B(0xff)}, + {{.type = CPITEM_LIST}, B(0x88)}, + {{.type = CPITEM_MAP}, B(0x89)}, + {{.type = CPITEM_IMAP}, B(0x8a)}, + {{.type = CPITEM_META}, B(0x8b)}, + {{.type = CPITEM_CONTAINER_END}, B(0xff)}, }; ARRAY_TEST(pack, pack_single, single_d) { ck_assert_int_eq(chainpack_pack(packstream, &_d.item), _d.cp.len); @@ -85,5 +84,51 @@ ARRAY_TEST(unpack, unpack_single, single_d) { struct cpitem item = {.buf = buf, .bufsiz = BUFSIZ}; ck_assert_int_eq(chainpack_unpack(f, &item), _d.cp.len); ck_assert_item(item, _d.item); + fclose(f); +} +END_TEST + + +static const struct bdata skip_d[] = { + B(0x86, 0x03, 'f', 'o', 'o'), /* String */ + B(0x8e, 'f', 'o', 'o', 0x00), /* CString */ + B(0x85, 0x06, 0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a), /* Blob */ +}; +ARRAY_TEST(unpack, skip) { + FILE *f = fmemopen((void *)_d.v, _d.len, "r"); + struct cpitem item = {.buf = NULL, .bufsiz = BUFSIZ}; + ck_assert_int_eq(chainpack_unpack(f, &item), _d.len); + ck_assert_int_eq(fgetc(f), EOF); + ck_assert(feof(f)); + fclose(f); +} +END_TEST + +static struct bdata genericd = B(0x01, 0x02, 0xf8, 0xff); +TEST(pack, pack_raw) { + struct cpitem item = { + .type = CPITEM_RAW, .rbuf = genericd.v, .as.Blob.len = genericd.len}; + ck_assert_int_eq(chainpack_pack(packstream, &item), genericd.len); + ck_assert_packbuf(genericd.v, genericd.len); +} +END_TEST +TEST(unpack, unpack_raw) { + FILE *f = fmemopen((void *)genericd.v, genericd.len, "r"); + uint8_t buf[BUFSIZ]; + struct cpitem item = {.type = CPITEM_RAW, .buf = buf, .bufsiz = BUFSIZ}; + ck_assert_int_eq(chainpack_unpack(f, &item), genericd.len); + ck_assert_int_eq(item.as.Blob.len, genericd.len); + ck_assert_mem_eq(item.buf, genericd.v, genericd.len); + fclose(f); +} +END_TEST + +TEST(unpack, unpack_drop) { + FILE *f = fmemopen((void *)genericd.v, genericd.len, "r"); + struct cpitem item = {.type = CPITEM_RAW, .buf = NULL, .bufsiz = BUFSIZ}; + ck_assert_int_eq(chainpack_unpack(f, &item), genericd.len); + ck_assert(feof(f)); + ck_assert(!ferror(f)); + fclose(f); } END_TEST diff --git a/tests/unit/libshvchainpack/cp_pack.c b/tests/unit/libshvchainpack/cp_pack.c index 89c3f6c..d1050d2 100644 --- a/tests/unit/libshvchainpack/cp_pack.c +++ b/tests/unit/libshvchainpack/cp_pack.c @@ -337,6 +337,7 @@ END_TEST static void pack_fopen_str(void) { FILE *f = cp_pack_fopen(pack, true); + ck_assert_ptr_nonnull(f); fputs("Num", f); fprintf(f, ": %d", 42); fclose(f); @@ -355,6 +356,7 @@ END_TEST static void pack_fopen_blob(void) { FILE *f = cp_pack_fopen(pack, false); + ck_assert_ptr_nonnull(f); setvbuf(f, NULL, _IONBF, 0); struct bdata v = B(0x42, 0x11); for (size_t i = 0; i < v.len; i++) diff --git a/tests/unit/libshvchainpack/cp_unpack.c b/tests/unit/libshvchainpack/cp_unpack.c index 84ac598..ab2776f 100644 --- a/tests/unit/libshvchainpack/cp_unpack.c +++ b/tests/unit/libshvchainpack/cp_unpack.c @@ -1,8 +1,8 @@ -#include "check.h" -#include "shv/cp.h" #include #include #include +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free #define SUITE "cp_unpack" #include @@ -38,29 +38,46 @@ TEST(unpack, cpon_unpack_ctx_realloc) { for (int i = 0; i < 3; i++) { ck_assert_uint_eq(cp_unpack(unpack, &item), 1); - ck_assert_int_eq(item.type, CP_ITEM_LIST); + ck_assert_int_eq(item.type, CPITEM_LIST); } for (int i = 0; i < 3; i++) { ck_assert_uint_eq(cp_unpack(unpack, &item), 1); - ck_assert_int_eq(item.type, CP_ITEM_CONTAINER_END); + ck_assert_int_eq(item.type, CPITEM_CONTAINER_END); } unpack_free(unpack); } END_TEST +TEST(unpack, drop_string) { + const char *str = "\"Some string we want to skip\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + cp_unpack(unpack, &item); + ck_assert_item_type(item, CPITEM_STRING); + cp_unpack_drop(unpack, &item); + ck_assert_item_type(item, CPITEM_STRING); + ck_assert(item.as.String.flags & CPBI_F_LAST); + cp_unpack(unpack, &item); + ck_assert_item_type(item, CPITEM_INVALID); + + unpack_free(unpack); +} +END_TEST + TEST(unpack, skip_int) { const char *str = "[1,2]"; cp_unpack_t unpack = unpack_cpon(str); struct cpitem item = (struct cpitem){}; cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_LIST); + ck_assert_item_type(item, CPITEM_LIST); cp_unpack_skip(unpack, &item); cp_unpack(unpack, &item); ck_assert_item_int(item, 2); cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + ck_assert_item_type(item, CPITEM_CONTAINER_END); unpack_free(unpack); } @@ -72,27 +89,27 @@ TEST(unpack, skip_string) { struct cpitem item = (struct cpitem){}; cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_LIST); + ck_assert_item_type(item, CPITEM_LIST); cp_unpack_skip(unpack, &item); cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + ck_assert_item_type(item, CPITEM_CONTAINER_END); unpack_free(unpack); } END_TEST TEST(unpack, skip_string_fin) { - const char *str = "[\"Some string we want to skip\"]"; + const char *str = "[\"Some string we want to skip\",\"Some other\"]"; cp_unpack_t unpack = unpack_cpon(str); struct cpitem item = (struct cpitem){}; cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_LIST); + ck_assert_item_type(item, CPITEM_LIST); cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_STRING); + ck_assert_item_type(item, CPITEM_STRING); cp_unpack_skip(unpack, &item); cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + ck_assert_item_type(item, CPITEM_CONTAINER_END); unpack_free(unpack); } @@ -104,27 +121,27 @@ TEST(unpack, skip_containers) { struct cpitem item = (struct cpitem){}; cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_LIST); + ck_assert_item_type(item, CPITEM_LIST); cp_unpack_skip(unpack, &item); cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + ck_assert_item_type(item, CPITEM_CONTAINER_END); unpack_free(unpack); } END_TEST TEST(unpack, finish_containers) { - const char *str = "[{0:<>null}]"; + const char *str = "[{0:<>[null]}]"; cp_unpack_t unpack = unpack_cpon(str); struct cpitem item = (struct cpitem){}; cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_LIST); + ck_assert_item_type(item, CPITEM_LIST); cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_MAP); - cp_unpack_finish(unpack, &item); + ck_assert_item_type(item, CPITEM_MAP); + cp_unpack_finish(unpack, &item, 1); cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + ck_assert_item_type(item, CPITEM_CONTAINER_END); unpack_free(unpack); } @@ -142,6 +159,17 @@ TEST(unpack, unpack_strdup) { } END_TEST +TEST(unpack, unpack_strndup) { + const char *str = "\"Some text\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + char *res = cp_unpack_strndup(unpack, &item, 5); + ck_assert_str_eq(res, "Some "); + free(res); + unpack_free(unpack); +} +END_TEST + TEST(unpack, unpack_memdup) { const char *str = "b\"123\""; cp_unpack_t unpack = unpack_cpon(str); @@ -159,12 +187,93 @@ TEST(unpack, unpack_memdup) { } END_TEST +TEST(unpack, unpack_memndup) { + const char *str = "b\"1234567890\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + + uint8_t *res; + size_t siz = 7; + cp_unpack_memndup(unpack, &item, &res, &siz); + struct bdata exp = B('1', '2', '3', '4', '5', '6', '7'); + ck_assert_int_eq(siz, exp.len); + ck_assert_mem_eq(res, exp.v, siz); + free(res); + + unpack_free(unpack); +} +END_TEST + +TEST(unpack, unpack_strdupo) { + const char *str = "\"Some text\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + struct obstack obstack; + obstack_init(&obstack); + char *res = cp_unpack_strdupo(unpack, &item, &obstack); + ck_assert_str_eq(res, "Some text"); + obstack_free(&obstack, NULL); + unpack_free(unpack); +} +END_TEST + +TEST(unpack, unpack_strndupo) { + const char *str = "\"Some text\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + struct obstack obstack; + obstack_init(&obstack); + char *res = cp_unpack_strndupo(unpack, &item, 5, &obstack); + ck_assert_str_eq(res, "Some "); + obstack_free(&obstack, NULL); + unpack_free(unpack); +} +END_TEST + +TEST(unpack, unpack_memdupo) { + const char *str = "b\"123\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + struct obstack obstack; + obstack_init(&obstack); + + uint8_t *res; + size_t siz; + cp_unpack_memdupo(unpack, &item, &res, &siz, &obstack); + struct bdata exp = B('1', '2', '3'); + ck_assert_int_eq(siz, exp.len); + ck_assert_mem_eq(res, exp.v, siz); + + obstack_free(&obstack, NULL); + unpack_free(unpack); +} +END_TEST + +TEST(unpack, unpack_memndupo) { + const char *str = "b\"1234567890\""; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + struct obstack obstack; + obstack_init(&obstack); + + uint8_t *res; + size_t siz = 7; + cp_unpack_memndupo(unpack, &item, &res, &siz, &obstack); + struct bdata exp = B('1', '2', '3', '4', '5', '6', '7'); + ck_assert_int_eq(siz, exp.len); + ck_assert_mem_eq(res, exp.v, siz); + + obstack_free(&obstack, NULL); + unpack_free(unpack); +} +END_TEST + TEST(unpack, unpack_strdup_invalid) { const char *str = "0"; cp_unpack_t unpack = unpack_cpon(str); struct cpitem item = (struct cpitem){}; ck_assert_ptr_null(cp_unpack_strdup(unpack, &item)); - ck_assert_int_eq(item.type, CP_ITEM_INT); + ck_assert_int_eq(item.type, CPITEM_INT); unpack_free(unpack); } END_TEST @@ -210,7 +319,7 @@ TEST(unpack, unpack_fopen_string) { struct cpitem item = (struct cpitem){}; FILE *f = cp_unpack_fopen(unpack, &item); - ck_assert_item_type(item, CP_ITEM_STRING); + ck_assert_item_type(item, CPITEM_STRING); int d; char buf[10]; ck_assert_int_eq(fscanf(f, "Some: %d:%9s", &d, buf), 2); @@ -228,7 +337,7 @@ TEST(unpack, unpack_fopen_blob) { struct cpitem item = (struct cpitem){}; FILE *f = cp_unpack_fopen(unpack, &item); - ck_assert_item_type(item, CP_ITEM_BLOB); + ck_assert_item_type(item, CPITEM_BLOB); char buf[10]; ck_assert_int_eq(fread(buf, 1, 10, f), 9); uint8_t exp[] = {0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9}; diff --git a/tests/unit/libshvchainpack/cpon.c b/tests/unit/libshvchainpack/cpon.c index daf8154..467ff15 100644 --- a/tests/unit/libshvchainpack/cpon.c +++ b/tests/unit/libshvchainpack/cpon.c @@ -6,6 +6,7 @@ #include "packstream.h" #include "item.h" +#include "bdata.h" TEST_CASE(pack, setup_packstream, teardown_packstream){}; @@ -16,80 +17,79 @@ static const struct { struct cpitem item; const char *cp; } single_d[] = { - {{.type = CP_ITEM_NULL}, "null"}, - {{.type = CP_ITEM_BOOL, .as.Bool = true}, "true"}, - {{.type = CP_ITEM_BOOL, .as.Bool = false}, "false"}, - {{.type = CP_ITEM_INT, .as.Int = 0}, "0"}, - {{.type = CP_ITEM_INT, .as.Int = -1}, "-1"}, - {{.type = CP_ITEM_INT, .as.Int = -2}, "-2"}, - {{.type = CP_ITEM_INT, .as.Int = 7}, "7"}, - {{.type = CP_ITEM_INT, .as.Int = 42}, "42"}, - {{.type = CP_ITEM_INT, .as.Int = 2147483647}, "2147483647"}, - {{.type = CP_ITEM_INT, .as.Int = 4294967295}, "4294967295"}, - {{.type = CP_ITEM_INT, .as.Int = 9007199254740991}, "9007199254740991"}, - {{.type = CP_ITEM_INT, .as.Int = -9007199254740991}, "-9007199254740991"}, - {{.type = CP_ITEM_UINT, .as.UInt = 0}, "0u"}, - {{.type = CP_ITEM_UINT, .as.UInt = 1}, "1u"}, - {{.type = CP_ITEM_UINT, .as.UInt = 127}, "127u"}, - {{.type = CP_ITEM_UINT, .as.UInt = 2147483647}, "2147483647u"}, - {{.type = CP_ITEM_UINT, .as.UInt = 4294967295}, "4294967295u"}, - {{.type = CP_ITEM_DECIMAL, .as.Decimal = (struct cpdecimal){}}, "0."}, - {{.type = CP_ITEM_DECIMAL, .as.Decimal = (struct cpdecimal){.mantisa = 223}}, + {{.type = CPITEM_NULL}, "null"}, + {{.type = CPITEM_BOOL, .as.Bool = true}, "true"}, + {{.type = CPITEM_BOOL, .as.Bool = false}, "false"}, + {{.type = CPITEM_INT, .as.Int = 0}, "0"}, + {{.type = CPITEM_INT, .as.Int = -1}, "-1"}, + {{.type = CPITEM_INT, .as.Int = -2}, "-2"}, + {{.type = CPITEM_INT, .as.Int = 7}, "7"}, + {{.type = CPITEM_INT, .as.Int = 42}, "42"}, + {{.type = CPITEM_INT, .as.Int = 2147483647}, "2147483647"}, + {{.type = CPITEM_INT, .as.Int = 4294967295}, "4294967295"}, + {{.type = CPITEM_INT, .as.Int = 9007199254740991}, "9007199254740991"}, + {{.type = CPITEM_INT, .as.Int = -9007199254740991}, "-9007199254740991"}, + {{.type = CPITEM_UINT, .as.UInt = 0}, "0u"}, + {{.type = CPITEM_UINT, .as.UInt = 1}, "1u"}, + {{.type = CPITEM_UINT, .as.UInt = 127}, "127u"}, + {{.type = CPITEM_UINT, .as.UInt = 2147483647}, "2147483647u"}, + {{.type = CPITEM_UINT, .as.UInt = 4294967295}, "4294967295u"}, + {{.type = CPITEM_DECIMAL, .as.Decimal = (struct cpdecimal){}}, "0."}, + {{.type = CPITEM_DECIMAL, .as.Decimal = (struct cpdecimal){.mantisa = 223}}, "223."}, - {{.type = CP_ITEM_DECIMAL, + {{.type = CPITEM_DECIMAL, .as.Decimal = (struct cpdecimal){.mantisa = 223, .exponent = -1}}, "22.3"}, - {{.type = CP_ITEM_DECIMAL, + {{.type = CPITEM_DECIMAL, .as.Decimal = (struct cpdecimal){.mantisa = 223, .exponent = 3}}, "223000."}, - {{.type = CP_ITEM_DECIMAL, + {{.type = CPITEM_DECIMAL, .as.Decimal = (struct cpdecimal){.mantisa = 223, .exponent = -5}}, "0.00223"}, - {{.type = CP_ITEM_DECIMAL, + {{.type = CPITEM_DECIMAL, .as.Decimal = (struct cpdecimal){.mantisa = 223, .exponent = 7}}, "223e7"}, - {{.type = CP_ITEM_DECIMAL, + {{.type = CPITEM_DECIMAL, .as.Decimal = (struct cpdecimal){.mantisa = 223, .exponent = -12}}, "223e-12"}, - {{.type = CP_ITEM_DATETIME, .as.Datetime = (struct cpdatetime){}}, + {{.type = CPITEM_DATETIME, .as.Datetime = (struct cpdatetime){}}, "d\"1970-01-01T00:00:00.000Z\""}, - {{.type = CP_ITEM_DATETIME, .as.Datetime = {.msecs = 1517529600001, .offutc = 0}}, + {{.type = CPITEM_DATETIME, .as.Datetime = {.msecs = 1517529600001, .offutc = 0}}, "d\"2018-02-02T00:00:00.001Z\""}, - {{.type = CP_ITEM_DATETIME, - .as.Datetime = {.msecs = 1517529600001, .offutc = 60}}, + {{.type = CPITEM_DATETIME, .as.Datetime = {.msecs = 1517529600001, .offutc = 60}}, "d\"2018-02-02T00:00:00.001+01:00\""}, - {{.type = CP_ITEM_DATETIME, + {{.type = CPITEM_DATETIME, .as.Datetime = {.msecs = 1692776567123, .offutc = -75}}, "d\"2023-08-23T07:42:47.123-01:15\""}, - {{.type = CP_ITEM_STRING, + {{.type = CPITEM_STRING, .rchr = "", .as.String = {.flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, "\"\""}, - {{.type = CP_ITEM_STRING, + {{.type = CPITEM_STRING, .rchr = "foo", .as.String = {.len = 3, .flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, "\"foo\""}, - {{.type = CP_ITEM_STRING, + {{.type = CPITEM_STRING, .rchr = "foo", .as.String = {.len = 3, .flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, "\"foo\""}, - {{.type = CP_ITEM_STRING, + {{.type = CPITEM_STRING, .rchr = "\f\a\b\t", .as.String = {.len = 4, .flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, "\"\\f\\a\\b\\t\""}, - {{.type = CP_ITEM_BLOB, + {{.type = CPITEM_BLOB, .rbuf = (const uint8_t[]){}, .as.String = {.flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, "b\"\""}, - {{.type = CP_ITEM_BLOB, + {{.type = CPITEM_BLOB, .rbuf = (const uint8_t[]){0x61, 0x62, 0xcd, 0x0b, 0x0d, 0x0a}, .as.String = {.len = 6, .flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, "b\"ab\\CD\\v\\r\\n\""}, - {{.type = CP_ITEM_BLOB, + {{.type = CPITEM_BLOB, .rbuf = (const uint8_t[]){}, .as.String = {.flags = CPBI_F_SINGLE | CPBI_F_STREAM}}, "b\"\""}, - {{.type = CP_ITEM_BLOB, + {{.type = CPITEM_BLOB, .rbuf = (const uint8_t[]){0xfe, 0x00, 0x42}, .as.String = {.len = 3, .flags = CPBI_F_SINGLE | CPBI_F_STREAM | CPBI_F_HEX}}, "x\"FE0042\""}, @@ -111,3 +111,33 @@ ARRAY_TEST(unpack, unpack_single, single_d) { ck_assert_item(item, _d.item); } END_TEST + + +static struct bdata genericd = B(0x01, 0x02, 0xf8, 0xff); +TEST(pack, pack_raw) { + struct cpitem item = { + .type = CPITEM_RAW, .rbuf = genericd.v, .as.Blob.len = genericd.len}; + ck_assert_int_eq(chainpack_pack(packstream, &item), genericd.len); + ck_assert_packbuf(genericd.v, genericd.len); +} +END_TEST +TEST(unpack, unpack_raw) { + FILE *f = fmemopen((void *)genericd.v, genericd.len, "r"); + uint8_t buf[BUFSIZ]; + struct cpitem item = {.type = CPITEM_RAW, .buf = buf, .bufsiz = BUFSIZ}; + ck_assert_int_eq(chainpack_unpack(f, &item), genericd.len); + ck_assert_int_eq(item.as.Blob.len, genericd.len); + ck_assert_mem_eq(item.buf, genericd.v, genericd.len); + fclose(f); +} +END_TEST + +TEST(unpack, unpack_drop) { + FILE *f = fmemopen((void *)genericd.v, genericd.len, "r"); + struct cpitem item = {.type = CPITEM_RAW, .buf = NULL, .bufsiz = BUFSIZ}; + ck_assert_int_eq(chainpack_unpack(f, &item), genericd.len); + ck_assert(feof(f)); + ck_assert(!ferror(f)); + fclose(f); +} +END_TEST diff --git a/tests/unit/libshvchainpack/meson.build b/tests/unit/libshvchainpack/meson.build index 4642eb2..1f38349 100644 --- a/tests/unit/libshvchainpack/meson.build +++ b/tests/unit/libshvchainpack/meson.build @@ -12,6 +12,7 @@ unittest_libshvchainpack = executable( ], dependencies: [libshvrpc_dep, check_suite, obstack], include_directories: [includes, unittest_utils_includes], + c_args: unittest_utils_c_args, ) test( 'unittest-libshvchainpack', diff --git a/tests/unit/libshvrpc/meson.build b/tests/unit/libshvrpc/meson.build index 49f6400..a3c6efc 100644 --- a/tests/unit/libshvrpc/meson.build +++ b/tests/unit/libshvrpc/meson.build @@ -1,14 +1,18 @@ unittest_libshvrpc = executable( 'unittest-libshvrpc', [ - 'rpcmsg_meta.c', - 'rpcmsg_pack.c', + 'rpcclient_links.c', + 'rpcclient_login.c', + 'rpcclient_ping.c', 'rpcmsg_access.c', + 'rpcmsg_head.c', + 'rpcmsg_pack.c', 'rpcurl.c', unittest_utils_src, ], dependencies: [libshvrpc_dep, check_suite, obstack], include_directories: [includes, unittest_utils_includes], + c_args: unittest_utils_c_args, ) test( 'unittest-libshvrpc', @@ -17,4 +21,5 @@ test( env: unittests_env, protocol: 'tap', depends: unittest_libshvrpc, + timeout: 120, ) diff --git a/tests/unit/libshvrpc/rpcclient_links.c b/tests/unit/libshvrpc/rpcclient_links.c new file mode 100644 index 0000000..b099810 --- /dev/null +++ b/tests/unit/libshvrpc/rpcclient_links.c @@ -0,0 +1,44 @@ +#include +#include +#include + +#define SUITE "rpcclient_links" +#include +#include "broker.h" +#include "rpcclient_ping.h" + + +TEST_CASE(all, setup_shvbroker, teardown_shvbroker) {} + + +TEST(all, stream_tcp) { + rpcclient_t c = rpcclient_stream_tcp_connect("localhost", shvbroker_tcp_port); + ck_assert_ptr_nonnull(c); + struct rpclogin_options opts = { + .username = "admin", + .password = "admin!123", + .login_type = RPC_LOGIN_PLAIN, + }; + ck_assert_int_eq(rpcclient_login(c, &opts), RPCCLIENT_LOGIN_OK); + + rpcclient_ping_test(c); + + rpcclient_destroy(c); +} +END_TEST + +TEST(all, stream_unix) { + rpcclient_t c = rpcclient_stream_unix_connect(shvbroker_unix_path); + ck_assert_ptr_nonnull(c); + struct rpclogin_options opts = { + .username = "admin", + .password = "admin!123", + .login_type = RPC_LOGIN_PLAIN, + }; + ck_assert_int_eq(rpcclient_login(c, &opts), RPCCLIENT_LOGIN_OK); + + rpcclient_ping_test(c); + + rpcclient_destroy(c); +} +END_TEST diff --git a/tests/unit/libshvrpc/rpcclient_login.c b/tests/unit/libshvrpc/rpcclient_login.c new file mode 100644 index 0000000..2b27f14 --- /dev/null +++ b/tests/unit/libshvrpc/rpcclient_login.c @@ -0,0 +1,57 @@ +#include +#include +#include + +#define SUITE "rpcclient_login" +#include +#include "broker.h" +#include "rpcclient_ping.h" + + +TEST_CASE(all, setup_shvbroker, teardown_shvbroker) {} + +TEST(all, plain) { + rpcclient_t c = rpcclient_stream_tcp_connect("localhost", shvbroker_tcp_port); + ck_assert_ptr_nonnull(c); + struct rpclogin_options opts = { + .username = "admin", + .password = "admin!123", + .login_type = RPC_LOGIN_PLAIN, + }; + ck_assert_int_eq(rpcclient_login(c, &opts), RPCCLIENT_LOGIN_OK); + + rpcclient_ping_test(c); + + rpcclient_destroy(c); +} +END_TEST + +TEST(all, sha1) { + rpcclient_t c = rpcclient_stream_tcp_connect("localhost", shvbroker_tcp_port); + ck_assert_ptr_nonnull(c); + struct rpclogin_options opts = { + .username = "admin", + .password = "57a261a7bcb9e6cf1db80df501cdd89cee82957e", + .login_type = RPC_LOGIN_SHA1, + }; + ck_assert_int_eq(rpcclient_login(c, &opts), RPCCLIENT_LOGIN_OK); + + rpcclient_ping_test(c); + + rpcclient_destroy(c); +} +END_TEST + +TEST(all, invalid) { + rpcclient_t c = rpcclient_stream_tcp_connect("localhost", shvbroker_tcp_port); + ck_assert_ptr_nonnull(c); + struct rpclogin_options opts = { + .username = "admin", + .password = "invalid", + .login_type = RPC_LOGIN_PLAIN, + }; + ck_assert_int_eq(rpcclient_login(c, &opts), RPCCLIENT_LOGIN_INVALID); + + rpcclient_destroy(c); +} +END_TEST diff --git a/tests/unit/libshvrpc/rpcclient_ping.c b/tests/unit/libshvrpc/rpcclient_ping.c new file mode 100644 index 0000000..0058f6d --- /dev/null +++ b/tests/unit/libshvrpc/rpcclient_ping.c @@ -0,0 +1,23 @@ +#include "rpcclient_ping.h" +#include +#include +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free +#include +#include + +void rpcclient_ping_test(rpcclient_t c) { + rpcmsg_pack_request_void(rpcclient_pack(c), ".broker/app", "ping", 3); + ck_assert(rpcclient_sendmsg(c)); + + struct obstack obs; + obstack_init(&obs); + struct cpitem item = (struct cpitem){}; + struct rpcmsg_meta meta; + ck_assert(rpcclient_nextmsg(c)); + ck_assert(rpcmsg_head_unpack(rpcclient_unpack(c), &item, &meta, NULL, &obs)); + ck_assert_int_eq(meta.type, RPCMSG_T_RESPONSE); + ck_assert_int_eq(meta.request_id, 3); + ck_assert(rpcclient_validmsg(c)); + obstack_free(&obs, NULL); +} diff --git a/tests/unit/libshvrpc/rpcclient_ping.h b/tests/unit/libshvrpc/rpcclient_ping.h new file mode 100644 index 0000000..c0c5fb0 --- /dev/null +++ b/tests/unit/libshvrpc/rpcclient_ping.h @@ -0,0 +1,7 @@ +#ifndef RPCCLIENT_PING_H +#define RPCCLIENT_PING_H +#include + +void rpcclient_ping_test(rpcclient_t); + +#endif diff --git a/tests/unit/libshvrpc/rpcmsg_access.c b/tests/unit/libshvrpc/rpcmsg_access.c index ed9015b..0402aa0 100644 --- a/tests/unit/libshvrpc/rpcmsg_access.c +++ b/tests/unit/libshvrpc/rpcmsg_access.c @@ -87,9 +87,9 @@ TEST(all, acc_unpack_fully) { cp_unpack_t unpack = unpack_cpon(str); struct cpitem item = (struct cpitem){}; cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_LIST); + ck_assert_item_type(item, CPITEM_LIST); ck_assert_int_eq(rpcmsg_access_unpack(unpack, &item), RPCMSG_ACC_BROWSE); cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_CONTAINER_END); + ck_assert_item_type(item, CPITEM_CONTAINER_END); unpack_free(unpack); } diff --git a/tests/unit/libshvrpc/rpcmsg_head.c b/tests/unit/libshvrpc/rpcmsg_head.c new file mode 100644 index 0000000..ad3ceca --- /dev/null +++ b/tests/unit/libshvrpc/rpcmsg_head.c @@ -0,0 +1,165 @@ +#include "check.h" +#include "shv/cp.h" +#include +#include +#include +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +#define SUITE "rpcmsg_head" +#include +#include "unpack.h" +#include "item.h" + + +TEST_CASE(all) {} + +static const struct { + const char *str; + const struct rpcmsg_meta meta; + bool valid; +} unpack_obstack_d[] = { + { + "<>i{1:null}", + (struct rpcmsg_meta){.type = RPCMSG_T_INVALID, .request_id = INT64_MIN}, + false, + }, + { + "<8:42,9:\".app\",10:\"ping\",13:\"some,dev,acc\">i{1:null", + (struct rpcmsg_meta){ + .type = RPCMSG_T_REQUEST, + .request_id = 42, + .path = ".app", + .method = "ping", + .access_grant = RPCMSG_ACC_DEVEL, + }, + true, + }, + { + "<8:1,9:\".broker/app\",10:\"ping\">i{}", + (struct rpcmsg_meta){ + .type = RPCMSG_T_REQUEST, + .request_id = 1, + .path = ".broker/app", + .method = "ping", + }, + true, + }, + { + "<1:1,8:24,11:[0,3]>i{2:null", + (struct rpcmsg_meta){ + .type = RPCMSG_T_RESPONSE, + .request_id = 24, + .cids = + { + .ptr = (void *)(const uint8_t[]){0x88, 0x40, 0x43, 0xff}, + .siz = 4, + }, + }, + true, + }, + { + "<1:1,8:4>i{}", + (struct rpcmsg_meta){ + .type = RPCMSG_T_RESPONSE, + .request_id = 4, + }, + true, + }, + { + "<1:1,8:86>i{3:null", + (struct rpcmsg_meta){ + .type = RPCMSG_T_ERROR, + .request_id = 86, + }, + true, + }, + { + "<1:1,9:\"value\",10:\"chng\">i{1:null", + (struct rpcmsg_meta){ + .type = RPCMSG_T_SIGNAL, + .request_id = INT64_MIN, + .path = "value", + .method = "chng", + }, + true, + }, + { + "<1:1,9:\"signal\",10:\"chng\">i{}", + (struct rpcmsg_meta){ + .type = RPCMSG_T_SIGNAL, + .request_id = INT64_MIN, + .path = "signal", + .method = "chng", + }, + true, + }, + { + "<1:1,8:175,9:\"\",10:\"ls\",11:4,13:\"dev\">i{1:null}", + (struct rpcmsg_meta){ + .type = RPCMSG_T_REQUEST, + .request_id = 175, + .path = "", + .method = "ls", + .cids = {.ptr = (void *)(const uint8_t[]){0x44}, .siz = 1}, + .access_grant = RPCMSG_ACC_DEVEL, + }, + true, + }, +}; +ARRAY_TEST(all, unpack_obstack) { + cp_unpack_t unpack = unpack_cpon(_d.str); + struct cpitem item = (struct cpitem){}; + struct rpcmsg_meta meta = (struct rpcmsg_meta){}; + struct obstack obstack; + obstack_init(&obstack); + + ck_assert(rpcmsg_head_unpack(unpack, &item, &meta, NULL, &obstack) == _d.valid); + ck_assert_int_eq(meta.type, _d.meta.type); + ck_assert_int_eq(meta.request_id, _d.meta.request_id); + ck_assert_pstr_eq(meta.path, _d.meta.path); + ck_assert_pstr_eq(meta.method, _d.meta.method); + ck_assert_int_eq(meta.access_grant, _d.meta.access_grant); + ck_assert_int_eq(meta.cids.siz, _d.meta.cids.siz); + ck_assert_mem_eq(meta.cids.ptr, _d.meta.cids.ptr, _d.meta.cids.siz); + if (item.type != CPITEM_CONTAINER_END) { + cp_unpack(unpack, &item); + ck_assert_item_type(item, CPITEM_NULL); + } /* Otherwise this was all that is in the message */ + + obstack_free(&obstack, NULL); + unpack_free(unpack); +} +END_TEST + + +TEST(all, unpack_extra) { + static const char *const str = "<1:1,8:4,41:8,42:\"foo\",43:[1,2,3]>i{}"; + cp_unpack_t unpack = unpack_cpon(str); + struct cpitem item = (struct cpitem){}; + struct rpcmsg_meta meta = (struct rpcmsg_meta){}; + struct rpcmsg_meta_limits limits = (struct rpcmsg_meta_limits){.extra = true}; + struct obstack obstack; + obstack_init(&obstack); + + ck_assert(rpcmsg_head_unpack(unpack, &item, &meta, &limits, &obstack)); + ck_assert_int_eq(meta.type, RPCMSG_T_RESPONSE); + ck_assert_int_eq(meta.request_id, 4); + ck_assert_ptr_nonnull(meta.extra); + ck_assert_int_eq(meta.extra->key, 41); + ck_assert_bdata(meta.extra->ptr.ptr, meta.extra->ptr.siz, B(0x48)); + ck_assert_ptr_nonnull(meta.extra->next); + ck_assert_int_eq(meta.extra->next->key, 42); + ck_assert_bdata(meta.extra->next->ptr.ptr, meta.extra->next->ptr.siz, + B(0x8e, 'f', 'o', 'o', '\0')); + ck_assert_ptr_nonnull(meta.extra->next->next); + ck_assert_int_eq(meta.extra->next->next->key, 43); + ck_assert_bdata(meta.extra->next->next->ptr.ptr, + meta.extra->next->next->ptr.siz, B(0x88, 0x41, 0x42, 0x43, 0xff)); + ck_assert_ptr_null(meta.extra->next->next->next); + ck_assert_int_eq(item.type, CPITEM_CONTAINER_END); + + obstack_free(&obstack, NULL); + unpack_free(unpack); +} +END_TEST diff --git a/tests/unit/libshvrpc/rpcmsg_meta.c b/tests/unit/libshvrpc/rpcmsg_meta.c deleted file mode 100644 index 878b2c3..0000000 --- a/tests/unit/libshvrpc/rpcmsg_meta.c +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include -#include -#define obstack_chunk_alloc malloc -#define obstack_chunk_free free - -#define SUITE "rpcmsg_meta" -#include -#include "unpack.h" -#include "item.h" - - -TEST_CASE(all) {} - -static const struct { - const char *str; - const struct rpcmsg_meta meta; -} unpack_obstack_d[] = { - {"<>null", (struct rpcmsg_meta){.request_id = INT64_MIN}}, - {"<8:42,9:\".app\",10:\"ping\",13:\"some,dev,acc\">null", - (struct rpcmsg_meta){ - .type = RPCMSG_T_REQUEST, - .request_id = 42, - .path = ".app", - .method = "ping", - .access_grant = RPCMSG_ACC_DEVEL, - }}, - {"<1:1,8:24u,11:[0,3]>null", - (struct rpcmsg_meta){ - .type = RPCMSG_T_RESPONSE, - .request_id = 24, - .cids = {.ptr = (void *)(const uint8_t[]){0x88, 0x40, 0x43, 0xff}, - .siz = 4}, - }}, -}; -ARRAY_TEST(all, unpack_obstack) { - cp_unpack_t unpack = unpack_cpon(_d.str); - struct cpitem item = (struct cpitem){}; - struct rpcmsg_meta meta = (struct rpcmsg_meta){}; - struct obstack obstack; - obstack_init(&obstack); - - ck_assert(rpcmsg_meta_unpack(unpack, &item, &meta, NULL, &obstack)); - ck_assert_int_eq(meta.type, _d.meta.type); - ck_assert_int_eq(meta.request_id, _d.meta.request_id); - ck_assert_pstr_eq(meta.path, _d.meta.path); - ck_assert_pstr_eq(meta.method, _d.meta.method); - ck_assert_int_eq(meta.access_grant, _d.meta.access_grant); - ck_assert_int_eq(meta.cids.siz, _d.meta.cids.siz); - ck_assert_mem_eq(meta.cids.ptr, _d.meta.cids.ptr, _d.meta.cids.siz); - cp_unpack(unpack, &item); - ck_assert_item_type(item, CP_ITEM_NULL); - - obstack_free(&obstack, NULL); - unpack_free(unpack); -} -END_TEST diff --git a/tests/unit/libshvrpc/rpcmsg_pack.c b/tests/unit/libshvrpc/rpcmsg_pack.c index f583873..938b27d 100644 --- a/tests/unit/libshvrpc/rpcmsg_pack.c +++ b/tests/unit/libshvrpc/rpcmsg_pack.c @@ -8,8 +8,14 @@ TEST_CASE(all, setup_packstream_pack_cpon, teardown_packstream_pack) {} TEST(all, request) { - rpcmsg_pack_request(packstream_pack, ".app", "ping", 42); - ck_assert_packstr("<1:1,8:42,9:\".app\",10:\"ping\">i{1"); + rpcmsg_pack_request(packstream_pack, ".app", "echo", 42); + ck_assert_packstr("<1:1,8:42,9:\".app\",10:\"echo\">i{1"); +} +END_TEST + +TEST(all, request_void) { + rpcmsg_pack_request_void(packstream_pack, ".broker/app", "ping", 42); + ck_assert_packstr("<1:1,8:42,9:\".broker/app\",10:\"ping\">i{1:null}"); } END_TEST @@ -25,4 +31,27 @@ TEST(all, chng) { } END_TEST -// TODO test pack response and error +TEST(all, resp) { + struct rpcmsg_meta meta = { + .request_id = 42, + .cids = {.ptr = (void *)(const uint8_t[]){0x88, 0x40, 0x43, 0xff}, .siz = 4}, + }; + rpcmsg_pack_response(packstream_pack, &meta); + ck_assert_packstr("<1:1,8:42,11:[0,3]>i{2"); +} +END_TEST + +TEST(all, error) { + struct rpcmsg_meta meta = {.request_id = 42}; + rpcmsg_pack_error(packstream_pack, &meta, RPCMSG_E_UNKNOWN, "Our error"); + ck_assert_packstr("<1:1,8:42>i{3:i{1:9,2:\"Our error\"}}"); +} +END_TEST + +TEST(all, ferr) { + struct rpcmsg_meta meta = {.request_id = 24}; + rpcmsg_pack_ferror( + packstream_pack, &meta, RPCMSG_E_INTERNAL_ERR, "Fail of %d", 42); + ck_assert_packstr("<1:1,8:24>i{3:i{1:4,2:\"Fail of 42\"}}"); +} +END_TEST diff --git a/tests/unit/libshvrpc/rpcurl.c b/tests/unit/libshvrpc/rpcurl.c index 03368bf..5a97388 100644 --- a/tests/unit/libshvrpc/rpcurl.c +++ b/tests/unit/libshvrpc/rpcurl.c @@ -27,14 +27,14 @@ static struct urld url_d[] = { (struct rpcurl){ .protocol = RPC_PROTOCOL_TCP, .location = "localhost", - .username = "test", + .login.username = "test", .port = 4242, }}, {"udp://test@localhost:4242", (struct rpcurl){ .protocol = RPC_PROTOCOL_UDP, .location = "localhost", - .username = "test", + .login.username = "test", .port = 4242, }}, {"tcp://localhost:4242?devid=foo&devmount=/dev/null", @@ -42,27 +42,27 @@ static struct urld url_d[] = { .protocol = RPC_PROTOCOL_TCP, .location = "localhost", .port = 4242, - .device_id = "foo", - .device_mountpoint = "/dev/null", + .login.device_id = "foo", + .login.device_mountpoint = "/dev/null", }}, {"tcp://localhost:4242?devid=foo&devmount=/dev/null&password=test", (struct rpcurl){ .protocol = RPC_PROTOCOL_TCP, .location = "localhost", .port = 4242, - .password = "test", - .device_id = "foo", - .device_mountpoint = "/dev/null", + .login.password = "test", + .login.device_id = "foo", + .login.device_mountpoint = "/dev/null", }}, {"tcp://localhost:4242?devid=foo&devmount=/dev/null&shapass=xxxxxxxx", (struct rpcurl){ .protocol = RPC_PROTOCOL_TCP, .location = "localhost", .port = 4242, - .password = "xxxxxxxx", - .login_type = RPC_LOGIN_SHA1, - .device_id = "foo", - .device_mountpoint = "/dev/null", + .login.password = "xxxxxxxx", + .login.login_type = RPC_LOGIN_SHA1, + .login.device_id = "foo", + .login.device_mountpoint = "/dev/null", }}, {"tcp://[::]:4242", (struct rpcurl){ @@ -111,7 +111,7 @@ static struct urld parse_d[] = { .protocol = RPC_PROTOCOL_TCP, .location = "localhost", .port = 3755, - .device_id = "foo", + .login.device_id = "foo", }}, }; static void parse_test(struct urld _d) { @@ -122,11 +122,11 @@ static void parse_test(struct urld _d) { ck_assert_int_eq(url->protocol, _d.url.protocol); ck_assert_pstr_eq(url->location, _d.url.location); ck_assert_int_eq(url->port, _d.url.port); - ck_assert_pstr_eq(url->username, _d.url.username); - ck_assert_pstr_eq(url->password, _d.url.password); - ck_assert_int_eq(url->login_type, _d.url.login_type); - ck_assert_pstr_eq(url->device_id, _d.url.device_id); - ck_assert_pstr_eq(url->device_mountpoint, _d.url.device_mountpoint); + ck_assert_pstr_eq(url->login.username, _d.url.login.username); + ck_assert_pstr_eq(url->login.password, _d.url.login.password); + ck_assert_int_eq(url->login.login_type, _d.url.login.login_type); + ck_assert_pstr_eq(url->login.device_id, _d.url.login.device_id); + ck_assert_pstr_eq(url->login.device_mountpoint, _d.url.login.device_mountpoint); rpcurl_free(url); } ARRAY_TEST(parse, parse_url, url_d) { diff --git a/tests/unit/utils/bdata.h b/tests/unit/utils/bdata.h index 5f0576a..f5bf0b6 100644 --- a/tests/unit/utils/bdata.h +++ b/tests/unit/utils/bdata.h @@ -14,4 +14,12 @@ struct bdata { .len = sizeof((const uint8_t[]){__VA_ARGS__}), \ } +#define ck_assert_bdata(BUF, LEN, BDATA) \ + do { \ + size_t __len = LEN; \ + struct bdata __b = BDATA; \ + ck_assert_int_eq(__len, __b.len); \ + ck_assert_mem_eq(BUF, __b.v, __len); \ + } while (false) + #endif diff --git a/tests/unit/utils/broker.c b/tests/unit/utils/broker.c new file mode 100644 index 0000000..844f607 --- /dev/null +++ b/tests/unit/utils/broker.c @@ -0,0 +1,86 @@ +#include "broker.h" +#include +#include +#include +#include +#include +#include +#include +#include "tmpdir.h" + +int shvbroker_tcp_port; +char *shvbroker_unix_path; + +static char *config_path; +static pid_t pid; + + +static bool isbound(int port) { + bool res = false; + char *expect; + ck_assert_int_ne( + asprintf(&expect, + "00000000000000000000000000000000:%04X 00000000000000000000000000000000:0000", + shvbroker_tcp_port), + -1); + char *l = NULL; + size_t n; + FILE *f = fopen("/proc/net/tcp6", "r"); + while (getline(&l, &n, f) != -1) { + if (strstr(l, expect) != NULL) { + res = true; + break; + } + } + fclose(f); + free(l); + free(expect); + return res; +} + +static int empty_port(void) { + while (true) { + int res = 3600 + (rand() % 1000); + if (!isbound(res)) + return res; + } +} + + +void setup_shvbroker(void) { + setup_tmpdir(); + config_path = tmpdir_path("shvc-brokerconfig.ini"); + FILE *f = fopen(config_path, "w"); + ck_assert_ptr_nonnull(f); + fputs("[listen]\n", f); + shvbroker_tcp_port = empty_port(); + fprintf(f, "internet = tcp://[::]:%d\n", shvbroker_tcp_port); + shvbroker_unix_path = tmpdir_path("shvbroker.sock"); + fprintf(f, "unix = localsocket:%s\n", shvbroker_unix_path); + fputs("[users.admin]\n", f); + fputs("password = admin!123\n", f); + fputs("roles = admin\n", f); + fputs("[users.shaadmin]\n", f); + fputs("sha1pass = 57a261a7bcb9e6cf1db80df501cdd89cee82957e\n", f); + fputs("roles = admin\n", f); + fputs("[roles.admin]\n", f); + fputs("rules = admin\n", f); + fputs("access = dev\n", f); + fputs("[rules.admin]\n", f); + fclose(f); + + char *args[] = {PYSHVBROKER, "-vvc", config_path, NULL}; + + ck_assert_int_eq(posix_spawn(&pid, PYSHVBROKER, NULL, NULL, args, NULL), 0); + + while (!isbound(shvbroker_tcp_port)) {} +} + +void teardown_shvbroker(void) { + unlink(config_path); + free(config_path); + kill(pid, SIGTERM); + waitpid(pid, NULL, 0); + unlink(shvbroker_unix_path); + free(shvbroker_unix_path); +} diff --git a/tests/unit/utils/broker.h b/tests/unit/utils/broker.h new file mode 100644 index 0000000..7641371 --- /dev/null +++ b/tests/unit/utils/broker.h @@ -0,0 +1,12 @@ +#ifndef BROKER_H +#define BROKER_H + + +extern int shvbroker_tcp_port; +extern char *shvbroker_unix_path; + +void setup_shvbroker(void); + +void teardown_shvbroker(void); + +#endif diff --git a/tests/unit/utils/item.h b/tests/unit/utils/item.h index 61efa6c..707ff14 100644 --- a/tests/unit/utils/item.h +++ b/tests/unit/utils/item.h @@ -15,38 +15,38 @@ do { \ ck_assert_int_eq(a.type, b.type); \ switch (a.type) { \ - case CP_ITEM_INVALID: \ - case CP_ITEM_NULL: \ + case CPITEM_INVALID: \ + case CPITEM_NULL: \ break; \ - case CP_ITEM_BOOL: \ + case CPITEM_BOOL: \ ck_assert(a.as.Bool == b.as.Bool); \ break; \ - case CP_ITEM_INT: \ + case CPITEM_INT: \ ck_assert_int_eq(a.as.Int, b.as.Int); \ break; \ - case CP_ITEM_UINT: \ + case CPITEM_UINT: \ ck_assert_uint_eq(a.as.UInt, b.as.UInt); \ break; \ - case CP_ITEM_DOUBLE: \ + case CPITEM_DOUBLE: \ ck_assert_double_eq(a.as.Double, b.as.Double); \ break; \ - case CP_ITEM_DECIMAL: \ + case CPITEM_DECIMAL: \ ck_assert_int_eq(a.as.Decimal.mantisa, b.as.Decimal.mantisa); \ ck_assert_int_eq(a.as.Decimal.exponent, b.as.Decimal.exponent); \ break; \ - case CP_ITEM_BLOB: \ - case CP_ITEM_STRING: \ + case CPITEM_BLOB: \ + case CPITEM_STRING: \ ck_assert_blob(a, b); \ break; \ - case CP_ITEM_DATETIME: \ + case CPITEM_DATETIME: \ ck_assert_int_eq(a.as.Datetime.msecs, b.as.Datetime.msecs); \ ck_assert_int_eq(a.as.Datetime.offutc, b.as.Datetime.offutc); \ break; \ - case CP_ITEM_LIST: \ - case CP_ITEM_MAP: \ - case CP_ITEM_IMAP: \ - case CP_ITEM_META: \ - case CP_ITEM_CONTAINER_END: \ + case CPITEM_LIST: \ + case CPITEM_MAP: \ + case CPITEM_IMAP: \ + case CPITEM_META: \ + case CPITEM_CONTAINER_END: \ break; \ default: \ abort(); \ @@ -58,7 +58,7 @@ #define ck_assert_item_bool(i, v) \ do { \ - ck_assert_int_eq(i.type, CP_ITEM_BOOL); \ + ck_assert_int_eq(i.type, CPITEM_BOOL); \ ck_assert_int_eq(i.as.Bool, v); \ } while (false) #define ck_assert_item_true(i) ck_assert_item_bool(i, true) @@ -66,25 +66,25 @@ #define ck_assert_item_int(i, v) \ do { \ - ck_assert_int_eq(i.type, CP_ITEM_INT); \ + ck_assert_int_eq(i.type, CPITEM_INT); \ ck_assert_int_eq(i.as.Int, v); \ } while (false) #define ck_assert_item_uint(i, v) \ do { \ - ck_assert_int_eq(i.type, CP_ITEM_UINT); \ + ck_assert_int_eq(i.type, CPITEM_UINT); \ ck_assert_uint_eq(i.as.Int, v); \ } while (false) #define ck_assert_item_double(i, v) \ do { \ - ck_assert_int_eq(i.type, CP_ITEM_DOUBLE); \ + ck_assert_int_eq(i.type, CPITEM_DOUBLE); \ ck_assert_double_eq(i.as.Double, v); \ } while (false) #define ck_assert_item_decimal(i, v) \ do { \ - ck_assert_int_eq(i.type, CP_ITEM_DECIMAL); \ + ck_assert_int_eq(i.type, CPITEM_DECIMAL); \ ck_assert_int_eq(i.as.Decimal.mantisa, v.mantisa); \ ck_assert_int_eq(i.as.Decimal.exponent, v.exponent); \ } while (false) diff --git a/tests/unit/utils/meson.build b/tests/unit/utils/meson.build index e155092..cf986bd 100644 --- a/tests/unit/utils/meson.build +++ b/tests/unit/utils/meson.build @@ -1,5 +1,10 @@ unittest_utils_src = files( + 'broker.c', 'packstream.c', + 'tmpdir.c', 'unpack.c', ) unittest_utils_includes = include_directories('.') +unittest_utils_c_args = [ + '-DPYSHVBROKER="' + pyshvbroker.full_path() + '"', +] diff --git a/tests/unit/utils/tmpdir.c b/tests/unit/utils/tmpdir.c new file mode 100644 index 0000000..6e55e1a --- /dev/null +++ b/tests/unit/utils/tmpdir.c @@ -0,0 +1,35 @@ +#include "tmpdir.h" +#include +#include +#include + + +char *tmpdir = NULL; + + +void setup_tmpdir(void) { + if (tmpdir) + return; + char *tmp = getenv("TMPDIR"); + if (tmp == NULL) + tmp = P_tmpdir; + ck_assert_int_gt(asprintf(&tmpdir, "%s/%s.XXXXXX", tmp, "shvc-testunit-"), 0); + ck_assert_ptr_eq(mkdtemp(tmpdir), tmpdir); +} + +void teardown_tmpdir(void) { + if (tmpdir == NULL) + return; + char *cmd; + ck_assert_int_gt(asprintf(&cmd, "rm -rf '%s'", tmpdir), 0); + ck_assert_int_eq(system(cmd), 0); + free(cmd); + free(tmpdir); + tmpdir = NULL; +} + +char *tmpdir_path(const char *name) { + char *res; + ck_assert_int_gt(asprintf(&res, "%s/%s", tmpdir, name), 0); + return res; +} diff --git a/tests/unit/utils/tmpdir.h b/tests/unit/utils/tmpdir.h new file mode 100644 index 0000000..947184e --- /dev/null +++ b/tests/unit/utils/tmpdir.h @@ -0,0 +1,13 @@ +#ifndef TMPDIR_H +#define TMPDIR_H + + +extern char *tmpdir; + +void setup_tmpdir(void); + +void teardown_tmpdir(void); + +char *tmpdir_path(const char *name); + +#endif From 071dbdcffbcbe6f67a2ad867148f4cc7dced4e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Fri, 15 Sep 2023 08:04:29 +0200 Subject: [PATCH 23/44] flake: change pyshv url because urltweak is no more --- flake.lock | 11 +++++------ flake.nix | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/flake.lock b/flake.lock index 30920ce..633a902 100644 --- a/flake.lock +++ b/flake.lock @@ -150,16 +150,15 @@ "shvapp": "shvapp" }, "locked": { - "lastModified": 1692968432, - "narHash": "sha256-WLpSNxzbH5W2eqHz+wdljFJERM+b5M49b5/e+o85agM=", - "ref": "urltweak", - "rev": "d208f1a53090fc4e46cb229ba61ede5a0dea12dd", - "revCount": 95, + "lastModified": 1694672975, + "narHash": "sha256-itjeksAIk2faG+UDJ0jB/Q2LiA1FHKXrGDlGTHpoOis=", + "ref": "refs/heads/master", + "rev": "5a4961a735cc02f11388b3f149fd43b3523545bd", + "revCount": 98, "type": "git", "url": "https://gitlab.com/elektroline-predator/pyshv.git" }, "original": { - "ref": "urltweak", "type": "git", "url": "https://gitlab.com/elektroline-predator/pyshv.git" } diff --git a/flake.nix b/flake.nix index c5e0d4b..22b0c06 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,7 @@ inputs = { check-suite.url = "github:cynerd/check-suite"; - pyshv.url = "git+https://gitlab.com/elektroline-predator/pyshv.git?ref=urltweak"; + pyshv.url = "git+https://gitlab.com/elektroline-predator/pyshv.git"; }; outputs = { From 92301cf7738020d08a7db919c8ee7db4249af615 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Fri, 15 Sep 2023 10:40:27 +0200 Subject: [PATCH 24/44] tests/unit/broker: fix spawn of the broker For some reason the IPv6 is not bound automatically in the container. This changes both pyshvbroker to bound on generic localhost as well as the IPv6 only bound check to the more generic one. --- tests/unit/utils/broker.c | 60 +++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/tests/unit/utils/broker.c b/tests/unit/utils/broker.c index 844f607..608d8c0 100644 --- a/tests/unit/utils/broker.c +++ b/tests/unit/utils/broker.c @@ -2,10 +2,16 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include +#include + #include "tmpdir.h" int shvbroker_tcp_port; @@ -16,33 +22,41 @@ static pid_t pid; static bool isbound(int port) { - bool res = false; - char *expect; - ck_assert_int_ne( - asprintf(&expect, - "00000000000000000000000000000000:%04X 00000000000000000000000000000000:0000", - shvbroker_tcp_port), - -1); - char *l = NULL; - size_t n; - FILE *f = fopen("/proc/net/tcp6", "r"); - while (getline(&l, &n, f) != -1) { - if (strstr(l, expect) != NULL) { - res = true; - break; - } + struct addrinfo hints; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = 0; + hints.ai_protocol = 0; + struct addrinfo *addrs; + char *p; + ck_assert_int_ne(asprintf(&p, "%d", port), -1); + int res = getaddrinfo("localhost", p, &hints, &addrs); + free(p); + if (res != 0) + return false; + + bool success = false; + struct addrinfo *addr; + int sock; + for (addr = addrs; !success && addr != NULL; addr = addr->ai_next) { + sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (sock == -1) + continue; + if (connect(sock, addr->ai_addr, addr->ai_addrlen) != -1) + success = true; + close(sock); } - fclose(f); - free(l); - free(expect); - return res; + + freeaddrinfo(addrs); + return success; } static int empty_port(void) { while (true) { int res = 3600 + (rand() % 1000); - if (!isbound(res)) + if (!isbound(res)) { return res; + } } } @@ -54,7 +68,7 @@ void setup_shvbroker(void) { ck_assert_ptr_nonnull(f); fputs("[listen]\n", f); shvbroker_tcp_port = empty_port(); - fprintf(f, "internet = tcp://[::]:%d\n", shvbroker_tcp_port); + fprintf(f, "internet = tcp://localhost:%d\n", shvbroker_tcp_port); shvbroker_unix_path = tmpdir_path("shvbroker.sock"); fprintf(f, "unix = localsocket:%s\n", shvbroker_unix_path); fputs("[users.admin]\n", f); @@ -73,6 +87,10 @@ void setup_shvbroker(void) { ck_assert_int_eq(posix_spawn(&pid, PYSHVBROKER, NULL, NULL, args, NULL), 0); + /* Wait for unix socket to be ready */ + struct stat stbuf; + while (stat(shvbroker_unix_path, &stbuf) == -1 && errno == ENOENT) {} + /* Wait for the TCP socket to be ready */ while (!isbound(shvbroker_tcp_port)) {} } From c13f4814193d2c45cfbd95a2aeae7c1eab9245e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Fri, 15 Sep 2023 12:43:55 +0200 Subject: [PATCH 25/44] Install header files to the subdirectory --- libshvbroker/meson.build | 2 +- libshvchainpack/meson.build | 2 +- libshvrpc/meson.build | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libshvbroker/meson.build b/libshvbroker/meson.build index ea399aa..db8c532 100644 --- a/libshvbroker/meson.build +++ b/libshvbroker/meson.build @@ -11,7 +11,7 @@ libshvbroker = library( ), install: not meson.is_subproject(), ) -install_headers(libshvbroker_headers) +install_headers(libshvbroker_headers, preserve_path: true) libshvbroker_dep = declare_dependency( include_directories: includes, diff --git a/libshvchainpack/meson.build b/libshvchainpack/meson.build index 4d22aaa..7cca63a 100644 --- a/libshvchainpack/meson.build +++ b/libshvchainpack/meson.build @@ -24,7 +24,7 @@ libshvchainpack = library( ), install: not meson.is_subproject(), ) -install_headers(libshvchainpack_headers) +install_headers(libshvchainpack_headers, preserve_path: true) libshvchainpack_dep = declare_dependency( dependencies: libshvchainpack_dependencies, diff --git a/libshvrpc/meson.build b/libshvrpc/meson.build index f9da07e..423aac2 100644 --- a/libshvrpc/meson.build +++ b/libshvrpc/meson.build @@ -32,7 +32,7 @@ libshvrpc = library( ), install: not meson.is_subproject(), ) -install_headers(libshvrpc_headers) +install_headers(libshvrpc_headers, preserve_path: true) libshvrpc_dep = declare_dependency( dependencies: libshvrpc_dependencies, From 75e26f53225376d9aaa524bdf6ec01ff037ca20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Fri, 15 Sep 2023 13:32:40 +0200 Subject: [PATCH 26/44] libshvrpc: link with libshvchainpack This ensures that user links not only with libshvrpc but also with libshvchainpack. One can't be used without the other. --- libshvrpc/meson.build | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libshvrpc/meson.build b/libshvrpc/meson.build index 423aac2..0d2f975 100644 --- a/libshvrpc/meson.build +++ b/libshvrpc/meson.build @@ -42,4 +42,8 @@ libshvrpc_dep = declare_dependency( pkg_mod = import('pkgconfig') -pkg_mod.generate(libshvrpc, description: 'Silicon Heaven RPC') +pkg_mod.generate( + libshvrpc, + libraries: [libshvchainpack], + description: 'Silicon Heaven RPC', +) From 76a5a1b79f12ffd2b032781e5372b27296008d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Thu, 21 Sep 2023 17:21:29 +0200 Subject: [PATCH 27/44] Add cpdeccmp and cpdecnorm to work with decimals --- include/shv/cp.h | 31 ++++++++++++++++++++++++- libshvchainpack/cp_pack.c | 4 ++-- libshvchainpack/cpdecimal.c | 13 ++++++++++- libshvchainpack/libshvchainpack.version | 1 + tests/unit/libshvchainpack/cpdecimal.c | 21 +++++++++++++++-- 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/include/shv/cp.h b/include/shv/cp.h index e7b8f03..a79aae9 100644 --- a/include/shv/cp.h +++ b/include/shv/cp.h @@ -147,12 +147,39 @@ struct cpdecimal { int exponent; }; +/*! Normalize decimal number. + * + * The normal format for decimal number is where mantisa is as small as possible + * without loosing precission. In other words unless mantisa is a zero the + * residue after division 10 must be non-zero. + * + * @param v: Decimal number to be normalized. + */ +void cpdecnorm(struct cpdecimal *v) __attribute__((nonnull)); + +/*! Compare two decimal numbers. + * + * The input must be normalized, otherwise assert is tripped. + * + * @returns a positive number if ``A`` is greater than ``B``, a negative number + * ``A`` is less than ``B`` and zero if ``A`` is equal to ``B``. + */ +#define cpdeccmp(A, B) \ + ({ \ + assert(A->mantisa % 10 || A->mantisa == 0); \ + assert(B->mantisa % 10 || B->mantisa == 0); \ + long long res = A->exponent - B->exponment; \ + if (res == 0) \ + res = A->mantisa - B->mantisa; \ + res; \ + }) + /*! Convert decimal number to double precision floating point number. * * @param v: Decimal value to be converted * @returns Approximate floating point representation of the decimal number. */ -double cpdectod(struct cpdecimal v); +double cpdectod(const struct cpdecimal v); /*! Convert double precision floating point number to decimal number. * @@ -383,6 +410,8 @@ struct cpon_state { }; /*! Pack next item to CPON data format. + * + * @returns Number of bytes read from @ref f. */ size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) __attribute__((nonnull)); diff --git a/libshvchainpack/cp_pack.c b/libshvchainpack/cp_pack.c index f7b1a9c..2f1fc94 100644 --- a/libshvchainpack/cp_pack.c +++ b/libshvchainpack/cp_pack.c @@ -1,7 +1,7 @@ #include #include -static ssize_t cp_pack_chainpack_func(void *ptr, const struct cpitem *item) { +static size_t cp_pack_chainpack_func(void *ptr, const struct cpitem *item) { struct cp_pack_chainpack *p = ptr; return chainpack_pack(p->f, item); } @@ -19,7 +19,7 @@ static void cpon_state_realloc(struct cpon_state *state) { state->ctx = realloc(state->ctx, state->cnt * sizeof *state->ctx); } -static ssize_t cp_pack_cpon_func(void *ptr, const struct cpitem *item) { +static size_t cp_pack_cpon_func(void *ptr, const struct cpitem *item) { struct cp_pack_cpon *p = ptr; return cpon_pack(p->f, &p->state, item); } diff --git a/libshvchainpack/cpdecimal.c b/libshvchainpack/cpdecimal.c index 7727ccd..376cf03 100644 --- a/libshvchainpack/cpdecimal.c +++ b/libshvchainpack/cpdecimal.c @@ -1,7 +1,18 @@ #include #include -double cpdectod(struct cpdecimal v) { +void cpdecnorm(struct cpdecimal *v) { + if (v->mantisa == 0) { + v->exponent = 0; + return; + } + while (v->mantisa % 10 == 0) { + v->mantisa /= 10; + v->exponent++; + } +} + +double cpdectod(const struct cpdecimal v) { double res = v.mantisa; if (v.exponent >= 0) { for (long i = 0; i < v.exponent; i++) diff --git a/libshvchainpack/libshvchainpack.version b/libshvchainpack/libshvchainpack.version index 4ca73ba..2b71384 100644 --- a/libshvchainpack/libshvchainpack.version +++ b/libshvchainpack/libshvchainpack.version @@ -2,6 +2,7 @@ V0.0 { global: # shv/cp.h cpitem_type_str; + cpdecnorm; cpdectod; cpdtodec; cpdttotm; diff --git a/tests/unit/libshvchainpack/cpdecimal.c b/tests/unit/libshvchainpack/cpdecimal.c index 763014f..cccf950 100644 --- a/tests/unit/libshvchainpack/cpdecimal.c +++ b/tests/unit/libshvchainpack/cpdecimal.c @@ -6,17 +6,34 @@ TEST_CASE(all) {} +static const struct { + struct cpdecimal val, norm; +} decnorm_d[] = { + {{.mantisa = 1}, {.mantisa = 1}}, + {{.mantisa = 100}, {.mantisa = 1, .exponent = 2}}, + {{.mantisa = 100, .exponent = -7}, {.mantisa = 1, .exponent = -5}}, + {{}, {}}, + {{.exponent = 100}, {}}, +}; +ARRAY_TEST(all, decnorm) { + struct cpdecimal d = _d.val; + cpdecnorm(&d); + ck_assert_int_eq(d.mantisa, _d.norm.mantisa); + ck_assert_int_eq(d.exponent, _d.norm.exponent); +} +END_TEST + static const struct { double f; struct cpdecimal d; -} _d[] = { +} dec_d[] = { {0., (struct cpdecimal){}}, {1., (struct cpdecimal){.mantisa = 1}}, {-1., (struct cpdecimal){.mantisa = -1}}, {42.124, (struct cpdecimal){.mantisa = 42124, .exponent = -3}}, }; -ARRAY_TEST(all, dectod, _d) { +ARRAY_TEST(all, dectod, dec_d) { ck_assert_double_eq_tol(cpdectod(_d.d), _d.f, 0.0000001); } END_TEST From 56dc0931878adfa23fa7fbe55ede14ff01f48fe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Sun, 24 Sep 2023 13:35:44 +0200 Subject: [PATCH 28/44] Implement demo device and rework rpchandler --- demo/device_handler.c | 132 ++++++++++++--- demo/device_handler.h | 13 +- demo/main.c | 25 +-- include/shv/cp.h | 3 +- include/shv/rpcapp.h | 17 ++ include/shv/rpchandler.h | 55 +++++-- include/shv/rpcmsg.h | 24 +-- include/shv/rpcnode.h | 14 -- include/shv/rpcrespond.h | 11 +- include/shv/rpctoplevel.h | 20 --- libshvrpc/libshvrpc.version | 14 +- libshvrpc/meson.build | 2 +- libshvrpc/rpcapp.c | 97 +++++++++++ libshvrpc/rpchandler.c | 314 ++++++++++++++++++++++++++---------- libshvrpc/rpcmsg_pack.c | 25 ++- libshvrpc/rpcrespond.c | 23 ++- libshvrpc/rpctoplevel.c | 80 --------- 17 files changed, 579 insertions(+), 290 deletions(-) create mode 100644 include/shv/rpcapp.h delete mode 100644 include/shv/rpctoplevel.h create mode 100644 libshvrpc/rpcapp.c delete mode 100644 libshvrpc/rpctoplevel.c diff --git a/demo/device_handler.c b/demo/device_handler.c index 02e4fc2..c3e36f3 100644 --- a/demo/device_handler.c +++ b/demo/device_handler.c @@ -1,33 +1,123 @@ #include "device_handler.h" +#include "shv/rpchandler.h" +#include -static enum rpchandler_func_res track(struct demo_handler *h, rpcreceive_t receive, - cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta) { - return false; + +static void rpc_ls(void *cookie, const char *path, struct rpchandler_ls_ctx *ctx) { + if (path == NULL || *path == '\0') + rpchandler_ls_result(ctx, "track"); + else if (!strcmp(path, "track")) { + char str[2]; + str[1] = '\0'; + for (int i = 1; i <= 9; i++) { + str[0] = '0' + i; + rpchandler_ls_result(ctx, str); + } + } } -static const char *pthcmp(const char *pth, const char *name) { - size_t len = strlen(name); - if (!strncmp(pth, name, len) && (pth[len] == '\0' || pth[len] == '/')) - return &pth[len]; - return NULL; +static int trackid(const char *path) { + const char prefix[] = "track/"; + const size_t prefix_len = sizeof(prefix) / sizeof(*prefix) - 1; + if (path && !strncmp(path, prefix, prefix_len) && path[prefix_len] > '0' && + path[prefix_len] <= '9' && path[prefix_len + 1] == '\0') { + return path[prefix_len] - '0' - 1; + } + return -1; } -enum rpchandler_func_res demo_device_handler_func(void *ptr, rpcreceive_t receive, - cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta) { - struct demo_handler *h = ptr; - if (meta->path == NULL || *meta->path == '\0') { - /* We won't be called for anything else because top level is handled by - * rpctoplevel. - */ - assert(!strcmp(meta->method, "ls")); + +static void rpc_dir(void *cookie, const char *path, struct rpchandler_dir_ctx *ctx) { + if (trackid(path) != -1) { + rpchandler_dir_result(ctx, "get", RPCNODE_DIR_RET_PARAM, + RPCNODE_DIR_F_GETTER, RPCMSG_ACC_READ, NULL); + rpchandler_dir_result(ctx, "set", RPCNODE_DIR_VOID_PARAM, + RPCNODE_DIR_F_SETTER, RPCMSG_ACC_WRITE, NULL); } - const char *pth1, *pth2; - if ((pth1 = pthcmp(meta->path, "track"))) { - if (*pth1 == '\0') { - return track(h, receive, unpack, item, meta); +} + +static enum rpchandler_func_res rpc_msg( + void *cookie, struct rpcreceive *receive, const struct rpcmsg_meta *meta) { + struct device_state *state = cookie; + int tid = trackid(meta->path); + if (tid != -1) { + if (!strcmp(meta->method, "get")) { + if (!rpcreceive_validmsg(receive)) + return RPCHFR_RECV_ERR; + cp_pack_t pack = rpcreceive_response_new(receive); + rpcmsg_pack_response(pack, meta); + cp_pack_list_begin(pack); + for (size_t i = 0; i < state->tracks[tid].cnt; i++) + cp_pack_int(pack, state->tracks[tid].values[i]); + cp_pack_container_end(pack); + cp_pack_container_end(pack); + rpcreceive_response_send(receive); + return RPCHFR_HANDLED; + } else if (!strcmp(meta->method, "set")) { + size_t siz = 2; + size_t cnt = 0; + int *res = malloc(sizeof(int) * siz); + bool invalid_param = false; + cp_unpack(receive->unpack, &receive->item); + if (receive->item.type == CPITEM_LIST) { + do { + cp_unpack(receive->unpack, &receive->item); + if (receive->item.type == CPITEM_INT) { + if (cnt >= siz) + res = realloc(res, sizeof(int) * (siz *= 2)); + res[cnt++] = receive->item.as.Int; + continue; + } else if (receive->item.type != CPITEM_CONTAINER_END) { + free(res); + printf("We got %d\n", receive->item.type); + invalid_param = true; + } + break; + } while (receive->item.type != CPITEM_CONTAINER_END); + } else + invalid_param = true; + if (!rpcreceive_validmsg(receive)) + return RPCHFR_RECV_ERR; + + cp_pack_t pack = rpcreceive_response_new(receive); + if (invalid_param) + rpcmsg_pack_error(pack, meta, RPCMSG_E_INVALID_PARAMS, + "Only list of integers is allowed"); + else { + free(state->tracks[tid].values); + state->tracks[tid].values = res; + state->tracks[tid].cnt = cnt; + rpcmsg_pack_response_void(pack, meta); + } + rpcreceive_response_send(receive); + return RPCHFR_HANDLED; } } + return RPCHFR_UNHANDLED; +} + + +const struct rpchandler_funcs device_handler_funcs = { + .ls = rpc_ls, + .dir = rpc_dir, + .msg = rpc_msg, +}; + + +struct device_state *device_state_new(void) { + struct device_state *res = malloc(sizeof *res); + for (int i = 0; i < 9; i++) { + res->tracks[i].cnt = i + 1; + res->tracks[i].values = malloc((i + 1) * sizeof(int)); + for (size_t y = 0; y <= i; y++) + res->tracks[i].values[y] = y + 1; + } + return res; +} - return false; +void device_state_free(struct device_state *state) { + for (int i = 0; i < 9; i++) + free(state->tracks[i].values); + free(state); } diff --git a/demo/device_handler.h b/demo/device_handler.h index 03cb635..d7c5e9e 100644 --- a/demo/device_handler.h +++ b/demo/device_handler.h @@ -3,12 +3,17 @@ #include -struct demo_handler { - rpchandler_func func; +struct device_state { + struct track { + int *values; + size_t cnt; + } tracks[9]; }; +extern const struct rpchandler_funcs device_handler_funcs; -enum rpchandler_func_res demo_device_handler_func(void *ptr, rpcreceive_t receive, - cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta); +struct device_state *device_state_new(void); + +void device_state_free(struct device_state *state); #endif diff --git a/demo/main.c b/demo/main.c index 9268c2c..5b7d72c 100644 --- a/demo/main.c +++ b/demo/main.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "opts.h" #include "device_handler.h" @@ -49,15 +49,17 @@ int main(int argc, char **argv) { } rpcurl_free(rpcurl); - rpctoplevel_t toplevel = rpctoplevel_new("demo-device", PROJECT_VERSION, true); + struct device_state *state = device_state_new(); - struct demo_handler h = {.func = demo_device_handler_func}; - const rpchandler_func *funcs[] = { - rpctoplevel_func(toplevel), - &h.func, - NULL, - }; - rpchandler_t handler = rpchandler_new(client, funcs, NULL); + rpcapp_t app = rpcapp_new("demo-device", PROJECT_VERSION); + + struct rpchandler_stage stages[4]; + stages[0] = rpcapp_handler_stage(app); + stages[1] = (struct rpchandler_stage){ + .funcs = &device_handler_funcs, .cookie = state}; + stages[2] = (struct rpchandler_stage){}; + + rpchandler_t handler = rpchandler_new(client, stages, NULL); assert(handler); int res = 0; @@ -67,7 +69,7 @@ int main(int argc, char **argv) { &pfd, 1, rpcclient_maxsleep(client, RPC_DEFAULT_IDLE_TIME) * 1000); if (pr == 0) { cp_pack_t pack = rpchandler_msg_new(handler); - rpcmsg_pack_request_void(pack, ".broker/app", "ping", 0); + rpcmsg_pack_request_void(pack, ".app", "ping", 0); rpchandler_msg_send(handler); } else if (pr == -1 || pfd.revents & POLLERR) { fprintf(stderr, "Poll error: %s\n", strerror(errno)); @@ -82,7 +84,8 @@ int main(int argc, char **argv) { } rpchandler_destroy(handler); - rpctoplevel_destroy(toplevel); + rpcapp_destroy(app); + device_state_free(state); rpcclient_destroy(client); rpcclient_logger_destroy(logger); return res; diff --git a/include/shv/cp.h b/include/shv/cp.h index a79aae9..292c5c5 100644 --- a/include/shv/cp.h +++ b/include/shv/cp.h @@ -233,7 +233,8 @@ enum cperror { CPERR_INVALID, /*! Data in stream had size outside of the value supported by SHVC. * - * This applies to numeric as well as too long strings or blobs. + * This applies to integers where SHVC for optimality chooses to use + * sometimes int sometimes long long. */ CPERR_OVERFLOW, }; diff --git a/include/shv/rpcapp.h b/include/shv/rpcapp.h new file mode 100644 index 0000000..0200f03 --- /dev/null +++ b/include/shv/rpcapp.h @@ -0,0 +1,17 @@ +#ifndef SHV_RPCAPP_H +#define SHV_RPCAPP_H + +#include + +typedef struct rpcapp *rpcapp_t; + +void rpcapp_destroy(rpcapp_t rpcapp); + +rpcapp_t rpcapp_new(const char *name, const char *version) + __attribute__((malloc, malloc(rpcapp_destroy, 1))); + +struct rpchandler_stage rpcapp_handler_stage(rpcapp_t rpcapp) + __attribute__((nonnull)); + + +#endif diff --git a/include/shv/rpchandler.h b/include/shv/rpchandler.h index 107e23e..b293605 100644 --- a/include/shv/rpchandler.h +++ b/include/shv/rpchandler.h @@ -5,10 +5,14 @@ #include #include #include +#include -typedef struct rpchandler *rpchandler_t; -typedef struct rpcreceive *rpcreceive_t; +struct rpcreceive { + cp_unpack_t unpack; + struct cpitem item; + struct obstack obstack; +}; enum rpchandler_func_res { RPCHFR_UNHANDLED, @@ -17,16 +21,33 @@ enum rpchandler_func_res { RPCHFR_SEND_ERR, }; -typedef enum rpchandler_func_res (*rpchandler_func)(void *ptr, rpcreceive_t receive, - cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta); +struct rpchandler_ls_ctx; + +struct rpchandler_dir_ctx; + +struct rpchandler_funcs { + enum rpchandler_func_res (*msg)(void *cookie, struct rpcreceive *receive, + const struct rpcmsg_meta *meta); + void (*ls)(void *cookie, const char *path, struct rpchandler_ls_ctx *ctx); + void (*dir)(void *cookie, const char *path, struct rpchandler_dir_ctx *ctx); +}; + +struct rpchandler_stage { + const struct rpchandler_funcs *funcs; + void *cookie; +}; + +typedef struct rpchandler *rpchandler_t; void rpchandler_destroy(rpchandler_t rpchandler); -rpchandler_t rpchandler_new(rpcclient_t client, const rpchandler_func **funcs, - const struct rpcmsg_meta_limits *limits) +rpchandler_t rpchandler_new(rpcclient_t client, + const struct rpchandler_stage *stages, const struct rpcmsg_meta_limits *limits) __attribute__((nonnull(1), malloc, malloc(rpchandler_destroy, 1))); +void rpchandler_change_stages(rpchandler_t handler, + const struct rpchandler_stage *stages) __attribute__((nonnull)); bool rpchandler_next(rpchandler_t rpchandler) __attribute__((nonnull)); @@ -44,13 +65,27 @@ bool rpchandler_msg_send(rpchandler_t rpchandler) __attribute__((nonnull)); bool rpchandler_msg_drop(rpchandler_t rpchandler) __attribute__((nonnull)); -bool rpcreceive_validmsg(rpcreceive_t receive) __attribute__((nonnull)); +bool rpcreceive_validmsg(struct rpcreceive *receive) __attribute__((nonnull)); + +cp_pack_t rpcreceive_response_new(struct rpcreceive *receive) + __attribute__((nonnull)); + +bool rpcreceive_response_send(struct rpcreceive *receive) __attribute__((nonnull)); + +bool rpcreceive_response_drop(struct rpcreceive *receive) __attribute__((nonnull)); + -cp_pack_t rpcreceive_response_new(rpcreceive_t receive) __attribute__((nonnull)); +bool rpchandler_ls_result(struct rpchandler_ls_ctx *context, const char *name) + __attribute__((nonnull)); -bool rpcreceive_response_send(rpcreceive_t receive) __attribute__((nonnull)); +const char *rpchandler_ls_name(struct rpchandler_ls_ctx *context) + __attribute__((nonnull)); -bool rpcreceive_response_drop(rpcreceive_t receive) __attribute__((nonnull)); +bool rpchandler_dir_result(struct rpchandler_dir_ctx *context, const char *name, + enum rpcnode_dir_signature signature, int flags, enum rpcmsg_access access, + const char *description) __attribute__((nonnull(1, 2))); +const char *rpchandler_dir_name(struct rpchandler_ls_ctx *context) + __attribute__((nonnull)); #endif diff --git a/include/shv/rpcmsg.h b/include/shv/rpcmsg.h index e7f798f..ede338f 100644 --- a/include/shv/rpcmsg.h +++ b/include/shv/rpcmsg.h @@ -48,8 +48,8 @@ size_t rpcmsg_pack_request(cp_pack_t pack, const char *path, const char *method, /*! Pack request message with no parameters. * - * This provides an easy way to just pack message without arguments. It calls - * `rpcmsg_pack_request` and packs additional `NULL` and `CONTAINER_END` to + * This provides an easy way to just pack message without arguments. It packs + * the same head as `rpcmsg_pack_request` plus additional `CONTAINER_END` to * complete the message. Such message can be immediately sent. * * @param pack: pack context the meta should be written to. @@ -59,21 +59,8 @@ size_t rpcmsg_pack_request(cp_pack_t pack, const char *path, const char *method, * @param rid: request identifier. Thanks to this number you can associate * response with requests. */ -__attribute__((nonnull(1, 3))) static inline size_t rpcmsg_pack_request_void( - cp_pack_t pack, const char *path, const char *method, int rid) { - size_t res = 0; -#define RES(V) \ - ({ \ - size_t __res = V; \ - res += __res; \ - __res; \ - }) - if (RES(rpcmsg_pack_request(pack, path, method, rid)) && - RES(cp_pack_null(pack)) && RES(cp_pack_container_end(pack))) - return res; -#undef RES - return 0; -} +size_t rpcmsg_pack_request_void(cp_pack_t pack, const char *path, + const char *method, int rid) __attribute__((nonnull(1, 3))); /*! Pack signal message meta and open imap. The followup packed data are * signaled values. The message needs to be terminated with container end @@ -277,6 +264,9 @@ bool rpcmsg_head_unpack(cp_unpack_t unpack, struct cpitem *item, size_t rpcmsg_pack_response(cp_pack_t, const struct rpcmsg_meta *meta) __attribute__((nonnull)); +size_t rpcmsg_pack_response_void(cp_pack_t, const struct rpcmsg_meta *meta) + __attribute__((nonnull)); + enum rpcmsg_error { RPCMSG_E_NO_ERROR, RPCMSG_E_INVALID_REQUEST, diff --git a/include/shv/rpcnode.h b/include/shv/rpcnode.h index fcb0e0b..5dd7871 100644 --- a/include/shv/rpcnode.h +++ b/include/shv/rpcnode.h @@ -4,12 +4,6 @@ #include #include -#define RPCNODE_DIR_ - -struct rpcnode_dir_request { - const char *name; - bool list_of_maps; -}; enum rpcnode_dir_signature { RPCNODE_DIR_VOID_VOID, @@ -23,19 +17,11 @@ enum rpcnode_dir_signature { #define RPCNODE_DIR_F_SETTER (1 << 2) #define RPCNODE_DIR_F_LARGE_RESULT_HINT (1 << 3) -bool rpcnode_handle_dir(cp_unpack_t unpack, struct cpitem *item, - struct rpcnode_dir_request *dirr, struct obstack *obstack) - __attribute__((nonnull)); - bool rpcnode_pack_dir(cp_pack_t pack, bool list_of_maps, const char *name, enum rpcnode_dir_signature signature, int flags, enum rpcmsg_access acc, const char *description) __attribute__((nonnull(1, 3))); -typedef int rpcnode_ls_request; - -rpcnode_ls_request rpcnode_unpack_ls(cp_unpack_t unpack, struct cpitem *item); - void rpcnode_pack_ls(cp_pack_t pack); diff --git a/include/shv/rpcrespond.h b/include/shv/rpcrespond.h index 29b2f49..56a59a4 100644 --- a/include/shv/rpcrespond.h +++ b/include/shv/rpcrespond.h @@ -13,19 +13,16 @@ void rpcresponder_destroy(rpcresponder_t responder); rpcresponder_t rpcresponder_new(void) __attribute__((malloc, malloc(rpcresponder_destroy, 1))); -rpchandler_func *rpcresponder_func(rpcresponder_t responder) - __attribute__((nonnull, returns_nonnull)); +struct rpchandler_stage rpcresponder_handler_stage(rpcresponder_t responder) + __attribute__((nonnull)); typedef struct rpcrespond *rpcrespond_t; rpcrespond_t rpcrespond_expect(rpcresponder_t responder, int request_id) __attribute__((nonnull)); -bool rpcrespond_waitfor(rpcrespond_t respond, cp_unpack_t *unpack, - struct cpitem **item, int timeout) __attribute__((nonnull)); - -const struct rpcmsg_meta *rpcrespond_meta(rpcrespond_t respond) - __attribute__((nonnull)); +bool rpcrespond_waitfor(rpcrespond_t respond, struct rpcreceive **receive, + struct rpcmsg_meta **meta, int timeout) __attribute__((nonnull)); bool rpcrespond_validmsg(rpcrespond_t respond) __attribute__((nonnull)); diff --git a/include/shv/rpctoplevel.h b/include/shv/rpctoplevel.h deleted file mode 100644 index 6aedd67..0000000 --- a/include/shv/rpctoplevel.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef SHV_RPCRESPOND_H -#define SHV_RPCRESPOND_H - -#include -#include -#include - - -typedef struct rpctoplevel *rpctoplevel_t; - -void rpctoplevel_destroy(rpctoplevel_t toplevel); - -rpctoplevel_t rpctoplevel_new(const char *app_name, const char *app_version, - bool device) __attribute__((malloc, malloc(rpctoplevel_destroy, 1))); - -rpchandler_func *rpctoplevel_func(rpctoplevel_t toplevel) - __attribute__((nonnull, returns_nonnull)); - - -#endif diff --git a/libshvrpc/libshvrpc.version b/libshvrpc/libshvrpc.version index 19ec927..a907573 100644 --- a/libshvrpc/libshvrpc.version +++ b/libshvrpc/libshvrpc.version @@ -2,12 +2,14 @@ V0.0 { global: # shv/rpcmsg.h rpcmsg_pack_request; + rpcmsg_pack_request_void; rpcmsg_pack_signal; rpcmsg_access_str; rpcmsg_access_extract; rpcmsg_access_unpack; rpcmsg_head_unpack; rpcmsg_pack_response; + rpcmsg_pack_response_void; rpcmsg_pack_error; rpcmsg_pack_ferror; rpcmsg_pack_vferror; @@ -43,11 +45,15 @@ V0.0 { rpcreceive_response_new; rpcreceive_response_send; rpcreceive_response_drop; + rpchandler_ls_result; + rpchandler_ls_name; + rpchandler_dir_result; + rpchandler_dir_name; - # shv/rpctoplevel.h - rpctoplevel_new; - rpctoplevel_destroy; - rpctoplevel_func; + # shv/rpcapp.h + rpcapp_destroy; + rpcapp_new; + rpcapp_handler_stage; local: *; }; diff --git a/libshvrpc/meson.build b/libshvrpc/meson.build index 0d2f975..e2913d7 100644 --- a/libshvrpc/meson.build +++ b/libshvrpc/meson.build @@ -1,5 +1,6 @@ libshvrpc_sources = [ files( + 'rpcapp.c', 'rpcclient_connect.c', 'rpcclient_datagram.c', 'rpcclient_log.c', @@ -11,7 +12,6 @@ libshvrpc_sources = [ 'rpcmsg_head.c', 'rpcmsg_pack.c', 'rpcrespond.c', - 'rpctoplevel.c', 'rpcurl.c', ), gperf.process('rpcmsg_access.gperf'), diff --git a/libshvrpc/rpcapp.c b/libshvrpc/rpcapp.c new file mode 100644 index 0000000..168a361 --- /dev/null +++ b/libshvrpc/rpcapp.c @@ -0,0 +1,97 @@ +#include +#include + +struct rpcapp { + const char *name; + const char *version; +}; + + +static void rpc_ls(void *cookie, const char *path, struct rpchandler_ls_ctx *ctx) { + if (path == NULL || *path == '\0') + rpchandler_ls_result(ctx, ".app"); +} + + +static void rpc_dir(void *cookie, const char *path, struct rpchandler_dir_ctx *ctx) { + if (!strcmp(path, ".app")) { + rpchandler_dir_result(ctx, "shvVersionMajor", RPCNODE_DIR_RET_VOID, + RPCNODE_DIR_F_GETTER, RPCMSG_ACC_BROWSE, NULL); + rpchandler_dir_result(ctx, "shvVersionMinor", RPCNODE_DIR_RET_VOID, + RPCNODE_DIR_F_GETTER, RPCMSG_ACC_BROWSE, NULL); + rpchandler_dir_result(ctx, "appName", RPCNODE_DIR_RET_VOID, + RPCNODE_DIR_F_GETTER, RPCMSG_ACC_BROWSE, NULL); + rpchandler_dir_result(ctx, "appVersion", RPCNODE_DIR_RET_VOID, + RPCNODE_DIR_F_GETTER, RPCMSG_ACC_BROWSE, NULL); + } +} + +static enum rpchandler_func_res rpc_msg( + void *cookie, struct rpcreceive *receive, const struct rpcmsg_meta *meta) { + struct rpcapp *rpcapp = cookie; + enum methods { + UNKNOWN, + SHV_VERSION_MAJOR, + SHV_VERSION_MINOR, + APP_NAME, + APP_VERSION, + } method = UNKNOWN; + if (meta->path && !strcmp(meta->path, ".app")) { + if (!strcmp(meta->method, "shvVersionMajor")) + method = SHV_VERSION_MAJOR; + else if (!strcmp(meta->method, "shvVersionMinor")) + method = SHV_VERSION_MINOR; + else if (!strcmp(meta->method, "appName")) + method = APP_NAME; + else if (!strcmp(meta->method, "appVersion")) + method = APP_VERSION; + } + if (method == UNKNOWN) + return RPCHFR_UNHANDLED; + + if (!rpcreceive_validmsg(receive)) + return RPCHFR_RECV_ERR; + cp_pack_t pack = rpcreceive_response_new(receive); + rpcmsg_pack_response(pack, meta); + switch (method) { + case SHV_VERSION_MAJOR: + cp_pack_int(pack, 3); + break; + case SHV_VERSION_MINOR: + cp_pack_int(pack, 0); + break; + case APP_NAME: + cp_pack_str(pack, rpcapp->name ?: "shvc"); // TODO + break; + case APP_VERSION: + cp_pack_str(pack, rpcapp->version ?: PROJECT_VERSION); // TODO + break; + case UNKNOWN: + abort(); /* Can't happen */ + } + cp_pack_container_end(pack); + rpcreceive_response_send(receive); + return RPCHFR_HANDLED; +} + +const struct rpchandler_funcs rpc_funcs = { + .ls = rpc_ls, + .dir = rpc_dir, + .msg = rpc_msg, +}; + + +rpcapp_t rpcapp_new(const char *name, const char *version) { + rpcapp_t res = malloc(sizeof *res); + res->name = name; + res->version = version; + return res; +} + +void rpcapp_destroy(rpcapp_t rpcapp) { + free(rpcapp); +} + +struct rpchandler_stage rpcapp_handler_stage(rpcapp_t rpcapp) { + return (struct rpchandler_stage){.funcs = &rpc_funcs, .cookie = rpcapp}; +} diff --git a/libshvrpc/rpchandler.c b/libshvrpc/rpchandler.c index e3c59c1..6543acf 100644 --- a/libshvrpc/rpchandler.c +++ b/libshvrpc/rpchandler.c @@ -1,3 +1,4 @@ +#include "shv/rpcmsg.h" #include #include #include @@ -7,103 +8,199 @@ #define obstack_chunk_free free struct rpchandler { - const rpchandler_func **funcs; + struct rpcreceive recv; + + const struct rpchandler_stage *stages; const struct rpcmsg_meta_limits *meta_limits; - struct rpcreceive { - rpcclient_t client; - /* Prevent from sending multiple messages. - * Sending in the receive thread can very easily result in deadlock if - * two SHVC handlers are put against each other. On the other hand it is - * very convenient to immediately respond to the request and thus we do - * a compromise, we allow sending one message after request and nothing - * more. - */ - bool can_respond; - /* We use two mutexes to implement priority locking. The thread - * receiving messages needs to have priority over others and thus it has - * a dedicated lock. Regular locks need to check if they can lock the - * prioprity lock and just release the regular lock if they can't. The - * priority thread takes priority lock to force others to yield the - * primary lock. - */ - pthread_mutex_t mutex, pmutex; - } recv; + rpcclient_t client; + /* Prevent from sending multiple messages. + * Sending in the receive thread can very easily result in deadlock if + * two SHVC handlers are put against each other. On the other hand it is + * very convenient to immediately respond to the request and thus we do + * a compromise, we allow sending one message after request and nothing + * more. + */ + bool can_respond; + /* We use two mutexes to implement priority locking. The thread + * receiving messages needs to have priority over others and thus it has + * a dedicated lock. Regular locks need to check if they can lock the + * prioprity lock and just release the regular lock if they can't. The + * priority thread takes priority lock to force others to yield the + * primary lock. + */ + pthread_mutex_t mutex, pmutex; int nextrid; - struct obstack obs; }; -struct rpchandler_response { - rpcclient_t client; - pthread_mutex_t *mutex, *pmutex; +struct rpchandler_x_ctx { + char *name; + cp_pack_t pack; + bool located; }; -rpchandler_t rpchandler_new(rpcclient_t client, const rpchandler_func **funcs, +struct rpchandler_ls_ctx { + struct rpchandler_x_ctx x; +}; + +struct rpchandler_dir_ctx { + struct rpchandler_x_ctx x; +}; + +rpchandler_t rpchandler_new(rpcclient_t client, + const struct rpchandler_stage *stages, const struct rpcmsg_meta_limits *limits) { struct rpchandler *res = malloc(sizeof *res); - res->funcs = funcs; + res->stages = stages; res->meta_limits = limits; - res->recv.client = client; - pthread_mutex_init(&res->recv.mutex, NULL); - pthread_mutex_init(&res->recv.pmutex, NULL); + res->client = client; + pthread_mutex_init(&res->mutex, NULL); + pthread_mutex_init(&res->pmutex, NULL); /* There can be login in front of this which uses 1-3 as request ID. * Technically we could use here even 1, because those requests are already * handled and thus no collision can happen, but let's not. */ res->nextrid = 4; - obstack_init(&res->obs); + obstack_init(&res->recv.obstack); return res; } void rpchandler_destroy(rpchandler_t rpchandler) { if (rpchandler == NULL) return; - obstack_free(&rpchandler->obs, NULL); - pthread_mutex_destroy(&rpchandler->recv.mutex); - pthread_mutex_destroy(&rpchandler->recv.pmutex); + obstack_free(&rpchandler->recv.obstack, NULL); + pthread_mutex_destroy(&rpchandler->mutex); + pthread_mutex_destroy(&rpchandler->pmutex); free(rpchandler); } -bool rpchandler_next(struct rpchandler *rpchandler) { - if (!(rpcclient_nextmsg(rpchandler->recv.client))) +static bool parse_ls_dir(struct rpcreceive *recv, struct rpcmsg_meta *meta, + struct rpchandler_x_ctx *ctx) { + ctx->name = NULL; + bool invalid_param = false; + if (recv->item.type != CPITEM_CONTAINER_END) { + cp_unpack(recv->unpack, &recv->item); + switch (recv->item.type) { + case CPITEM_NULL: + case CPITEM_LIST: /* TODO backward compatible */ + break; + case CPITEM_STRING: + ctx->name = + cp_unpack_strdupo(recv->unpack, &recv->item, &recv->obstack); + break; + default: + invalid_param = true; + break; + } + } + if (recv->item.type == CPITEM_INVALID && recv->item.as.Error == CPERR_IO) return false; + if (!rpcreceive_validmsg(recv)) + return true; - bool res = false; - void *obs_base = obstack_base(&rpchandler->obs); - struct cpitem item; - cpitem_unpack_init(&item); - struct rpcmsg_meta meta; - if (!rpcmsg_head_unpack(rpcclient_unpack(rpchandler->recv.client), &item, - &meta, NULL, &rpchandler->obs)) - goto done; - - rpchandler->recv.can_respond = meta.type == RPCMSG_T_REQUEST; - enum rpchandler_func_res fres = RPCHFR_UNHANDLED; - for (const rpchandler_func **h = rpchandler->funcs; - *h != NULL && fres != RPCHFR_UNHANDLED; h++) { - fres = (***h)((void *)*h, &rpchandler->recv, - rpcclient_unpack(rpchandler->recv.client), &item, &meta); + ctx->pack = rpcreceive_response_new(recv); + if (invalid_param) { + rpcmsg_pack_error(ctx->pack, meta, RPCMSG_E_INVALID_PARAMS, + "Use Null or String with node name"); + rpcreceive_response_send(recv); + return false; } + return true; +} + +static bool handle_ls(const struct rpchandler_stage *stages, + struct rpcreceive *recv, struct rpcmsg_meta *meta) { + struct rpchandler_ls_ctx ctx; + if (!parse_ls_dir(recv, meta, &ctx.x)) + return false; + + ctx.x.located = false; + rpcmsg_pack_response(ctx.x.pack, meta); + if (ctx.x.name == NULL) + cp_pack_list_begin(ctx.x.pack); + for (const struct rpchandler_stage *s = stages; s->funcs; s++) + if (s->funcs->ls) + s->funcs->ls(s->cookie, meta->path, &ctx); + if (ctx.x.name == NULL) + cp_pack_container_end(ctx.x.pack); + else + cp_pack_bool(ctx.x.pack, ctx.x.located); + cp_pack_container_end(ctx.x.pack); + rpcreceive_response_send(recv); + return true; +} +static bool handle_dir(const struct rpchandler_stage *stages, + struct rpcreceive *recv, struct rpcmsg_meta *meta) { + struct rpchandler_dir_ctx ctx; + if (!parse_ls_dir(recv, meta, &ctx.x)) + return false; + + ctx.x.located = false; + rpcmsg_pack_response(ctx.x.pack, meta); + if (ctx.x.name == NULL) + cp_pack_list_begin(ctx.x.pack); + rpchandler_dir_result( + &ctx, "dir", RPCNODE_DIR_RET_PARAM, 0, RPCMSG_ACC_BROWSE, NULL); + rpchandler_dir_result( + &ctx, "ls", RPCNODE_DIR_RET_PARAM, 0, RPCMSG_ACC_BROWSE, NULL); + for (const struct rpchandler_stage *s = stages; s->funcs; s++) + if (s->funcs->dir) + s->funcs->dir(s->cookie, meta->path, &ctx); + if (ctx.x.name == NULL) + cp_pack_container_end(ctx.x.pack); + else if (!ctx.x.located) + cp_pack_null(ctx.x.pack); + cp_pack_container_end(ctx.x.pack); + rpcreceive_response_send(recv); + return true; +} + +static bool handle_msg(const struct rpchandler_stage *stages, + struct rpcreceive *recv, struct rpcmsg_meta *meta) { + enum rpchandler_func_res fres = RPCHFR_UNHANDLED; + for (const struct rpchandler_stage *s = stages; + s->funcs && fres == RPCHFR_UNHANDLED; s++) + fres = s->funcs->msg(s->cookie, recv, meta); if (fres == RPCHFR_UNHANDLED) { /* Default handler for requests, other types are dropped. */ - if (!rpcclient_validmsg(rpchandler->recv.client)) - goto done; - if (meta.type == RPCMSG_T_REQUEST) { - cp_pack_t pack = rpcreceive_response_new(&rpchandler->recv); - rpcmsg_pack_ferror(pack, &meta, RPCMSG_E_METHOD_NOT_FOUND, - "No such method '%s' on path '%s'", meta.method, meta.path); - rpcreceive_response_send(&rpchandler->recv); + if (!rpcreceive_validmsg(recv)) + return false; + if (meta->type == RPCMSG_T_REQUEST) { + cp_pack_t pack = rpcreceive_response_new(recv); + rpcmsg_pack_ferror(pack, meta, RPCMSG_E_METHOD_NOT_FOUND, + "No such method '%s' on path '%s'", meta->method, meta->path); + rpcreceive_response_send(recv); } } // TODO handle other fres + return true; +} - res = true; -done: - obstack_free(&rpchandler->obs, obs_base); +bool rpchandler_next(struct rpchandler *rpchandler) { + if (!(rpcclient_nextmsg(rpchandler->client))) + return false; + + bool res = false; + void *obs_base = obstack_base(&rpchandler->recv.obstack); + cpitem_unpack_init(&rpchandler->recv.item); + struct rpcmsg_meta meta; + if (rpcmsg_head_unpack(rpcclient_unpack(rpchandler->client), + &rpchandler->recv.item, &meta, NULL, &rpchandler->recv.obstack)) { + rpchandler->recv.unpack = rpcclient_unpack(rpchandler->client); + rpchandler->can_respond = meta.type == RPCMSG_T_REQUEST; + if (!strcmp(meta.method, "ls")) + res = handle_ls(rpchandler->stages, &rpchandler->recv, &meta); + else if (!strcmp(meta.method, "dir")) + res = handle_dir(rpchandler->stages, &rpchandler->recv, &meta); + else + res = handle_msg(rpchandler->stages, &rpchandler->recv, &meta); + } + + obstack_free(&rpchandler->recv.obstack, obs_base); return res; } @@ -125,52 +222,97 @@ int rpchandler_next_request_id(rpchandler_t rpchandler) { cp_pack_t rpchandler_msg_new(rpchandler_t rpchandler) { while (true) { - pthread_mutex_lock(&rpchandler->recv.mutex); - if (pthread_mutex_trylock(&rpchandler->recv.pmutex) == 0) { - pthread_mutex_unlock(&rpchandler->recv.pmutex); + pthread_mutex_lock(&rpchandler->mutex); + if (pthread_mutex_trylock(&rpchandler->pmutex) == 0) { + pthread_mutex_unlock(&rpchandler->pmutex); break; } else - pthread_mutex_unlock(&rpchandler->recv.mutex); + pthread_mutex_unlock(&rpchandler->mutex); } - return rpcclient_pack(rpchandler->recv.client); + return rpcclient_pack(rpchandler->client); } bool rpchandler_msg_send(rpchandler_t rpchandler) { - bool res = rpcclient_sendmsg(rpchandler->recv.client); - pthread_mutex_unlock(&rpchandler->recv.mutex); + bool res = rpcclient_sendmsg(rpchandler->client); + pthread_mutex_unlock(&rpchandler->mutex); return res; } bool rpchandler_msg_drop(rpchandler_t rpchandler) { - bool res = rpcclient_dropmsg(rpchandler->recv.client); - pthread_mutex_unlock(&rpchandler->recv.mutex); + bool res = rpcclient_dropmsg(rpchandler->client); + pthread_mutex_unlock(&rpchandler->mutex); return res; } -bool rpcreceive_validmsg(rpcreceive_t receive) { - return rpcclient_validmsg(receive->client); +bool rpcreceive_validmsg(struct rpcreceive *receive) { + struct rpchandler *rpchandler = (struct rpchandler *)receive; + receive->unpack = NULL; + return rpcclient_validmsg(rpchandler->client); } -cp_pack_t rpcreceive_response_new(rpcreceive_t receive) { - assert(receive->can_respond); - pthread_mutex_lock(&receive->pmutex); - pthread_mutex_lock(&receive->mutex); - pthread_mutex_unlock(&receive->pmutex); - return rpcclient_pack(receive->client); +cp_pack_t rpcreceive_response_new(struct rpcreceive *receive) { + struct rpchandler *rpchandler = (struct rpchandler *)receive; + /* DUe to the logging the message needs to be first read to release the lock + * for the logging before we start writing. + */ + assert(receive->unpack == NULL); + assert(rpchandler->can_respond); + pthread_mutex_lock(&rpchandler->pmutex); + pthread_mutex_lock(&rpchandler->mutex); + pthread_mutex_unlock(&rpchandler->pmutex); + return rpcclient_pack(rpchandler->client); } -bool rpcreceive_response_send(rpcreceive_t receive) { - assert(receive->can_respond); - bool res = rpcclient_sendmsg(receive->client); - receive->can_respond = false; - pthread_mutex_unlock(&receive->mutex); +bool rpcreceive_response_send(struct rpcreceive *receive) { + struct rpchandler *rpchandler = (struct rpchandler *)receive; + assert(rpchandler->can_respond); + bool res = rpcclient_sendmsg(rpchandler->client); + rpchandler->can_respond = false; + pthread_mutex_unlock(&rpchandler->mutex); return res; } -bool rpcreceive_response_drop(rpcreceive_t receive) { - assert(receive->can_respond); - bool res = rpcclient_dropmsg(receive->client); - pthread_mutex_unlock(&receive->mutex); +bool rpcreceive_response_drop(struct rpcreceive *receive) { + struct rpchandler *rpchandler = (struct rpchandler *)receive; + assert(rpchandler->can_respond); + bool res = rpcclient_dropmsg(rpchandler->client); + pthread_mutex_unlock(&rpchandler->mutex); return res; } + +bool rpchandler_ls_result(struct rpchandler_ls_ctx *ctx, const char *name) { + if (ctx->x.name) + ctx->x.located = !strcmp(ctx->x.name, name); + else + cp_pack_str(ctx->x.pack, name); + // TODO error? + return true; +} + + +bool rpchandler_dir_result(struct rpchandler_dir_ctx *ctx, const char *name, + enum rpcnode_dir_signature signature, int flags, enum rpcmsg_access access, + const char *description) { + if (ctx->x.name) { + if (strcmp(ctx->x.name, name)) + return true; + ctx->x.located = true; + } + cp_pack_map_begin(ctx->x.pack); + cp_pack_str(ctx->x.pack, "name"); + cp_pack_str(ctx->x.pack, name); + cp_pack_str(ctx->x.pack, "signature"); + cp_pack_int(ctx->x.pack, signature); + cp_pack_str(ctx->x.pack, "flags"); + cp_pack_int(ctx->x.pack, flags); + cp_pack_str(ctx->x.pack, "access"); + cp_pack_str(ctx->x.pack, rpcmsg_access_str(access)); + if (description) { + cp_pack_str(ctx->x.pack, "description"); + cp_pack_str(ctx->x.pack, description); + } + cp_pack_container_end(ctx->x.pack); + // TODO error? + return true; +} diff --git a/libshvrpc/rpcmsg_pack.c b/libshvrpc/rpcmsg_pack.c index 49ea3ff..bfaaba5 100644 --- a/libshvrpc/rpcmsg_pack.c +++ b/libshvrpc/rpcmsg_pack.c @@ -20,8 +20,7 @@ static inline size_t meta_begin(cp_pack_t pack) { return res; } - -size_t rpcmsg_pack_request( +size_t _rpcmsg_pack_request( cp_pack_t pack, const char *path, const char *method, int rid) { size_t res = 0; RES(meta_begin(pack)); @@ -36,10 +35,25 @@ size_t rpcmsg_pack_request( RES(cp_pack_container_end(pack)); RES(cp_pack_imap_begin(pack)); + return res; +} + +size_t rpcmsg_pack_request( + cp_pack_t pack, const char *path, const char *method, int rid) { + size_t res = 0; + RES(_rpcmsg_pack_request(pack, path, method, rid)); RES(cp_pack_int(pack, RPCMSG_KEY_PARAMS)); return res; } +size_t rpcmsg_pack_request_void( + cp_pack_t pack, const char *path, const char *method, int rid) { + size_t res = 0; + RES(_rpcmsg_pack_request(pack, path, method, rid)); + RES(cp_pack_container_end(pack)); + return res; +} + size_t rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) { size_t res = 0; RES(meta_begin(pack)); @@ -88,6 +102,13 @@ size_t rpcmsg_pack_response(cp_pack_t pack, const struct rpcmsg_meta *meta) { return res; } +size_t rpcmsg_pack_response_void(cp_pack_t pack, const struct rpcmsg_meta *meta) { + size_t res = 0; + RES(_pack_response(pack, meta)); + RES(cp_pack_container_end(pack)); + return res; +} + size_t rpcmsg_pack_error(cp_pack_t pack, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *msg) { size_t res = 0; diff --git a/libshvrpc/rpcrespond.c b/libshvrpc/rpcrespond.c index 13c5ad5..54e2c7c 100644 --- a/libshvrpc/rpcrespond.c +++ b/libshvrpc/rpcrespond.c @@ -2,10 +2,9 @@ #include struct rpcresponder { - rpchandler_func func; struct rpcrespond { int request_id; - rpcreceive_t receive; + struct rpcreceive *receive; cp_unpack_t unpack; struct cpitem *item; const struct rpcmsg_meta *meta; @@ -15,9 +14,9 @@ struct rpcresponder { pthread_mutex_t lock; }; -static enum rpchandler_func_res func(void *ptr, rpcreceive_t receive, - cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta) { - struct rpcresponder *resp = ptr; +static enum rpchandler_func_res rpc_msg( + void *cookie, struct rpcreceive *receive, const struct rpcmsg_meta *meta) { + struct rpcresponder *resp = cookie; if (meta->type != RPCMSG_T_RESPONSE && meta->type != RPCMSG_T_ERROR) return RPCHFR_UNHANDLED; enum rpchandler_func_res res = RPCHFR_HANDLED; @@ -27,8 +26,6 @@ static enum rpchandler_func_res func(void *ptr, rpcreceive_t receive, while (r) { if (r->request_id == meta->request_id) { r->receive = receive; - r->unpack = unpack; - r->item = item; r->meta = meta; sem_post(&r->sem); sem_wait(&r->sem_complete); @@ -48,9 +45,10 @@ static enum rpchandler_func_res func(void *ptr, rpcreceive_t receive, return res; } +static struct rpchandler_funcs rpc_funcs = {.msg = rpc_msg}; + rpcresponder_t rpcresponder_new(void) { struct rpcresponder *res = malloc(sizeof *res); - res->func = func; res->resp = NULL; pthread_mutex_init(&res->lock, NULL); return res; @@ -67,8 +65,8 @@ void rpcresponder_destroy(rpcresponder_t resp) { free(resp); } -rpchandler_func *rpcresponder_func(rpcresponder_t responder) { - return &responder->func; +struct rpchandler_stage rpcresponder_handler_stage(rpcresponder_t responder) { + return (struct rpchandler_stage){.funcs = &rpc_funcs, .cookie = responder}; } @@ -89,9 +87,10 @@ rpcrespond_t rpcrespond_expect(rpcresponder_t responder, int request_id) { return res; } -bool rpcrespond_waitfor(rpcrespond_t respond, cp_unpack_t *unpack, - struct cpitem **item, int timeout) { +bool rpcrespond_waitfor(rpcrespond_t respond, struct rpcreceive **receive, + struct rpcmsg_meta **meta, int timeout) { sem_wait(&respond->sem); + // TODO wait return 1; } diff --git a/libshvrpc/rpctoplevel.c b/libshvrpc/rpctoplevel.c deleted file mode 100644 index 32bc0a9..0000000 --- a/libshvrpc/rpctoplevel.c +++ /dev/null @@ -1,80 +0,0 @@ -#include -#include -#include -#include -#define obstack_chunk_alloc malloc -#define obstack_chunk_free free - -struct rpctoplevel { - rpchandler_func func; - const char *app_name; - const char *app_version; - bool device; -}; - -static enum rpchandler_func_res func(void *ptr, rpcreceive_t receive, - cp_unpack_t unpack, struct cpitem *item, const struct rpcmsg_meta *meta) { - struct rpctoplevel *h = ptr; - if (meta->type != RPCMSG_T_REQUEST || - (meta->path != NULL && *meta->path != '\0')) - return false; - cp_pack_t pack = rpcreceive_response_new(receive); - - if (!strcmp(meta->method, "appName")) { - if (!rpcreceive_validmsg(receive)) - return RPCHFR_RECV_ERR; - cp_pack_str(pack, h->app_name); - cp_pack_container_end(pack); - - } else if (!strcmp(meta->method, "appVersion")) { - if (!rpcreceive_validmsg(receive)) - return RPCHFR_RECV_ERR; - cp_pack_str(pack, h->app_version); - cp_pack_container_end(pack); - - } else { - struct obstack obstack; - obstack_init(&obstack); - - if (!strcmp(meta->method, "echo")) { - // TODO - // - - /*} else if (!strcmp(meta->method, "dir")) {*/ - /*struct rpcnode_dir_request req;*/ - /*if (!rpcnode_unpack_dir(unpack, item, &req, &obstack)) {*/ - /*obstack_free(&obstack, NULL);*/ - /*[>return;<]*/ - /*}*/ - - } else { - rpcmsg_pack_ferror(pack, meta, RPCMSG_E_METHOD_NOT_FOUND, - "No such path '%s' or method '%s' or insufficient access rights.", - meta->path, meta->method); - } - } - - if (!rpcreceive_response_send(receive)) - return RPCHFR_SEND_ERR; - return RPCHFR_HANDLED; -} - -rpctoplevel_t rpctoplevel_new( - const char *app_name, const char *app_version, bool device) { - struct rpctoplevel *res = malloc(sizeof *res); - *res = (struct rpctoplevel){ - .func = func, - .app_name = app_name, - .app_version = app_version, - .device = device, - }; - return res; -} - -void rpctoplevel_destroy(rpctoplevel_t toplevel) { - free(toplevel); -} - -rpchandler_func *rpctoplevel_func(rpctoplevel_t toplevel) { - return &toplevel->func; -} From 444b7c7e7804bc6975f875aeb6f824f735b3be63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Wed, 27 Sep 2023 14:58:11 +0200 Subject: [PATCH 29/44] Improved implementation of RPC Handler --- LICENSE | 21 + demo/device_handler.c | 3 - demo/main.c | 26 +- docs/api/chainpack.md | 7 +- docs/api/cp.md | 4 +- docs/api/cp_pack.md | 4 +- docs/api/cp_unpack.md | 4 +- docs/api/index.md | 7 +- docs/api/libshvchainpack.md | 13 + docs/api/libshvrpc.md | 13 + docs/api/rpcclient.md | 19 + docs/api/rpchandler.md | 17 + docs/api/rpchandler_app.md | 8 + docs/api/rpchandler_responses.md | 8 + docs/api/rpcmsg.md | 8 + docs/api/rpcserver.md | 8 + docs/api/rpcurl.md | 7 +- docs/conf.py | 4 +- docs/index.md | 2 +- docs/template/subprojects.md | 16 - include/meson.build | 4 + include/shv/chainpack.h | 17 +- include/shv/cp.h | 293 +++- include/shv/cp_pack.h | 282 ++++ include/shv/cp_unpack.h | 212 ++- include/shv/rpcapp.h | 17 - include/shv/rpcbroker.h | 1 + include/shv/rpcclient.h | 30 +- include/shv/rpcclient_impl.h | 11 +- include/shv/rpchandler.h | 18 +- include/shv/rpchandler_app.h | 19 + include/shv/rpchandler_responses.h | 28 + include/shv/rpclogin.h | 8 - include/shv/rpcmsg.h | 34 +- include/shv/rpcnode.h | 1 + include/shv/rpcping.h | 9 - include/shv/rpcrespond.h | 30 - include/shv/rpcserver.h | 1 + include/shv/rpcurl.h | 10 + libshvchainpack/chainpack.c | 739 --------- libshvchainpack/common.c | 2 + libshvchainpack/cp_unpack.c | 10 +- libshvchainpack/cpon.c | 1433 ----------------- libshvrpc/libshvrpc.version | 33 +- libshvrpc/meson.build | 7 +- libshvrpc/rpcclient_log.c | 1 + libshvrpc/rpcclient_login.c | 29 +- libshvrpc/rpchandler.c | 66 +- libshvrpc/{rpcapp.c => rpchandler_app.c} | 22 +- .../{rpcrespond.c => rpchandler_responses.c} | 38 +- libshvrpc/rpcmsg_head.c | 4 +- libshvrpc/rpcmsg_unpack.c | 39 + libshvrpc/strset.c | 69 + libshvrpc/strset.h | 33 + meson.build | 1 - shvc/main.c | 18 +- tests/unit/libshvchainpack/cp_unpack.c | 22 + tests/unit/libshvrpc/meson.build | 26 +- tests/unit/libshvrpc/rpcclient_links.c | 4 +- tests/unit/libshvrpc/rpcclient_login.c | 8 +- tests/unit/libshvrpc/rpcmsg_head.c | 2 - tests/unit/libshvrpc/rpcmsg_pack.c | 2 +- tests/unit/libshvrpc/strset.c | 92 ++ 63 files changed, 1390 insertions(+), 2534 deletions(-) create mode 100644 LICENSE create mode 100644 docs/api/libshvchainpack.md create mode 100644 docs/api/libshvrpc.md create mode 100644 docs/api/rpcclient.md create mode 100644 docs/api/rpchandler.md create mode 100644 docs/api/rpchandler_app.md create mode 100644 docs/api/rpchandler_responses.md create mode 100644 docs/api/rpcmsg.md create mode 100644 docs/api/rpcserver.md delete mode 100644 docs/template/subprojects.md delete mode 100644 include/shv/rpcapp.h create mode 100644 include/shv/rpchandler_app.h create mode 100644 include/shv/rpchandler_responses.h delete mode 100644 include/shv/rpclogin.h delete mode 100644 include/shv/rpcping.h delete mode 100644 include/shv/rpcrespond.h delete mode 100644 libshvchainpack/chainpack.c delete mode 100644 libshvchainpack/cpon.c rename libshvrpc/{rpcapp.c => rpchandler_app.c} (79%) rename libshvrpc/{rpcrespond.c => rpchandler_responses.c} (66%) create mode 100644 libshvrpc/rpcmsg_unpack.c create mode 100644 libshvrpc/strset.c create mode 100644 libshvrpc/strset.h create mode 100644 tests/unit/libshvrpc/strset.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bd0316c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Elektroline a.s (https://www.elektroline.cz/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/demo/device_handler.c b/demo/device_handler.c index c3e36f3..16c7228 100644 --- a/demo/device_handler.c +++ b/demo/device_handler.c @@ -1,5 +1,4 @@ #include "device_handler.h" -#include "shv/rpchandler.h" #include @@ -70,10 +69,8 @@ static enum rpchandler_func_res rpc_msg( continue; } else if (receive->item.type != CPITEM_CONTAINER_END) { free(res); - printf("We got %d\n", receive->item.type); invalid_param = true; } - break; } while (receive->item.type != CPITEM_CONTAINER_END); } else invalid_param = true; diff --git a/demo/main.c b/demo/main.c index 5b7d72c..4ae348b 100644 --- a/demo/main.c +++ b/demo/main.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "opts.h" #include "device_handler.h" @@ -35,26 +35,26 @@ int main(int argc, char **argv) { logger = rpcclient_logger_new(stderr, conf.verbose); client->logger = logger; } - switch (rpcclient_login(client, &rpcurl->login)) { - case RPCCLIENT_LOGIN_OK: - break; - case RPCCLIENT_LOGIN_INVALID: + char *loginerr; + if (!rpcclient_login(client, &rpcurl->login, &loginerr)) { + if (loginerr) { fprintf(stderr, "Invalid login for connecting to the: %s\n", conf.url); - rpcclient_destroy(client); - rpcurl_free(rpcurl); - return 1; - case RPCCLIENT_LOGIN_ERROR: + fprintf(stderr, "%s\n", loginerr); + } else { fprintf(stderr, "Communication error with server\n"); - return 2; + } + rpcclient_destroy(client); + rpcurl_free(rpcurl); + return 1; } rpcurl_free(rpcurl); struct device_state *state = device_state_new(); - rpcapp_t app = rpcapp_new("demo-device", PROJECT_VERSION); + rpchandler_app_t app = rpchandler_app_new("demo-device", PROJECT_VERSION); struct rpchandler_stage stages[4]; - stages[0] = rpcapp_handler_stage(app); + stages[0] = rpchandler_app_stage(app); stages[1] = (struct rpchandler_stage){ .funcs = &device_handler_funcs, .cookie = state}; stages[2] = (struct rpchandler_stage){}; @@ -84,7 +84,7 @@ int main(int argc, char **argv) { } rpchandler_destroy(handler); - rpcapp_destroy(app); + rpchandler_app_destroy(app); device_state_free(state); rpcclient_destroy(client); rpcclient_logger_destroy(logger); diff --git a/docs/api/chainpack.md b/docs/api/chainpack.md index 2b8dbd7..cbd3ed6 100644 --- a/docs/api/chainpack.md +++ b/docs/api/chainpack.md @@ -1,9 +1,8 @@ # Chainpack tools -The basic utilities for working with Chainpack data. This provides definitions -and simple operations you need to unpack and pack data in Chainpack format. This -is not a most user friendly way to work with Chainpack. It is suggester rather -to use [packer and unpacker](./cp.md) instead. +```c +#include +``` ```{autodoxygenfile} chainpack.h ``` diff --git a/docs/api/cp.md b/docs/api/cp.md index 1f4fa77..e829b92 100644 --- a/docs/api/cp.md +++ b/docs/api/cp.md @@ -1,6 +1,8 @@ # Chainpack and CPON packer and unpacker -The serialization and deserialization of Chainpack and CPON data formats. +```c +#include +``` ```{autodoxygenfile} shv/cp.h ``` diff --git a/docs/api/cp_pack.md b/docs/api/cp_pack.md index e3c2299..2bbb0b3 100644 --- a/docs/api/cp_pack.md +++ b/docs/api/cp_pack.md @@ -1,6 +1,8 @@ # Generic packer -Generic packer API with utility functions to pack data more easilly. +```c +#include +``` ```{autodoxygenfile} cp_pack.h ``` diff --git a/docs/api/cp_unpack.md b/docs/api/cp_unpack.md index 09bf664..a8a47ac 100644 --- a/docs/api/cp_unpack.md +++ b/docs/api/cp_unpack.md @@ -1,6 +1,8 @@ # Generic unpacker -Generic unpacker API with utility functions to unpack data more easilly. +```c +#include +``` ```{autodoxygenfile} cp_unpack.h ``` diff --git a/docs/api/index.md b/docs/api/index.md index 00c854d..1ae48f2 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -3,9 +3,6 @@ ```{toctree} :maxdepth: 3 -cp -cp_pack -cp_unpack -chainpack -rpcurl +libshvchainpack +libshvrpc ``` diff --git a/docs/api/libshvchainpack.md b/docs/api/libshvchainpack.md new file mode 100644 index 0000000..b778ba4 --- /dev/null +++ b/docs/api/libshvchainpack.md @@ -0,0 +1,13 @@ +# libshvchainpack + +This is API provided by libshvchainpack library. It is intended just to work +with ChainPack and CPON data formats. + +```{toctree} +:maxdepth: 2 + +cp +cp_pack +cp_unpack +chainpack +``` diff --git a/docs/api/libshvrpc.md b/docs/api/libshvrpc.md new file mode 100644 index 0000000..15da854 --- /dev/null +++ b/docs/api/libshvrpc.md @@ -0,0 +1,13 @@ +# libshvrpc + +This is API provided by libshvrpc library. + +```{toctree} +:maxdepth: 2 + +rpcclient +rpcserver +rpcurl +rpcmsg +rpchandler +``` diff --git a/docs/api/rpcclient.md b/docs/api/rpcclient.md new file mode 100644 index 0000000..f69c87b --- /dev/null +++ b/docs/api/rpcclient.md @@ -0,0 +1,19 @@ +# RPC Client + +## Usage + +```c +#include +``` + +```{autodoxygenfile} shv/rpcclient.h +``` + +## Implementation + +```c +#include +``` + +```{autodoxygenfile} shv/rpcclient_impl.h +``` diff --git a/docs/api/rpchandler.md b/docs/api/rpchandler.md new file mode 100644 index 0000000..e07597e --- /dev/null +++ b/docs/api/rpchandler.md @@ -0,0 +1,17 @@ +# RPC Handler + +```c +#include +``` + +```{autodoxygenfile} shv/rpchandler.h +``` + +For the RPC Handler there are some prepared handlers you want to use: + +```{toctree} +:maxdepth: 2 + +rpchandler_responses +rpchandler_app +``` diff --git a/docs/api/rpchandler_app.md b/docs/api/rpchandler_app.md new file mode 100644 index 0000000..cd23031 --- /dev/null +++ b/docs/api/rpchandler_app.md @@ -0,0 +1,8 @@ +# App Handler + +```c +#include +``` + +```{autodoxygenfile} shv/rpchandler_app.h +``` diff --git a/docs/api/rpchandler_responses.md b/docs/api/rpchandler_responses.md new file mode 100644 index 0000000..b54677b --- /dev/null +++ b/docs/api/rpchandler_responses.md @@ -0,0 +1,8 @@ +# Responses Handler + +```c +#include +``` + +```{autodoxygenfile} shv/rpchandler_responses.h +``` diff --git a/docs/api/rpcmsg.md b/docs/api/rpcmsg.md new file mode 100644 index 0000000..1f53010 --- /dev/null +++ b/docs/api/rpcmsg.md @@ -0,0 +1,8 @@ +# RPC Message + +```c +#include +``` + +```{autodoxygenfile} shv/rpcmsg.h +``` diff --git a/docs/api/rpcserver.md b/docs/api/rpcserver.md new file mode 100644 index 0000000..4c37d4f --- /dev/null +++ b/docs/api/rpcserver.md @@ -0,0 +1,8 @@ +# RPC Server + +```c +#include +``` + +```{autodoxygenfile} shv/rpcserver.h +``` diff --git a/docs/api/rpcurl.md b/docs/api/rpcurl.md index edf6f7a..30e983c 100644 --- a/docs/api/rpcurl.md +++ b/docs/api/rpcurl.md @@ -1,5 +1,8 @@ -RPC URL -======= +# RPC URL + +```c +#include +``` ```{autodoxygenfile} rpcurl.h ``` diff --git a/docs/conf.py b/docs/conf.py index b54bb1b..c2d525f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -44,8 +44,10 @@ breathe_projects_source = {"public_api": ("../include", files)} breathe_default_project = "public_api" breathe_doxygen_config_options = { - "PREDEFINED": "__attribute__(...)=", + "PREDEFINED": "__attribute__(...)= restrict=", "MACRO_EXPANSION": "YES", + "EXPAND_ONLY_PREDEF": "YES", + "EXTRACT_STATIC": "YES", } diff --git a/docs/index.md b/docs/index.md index 51f8b8b..86f8969 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,7 +5,7 @@ Table of contents ----------------- ```{toctree} -:maxdepth: 2 +:maxdepth: 3 api/index ``` diff --git a/docs/template/subprojects.md b/docs/template/subprojects.md deleted file mode 100644 index 2e82262..0000000 --- a/docs/template/subprojects.md +++ /dev/null @@ -1,16 +0,0 @@ -Meson subprojects -================= - -Meson provides a convenient way to include subprojects withtout using git -submodules. The advantage is especially when we consider those subprojects -optional. Meson is able to decide if dependency is in the system and thus not -required or if it needs to be fetched and build with this project. In case of -git's submodules that would be on the user to decide. - -The implementation is with Meson's wrap files. You need to create [wrap -file](https://mesonbuild.com/Wrap-dependency-system-manual.html) in -the `subprojects` directory. Make sure that you also add this file to -`subprojects/.gitignore` so it is included in your commit. - -If you do not plan on using subprojects then you can remove the `subprojects` -directory. With that you also need to remove `subprojects` from `flake.nix`. diff --git a/include/meson.build b/include/meson.build index d3e2bce..d7a3052 100644 --- a/include/meson.build +++ b/include/meson.build @@ -10,7 +10,11 @@ libshvrpc_headers = files( 'shv/rpcclient.h', 'shv/rpcclient_impl.h', 'shv/rpchandler.h', + 'shv/rpchandler_app.h', + 'shv/rpchandler_responses.h', 'shv/rpcmsg.h', + 'shv/rpcnode.h', + 'shv/rpcserver.h', 'shv/rpcurl.h', ) libshvbroker_headers = files('shv/rpcbroker.h') diff --git a/include/shv/chainpack.h b/include/shv/chainpack.h index 86d4388..26ca7cf 100644 --- a/include/shv/chainpack.h +++ b/include/shv/chainpack.h @@ -1,5 +1,14 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_CHAINPACK_H #define SHV_CHAINPACK_H +/*! @file + * The basic utilities for working with Chainpack data. This provides + * definitions and simple operations you need to unpack and pack data in + * Chainpack format. This is not a most user friendly way to work with + * Chainpack. It is suggester rather to use [packer and unpacker](./cp.md) + * instead. + **/ + #include #include #include @@ -161,7 +170,7 @@ static inline int chainpack_int_value1(uint8_t c, unsigned bytes) { * * You can use this only with numbers from 0 to 63. * - * @param c: Number to be packed. + * @param num: Number to be packed. * @returns Byte with packed number in the scheme byte. */ static inline uint8_t chainpack_w_scheme_uint(unsigned num) { @@ -172,7 +181,7 @@ static inline uint8_t chainpack_w_scheme_uint(unsigned num) { * * You can use this only with numbers from 0 to 63. * - * @param c: Number to be packed. + * @param num: Number to be packed. * @returns byte with packed number in the scheme byte. */ static inline uint8_t chainpack_w_scheme_int(int num) { @@ -181,7 +190,7 @@ static inline uint8_t chainpack_w_scheme_int(int num) { /*! Deduce number of bytes needed to pack unsigned integer number. * - * @param c: Number to be packed. + * @param num: Number to be packed. * @returns number of bytes needed to fit packed number. */ static inline unsigned chainpack_w_uint_bytes(unsigned long long num) { @@ -206,7 +215,7 @@ static inline unsigned chainpack_w_uint_bytes(unsigned long long num) { /*! Deduce number of bytes needed to pack signed integer number. * - * @param c: Number to be packed. + * @param num: Number to be packed. * @returns number of bytes needed to fit packed number. */ static inline unsigned chainpack_w_int_bytes(long long num) { diff --git a/include/shv/cp.h b/include/shv/cp.h index 292c5c5..1fc7cb2 100644 --- a/include/shv/cp.h +++ b/include/shv/cp.h @@ -1,5 +1,10 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_CP_H #define SHV_CP_H +/*! @file + * Serialization and deserialization of ChainPack and CPON data formats + * implemented on top of `FILE` stream. + */ #include #include @@ -50,30 +55,27 @@ enum cpitem_type { CPITEM_DATETIME, /*! Blob is sequence of bytes. * - * To receive them you need to provide @ref cpitem.cpitem_buf.buf. Unpacker - * copies up to the @ref cpitem.bufsiz bytes to it and stores info about - * that in @ref cpbufinfo "cpitem.as.Blob". You might need to call - * unpacker multiple times to receive all bytes before you can unpack next - * item. + * To receive them you need to provide @ref cpitem.buf. Unpacker copies up + * to the @ref cpitem.bufsiz bytes to it and stores info about that in @ref + * cpbufinfo "cpitem.as.Blob". You might need to call unpacker multiple + * times to receive all bytes before you can unpack next item. * * Packer does the opposite. When you are packing you need to point @ref - * cpitem.cpitem_buf.rbuf to your data and set @ref cpbufinfo - * "cpitem.as.Blob" appropriately. Do not forget to correctly manage @ref - * cpbufinfo.flags and especially the @ref CPBI_F_FIRST and @ref CPBI_F_LAST - * flags. + * cpitem.rbuf to your data and set @ref cpbufinfo "cpitem.as.Blob" + * appropriately. Do not forget to correctly manage @ref cpbufinfo.flags and + * especially the @ref CPBI_F_FIRST and @ref CPBI_F_LAST flags. */ CPITEM_BLOB, /*! Sequence of bytes that you should represent as a string. * - * To receive them you need to provide @ref cpitem.cpitem_buf.chr. Unpacker - * copies up to the @ref cpitem.bufsiz bytes to it and stores info about - * that in @ref cpbufinfo "cpitem.as.String". You might need to call - * unpacker multiple times to receive all bytes before you can unpack next - * item. + * To receive them you need to provide @ref cpitem.chr. Unpacker copies up + * to the @ref cpitem.bufsiz bytes to it and stores info about that in @ref + * cpbufinfo "cpitem.as.String". You might need to call unpacker multiple + * times to receive all bytes before you can unpack next item. * * Packer does the opposite. When you are packing you need to point @ref - * cpitem.cpitem_buf.rchr to your data and set @ref cpbufinfo - * "cpitem.as.String" appropriately. + * cpitem.rchr to your data and set @ref cpbufinfo "cpitem.as.String" + * appropriately. */ CPITEM_STRING, /*! Start of the list container. The followup items are part of this list up @@ -114,14 +116,14 @@ enum cpitem_type { * inserted in the data) or it is just more efficient to copy data without * interpreting them. For that this special type is available. * - * For unpacker you need to provide pointer to the writable buffer in - * @ref cpitem.cpitem_buf.buf and unpacker will store up to @ref - * cpitem.bufsiz bytes in it. Number of valid bytes will be stored in - * @ref cpbufinfo.len "cpitem.as.Blob.len". + * For unpacker you need to provide pointer to the writable buffer in @ref + * cpitem.buf and unpacker will store up to @ref cpitem.bufsiz bytes in it. + * Number of valid bytes will be stored in @ref cpbufinfo.len + * "cpitem.as.Blob.len". * - * For packer you need to provide pointer to the data in @ref - * cpitem.cpitem_buf.rbuf and packer will write @ref cpbufinfo.len - * "cpitem.as.Blob.len" bytes without interpreting them. + * For packer you need to provide pointer to the data in @ref cpitem.rbuf + * and packer will write @ref cpbufinfo.len "cpitem.as.Blob.len" bytes + * without interpreting them. */ CPITEM_RAW = 256, }; @@ -143,7 +145,9 @@ const char *cpitem_type_str(enum cpitem_type tp); * 10^32767 and estimation of number of atoms in the observeble universe 10^82). */ struct cpdecimal { + /*! Mantisa in `mantisa * 10^exponent`. */ long long mantisa; + /*! Exponent in `mantisa * 10^exponent`. */ int exponent; }; @@ -258,15 +262,15 @@ enum cperror { * data as hex. */ #define CPBI_F_HEX (1 << 3) -/*! Combination of @ref CPBI_F_FIRST and CPBI_F_LAST. It is pretty common to use - * these two together when you are packing short strings or blobs and thus they - * are provided in this macro. +/*! Combination of @ref CPBI_F_FIRST and @ref CPBI_F_LAST. It is pretty common + * to use these two together when you are packing short strings or blobs and + * thus they are provided in this macro. */ #define CPBI_F_SINGLE (CPBI_F_FIRST | CPBI_F_LAST) /*! Info about data stored in the @ref cpitem buffer. */ struct cpbufinfo { - /*! Number of valid bytes in @ref cpitem.cpitem_buf. + /*! Number of valid bytes in @ref cpitem.buf. * * The special case is when you invoke packer just to get packed size (that * is `FILE` is `NULL`). In such case buffer is not accessed and thus it can @@ -300,8 +304,7 @@ struct cpbufinfo { struct cpitem { /*! Type of the item. */ enum cpitem_type type; - /*! TODO - */ + /*! Union to access different representations based on the @ref type. */ union cpitem_as { /*! Used to store value for @ref CPITEM_BOOL. */ bool Bool; @@ -322,39 +325,54 @@ struct cpitem { /*! Info about type of unpack error for @ref CPITEM_INVALID. */ enum cperror Error; } - /*! Wtf */ - as; // TODO possibly just use anonymous union - /*! Pointer to the buffer with data chunk. - * - * It is defined both as `const` as well as modifiable. The pack functions - * use `buf` while unpack functions use `rbuf`. Be aware that this can - * allow write to the buffer marked as `const` if you pass it to unpack - * function. - * - * The `chr` and `rchr` are additionally provided just to allow easy - * assignment of the strings without changing type (which would be required - * due to change of the sign). - * - * The special case is when you set buffer to `NULL` while `bufsiz != 0`, - * this discards up to `bufsiz` bytes instead of copying. This is used to - * skip data. + /*! Access to the value for most of the types. The only types not handled + * through this are @ref CPITEM_STRING and @ref CPITEM_BLOB. */ - union cpitem_buf { - /*! Buffer used to store binary data when unpacking. */ + as; + // @cond + union { + // @endcond + /*! Buffer used to store binary data when unpacking. + * + * The special case is when @ref bufsiz is nonzero while this is set to + * `NULL`, in such situation the read bytes are discarded. The number of + * discarded bytes is given by @ref bufsiz. + * + * This is aliased with @ref rbuf, @ref chr and @ref rchr with union! + */ uint8_t *buf; - /*! Buffer used to receive binary data when packing. */ + /*! Buffer used to pass binary data to packer. + * + * This is provided as an addition to the @ref buf for packing + * operations to allow usage of constant buffers. + * + * This is aliased with @ref buf, @ref chr and @ref rchr with union! + */ const uint8_t *rbuf; - /*! Buffer used to store string data when unpacking. */ + /*! Buffer used to store string data when unpacking. + * + * This is provided as an addition to the @ref buf for unpacking + * strings. + * + * This is aliased with @ref buf, @ref rbuf and @ref rchr with union! + */ char *chr; - /*! Buffer used to receive string data when packing. */ + /*! Buffer used to pass string data to packer. + * + * This is provided as an addition to the @ref rbuf for packing + * operations to allow usage of constant buffers. + * + * This is aliased with @ref buf, @ref rbuf and @ref chr with union! + */ const char *rchr; }; - /*! Size of the @ref cpitem_buf.buf (@ref cpitem_buf.rbuf, @ref - * cpitem_buf.chr and @ref cpitem_buf.rchr). This is used only by unpacking - * functions to know the limit of the buffer. + /*! Size of the @ref buf (@ref rbuf, @ref chr and @ref rchr). This is used + * only by unpacking functions to know the limit of the buffer. * * It is common to set this to zero to receive type of the item and only - * after that you would set the pointer to the buffer and its size. + * after that you would set the pointer to the buffer and its size. Make + * sure that you set it back to zero afterward to prevent access to the + * pointer used previously here. */ size_t bufsiz; }; @@ -377,54 +395,189 @@ static inline void cpitem_unpack_init(struct cpitem *item) { item->bufsiz = 0; } +/*! Extract integer from the item and check if it fits to the destination. + * + * ChainPack and CPON support pretty large integer types. That is limited by + * platform to `long long` but in applications it is more common to use `int` or + * other types. By simple assignment the number can just be mangled to some + * invalid value if it is too big and thus this macro is provided to extract + * integer from item while it checks for the destination limits. + * + * @param ITEM: item from which integer is extract. + * @param DEST: destination integer variable (not pointer, the variable + * directly). + * @returns `true` in case value was @ref CPITEM_INT and value fits to the + * destination, otherwise `false` is returned. The destination is not modified + * when `false` is returned. + */ +#define cpitem_extract_int(ITEM, DEST) \ + ({ \ + struct cpitem *__item = ITEM; \ + bool __valid = false; \ + if (__item->type == CPITEM_INT) { \ + if (sizeof(DEST) < sizeof(long long)) { \ + long long __lim = 1LL << ((sizeof(DEST) * 8) - 1); \ + __valid = __item->as.Int >= -__lim && __item->as.Int < __lim; \ + } else \ + __valid = true; \ + (DEST) = __item->as.Int; \ + } \ + __valid; \ + }) + +/*! Extract unsigned integer from the item and check if it fits to the + * destination. + * + * This is variant of @ref cpitem_extract_int for unsigned integers. Please see + * documentation for @ref cpitem_extract_int for an explanation. + * + * @param ITEM: item from which unsigned integer is extract. + * @param DEST: destination unsigned integer variable (not pointer, the variable + * directly). + * @returns `true` in case value was @ref CPITEM_UINT and value fits to the + * destination, otherwise `false` is returned. The destination is not modified + * when `false` is returned. + */ +#define cpitem_extract_uint(ITEM, DEST) \ + ({ \ + struct cpitem *__item = ITEM; \ + bool __valid = false; \ + if (__item->type == CPITEM_UINT) { \ + if (sizeof(DEST) < sizeof(unsigned long long)) { \ + unsigned long long __lim = 1LL << ((sizeof(DEST) * 8) - 1); \ + __valid = __item->as.Int < __lim; \ + } else \ + __valid = true; \ + (DEST) = __item->as.Int; \ + } \ + __valid; \ + }) + /*! Unpack item from ChainPack data format. * - * @returns Number of bytes read from @ref f. + * @param f: File from which ChainPack bytes are read from. + * @param item: Item where info about the unpacked item and its value is placed + * to. + * @returns Number of bytes read from **f**. */ size_t chainpack_unpack(FILE *f, struct cpitem *item) __attribute__((nonnull)); /*! Pack next item to ChainPack data format. * - * @returns Number of bytes written to @ref f. On error it returns value of - * zero. Note that some bytes might have been successfully written before error - * was detected. Number of written bytes in case of an error is irrelevant - * because packed data is invalid anyway. Be aware that some inputs might - * inevitably produce no output (strings and blobs that are not first or last - * and have zero length produce no output). + * @param f: File to which ChainPack bytes are written to. It can be `NULL` and + * in such a case packer only calculates number of bytes item would take when + * packed. + * @param item: Item to be packed. + * @returns Number of bytes written to **f**. On error it returns value of zero. + * Note that some bytes might have been successfully written before error was + * detected. Number of written bytes in case of an error is irrelevant because + * packed data is invalid anyway. Be aware that some inputs might inevitably + * produce no output (strings and blobs that are not first or last and have zero + * length produce no output). */ size_t chainpack_pack(FILE *f, const struct cpitem *item) __attribute__((nonnull(2))); +/*! State for the CPON packer and unpacker. + * + * Compared to the ChainPack, CPON needs additional information because of these + * issues: + * + * * Containers are terminated with either `}` or `]` based on the type compared + * to the unified termination in ChainPack and CP API. + * * Map and IMap containers use item separators (`,` and `:`) where in + * ChainPack simple end of an item is the separator. + * + * Because of that CPON packer and unpacker needs to remember stack of + * containers as well as additional flags to decide on the separator to use. + */ struct cpon_state { - const char *indent; + /*! Counter of opened containers. */ size_t depth; + /*! Container context. */ struct cpon_state_ctx { + /*! Container type */ enum cpitem_type tp; + /*! Flag signaling that there is not a single item in the container yet. + */ bool first; + /*! Flag signaling that even number of items were packed to the + * container. This decides if `,` or `:` should be used as separator. + */ bool even; + /*! Flag signaling that previous item was meta and thus that next item + * should not be separated by any separator. + */ bool meta; - } * ctx; + } + /*! Array of container contexts. This array can be preallocated or it + * can be dynamically allocated on demand by @ref cpon_state::realloc. + */ + * ctx; + /*! Size of container contexts array (@ref cpon_state.ctx). + * + * This is fixed size if @ref cpon_state.realloc is `NULL` or it has to be + * modified by that function. + */ size_t cnt; + /*! Pointer to the function that is called when there is not enough space to + * store additional container context. This function needs to reallocate the + * current array to increase its size or it can just do nothing but it can't + * decrease the array size under the @ref depth. The new size needs to be + * stored in @ref cnt. + * + * This field can be set to `NULL` and in such case the dynamic allocation + * of container contexts won't be possible. + */ void (*realloc)(struct cpon_state *state); + /*! Indent to be used for CPON packer. This is rather a packer configuration + * but it is placed in the state because it makes it a convenient way to + * pass it consistently to every @ref cpon_pack call. + * + * You can set it to `NULL` to pack CPON on a singe line. + */ + const char *indent; }; /*! Pack next item to CPON data format. * - * @returns Number of bytes read from @ref f. + * The unpacker is strict in CPON format but only if @ref cpon_state.cnt is + * more than @ref cpon_state.depth. In other words if unpacker can't remember + * context it will disregard difference between `:` and `,` as well as `}` and + * `]`. + * + * @param f: File from which CPON bytes are read from. + * @param state: CPON state (can't be shared with packer!) that preserves + * context information between function calls. + * @param item: Item where info about the unpacked item and its value is placed + * to. + * @returns Number of bytes read from **f**. */ size_t cpon_unpack(FILE *f, struct cpon_state *state, struct cpitem *item) __attribute__((nonnull)); /*! Unpack next item from CPON data format. * - * @returns Number of bytes written to @ref f. On error it returns value of - * zero. Note that some bytes might have been successfully written before error - * was detected. Number of written bytes in case of an error is irrelevant - * because packed data is invalid anyway. Be aware that some inputs might - * inevitably produce no output (strings and blobs that are not first or last - * and have zero length produce no output). + * The packer can reliably pack only if @ref cpon_state.cnt is more than @ref + * cpon_state.depth. Any container that is beyond that limit is ignored and + * replaced by sequence `...` (invalid in CPON). + * + * @param f: File to which CPON bytes are written to. It can be `NULL` and in + * such a case packer only calculates number of bytes item would take when + * packed. Be aware that this operation updates CPON state and thus you can't + * just simply call it with `NULL` and then call it with same item with + * `FILE`. + * @param state: CPON state (can't be shared with unpacker!) that preserves + * context information between function calls. + * @param item: Item to be packed. + * @returns Number of bytes written to **f**. On error it returns value of + * zero. Note that some bytes might have been successfully written before + * error was detected. Number of written bytes in case of an error is + * irrelevant because packed data is invalid anyway. Be aware that some inputs + * might inevitably produce no output (strings and blobs that are not first or + * last and have zero length produce no output). */ size_t cpon_pack(FILE *f, struct cpon_state *state, const struct cpitem *item) __attribute__((nonnull(2, 3))); diff --git a/include/shv/cp_pack.h b/include/shv/cp_pack.h index f7fed4f..670d08f 100644 --- a/include/shv/cp_pack.h +++ b/include/shv/cp_pack.h @@ -1,33 +1,119 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_CP_PACK_H #define SHV_CP_PACK_H +/*! @file + * Generic packer API with utility functions to pack data more easilly. + */ #include #include #include #include +/*! Definition of function that provides generic packer. + * + * This function is called to pack next item and all its context, including + * the output, is provided by **ptr**. + * + * This abstraction allows us to work with any packer in the same way and it + * also provides a way to overlay additional handling on top of the low level + * packer (such as logging). + * + * @param ptr: Pointer to the context information that is pointer to the @ref + * cp_pack_t. + * @param item: Item to be packed. + * @returns Number of bytes written. On error it returns value of zero. Note + * that some bytes might have been successfully written before error was + * detected. Number of written bytes in case of an error is irrelevant because + * packed data is invalid anyway. Be aware that some inputs might inevitably + * produce no output (strings and blobs that are not first or last and have + * zero length produce no output). + */ typedef size_t (*cp_pack_func_t)(void *ptr, const struct cpitem *item); +/*! Generic packer. + * + * This is pointer to the function pointer that implements packing. The function + * is called by dereferencing this generic packer and the pointer to it is + * passed to the function as the first argument. This double pointer provides + * you a way to store any context info side by pointer to the unpack function. + * + * To understand this you can look into the @ref cp_pack_chainpack and @ref + * cp_pack_cpon definitions. They have @ref cp_pack_func_t as a first field and + * thus this function gets pointer to the structure in the first argument. + */ typedef cp_pack_func_t *cp_pack_t; +/*! Handle for the ChainPack generic packer. */ struct cp_pack_chainpack { + /*! Generic packer function. */ cp_pack_func_t func; + /*! File object used in @ref chainpack_pack() calls. */ FILE *f; }; +/*! Initialize @ref cp_pack_chainpack. + * + * The initialization only fills in @ref cp_pack_chainpack.func and sets @ref + * cp_pack_chainpack.f to the **f** passed to it. There is no need for a + * special resource deallocation afterward. + * + * @param pack: Pointer to the handle to be initialized. + * @param f: File used to write ChainPack bytes. + * @returns Generic packer. + */ cp_pack_t cp_pack_chainpack_init(struct cp_pack_chainpack *pack, FILE *f) __attribute__((nonnull)); +/*! Handle for the CPON generic packer. */ struct cp_pack_cpon { + /*! Generic packer function. */ cp_pack_func_t func; + /*! File object used in @ref cpon_pack() calls. */ FILE *f; + /*! Context information state for the CPON. + * + * Function @ref cp_pack_cpon_init() will prepare this state for the + * unconstrained allocation. If you prefer to limit the depth you can change + * the @ref cpon_state.realloc to your own implementation. + * + * After generic packer end of use you need to free @ref cpon_state.ctx! + */ struct cpon_state state; }; +/*! Initialize @ref cp_pack_cpon. + * + * The initialization only fills in @ref cp_pack_chainpack.func, sets @ref + * cp_pack_chainpack.f to the **f** passed to it, initializes @ref cpon_state + * to zeroes and sets @ref cpon_state.realloc to unconstrained allocator and + * @ref cpon_state.indent to the **indent** passed to it. + * + * Remember to release resources allocated by @ref cpon_state.ctx! + * + * @param pack: Pointer to the handle to be initialized. + * @param f: File used to write CPON bytes. + * @param indent: Indent to be used for CPON output. You can pass `NULL` to get + * CPON on a single line. + * @returns Generic packer. + */ cp_pack_t cp_pack_cpon_init(struct cp_pack_cpon *pack, FILE *f, const char *indent) __attribute__((nonnull(1, 2))); +/*! Pack item with generic packer. + * + * The calling is described in @ref cp_pack_func_t. + * + * @param PACK: Generic packer to be used for unpacking. + * @param ITEM: Item to be packed. + * @returns Number of bytes written. On error it returns value of zero. Note + * that some bytes might have been successfully written before error was + * detected. Number of written bytes in case of an error is irrelevant because + * packed data is invalid anyway. Be aware that some inputs might inevitably + * produce no output (strings and blobs that are not first or last and have + * zero length produce no output). + */ #define cp_pack(PACK, ITEM) \ ({ \ cp_pack_t __pack = PACK; \ @@ -35,12 +121,23 @@ cp_pack_t cp_pack_cpon_init(struct cp_pack_cpon *pack, FILE *f, }) +/*! Pack *Null* to the generic packer. + * + * @param pack: Generic packer. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_null(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_NULL; return cp_pack(pack, &i); } +/*! Pack *Boolean* to the generic packer. + * + * @param pack: Generic packer. + * @param v: Boolean value to be packed. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_bool(cp_pack_t pack, bool v) { struct cpitem i; i.type = CPITEM_BOOL; @@ -48,6 +145,12 @@ static inline size_t cp_pack_bool(cp_pack_t pack, bool v) { return cp_pack(pack, &i); } +/*! Pack *Int* to the generic packer. + * + * @param pack: Generic packer. + * @param v: Integer value to be packed. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_int(cp_pack_t pack, long long v) { struct cpitem i; i.type = CPITEM_INT; @@ -55,6 +158,12 @@ static inline size_t cp_pack_int(cp_pack_t pack, long long v) { return cp_pack(pack, &i); } +/*! Pack *UInt* to the generic packer. + * + * @param pack: Generic packer. + * @param v: Uinsigned integer value to be packed. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_uint(cp_pack_t pack, unsigned long long v) { struct cpitem i; i.type = CPITEM_UINT; @@ -62,6 +171,12 @@ static inline size_t cp_pack_uint(cp_pack_t pack, unsigned long long v) { return cp_pack(pack, &i); } +/*! Pack *Double* to the generic packer. + * + * @param pack: Generic packer. + * @param v: Float point number value to be packed. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_double(cp_pack_t pack, double v) { struct cpitem i; i.type = CPITEM_DOUBLE; @@ -69,6 +184,12 @@ static inline size_t cp_pack_double(cp_pack_t pack, double v) { return cp_pack(pack, &i); } +/*! Pack *Decimal* to the generic packer. + * + * @param pack: Generic packer. + * @param v: Decimal number value to be packed. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_decimal(cp_pack_t pack, struct cpdecimal v) { struct cpitem i; i.type = CPITEM_DECIMAL; @@ -76,6 +197,12 @@ static inline size_t cp_pack_decimal(cp_pack_t pack, struct cpdecimal v) { return cp_pack(pack, &i); } +/*! Pack *Datetime* to the generic packer. + * + * @param pack: Generic packer. + * @param v: Date and time value to be packed. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_datetime(cp_pack_t pack, struct cpdatetime v) { struct cpitem i; i.type = CPITEM_DATETIME; @@ -83,6 +210,13 @@ static inline size_t cp_pack_datetime(cp_pack_t pack, struct cpdatetime v) { return cp_pack(pack, &i); } +/*! Pack *Blob* to the generic packer. + * + * @param pack: Generic packer. + * @param buf: Buffer to be packed as a signle blob. + * @param len: Size of the **buf** (valid number of bytes to be packed). + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_blob(cp_pack_t pack, const uint8_t *buf, size_t len) { struct cpitem i; i.type = CPITEM_BLOB; @@ -95,6 +229,18 @@ static inline size_t cp_pack_blob(cp_pack_t pack, const uint8_t *buf, size_t len return cp_pack(pack, &i); } +/*! Pack *Blob* size to the generic packer. + * + * This is initial call for the packing blob in parts. You need to follow this + * one or more calls to @ref cp_pack_blob_data that needs to pack in total + * number of bytes specified to this call. + * + * @param pack: Generic packer. + * @param item: Item that needs to be used with subsequent @ref + * cp_pack_blob_data calls. The item doesn't have to be initialized. + * @param siz: Number of bytes to be packed in total. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_blob_size( cp_pack_t pack, struct cpitem *item, size_t siz) { item->type = CPITEM_BLOB; @@ -106,6 +252,21 @@ static inline size_t cp_pack_blob_size( return cp_pack(pack, item); } +/*! Pack *Blob* data to the generic packer. + * + * This is subsequent call after @ref cp_pack_blob_size and provides a way to + * pack blobs in parts instead of all at once. The sum of all sizes passed to + * this function should be the size initially passed to the @ref + * cp_pack_blob_size. + * + * @param pack: Generic packer. + * @param item: Item previously used with @ref cp_pack_blob_size. + * @param buf: Buffer to the bytes to be packed to the blob. + * @param siz: Size of the **buf** (valid number of bytes to be packed). It is + * discouraged to use here `0`, because that results in this function + * returining zero. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_blob_data( cp_pack_t pack, struct cpitem *item, const uint8_t *buf, size_t siz) { assert(item->type == CPITEM_BLOB); @@ -119,6 +280,13 @@ static inline size_t cp_pack_blob_data( return cp_pack(pack, item); } +/*! Pack *String* to the generic packer. + * + * @param pack: Generic packer. + * @param buf: Buffer containing characters to be packed as a signle string. + * @param len: Size of the **buf** (valid number of characters to be packed). + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_string(cp_pack_t pack, const char *buf, size_t len) { struct cpitem i; i.type = CPITEM_STRING; @@ -131,10 +299,23 @@ static inline size_t cp_pack_string(cp_pack_t pack, const char *buf, size_t len) return cp_pack(pack, &i); } +/*! Pack C-*String* to the generic packer. + * + * @param pack: Generic packer. + * @param str: C-String (null terminated string) to be packed. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_str(cp_pack_t pack, const char *str) { return cp_pack_string(pack, str, strlen(str)); } +/*! Pack *String* generated from format string to the generic packer. + * + * @param pack: Generic packer. + * @param fmt: printf format string. + * @param args: list of variadic arguments to be passed to the printf. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_vfstr(cp_pack_t pack, const char *fmt, va_list args) { va_list cargs; va_copy(cargs, args); @@ -145,6 +326,12 @@ static inline size_t cp_pack_vfstr(cp_pack_t pack, const char *fmt, va_list args return cp_pack_string(pack, str, siz - 1); } +/*! Pack *String* generated from format string to the generic packer. + * + * @param pack: Generic packer. + * @param fmt: printf format string. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_fstr(cp_pack_t pack, const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -153,6 +340,18 @@ static inline size_t cp_pack_fstr(cp_pack_t pack, const char *fmt, ...) { return res; } +/*! Pack *String* size to the generic packer. + * + * This is initial call for the packing string in parts. You need to follow this + * with one or more calls to @ref cp_pack_string_data that needs to pack in + * total number of bytes specified to this call. + * + * @param pack: Generic packer. + * @param item: Item that needs to be used with subsequent @ref + * cp_pack_string_data calls. The item doesn't have to be initialized. + * @param siz: Number of bytes to be packed in total. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_string_size( cp_pack_t pack, struct cpitem *item, size_t siz) { item->type = CPITEM_STRING; @@ -164,6 +363,21 @@ static inline size_t cp_pack_string_size( return cp_pack(pack, item); } +/*! Pack *String* data to the generic packer. + * + * This is subsequent call after @ref cp_pack_string_size and provides a way to + * pack strings in parts instead of all at once. The sum of all sizes passed to + * this function should be the size initially passed to the @ref + * cp_pack_string_size. + * + * @param pack: Generic packer. + * @param item: Item previously used with @ref cp_pack_string_size. + * @param buf: Buffer to the bytes to be packed to the string. + * @param siz: Size of the **buf** (valid number of bytes to be packed). It is + * discouraged to use here `0`, because that results in this function + * returining zero. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_string_data( cp_pack_t pack, struct cpitem *item, const char *buf, size_t siz) { assert(item->type == CPITEM_STRING); @@ -177,39 +391,96 @@ static inline size_t cp_pack_string_data( return cp_pack(pack, item); } +/*! Pack *List* begining to the generic packer. + * + * You can follow this with items that are part of the list and then you need + * to terminate the it with @ref cp_pack_container_end. + * + * @param pack: Generic packer. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_list_begin(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_LIST; return cp_pack(pack, &i); } +/*! Pack *Map* begining to the generic packer. + * + * You can follow this with always pair of items where first item is string key + * and second item is value. After that you need to terminate *Map* with @ref + * cp_pack_container_end. + * + * @param pack: Generic packer. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_map_begin(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_MAP; return cp_pack(pack, &i); } +/*! Pack *IMap* begining to the generic packer. + * + * You can follow this with always pair of items where first item is integer key + * and second item is value. After that you need to terminate *IMap* with @ref + * cp_pack_container_end. + * + * @param pack: Generic packer. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_imap_begin(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_IMAP; return cp_pack(pack, &i); } +/*! Pack *Meta* begining to the generic packer. + * + * You can follow this with always pair of items where first item is integer or + * string key and second item is value. After that you need to terminate *Meta* + * with @ref cp_pack_container_end and follow it with item that meta is tied to. + * + * @param pack: Generic packer. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_meta_begin(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_META; return cp_pack(pack, &i); } +/*! Pack container end to the generic packer. + * + * This terminates containers openned with @ref cp_pack_list_begin, @ref + * cp_pack_map_begin, @ref cp_pack_imap_begin and @ref cp_pack_meta_begin. + * + * @param pack: Generic packer. + * @returns Number of bytes written. See @ref cp_pack. + */ static inline size_t cp_pack_container_end(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_CONTAINER_END; return cp_pack(pack, &i); } +/*! Open `FILE` stream that you can use for writing strings or blobs. + * + * This has overhead of establishing a `FILE` object but on the other hand it + * allows easy string and blob packing. + * + * Do not use **pack** for anything until you close the returned `FILE`. + * + * @param pack: Generic packer. + * @param str: Boolean deciding if you are writing characters or bytes (*String* + * or *Blob*) + * @returns Instance of `FILE`. It can return `NULL` when creation of `FILE` + * stream fails. + */ FILE *cp_pack_fopen(cp_pack_t pack, bool str) __attribute__((nonnull)); // clang-format off +/// @cond #define __cp_pack_value(PACK, VALUE) \ _Generic((VALUE), \ bool: cp_pack_bool, \ @@ -238,6 +509,17 @@ FILE *cp_pack_fopen(cp_pack_t pack, bool str) __attribute__((nonnull)); )((PACK), (VALUE), (SIZ)) // clang-format on #define __cp_pack_value_select(_1, _2, X, ...) X +/// @endcond +/*! Pack value with generic packer with type detection. + * + * This allows you to be lazy and instead of expicitly typing the correct pack + * function you can use this which selects the correct one based on the value + * passed to it. The most of the C types are supported. + * + * This macro can be called with either just value or with value and size. The + * simple example would be `cp_pack_value(pack, 42)` and `cp_pack_value(pack, + * buf, siz)`. + */ #define cp_pack_value(PACK, ...) \ __cp_pack_value_select(__VA_ARGS__, __cp_pack_value_l, __cp_pack_value)( \ PACK, __VA_ARGS__) diff --git a/include/shv/cp_unpack.h b/include/shv/cp_unpack.h index a1fa336..7bc3c74 100644 --- a/include/shv/cp_unpack.h +++ b/include/shv/cp_unpack.h @@ -1,42 +1,130 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_CP_UNPACK_H #define SHV_CP_UNPACK_H +/*! @file + * Generic unpacker API with utility functions to unpack data more easilly. + */ #include #include +/*! Definition of function that provides generic unpacker. + * + * This function is called to unpack next item and all its context, including + * the input, is provided by **ptr**. + * + * This abstraction allows us to work with any unpacker in the same way and it + * also provides a way to overlay additional handling on top of the low level + * unpacker (such as logging). + * + * @param ptr: Pointer to the context information that is pointer to the @ref + * cp_unpack_t. + * @param item: Item where info about the unpacked item and its value is placed + * to. + * @returns Number of bytes read. + */ typedef size_t (*cp_unpack_func_t)(void *ptr, struct cpitem *item); +/*! Generic unpacker. + * + * This is pointer to the function pointer that implements unpacking. The + * function is called by dereferencing this generic unpacker and the pointer to + * it is passed to the function as the first argument. This double pointer + * provides you a way to store any context info side by pointer to the unpack + * function. + * + * To understand this you can look into the @ref cp_unpack_chainpack and @ref + * cp_unpack_cpon definitions. They have @ref cp_unpack_func_t as a first field + * and thus this function gets pointer to the structure in the first argument. + */ typedef cp_unpack_func_t *cp_unpack_t; +/*! Handle for the ChainPack generic unpacker. */ struct cp_unpack_chainpack { + /*! Generic unpacker function. */ cp_unpack_func_t func; + /*! File object used in @ref chainpack_unpack() calls. */ FILE *f; }; +/*! Initialize @ref cp_unpack_chainpack. + * + * The initialization only fills in @ref cp_unpack_chainpack.func and sets @ref + * cp_unpack_chainpack.f to the **f** passed to it. There is no need for a + * special resource deallocation afterward. + * + * @param pack: Pointer to the handle to be initialized. + * @param f: File used to read ChainPack bytes. + * @returns Generic unpacker. + */ cp_unpack_t cp_unpack_chainpack_init(struct cp_unpack_chainpack *pack, FILE *f) __attribute__((nonnull)); +/*! Handle for the CPON generic unpacker. */ struct cp_unpack_cpon { + /*! Generic unpacker function. */ cp_unpack_func_t func; + /*! File object used in @ref cpon_unpack calls. */ FILE *f; + /*! Context information state for the CPON. + * + * Function @ref cp_unpack_cpon_init() will prepare this state for the + * unconstrained allocation. If you prefer to limit the depth you can change + * the @ref cpon_state.realloc to your own implementation. + * + * After generic unpacker end of use you need to free @ref cpon_state.ctx! + */ struct cpon_state state; }; +/*! Initialize @ref cp_unpack_cpon. + * + * The initialization only fills in @ref cp_unpack_chainpack.func, sets @ref + * cp_unpack_chainpack.f to the **f** passed to it, initializes @ref cpon_state + * to zeroes and sets @ref cpon_state.realloc to unconstrained allocator. + * + * Remember to release resources allocated by @ref cpon_state.ctx! + * + * @param pack: Pointer to the handle to be initialized. + * @param f: File used to read CPON bytes. + * @returns Generic unpacker. + */ cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *pack, FILE *f) __attribute__((nonnull)); +/*! Unpack item with generic unpacker. + * + * The calling is described in @ref cp_unpack_func_t. You want to repeatedly + * pass the same @ref cpitem instance to unpack multiple items. + * + * @param UNPACK: Generic unpacker to be used for unpacking. + * @param ITEM: Item where info about the unpacked item and its value is placed + * to. + * @returns Number of bytes read. + */ #define cp_unpack(UNPACK, ITEM) \ ({ \ cp_unpack_t __unpack = UNPACK; \ (*__unpack)(__unpack, (ITEM)); \ }) -#define cp_unpack_expect_type(UNPACK, ITEM, TYPE) \ +/*! Unpack item with generic unpacker and provide its type. + * + * This is variant of @ref cp_unpack() that instead of number of read bytes + * returns type of the item read. + * + * @param UNPACK: Generic unpacker to be used for unpacking. + * @param ITEM: Item where info about the unpacked item and its value is placed + * to. + * @returns Type of the unpacked item. + */ +#define cp_unpack_type(UNPACK, ITEM) \ ({ \ + cp_unpack_t __unpack = UNPACK; \ struct cpitem *__item = ITEM; \ - cp_unpack(UNPACK, __item); \ - __item->type == TYPE; \ + (*__unpack)(__unpack, __item); \ + __item->type; \ }) /*! Instead of getting next item this drops the any unread blocks from current @@ -89,48 +177,47 @@ size_t cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item, unsigned depth) __attribute__((nonnull)); -#define cp_extract_int(ITEM, DEST) \ - ({ \ - struct cpitem *__item = ITEM; \ - bool __valid = false; \ - if (__item->type == CPITEM_INT) { \ - if (sizeof(DEST) < sizeof(long long)) { \ - long long __lim = 1LL << ((sizeof(DEST) * 8) - 1); \ - __valid = __item->as.Int >= -__lim && __item->as.Int < __lim; \ - } else \ - __valid = true; \ - (DEST) = __item->as.Int; \ - } \ - __valid; \ - }) - -#define cp_extact_uint(ITEM, DEST) \ - ({ \ - struct cpitem *__item = ITEM; \ - bool __valid = false; \ - if (__item->type == CPITEM_UINT) { \ - if (sizeof(DEST) < sizeof(unsigned long long)) { \ - unsigned long long __lim = 1LL << ((sizeof(DEST) * 8) - 1); \ - __valid = __item->as.Int < __lim; \ - } else \ - __valid = true; \ - (DEST) = __item->as.Int; \ - } \ - __valid; \ - }) +/*! Unpack integer and place it to the destination. + * + * This combines @ref cp_unpack with @ref cpitem_extract_int. + * + * @param UNPACK: Generic unpacker to be used for unpacking. + * @param ITEM: Item where info about the unpacked item and its value is placed + * to. You can use it to identify the real type or error in case of failure. + * @param DEST: destination integer variable (not pointer, the variable + * directly). + * @returns Number of read bytes or zero or negative number of read bytes in + * case of an error. The error can be either due to the unpack failure or due + * to number being too big to be stored in **DEST**. The real issue can be + * deduced from **ITEM**. + */ #define cp_unpack_int(UNPACK, ITEM, DEST) \ ({ \ struct cpitem *__uitem = ITEM; \ ssize_t res = cp_unpack(UNPACK, __uitem); \ - cp_extract_int(__uitem, DEST) ? res : -res; \ + cpitem_extract_int(__uitem, DEST) ? res : -res; \ }) +/*! Unpack unsigned integer and place it to the destination. + * + * This combines @ref cp_unpack with @ref cpitem_extract_uint. + * + * @param UNPACK: Generic unpacker to be used for unpacking. + * @param ITEM: Item where info about the unpacked item and its value is placed + * to. You can use it to identify the real type or error in case of failure. + * @param DEST: destination integer variable (not pointer, the variable + * directly). + * @returns Number of read bytes or zero or negative number of read bytes in + * case of an error. The error can be either due to the unpack failure or due + * to number being too big to be stored in **DEST**. The real issue can be + * deduced from **ITEM**. + */ #define cp_unpack_uint(UNPACK, ITEM, DEST) \ ({ \ struct cpitem *__uitem = ITEM; \ ssize_t res = cp_unpack(UNPACK, __uitem); \ - cp_extract_uint(__uitem, DEST) ? res : -res; \ + cpitem_extract_uint(__uitem, DEST) ? res : -res; \ }) /*! Unpack a single by from string. @@ -242,24 +329,6 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_strncpy( return item->as.String.len; } -/*! Expect one of the strings to be present and provide index to say which. - * - * Parsing of maps is a common operation. Regularly keys can be in any order and - * thus we need to expect multiple different strings to be present. This handles - * that for you. - * - * Note that the implementation is based on decision tree that is generated in - * runtime. If you know the strings upfront you can get a better performance - * if you fetch bytes and use some hash searching algorithm instead. - * - * @param unpack: Unpack handle. - * @param item: Item used for the `cp_unpack` calls and was used in the last - * one. - */ -int cp_unpack_expect_str(cp_unpack_t unpack, struct cpitem *item, - const char **strings) __attribute__((nonnull)); - - /*! Copy blob to malloc allocated buffer and provide it. * * This unpacks the whole blob and returns it. @@ -300,7 +369,7 @@ void cp_unpack_memndup(cp_unpack_t unpack, struct cpitem *item, uint8_t **data, * @param unpack: Unpack handle. * @param item: Item used for the `cp_unpack` calls and was used in the last * one. - * @param data: Pointer where pointer to the data would be placed. + * @param buf: Pointer where pointer to the data would be placed. * @param siz: Pointer where number of valid data bytes were unpacked. * @param obstack: Obstack used to allocate the space needed for the blob * object. @@ -317,7 +386,7 @@ void cp_unpack_memdupo(cp_unpack_t unpack, struct cpitem *item, uint8_t **buf, * @param unpack: Unpack handle. * @param item: Item used for the `cp_unpack` calls and was used in the last * one. - * @param data: Pointer where pointer to the data would be placed. + * @param buf: Pointer where pointer to the data would be placed. * @param siz: Pointer to maximum number of bytes to be copied that is updated * with number of valid bytes actually copied over. * @param obstack: Obstack used to allocate the space needed for the blob @@ -334,7 +403,7 @@ void cp_unpack_memndupo(cp_unpack_t unpack, struct cpitem *item, uint8_t **buf, * @param item: Item used for the `cp_unpack` calls and was used in the last * one. * @param dest: Destination buffer where unpacked bytes are placed to. - * @param n: Maximum number of bytes to be used in `dest`. + * @param siz: Maximum number of bytes to be used in `dest`. * @returns number of bytes written to `dest` or `-1` in case of unpack error. */ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_memcpy( @@ -342,11 +411,25 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_memcpy( item->buf = dest; item->bufsiz = siz; cp_unpack(unpack, item); + item->bufsiz = 0; if (item->type != CPITEM_BLOB) return -1; return item->as.Blob.len; } +/*! Helper macro for unpacking lists. + * + * Lists are sequence of items starting with @ref CPITEM_LIST item and ending + * with @ref CPITEM_CONTAINER_END. This provides loop that iterates over items + * in the list. + * + * You need to call this when you unpack @ref CPITEM_LIST and thus this does + * not check if you are calling it in list or not. It immediately starts + * unpacking next item and thus first item in the list. + * + * @param UNPACK: Generic unpacker. + * @param ITEM: Pointer to the @ref cpitem that was used to unpack last item. + */ #define for_cp_unpack_list(UNPACK, ITEM) \ while (({ \ struct cpitem *__item = ITEM; \ @@ -354,6 +437,14 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_memcpy( __item->type != CPITEM_INVALID && __item->type != CPITEM_CONTAINER_END; \ })) +/*! Helper macro for unpacking lists with items counter. + * + * This is variant of @ref for_cp_unpack_list with items counter from zero. + * + * @param UNPACK: Generic unpacker. + * @param ITEM: Pointer to the @ref cpitem that was used to unpack last item. + * @param CNT: Counter variable name. The variable is defined in the for loop. + */ #define for_cp_unpack_ilist(UNPACK, ITEM, CNT) \ for (unsigned CNT = 0; ({ \ struct cpitem *__item = ITEM; \ @@ -363,6 +454,15 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_memcpy( }); \ CNT++) +/*! Helper macro for unpacking maps. + * + * This provides loop that unpacks and validates key. You need to unpack the key + * value on your own and for correct functionality you need to fully unpack + * value as well (you can skip it if you do not need it). + * + * @param UNPACK: Generic unpacker. + * @param ITEM: Pointer to the @ref cpitem that was used to unpack last item. + */ #define for_cp_unpack_map(UNPACK, ITEM) \ while (({ \ struct cpitem *__item = ITEM; \ @@ -370,6 +470,8 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_memcpy( __item->type == CPITEM_STRING; \ })) +/*! Helper macro for unpacking integer maps. + */ #define for_cp_unpack_imap(UNPACK, ITEM) \ while (({ \ struct cpitem *__item = ITEM; \ diff --git a/include/shv/rpcapp.h b/include/shv/rpcapp.h deleted file mode 100644 index 0200f03..0000000 --- a/include/shv/rpcapp.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef SHV_RPCAPP_H -#define SHV_RPCAPP_H - -#include - -typedef struct rpcapp *rpcapp_t; - -void rpcapp_destroy(rpcapp_t rpcapp); - -rpcapp_t rpcapp_new(const char *name, const char *version) - __attribute__((malloc, malloc(rpcapp_destroy, 1))); - -struct rpchandler_stage rpcapp_handler_stage(rpcapp_t rpcapp) - __attribute__((nonnull)); - - -#endif diff --git a/include/shv/rpcbroker.h b/include/shv/rpcbroker.h index 1c72102..d5d1ade 100644 --- a/include/shv/rpcbroker.h +++ b/include/shv/rpcbroker.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCBROKER_H #define SHV_RPCBROKER_H diff --git a/include/shv/rpcclient.h b/include/shv/rpcclient.h index 1b5d63f..725ed59 100644 --- a/include/shv/rpcclient.h +++ b/include/shv/rpcclient.h @@ -1,25 +1,37 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCCLIENT_H #define SHV_RPCCLIENT_H +/*! @file + * Handle managing a single connection for SHV RPC. + */ + +#include #include -#include +#include #include +#include /*! Number of seconds that is default idle time before brokers disconnect * clients for inactivity. */ #define RPC_DEFAULT_IDLE_TIME 180 +/*! Handle used to manage SHV RPC client. */ typedef struct rpcclient *rpcclient_t; +/*! Establish a new connection based on the provided URL. + * + * This only estableshes the connection. You might need to perform login if you + * are connecting to the SHV Broker. + * + * @param url: RPC URL with connection info. + * @returns SHV RPC handle or `NULL` in case connection failed. Based on the + */ rpcclient_t rpcclient_connect(const struct rpcurl *url); -enum rpcclient_login_res { - RPCCLIENT_LOGIN_OK, - RPCCLIENT_LOGIN_INVALID, - RPCCLIENT_LOGIN_ERROR, -} rpcclient_login(rpcclient_t client, const struct rpclogin_options *opts) - __attribute__((nonnull)); +bool rpcclient_login(rpcclient_t client, const struct rpclogin_options *opts, + char **errmsg) __attribute__((nonnull(1, 2))); rpcclient_t rpcclient_stream_new(int readfd, int writefd); @@ -35,6 +47,8 @@ rpcclient_t rpcclient_serial_connect(const char *path); #define rpcclient_destroy(CLIENT) ((CLIENT)->disconnect(CLIENT)) +#define rpcclient_connected(CLIENT) ((CLIENT)->rfd != -1) + #define rpcclient_nextmsg(CLIENT) ((CLIENT)->msgfetch(CLIENT, true)) #define rpcclient_validmsg(CLIENT) ((CLIENT)->msgfetch(CLIENT, false)) @@ -47,6 +61,8 @@ rpcclient_t rpcclient_serial_connect(const char *path); #define rpcclient_dropmsg(CLIENT) ((CLIENT)->msgflush(CLIENT, false)) +#define rpcclient_pollfd(CLIENT) ((CLIENT)->rfd) + #define rpcclient_logger(CLIENT) ((CLIENT)->logger) diff --git a/include/shv/rpcclient_impl.h b/include/shv/rpcclient_impl.h index 4fd0a62..9a00a5e 100644 --- a/include/shv/rpcclient_impl.h +++ b/include/shv/rpcclient_impl.h @@ -1,9 +1,14 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCCLIENT_IMPL_H #define SHV_RPCCLIENT_IMPL_H -#include +/*! @file + * Implementation of the SHV RPC client handle. The implementation is defined + * publically to allow possibly anyone to provide custom implementation but at + * the same time it is split to clearly differentiate that direct access from + * the users to the handle is not desirable. + */ + #include -#include -#include #include #include diff --git a/include/shv/rpchandler.h b/include/shv/rpchandler.h index b293605..fba9e96 100644 --- a/include/shv/rpchandler.h +++ b/include/shv/rpchandler.h @@ -1,8 +1,10 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCHANDLER_H #define SHV_RPCHANDLER_H #include #include +#include #include #include #include @@ -75,9 +77,23 @@ bool rpcreceive_response_send(struct rpcreceive *receive) __attribute__((nonnull bool rpcreceive_response_drop(struct rpcreceive *receive) __attribute__((nonnull)); -bool rpchandler_ls_result(struct rpchandler_ls_ctx *context, const char *name) +void rpchandler_ls_result(struct rpchandler_ls_ctx *context, const char *name) __attribute__((nonnull)); +void rpchandler_ls_result_const(struct rpchandler_ls_ctx *context, + const char *name) __attribute__((nonnull)); + +void rpchandler_ls_result_vfmt(struct rpchandler_ls_ctx *context, + const char *fmt, va_list args) __attribute__((nonnull)); + +__attribute__((nonnull)) static inline void rpchandler_ls_result_fmt( + struct rpchandler_ls_ctx *context, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + rpchandler_ls_result_vfmt(context, fmt, args); + va_end(args); +} + const char *rpchandler_ls_name(struct rpchandler_ls_ctx *context) __attribute__((nonnull)); diff --git a/include/shv/rpchandler_app.h b/include/shv/rpchandler_app.h new file mode 100644 index 0000000..a4101bd --- /dev/null +++ b/include/shv/rpchandler_app.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef SHV_RPCHANDLER_APP_H +#define SHV_RPCHANDLER_APP_H +/*! @file */ + +#include + +typedef struct rpchandler_app *rpchandler_app_t; + +void rpchandler_app_destroy(rpchandler_app_t rpchandler_app); + +rpchandler_app_t rpchandler_app_new(const char *name, const char *version) + __attribute__((malloc, malloc(rpchandler_app_destroy, 1))); + +struct rpchandler_stage rpchandler_app_stage(rpchandler_app_t rpchandler_app) + __attribute__((nonnull)); + + +#endif diff --git a/include/shv/rpchandler_responses.h b/include/shv/rpchandler_responses.h new file mode 100644 index 0000000..531ff1d --- /dev/null +++ b/include/shv/rpchandler_responses.h @@ -0,0 +1,28 @@ +#ifndef SHV_RPCHANDLER_RESPONSES_H +#define SHV_RPCHANDLER_RESPONSES_H + +#include + + +typedef struct rpchandler_responses *rpchandler_responses_t; + +void rpchandler_responses_destroy(rpchandler_responses_t responder); + +rpchandler_responses_t rpchandler_responses_new(void) + __attribute__((malloc, malloc(rpchandler_responses_destroy, 1))); + +struct rpchandler_stage rpchandler_responses_stage( + rpchandler_responses_t responder) __attribute__((nonnull)); + +typedef struct rpcresponse *rpcresponse_t; + +rpcresponse_t rpcresponse_expect(rpchandler_responses_t responder, int request_id) + __attribute__((nonnull)); + +bool rpcresponse_waitfor(rpcresponse_t respond, struct rpcreceive **receive, + struct rpcmsg_meta **meta, int timeout) __attribute__((nonnull)); + +bool rpcresponse_validmsg(rpcresponse_t respond) __attribute__((nonnull)); + + +#endif diff --git a/include/shv/rpclogin.h b/include/shv/rpclogin.h deleted file mode 100644 index 6d13ab5..0000000 --- a/include/shv/rpclogin.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef SHV_RPCLOGIN_H -#define SHV_RPCLOGIN_H -#include -#include -#include - - -#endif diff --git a/include/shv/rpcmsg.h b/include/shv/rpcmsg.h index ede338f..dc1459c 100644 --- a/include/shv/rpcmsg.h +++ b/include/shv/rpcmsg.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCMSG_H #define SHV_RPCMSG_H @@ -106,7 +107,7 @@ enum rpcmsg_access { /*! Convert access level to its string representation. * * @param access: Access level to be converted. - * @retunrs Pointer to string constant representing the access level. + * @returns Pointer to string constant representing the access level. */ const char *rpcmsg_access_str(enum rpcmsg_access access); @@ -239,22 +240,24 @@ struct rpcmsg_meta_limits { * @param unpack: Unpack handle. * @param item: Item used for the `cp_unpack` calls and was used in the last * @param meta: Pointer to the structure where unpacked data will be placed. In - * case of integers the value is directly stored. For strings and byte arrays - * the data are stored on obstack, if provided, and meta contains pointers to - * this data. If you do not provide `obstack` then pointers in the meta need to - * point to valid buffers. + * case of integers the value is directly stored. For strings and byte arrays + * the data are stored on obstack, if provided, and meta contains pointers to + * this data. If you do not provide `obstack` then pointers in the meta need + * to point to valid buffers. * @param limits: Optional pointer to the limits imposed on the meta attributes. - * This is used for two purposes: It specifies size of the buffers if you do not - * provide `obstack; It limits maximal length for data copied to the obstack. - * Please see `struct rpcmsg_meta_limits` documentation for explanation. + * This is used for two purposes: It specifies size of the buffers if you do + * not provide `obstack; It limits maximal length for data copied to the + * obstack. Please see `struct rpcmsg_meta_limits` documentation for + * explanation. * @param obstack: pointer to the obstack used to allocate space for received - * strings and byte arrays. You can pass `NULL` but in such case you need to - * provide your own buffers in `meta`. + * strings and byte arrays. You can pass `NULL` but in such case you need to + * provide your own buffers in `meta`. * @returns `false` in case unpack reports error, if meta contains invalid value - * for supported key, or when there is not enough space to store caller IDs. In - * other cases `true` is returned. In short this return primarily signals if it - * is possible to generate response to this message for request messages and if - * it is possible to parameters, response or error for other message types. + * for supported key, or when there is not enough space to store caller IDs. + * In other cases `true` is returned. In short this return primarily signals + * if it is possible to generate response to this message for request messages + * and if it is possible to parameters, response or error for other message + * types. */ bool rpcmsg_head_unpack(cp_unpack_t unpack, struct cpitem *item, struct rpcmsg_meta *meta, struct rpcmsg_meta_limits *limits, @@ -291,4 +294,7 @@ size_t rpcmsg_pack_vferror(cp_pack_t, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, va_list args) __attribute__((nonnull(1, 2))); +bool rpcmsg_unpack_error(cp_unpack_t unpack, struct cpitem *item, + enum rpcmsg_error *errnum, char **errmsg) __attribute__((nonnull(1, 2))); + #endif diff --git a/include/shv/rpcnode.h b/include/shv/rpcnode.h index 5dd7871..60f5b31 100644 --- a/include/shv/rpcnode.h +++ b/include/shv/rpcnode.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCNODE_H #define SHV_RPCNODE_H diff --git a/include/shv/rpcping.h b/include/shv/rpcping.h deleted file mode 100644 index 77bb343..0000000 --- a/include/shv/rpcping.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef SHV_RPCPING_H -#define SHV_RPCPING_H -#include - -static inline size_t rpcping_pack(cp_pack_t pack, int rid) { - return rpcmsg_pack_request_void(pack, "broker/app", "ping", rid); -} - -#endif diff --git a/include/shv/rpcrespond.h b/include/shv/rpcrespond.h deleted file mode 100644 index 56a59a4..0000000 --- a/include/shv/rpcrespond.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SHV_RPCRESPOND_H -#define SHV_RPCRESPOND_H - -#include -#include -#include - - -typedef struct rpcresponder *rpcresponder_t; - -void rpcresponder_destroy(rpcresponder_t responder); - -rpcresponder_t rpcresponder_new(void) - __attribute__((malloc, malloc(rpcresponder_destroy, 1))); - -struct rpchandler_stage rpcresponder_handler_stage(rpcresponder_t responder) - __attribute__((nonnull)); - -typedef struct rpcrespond *rpcrespond_t; - -rpcrespond_t rpcrespond_expect(rpcresponder_t responder, int request_id) - __attribute__((nonnull)); - -bool rpcrespond_waitfor(rpcrespond_t respond, struct rpcreceive **receive, - struct rpcmsg_meta **meta, int timeout) __attribute__((nonnull)); - -bool rpcrespond_validmsg(rpcrespond_t respond) __attribute__((nonnull)); - - -#endif diff --git a/include/shv/rpcserver.h b/include/shv/rpcserver.h index ba30ef4..8c2815b 100644 --- a/include/shv/rpcserver.h +++ b/include/shv/rpcserver.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCSERVER_H #define SHV_RPCSERVER_H #include diff --git a/include/shv/rpcurl.h b/include/shv/rpcurl.h index 5edc2d5..192b0e6 100644 --- a/include/shv/rpcurl.h +++ b/include/shv/rpcurl.h @@ -1,5 +1,15 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCURL_H #define SHV_RPCURL_H +/*! @file + * RPC URL is universal identifier that is used to specify the way client + * connects to the server as well as server listen configuration. The URL is a + * complete set parameters needed to connect client (including the client + * device) to the SHV network. + * + * The description of the format can be found [in pySHV + * documentation](https://elektroline-predator.gitlab.io/pyshv/master/url.html). + */ /*! Protocol used to connect client to the server or to listen for connection on */ diff --git a/libshvchainpack/chainpack.c b/libshvchainpack/chainpack.c deleted file mode 100644 index 1ad9267..0000000 --- a/libshvchainpack/chainpack.c +++ /dev/null @@ -1,739 +0,0 @@ -#include - -#include -#include - - -// UTC msec since 2.2. 2018 folowed by signed UTC offset in 1/4 hour -// Fri Feb 02 2018 00:00:00 == 1517529600 EPOCH -static const int64_t SHV_EPOCH_MSEC = 1517529600000; - -const char *chainpack_packing_schema_name(int sch) { - switch (sch) { - case CP_INVALID: - return "INVALID"; - case CP_FALSE: - return "FALSE"; - case CP_TRUE: - return "TRUE"; - - case CP_Null: - return "Null"; - case CP_UInt: - return "UInt"; - case CP_Int: - return "Int"; - case CP_Double: - return "Double"; - case CP_Bool: - return "Bool"; - case CP_Blob: - return "Blob"; - case CP_String: - return "String"; - case CP_CString: - return "CString"; - case CP_List: - return "List"; - case CP_Map: - return "Map"; - case CP_IMap: - return "IMap"; - case CP_DateTimeEpoch_depr: - return "DateTimeEpoch_depr"; - case CP_DateTime: - return "DateTime"; - case CP_MetaMap: - return "MetaMap"; - case CP_Decimal: - return "Decimal"; - - case CP_TERM: - return "TERM"; - } - return ""; -} - -static void copy_bytes_cstring( - cpcp_pack_context *pack_context, const void *str, size_t len) { - size_t i; - for (i = 0; i < len; ++i) { - if (pack_context->err_no != CPCP_RC_OK) - return; - uint8_t ch = ((const uint8_t *)str)[i]; - switch (ch) { - case '\0': - case '\\': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, ch); - break; - default: - cpcp_pack_copy_byte(pack_context, ch); - } - } -} - -#if defined(__GNUC__) && __GNUC__ >= 4 - -static int significant_bits_part_length(uint64_t n) { - int len = 0; - int llbits = sizeof(long long) * CHAR_BIT; - - if (n == 0) - return 0; - - if ((llbits < 64) && (n & 0xFFFFFFFF00000000)) { - len += 32; - n >>= 32; - } - - len += llbits - __builtin_clzll(n); - - return len; -} - -#else /* Fallback for generic compiler */ - -// see https://en.wikipedia.org/wiki/Find_first_set#CLZ -static const uint8_t sig_table_4bit[16] = { - 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4}; - -static int significant_bits_part_length(uint64_t n) { - int len = 0; - - if (n & 0xFFFFFFFF00000000) { - len += 32; - n >>= 32; - } - if (n & 0xFFFF0000) { - len += 16; - n >>= 16; - } - if (n & 0xFF00) { - len += 8; - n >>= 8; - } - if (n & 0xF0) { - len += 4; - n >>= 4; - } - len += sig_table_4bit[n]; - return len; -} - -#endif /* end of significant_bits_part_length function */ - -// number of bytes needed to encode bit_len -static int bytes_needed(int bit_len) { - int cnt; - if (bit_len <= 28) - cnt = (bit_len - 1) / 7 + 1; - else - cnt = (bit_len - 1) / 8 + 2; - return cnt; -} - -/* UInt - 0 ... 7 bits 1 byte |0|x|x|x|x|x|x|x|<-- LSB - 8 ... 14 bits 2 bytes |1|0|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB -15 ... 21 bits 3 bytes |1|1|0|x|x|x|x|x| |x|x|x|x|x|x|x|x| -|x|x|x|x|x|x|x|x|<-- LSB 22 ... 28 bits 4 bytes |1|1|1|0|x|x|x|x| -|x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB 29+ bits 5+ -bytes |1|1|1|1|n|n|n|n| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| -... <-- LSB n == 0 -> 4 bytes number (32 bit number) n == 1 -> 5 bytes -number n == 14 -> 18 bytes number n == 15 -> for future (number of bytes will be -specified in next byte) -*/ - -static void pack_uint_data_helper( - cpcp_pack_context *pack_context, uint64_t num, int bit_len) { - int byte_cnt = bytes_needed(bit_len); - uint8_t bytes[byte_cnt]; - int i; - for (i = byte_cnt - 1; i >= 0; --i) { - uint8_t r = num & 255; - bytes[i] = r; - num = num >> 8; - } - - uint8_t *head = bytes; - if (bit_len <= 28) { - uint8_t mask = (uint8_t)(0xf0 << (4 - byte_cnt)); - *head = (uint8_t)(*head & ~mask); - mask = (uint8_t)(mask << 1); - *head = *head | mask; - } else { - *head = (uint8_t)(0xf0 | (byte_cnt - 5)); - } - - for (i = 0; i < byte_cnt; ++i) { - uint8_t r = bytes[i]; - cpcp_pack_copy_byte(pack_context, r); - } -} - -void chainpack_pack_uint_data(cpcp_pack_context *pack_context, uint64_t num) { - const size_t UINT_BYTES_MAX = 18; - if (sizeof(num) > UINT_BYTES_MAX) { - pack_context->err_no = CPCP_RC_LOGICAL_ERROR; //("writeData_UInt: value - // too big to pack!"); - return; - } - - int bitlen = significant_bits_part_length(num); - pack_uint_data_helper(pack_context, num, bitlen); -} - -/* - 0 ... 7 bits 1 byte |0|s|x|x|x|x|x|x|<-- LSB - 8 ... 14 bits 2 bytes |1|0|s|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB -15 ... 21 bits 3 bytes |1|1|0|s|x|x|x|x| |x|x|x|x|x|x|x|x| -|x|x|x|x|x|x|x|x|<-- LSB 22 ... 28 bits 4 bytes |1|1|1|0|s|x|x|x| -|x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x|<-- LSB 29+ bits 5+ -bytes |1|1|1|1|n|n|n|n| |s|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| |x|x|x|x|x|x|x|x| -... <-- LSB n == 0 -> 4 bytes number (32 bit number) n == 1 -> 5 bytes -number n == 14 -> 18 bytes number n == 15 -> for future (number of bytes will be -specified in next byte) -*/ - -// return max bit length >= bit_len, which can be encoded by same number of bytes -static int expand_bit_len(int bit_len) { - int ret; - int byte_cnt = bytes_needed(bit_len); - if (bit_len <= 28) { - ret = byte_cnt * (8 - 1) - 1; - } else { - ret = (byte_cnt - 1) * 8 - 1; - } - return ret; -} - -static void chainpack_pack_int_data(cpcp_pack_context *pack_context, int64_t snum) { - uint64_t num = (uint64_t)(snum < 0 ? -snum : snum); - bool neg = (snum < 0); - - int bitlen = significant_bits_part_length(num); - bitlen++; // add sign bit - if (neg) { - int sign_pos = expand_bit_len(bitlen); - uint64_t sign_bit_mask = (uint64_t)1 << sign_pos; - num |= sign_bit_mask; - } - pack_uint_data_helper(pack_context, num, bitlen); -} - -void chainpack_pack_uint(cpcp_pack_context *pack_context, uint64_t i) { - if (pack_context->err_no) - return; - if (i < 64) { - cpcp_pack_copy_byte(pack_context, i % 64); - } else { - cpcp_pack_copy_byte(pack_context, CP_UInt); - chainpack_pack_uint_data(pack_context, i); - } -} - -void chainpack_pack_int(cpcp_pack_context *pack_context, int64_t i) { - if (pack_context->err_no) - return; - if (i >= 0 && i < 64) { - cpcp_pack_copy_byte(pack_context, (uint8_t)((i % 64) + 64)); - } else { - cpcp_pack_copy_byte(pack_context, CP_Int); - chainpack_pack_int_data(pack_context, i); - } -} - -void chainpack_pack_decimal(cpcp_pack_context *pack_context, const cpcp_decimal *v) { - if (pack_context->err_no) - return; - cpcp_pack_copy_byte(pack_context, CP_Decimal); - chainpack_pack_int_data(pack_context, v->mantisa); - chainpack_pack_int_data(pack_context, v->exponent); -} - -void chainpack_pack_double(cpcp_pack_context *pack_context, double d) { - if (pack_context->err_no) - return; - - cpcp_pack_copy_byte(pack_context, CP_Double); - - const uint8_t *bytes = (const uint8_t *)&d; - int len = sizeof(double); - - int n = 1; - int i; - if (*(char *)&n == 1) { - // little endian if true - for (i = 0; i < len; i++) - cpcp_pack_copy_byte(pack_context, bytes[i]); - } else { - for (i = len - 1; i >= 0; i--) - cpcp_pack_copy_byte(pack_context, bytes[i]); - } -} - -void chainpack_pack_date_time( - cpcp_pack_context *pack_context, const cpcp_date_time *v) { - if (pack_context->err_no) - return; - - cpcp_pack_copy_byte(pack_context, CP_DateTime); - - // some arguable optimizations when msec == 0 or TZ_offset == 0 - // this can save byte in packed date-time, but packing scheme is more - // complicated - int64_t msecs = v->msecs_since_epoch - SHV_EPOCH_MSEC; - int offset = (v->minutes_from_utc / 15) & 0x7F; - int ms = (int)(msecs % 1000); - if (ms == 0) - msecs /= 1000; - if (offset != 0) { - msecs *= 128; - msecs |= offset; - } - msecs *= 4; - if (offset != 0) - msecs |= 1; - if (ms == 0) - msecs |= 2; - chainpack_pack_int_data(pack_context, msecs); -} - -void chainpack_pack_null(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - - cpcp_pack_copy_byte(pack_context, CP_Null); -} - -static void chainpack_pack_true(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - cpcp_pack_copy_byte(pack_context, CP_TRUE); -} - -static void chainpack_pack_false(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - cpcp_pack_copy_byte(pack_context, CP_FALSE); -} - -void chainpack_pack_boolean(cpcp_pack_context *pack_context, bool b) { - if (pack_context->err_no) - return; - if (b) - chainpack_pack_true(pack_context); - else - chainpack_pack_false(pack_context); -} - -void chainpack_pack_list_begin(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - cpcp_pack_copy_byte(pack_context, CP_List); -} - -void chainpack_pack_map_begin(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - cpcp_pack_copy_byte(pack_context, CP_Map); -} - -void chainpack_pack_imap_begin(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - cpcp_pack_copy_byte(pack_context, CP_IMap); -} - -void chainpack_pack_meta_begin(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - cpcp_pack_copy_byte(pack_context, CP_MetaMap); -} - -void chainpack_pack_container_end(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - cpcp_pack_copy_byte(pack_context, CP_TERM); -} - -void chainpack_pack_blob( - cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len) { - chainpack_pack_blob_start(pack_context, buff_len, buff, buff_len); -} - -void chainpack_pack_blob_start(cpcp_pack_context *pack_context, - size_t string_len, const uint8_t *buff, size_t buff_len) { - cpcp_pack_copy_byte(pack_context, CP_Blob); - chainpack_pack_uint_data(pack_context, string_len); - cpcp_pack_copy_bytes(pack_context, buff, buff_len); -} - -void chainpack_pack_blob_cont( - cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len) { - cpcp_pack_copy_bytes(pack_context, buff, buff_len); -} - -void chainpack_pack_string( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { - chainpack_pack_string_start(pack_context, buff_len, buff, buff_len); -} - -void chainpack_pack_string_start(cpcp_pack_context *pack_context, - size_t string_len, const char *buff, size_t buff_len) { - cpcp_pack_copy_byte(pack_context, CP_String); - chainpack_pack_uint_data(pack_context, string_len); - cpcp_pack_copy_bytes(pack_context, buff, buff_len); -} - -void chainpack_pack_string_cont( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { - cpcp_pack_copy_bytes(pack_context, buff, buff_len); -} - -void chainpack_pack_cstring( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { - chainpack_pack_cstring_start(pack_context, buff, buff_len); - chainpack_pack_cstring_finish(pack_context); -} - -void chainpack_pack_cstring_terminated( - cpcp_pack_context *pack_context, const char *str) { - size_t len = strlen(str); - chainpack_pack_cstring(pack_context, str, len); -} - -void chainpack_pack_cstring_start( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { - cpcp_pack_copy_byte(pack_context, CP_CString); - copy_bytes_cstring(pack_context, buff, buff_len); -} - -void chainpack_pack_cstring_cont( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { - copy_bytes_cstring(pack_context, buff, buff_len); -} - -void chainpack_pack_cstring_finish(cpcp_pack_context *pack_context) { - cpcp_pack_copy_byte(pack_context, '\0'); -} - -//============================ U N P A C K ================================= - -/// @pbitlen is used to enable same function usage for signed int unpacking -static void unpack_uint(cpcp_unpack_context *unpack_context, int *pbitlen) { - if (pbitlen) - *pbitlen = 0; - uint64_t num = 0; - int bitlen = 0; - - const char *p; - UNPACK_TAKE_BYTE(p); - uint8_t head = (uint8_t)(*p); - - int bytes_to_read_cnt; - if ((head & 128) == 0) { - bytes_to_read_cnt = 0; - num = head & 127; - bitlen = 7; - } else if ((head & 64) == 0) { - bytes_to_read_cnt = 1; - num = head & 63; - bitlen = 6 + 8; - } else if ((head & 32) == 0) { - bytes_to_read_cnt = 2; - num = head & 31; - bitlen = 5 + 2 * 8; - } else if ((head & 16) == 0) { - bytes_to_read_cnt = 3; - num = head & 15; - bitlen = 4 + 3 * 8; - } else { - bytes_to_read_cnt = (head & 0xf) + 4; - bitlen = bytes_to_read_cnt * 8; - } - int i; - for (i = 0; i < bytes_to_read_cnt; ++i) { - UNPACK_TAKE_BYTE(p); - uint8_t r = (uint8_t)(*p); - num = (num << 8) + r; - }; - - unpack_context->item.as.UInt = num; - unpack_context->item.type = CPCP_ITEM_UINT; - if (pbitlen) - *pbitlen = bitlen; -} - -static void unpack_int(cpcp_unpack_context *unpack_context, int64_t *pval) { - int64_t snum = 0; - int bitlen; - unpack_uint(unpack_context, &bitlen); - if (unpack_context->err_no == CPCP_RC_OK) { - const uint64_t sign_bit_mask = (uint64_t)1 << (bitlen - 1); - uint64_t num = unpack_context->item.as.UInt; - snum = (int64_t)num; - - // Note: masked value assignment to bool variable would be undefined on - // some platforms. - - if (num & sign_bit_mask) { - snum &= ~(int64_t)sign_bit_mask; - snum = -snum; - } - } - if (pval) - *pval = snum; -} - -void unpack_string(cpcp_unpack_context *unpack_context) { - const char *p; - cpcp_string *it = &unpack_context->item.as.String; - - bool is_cstr = it->string_size < 0; - if (is_cstr) { - for (it->chunk_size = 0; it->chunk_size < it->chunk_buff_len;) { - UNPACK_TAKE_BYTE(p); - if (*p == '\\') { - UNPACK_TAKE_BYTE(p); - if (!p) - return; - switch (*p) { - case '\\': - (it->chunk_start)[it->chunk_size++] = '\\'; - break; - case '0': - (it->chunk_start)[it->chunk_size++] = '\0'; - break; - default: - (it->chunk_start)[it->chunk_size++] = *p; - break; - } - break; - } - if (*p == '\0') { - // end of string - it->last_chunk = 1; - break; - } - - (it->chunk_start)[it->chunk_size++] = *p; - } - } else { - it->chunk_size = 0; - while (it->size_to_load > 0 && it->chunk_size < it->chunk_buff_len) { - UNPACK_TAKE_BYTE(p); - (it->chunk_start)[it->chunk_size++] = *p; - it->size_to_load--; - } - it->last_chunk = (it->size_to_load == 0); - } - it->chunk_cnt++; - unpack_context->item.type = CPCP_ITEM_STRING; -} - -void unpack_blob(cpcp_unpack_context *unpack_context) { - unpack_string(unpack_context); - unpack_context->item.type = CPCP_ITEM_BLOB; -} - -void chainpack_unpack_next(cpcp_unpack_context *unpack_context) { - if (unpack_context->err_no) - return; - - if (unpack_context->item.type == CPCP_ITEM_STRING || - unpack_context->item.type == CPCP_ITEM_BLOB) { - cpcp_string *str_it = &unpack_context->item.as.String; - if (!str_it->last_chunk) { - if (unpack_context->item.type == CPCP_ITEM_STRING) - unpack_string(unpack_context); - else - unpack_blob(unpack_context); - return; - } - } - - const char *p; - UNPACK_TAKE_BYTE(p); - - uint8_t packing_schema = (uint8_t)(*p); - - cpcp_container_state *top_cont_state = - cpcp_unpack_context_top_container_state(unpack_context); - if (top_cont_state && packing_schema != CP_TERM) { - top_cont_state->item_count++; - } - - unpack_context->item.type = CPCP_ITEM_INVALID; - if (packing_schema < 128) { - if (packing_schema & 64) { - // tiny Int - unpack_context->item.type = CPCP_ITEM_INT; - unpack_context->item.as.Int = packing_schema & 63; - } else { - // tiny UInt - unpack_context->item.type = CPCP_ITEM_UINT; - unpack_context->item.as.UInt = packing_schema & 63; - } - } else { - switch (packing_schema) { - case CP_Null: { - unpack_context->item.type = CPCP_ITEM_NULL; - break; - } - case CP_TRUE: { - unpack_context->item.type = CPCP_ITEM_BOOLEAN; - unpack_context->item.as.Bool = 1; - break; - } - case CP_FALSE: { - unpack_context->item.type = CPCP_ITEM_BOOLEAN; - unpack_context->item.as.Bool = 0; - break; - } - case CP_Int: { - int64_t n; - unpack_int(unpack_context, &n); - unpack_context->item.type = CPCP_ITEM_INT; - unpack_context->item.as.Int = n; - break; - } - case CP_UInt: { - unpack_uint(unpack_context, NULL); - break; - } - case CP_Double: { - unpack_context->item.type = CPCP_ITEM_DOUBLE; - uint8_t *bytes = (uint8_t *)&(unpack_context->item.as.Double); - int len = sizeof(double); - - int n = 1; - int i; - if (*(char *)&n == 1) { - // little endian if true - for (i = 0; i < len; i++) { - UNPACK_TAKE_BYTE(p); - bytes[i] = (uint8_t)(*p); - } - } else { - for (i = len - 1; i >= 0; i--) { - UNPACK_TAKE_BYTE(p); - bytes[i] = (uint8_t)(*p); - } - } - break; - } - case CP_Decimal: { - int64_t mant; - unpack_int(unpack_context, &mant); - int64_t exp; - unpack_int(unpack_context, &exp); - unpack_context->item.type = CPCP_ITEM_DECIMAL; - unpack_context->item.as.Decimal.mantisa = mant; - unpack_context->item.as.Decimal.exponent = (int)exp; - break; - } - case CP_DateTime: { - int64_t d; - unpack_int(unpack_context, &d); - int8_t offset = 0; - bool has_tz_offset = d & 1; - bool has_not_msec = d & 2; - d >>= 2; - if (has_tz_offset) { - offset = d & 0x7F; - offset = (int8_t)(offset << 1); - offset >>= 1; // sign extension - d >>= 7; - } - if (has_not_msec) - d *= 1000; - d += SHV_EPOCH_MSEC; - - unpack_context->item.type = CPCP_ITEM_DATE_TIME; - cpcp_date_time *it = &unpack_context->item.as.DateTime; - it->msecs_since_epoch = d; - it->minutes_from_utc = offset * (int)15; - break; - } - case CP_MetaMap: { - unpack_context->item.type = CPCP_ITEM_META; - cpcp_unpack_context_push_container_state( - unpack_context, unpack_context->item.type); - break; - } - case CP_Map: { - unpack_context->item.type = CPCP_ITEM_MAP; - cpcp_unpack_context_push_container_state( - unpack_context, unpack_context->item.type); - break; - } - case CP_IMap: { - unpack_context->item.type = CPCP_ITEM_IMAP; - cpcp_unpack_context_push_container_state( - unpack_context, unpack_context->item.type); - break; - } - case CP_List: { - unpack_context->item.type = CPCP_ITEM_LIST; - cpcp_unpack_context_push_container_state( - unpack_context, unpack_context->item.type); - break; - } - case CP_TERM: { - unpack_context->item.type = CPCP_ITEM_CONTAINER_END; - cpcp_unpack_context_pop_container_state(unpack_context); - break; - } - case CP_Blob: { - cpcp_string *it = &unpack_context->item.as.String; - cpcp_string_init(it, unpack_context); - unpack_uint(unpack_context, NULL); - if (unpack_context->err_no == CPCP_RC_OK) { - it->string_size = (long)(unpack_context->item.as.UInt); - it->size_to_load = it->string_size; - unpack_blob(unpack_context); - } - break; - } - case CP_String: { - cpcp_string *it = &unpack_context->item.as.String; - cpcp_string_init(it, unpack_context); - unpack_uint(unpack_context, NULL); - if (unpack_context->err_no == CPCP_RC_OK) { - it->string_size = (long)(unpack_context->item.as.UInt); - it->size_to_load = it->string_size; - unpack_string(unpack_context); - } - break; - } - case CP_CString: { - cpcp_string *it = &unpack_context->item.as.String; - cpcp_string_init(it, unpack_context); - it->string_size = -1; - it->size_to_load = it->string_size; - unpack_string(unpack_context); - break; - } - default: - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Invalid type info."); - } - } -} - -uint64_t chainpack_unpack_uint_data(cpcp_unpack_context *unpack_context, bool *ok) { - int err_code; - uint64_t n = chainpack_unpack_uint_data2(unpack_context, &err_code); - if (ok) - *ok = (err_code == CPCP_RC_OK); - return n; -} - -uint64_t chainpack_unpack_uint_data2( - cpcp_unpack_context *unpack_context, int *err_code) { - unpack_uint(unpack_context, NULL); - if (err_code) - *err_code = unpack_context->err_no; - return unpack_context->item.as.UInt; -} diff --git a/libshvchainpack/common.c b/libshvchainpack/common.c index 225ad1a..5887ba7 100644 --- a/libshvchainpack/common.c +++ b/libshvchainpack/common.c @@ -30,6 +30,8 @@ bool common_unpack(size_t *res, FILE *f, struct cpitem *item) { } bool common_pack(size_t *res, FILE *f, const struct cpitem *item) { + if (ferror(f)) /* No reason to write to file in error */ + return true; switch (item->type) { case CPITEM_INVALID: /* Do nothing for invalid item */ diff --git a/libshvchainpack/cp_unpack.c b/libshvchainpack/cp_unpack.c index 71ebb04..c9f6e19 100644 --- a/libshvchainpack/cp_unpack.c +++ b/libshvchainpack/cp_unpack.c @@ -103,10 +103,12 @@ static void *cp_unpack_dup( cp_unpack(unpack, item); if (item->type != (size ? CPITEM_BLOB : CPITEM_STRING)) { free(res); + item->bufsiz = 0; return NULL; } off += item->as.Blob.len; } while (off < len && !(item->as.Blob.flags & CPBI_F_LAST)); + item->bufsiz = 0; if (size) { *size = off; return realloc(res, off); @@ -165,6 +167,7 @@ static void *cp_unpack_dupo(cp_unpack_t unpack, struct cpitem *item, } } while (obstack_object_size(obstack) < len && !(item->as.Blob.flags & CPBI_F_LAST)); + item->bufsiz = 0; if (size) *size = obstack_object_size(obstack) - zeropad; else if (zeropad == 0) @@ -192,12 +195,6 @@ void cp_unpack_memndupo(cp_unpack_t unpack, struct cpitem *item, uint8_t **buf, *buf = cp_unpack_dupo(unpack, item, siz, *siz, obstack); } -int cp_unpack_expect_str( - cp_unpack_t unpack, struct cpitem *item, const char **strings) { - // TODO - return -2; -} - struct cookie { cp_unpack_t unpack; @@ -211,6 +208,7 @@ static ssize_t _read(void *cookie, char *buf, size_t size, bool blob) { c->item->chr = buf; c->item->bufsiz = size; cp_unpack(c->unpack, c->item); + c->item->bufsiz = 0; if (c->item->type != (blob ? CPITEM_BLOB : CPITEM_STRING)) return -1; return c->item->as.String.len; diff --git a/libshvchainpack/cpon.c b/libshvchainpack/cpon.c deleted file mode 100644 index 19b4c80..0000000 --- a/libshvchainpack/cpon.c +++ /dev/null @@ -1,1433 +0,0 @@ -#include - -#include -#include - -static inline uint8_t hexify(uint8_t b) { - if (b <= 9) - return b + '0'; - if (b >= 10 && b <= 15) - return (uint8_t)(b - 10 + 'a'); - return '?'; -} - -static inline int unhex(uint8_t b) { - if (b >= '0' && b <= '9') - return b - '0'; - if (b >= 'a' && b <= 'f') - return b - 'a' + 10; - if (b >= 'A' && b <= 'F') - return b - 'A' + 10; - return -1; -} - -static size_t uint_to_str(char *buff, size_t buff_len, uint64_t n) { - size_t len = 0; - if (n == 0) { - if (len < buff_len) - buff[len] = '0'; - len++; - } else - while (n > 0) { - char r = (char)(n % 10); - n /= 10; - if (len < buff_len) - buff[len++] = '0' + r; - } - if (len < buff_len) { - size_t i; - for (i = 0; i < len / 2; i++) { - char c = buff[i]; - buff[i] = buff[len - i - 1]; - buff[len - i - 1] = c; - } - } - return len; -} - -static size_t uint_to_str_lpad( - char *buff, size_t buff_len, uint64_t n, size_t width, char pad_char) { - size_t len = uint_to_str(buff, buff_len, n); - if (len < width && width <= buff_len) { - size_t i; - for (i = 0; i < len; ++i) { - buff[width - i - 1] = buff[len - i - 1]; - buff[len - i - 1] = pad_char; - } - return width; - } - return len; -} - -static size_t uint_dot_uint_to_str( - char *buff, size_t buff_len, uint64_t n1, uint64_t n2) { - size_t len = uint_to_str(buff, buff_len, n1); - if (len < buff_len) - buff[len] = '.'; - size_t dot_pos = len; - len++; - if (n2 > 0) { - len += uint_to_str(buff + len, (len < buff_len) ? buff_len - len : 0, n2); - if (len < buff_len) { - // remove trailing zeros - for (; len > dot_pos && buff[len - 1] == '0'; len--) - ; - } - } - return len; -} - -static size_t int_to_str(char *buff, size_t buff_len, int64_t n) { - size_t len = 0; - char *buff2 = buff; - if (n < 0) { - buff2 = buff + 1; - if (len < buff_len) - buff[len] = '-'; - len++; - n = -n; - } - len += uint_to_str(buff2, buff_len, (uint64_t)n); - return len; -} - -static size_t double_to_str(char *buff, size_t buff_len, double d) { - const int prec = 6; - unsigned prec_num = 1; - int i; - for (i = 0; i < prec; ++i) - prec_num *= 10; - - size_t len = 0; - if (d == 0) { - if (len < buff_len) - buff[len] = '0'; - len++; - if (len < buff_len) - buff[len] = '.'; - len++; - } else { - bool neg = d < 0; - if (neg) { - if (len < buff_len) - buff[len] = '-'; - len++; - d = -d; - } - if (d < 1e7 && d >= 0.1) { - /// float point notation - if (d < 1) { - for (i = 0; i < prec; ++i) - d *= 10; - unsigned ud = (unsigned)d; - len += uint_dot_uint_to_str( - buff + len, (len < buff_len) ? buff_len - len : 0, 0, ud); - } else { - double myprec = 1; - while (d >= 10) { - d /= 10; - myprec /= 10; - } - for (i = 0; i < prec; ++i) { - d *= 10; - myprec *= 10; - } - unsigned ud = (unsigned)d; - unsigned myprecnum = (unsigned)myprec; - len += uint_dot_uint_to_str(buff + len, - (len < buff_len) ? buff_len - len : 0, ud / myprecnum, - ud % myprecnum); - } - } else { - /// exponential notation - int exp = 0; - if (d < 1) { - while (d < 1) { - d *= 10; - exp--; - } - for (i = 0; i < prec; ++i) - d *= 10; - unsigned ud = (unsigned)d; - len += uint_dot_uint_to_str(buff + len, - (len < buff_len) ? buff_len - len : 0, ud / prec_num, - ud % prec_num); - } else { - while (d >= 10) { - d /= 10; - exp++; - } - for (i = 0; i < prec; ++i) - d *= 10; - unsigned ud = (unsigned)d; - len += uint_dot_uint_to_str(buff + len, - (len < buff_len) ? buff_len - len : 0, ud / prec_num, - ud % prec_num); - } - if (len < buff_len && buff[len - 1] == '.') // remove trailing dot - len--; - if (len < buff_len) - buff[len] = 'e'; - len++; - len += int_to_str( - buff + len, (len < buff_len) ? buff_len - len : 0, exp); - } - } - return len; -} - -// see -// http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_15 -// see https://stackoverflow.com/questions/16647819/timegm-cross-platform -// see https://www.boost.org/doc/libs/1_62_0/boost/chrono/io/time_point_io.hpp -static int is_leap(int y) { - return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0); -} - -static int32_t days_from_0(int32_t year) { - year--; - return 365 * year + (year / 400) - (year / 100) + (year / 4); -} - -static int32_t days_from_1970(int32_t year) { - static int32_t days_from_0_to_1970 = 0; - if (days_from_0_to_1970 == 0) - days_from_0_to_1970 = days_from_0(1970); - return days_from_0(year) - days_from_0_to_1970; -} - -static int days_from_1jan(int year, int month, int mday) { - static const int days[2][12] = { - {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, - {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}}; - - return days[is_leap(year)][month] + mday - 1; -} - -int64_t cpon_timegm(struct tm *tm) { - // leap seconds are not part of Posix - int64_t res = 0; - int year = tm->tm_year + 1900; - int month = tm->tm_mon; // 0 - 11 - int mday = tm->tm_mday; // 1 - 31 - res = days_from_1970(year); - res += days_from_1jan(year, month, mday); - res *= 24; - res += tm->tm_hour; - res *= 60; - res += tm->tm_min; - res *= 60; - res += tm->tm_sec; - return res; -} - -// Returns year/month/day triple in civil calendar -// Preconditions: z is number of days since 1970-01-01 and is in the range: -// [numeric_limits::min(), -// numeric_limits::max()-719468]. -static void civil_from_days(long long z, int *py, unsigned *pm, unsigned *pd) { - int y; - unsigned m; - unsigned d; - z += 719468; - const long long era = (z >= 0 ? z : z - 146096) / 146097; - const unsigned doe = (const unsigned)(z - era * 146097); // [0, 146096] - const unsigned yoe = - (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] - y = (int)(((long)yoe) + era * 400); - const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] - const unsigned mp = (5 * doy + 2) / 153; // [0, 11] - d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] - if (mp < 10) - m = mp + 3; - else - m = mp - 9; - y += (m <= 2); - --m; - if (py) - *py = y; - if (pm) - *pm = m; - if (pd) - *pd = d; -} - -void cpon_gmtime(int64_t epoch_sec, struct tm *tm) { - if (!tm) - return; - - const long seconds_in_day = 3600 * 24; - long long days_since_epoch = (epoch_sec / seconds_in_day); - long long hms = epoch_sec - seconds_in_day * days_since_epoch; - if (hms < 0) { - days_since_epoch -= 1; - hms = seconds_in_day + hms; - } - - int y; - unsigned m, d; - civil_from_days(days_since_epoch, &y, &m, &d); - tm->tm_year = y - 1900; - tm->tm_mon = (int)m; - tm->tm_mday = (int)d; - - tm->tm_hour = (int)(hms / 3600); - const int ms = (int)(hms % 3600); - tm->tm_min = ms / 60; - tm->tm_sec = ms % 60; - - tm->tm_isdst = -1; -} - -static const char CCPON_STR_NULL[] = "null"; -static const char CCPON_STR_TRUE[] = "true"; -static const char CCPON_STR_FALSE[] = "false"; -static const char CCPON_STR_IMAP_BEGIN[] = "i{"; -static const char CCPON_DATE_TIME_BEGIN[] = "d\""; - -enum { - CCPON_C_KEY_DELIM = ':', - CCPON_C_FIELD_DELIM = ',', - CCPON_C_LIST_BEGIN = '[', - CCPON_C_LIST_END = ']', - CCPON_C_ARRAY_END = ']', - CCPON_C_MAP_BEGIN = '{', - CCPON_C_MAP_END = '}', - CCPON_C_META_BEGIN = '<', - CCPON_C_META_END = '>', - CCPON_C_UNSIGNED_END = 'u' -}; - -#ifdef FORCE_NO_LIBRARY - -static void *memcpy(void *dst, const void *src, size_t n) { - unsigned int i; - uint8_t *d = (uint8_t *)dst, *s = (uint8_t *)src; - for (i = 0; i < n; i++) { - *d++ = *s++; - } - return dst; -} - -#endif - -//============================ P A C K ================================= -void cpon_pack_copy_str(cpcp_pack_context *pack_context, const char *str) { - size_t len = strlen(str); - cpcp_pack_copy_bytes(pack_context, str, len); -} - -static void start_block(cpcp_pack_context *pack_context) { - pack_context->nest_count++; -} - -static void indent_element( - cpcp_pack_context *pack_context, bool is_oneliner, bool is_first_field) { - if (pack_context->cpon_options.indent) { - if (is_oneliner) { - if (!is_first_field) - cpcp_pack_copy_byte(pack_context, ' '); - } else { - cpcp_pack_copy_bytes(pack_context, "\n", 1); - int i; - for (i = 0; i < pack_context->nest_count; ++i) { - cpon_pack_copy_str(pack_context, pack_context->cpon_options.indent); - } - } - } -} - -static void end_block(cpcp_pack_context *pack_context, bool is_oneliner) { - pack_context->nest_count--; - if (pack_context->cpon_options.indent) { - indent_element(pack_context, is_oneliner, true); - } -} - -void cpon_pack_uint(cpcp_pack_context *pack_context, uint64_t i) { - if (pack_context->err_no) - return; - - // at least 21 characters for 64-bit types. - static const unsigned LEN = 32; - char str[LEN]; - size_t n = uint_to_str(str, LEN, i); - if (n >= LEN) { - pack_context->err_no = CPCP_RC_LOGICAL_ERROR; - return; - } - cpcp_pack_copy_bytes(pack_context, str, n); - if (!pack_context->cpon_options.json_output) - cpcp_pack_copy_byte(pack_context, 'u'); -} - -void cpon_pack_int(cpcp_pack_context *pack_context, int64_t i) { - if (pack_context->err_no) - return; - - // at least 21 characters for 64-bit types. - static const unsigned LEN = 32; - char str[LEN]; - size_t n = int_to_str(str, LEN, i); - if (n >= LEN) { - pack_context->err_no = CPCP_RC_LOGICAL_ERROR; - return; - } - cpcp_pack_copy_bytes(pack_context, str, n); -} - - -void cpon_pack_decimal(cpcp_pack_context *pack_context, const cpcp_decimal *v) { - // at least 21 characters for 64-bit types. - static const size_t LEN = 64; - char buff[LEN]; - size_t n = cpcp_decimal_to_string(buff, LEN, v->mantisa, v->exponent); - if (n == 0) { - pack_context->err_no = CPCP_RC_LOGICAL_ERROR; - return; - } - - cpcp_pack_copy_bytes(pack_context, buff, (size_t)n); -} - -void cpon_pack_double(cpcp_pack_context *pack_context, double d) { - if (pack_context->err_no) - return; - - // at least 21 characters for 64-bit types. - static const unsigned LEN = 32; - char str[LEN]; - size_t n = double_to_str(str, LEN, d); - if (n >= LEN) { - pack_context->err_no = CPCP_RC_LOGICAL_ERROR; - return; - } - cpcp_pack_copy_bytes(pack_context, str, n); -} - -void cpon_pack_date_time(cpcp_pack_context *pack_context, const cpcp_date_time *v) { - /// ISO 8601 with msecs extension - cpcp_pack_copy_bytes( - pack_context, CCPON_DATE_TIME_BEGIN, sizeof(CCPON_DATE_TIME_BEGIN) - 1); - cpon_pack_date_time_str(pack_context, v->msecs_since_epoch, - v->minutes_from_utc, CCPON_Auto, true); - cpcp_pack_copy_bytes(pack_context, "\"", 1); -} - -void cpon_pack_date_time_str(cpcp_pack_context *pack_context, int64_t epoch_msecs, - int min_from_utc, cpon_msec_policy msec_policy, bool with_tz) { - struct tm tm; - cpon_gmtime(epoch_msecs / 1000 + min_from_utc * 60, &tm); - static const unsigned LEN = 32; - char str[LEN]; - size_t len = uint_to_str_lpad(str, LEN, (unsigned)tm.tm_year + 1900, 2, '0'); - if (len < LEN) - str[len] = '-'; - len++; - len += uint_to_str_lpad(str + len, (len < LEN) ? LEN - len : 0, - (unsigned)tm.tm_mon + 1, 2, '0'); - if (len < LEN) - str[len] = '-'; - len++; - len += uint_to_str_lpad( - str + len, (len < LEN) ? LEN - len : 0, (unsigned)tm.tm_mday, 2, '0'); - if (len < LEN) - str[len] = 'T'; - len++; - len += uint_to_str_lpad( - str + len, (len < LEN) ? LEN - len : 0, (unsigned)tm.tm_hour, 2, '0'); - if (len < LEN) - str[len] = ':'; - len++; - len += uint_to_str_lpad( - str + len, (len < LEN) ? LEN - len : 0, (unsigned)tm.tm_min, 2, '0'); - if (len < LEN) - str[len] = ':'; - len++; - len += uint_to_str_lpad( - str + len, (len < LEN) ? LEN - len : 0, (unsigned)tm.tm_sec, 2, '0'); - if (len < LEN) - cpcp_pack_copy_bytes(pack_context, str, len); - int msec = (int)(epoch_msecs % 1000); - if ((msec > 0 && msec_policy == CCPON_Auto) || msec_policy == CCPON_Always) { - cpcp_pack_copy_bytes(pack_context, ".", 1); - len = uint_to_str_lpad(str, LEN, (unsigned)msec, 3, '0'); - if (len < LEN) - cpcp_pack_copy_bytes(pack_context, str, len); - } - if (with_tz) { - if (min_from_utc == 0) { - cpcp_pack_copy_bytes(pack_context, "Z", 1); - } else { - if (min_from_utc < 0) { - cpcp_pack_copy_bytes(pack_context, "-", 1); - min_from_utc = -min_from_utc; - } else { - cpcp_pack_copy_bytes(pack_context, "+", 1); - } - if (min_from_utc % 60) { - len = 0; - len += uint_to_str_lpad(str + len, (len < LEN) ? LEN - len : 0, - (unsigned)min_from_utc / 60, 2, '0'); - len += uint_to_str_lpad(str + len, (len < LEN) ? LEN - len : 0, - (unsigned)min_from_utc % 60, 2, '0'); - } else { - len = 0; - len += uint_to_str_lpad(str + len, (len < LEN) ? LEN - len : 0, - (unsigned)min_from_utc / 60, 2, '0'); - } - if (len < LEN) - cpcp_pack_copy_bytes(pack_context, str, len); - } - } -} - -void cpon_pack_null(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - cpcp_pack_copy_bytes(pack_context, CCPON_STR_NULL, sizeof(CCPON_STR_NULL) - 1); -} - -static void cpon_pack_true(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - cpcp_pack_copy_bytes(pack_context, CCPON_STR_TRUE, sizeof(CCPON_STR_TRUE) - 1); -} - -static void cpon_pack_false(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - cpcp_pack_copy_bytes( - pack_context, CCPON_STR_FALSE, sizeof(CCPON_STR_FALSE) - 1); -} - -void cpon_pack_boolean(cpcp_pack_context *pack_context, bool b) { - if (pack_context->err_no) - return; - - if (b) - cpon_pack_true(pack_context); - else - cpon_pack_false(pack_context); -} - -void cpon_pack_list_begin(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - - if (cpcp_pack_copy_byte(pack_context, CCPON_C_LIST_BEGIN) > 0) - start_block(pack_context); -} - -void cpon_pack_list_end(cpcp_pack_context *pack_context, bool is_oneliner) { - if (pack_context->err_no) - return; - - end_block(pack_context, is_oneliner); - cpcp_pack_copy_byte(pack_context, CCPON_C_LIST_END); -} - -void cpon_pack_map_begin(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - - if (cpcp_pack_copy_byte(pack_context, CCPON_C_MAP_BEGIN)) - start_block(pack_context); -} - -void cpon_pack_map_end(cpcp_pack_context *pack_context, bool is_oneliner) { - if (pack_context->err_no) - return; - - end_block(pack_context, is_oneliner); - cpcp_pack_copy_byte(pack_context, CCPON_C_MAP_END); -} - -void cpon_pack_imap_begin(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - - cpcp_pack_copy_bytes( - pack_context, CCPON_STR_IMAP_BEGIN, sizeof(CCPON_STR_IMAP_BEGIN) - 1); - start_block(pack_context); -} - -void cpon_pack_imap_end(cpcp_pack_context *pack_context, bool is_oneliner) { - cpon_pack_map_end(pack_context, is_oneliner); -} - -void cpon_pack_meta_begin(cpcp_pack_context *pack_context) { - if (pack_context->err_no) - return; - - if (cpcp_pack_copy_byte(pack_context, CCPON_C_META_BEGIN) > 0) - start_block(pack_context); -} - -void cpon_pack_meta_end(cpcp_pack_context *pack_context, bool is_oneliner) { - if (pack_context->err_no) - return; - - end_block(pack_context, is_oneliner); - cpcp_pack_copy_byte(pack_context, CCPON_C_META_END); -} - -static char *copy_data_escaped( - cpcp_pack_context *pack_context, const void *str, size_t len) { - size_t i; - for (i = 0; i < len; ++i) { - if (pack_context->err_no != CPCP_RC_OK) - return NULL; - uint8_t ch = ((const uint8_t *)str)[i]; - switch (ch) { - case '\0': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, '0'); - break; - case '\\': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, '\\'); - break; - case '\t': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, 't'); - break; - case '\b': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, 'b'); - break; - case '\r': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, 'r'); - break; - case '\n': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, 'n'); - break; - case '"': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, '"'); - break; - default: - cpcp_pack_copy_byte(pack_context, ch); - } - } - return pack_context->current; -} - -static char *copy_blob_escaped( - cpcp_pack_context *pack_context, const void *str, size_t len) { - size_t i; - for (i = 0; i < len; ++i) { - if (pack_context->err_no != CPCP_RC_OK) - return NULL; - uint8_t ch = ((const uint8_t *)str)[i]; - switch (ch) { - case '\\': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, '\\'); - break; - case '\t': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, 't'); - break; - case '\r': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, 'r'); - break; - case '\n': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, 'n'); - break; - case '"': - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, '"'); - break; - default: - if (ch < 32 || ch >= 127) { - cpcp_pack_copy_byte(pack_context, '\\'); - cpcp_pack_copy_byte(pack_context, hexify(ch / 16)); - cpcp_pack_copy_byte(pack_context, hexify(ch % 16)); - } else { - cpcp_pack_copy_byte(pack_context, ch); - } - } - } - return pack_context->current; -} - -void cpon_pack_blob( - cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len) { - cpon_pack_blob_start(pack_context, 0, 0); - cpon_pack_blob_cont(pack_context, buff, (unsigned int)buff_len); - cpon_pack_blob_finish(pack_context); -} - -void cpon_pack_blob_start( - cpcp_pack_context *pack_context, const uint8_t *buff, size_t buff_len) { - cpcp_pack_copy_byte(pack_context, 'b'); - cpcp_pack_copy_byte(pack_context, '"'); - copy_blob_escaped(pack_context, buff, buff_len); -} - -void cpon_pack_blob_cont( - cpcp_pack_context *pack_context, const uint8_t *buff, unsigned buff_len) { - copy_blob_escaped(pack_context, buff, buff_len); -} - -void cpon_pack_blob_finish(cpcp_pack_context *pack_context) { - cpcp_pack_copy_byte(pack_context, '"'); -} - -void cpon_pack_string(cpcp_pack_context *pack_context, const char *s, size_t l) { - cpcp_pack_copy_byte(pack_context, '"'); - copy_data_escaped(pack_context, s, l); - cpcp_pack_copy_byte(pack_context, '"'); -} - -void cpon_pack_string_terminated(cpcp_pack_context *pack_context, const char *s) { - size_t len = s ? strlen(s) : 0; - cpon_pack_string(pack_context, s, len); -} - -void cpon_pack_string_start( - cpcp_pack_context *pack_context, const char *buff, size_t buff_len) { - cpcp_pack_copy_byte(pack_context, '"'); - copy_data_escaped(pack_context, buff, buff_len); -} - -void cpon_pack_string_cont( - cpcp_pack_context *pack_context, const char *buff, unsigned buff_len) { - copy_data_escaped(pack_context, buff, buff_len); -} - -void cpon_pack_string_finish(cpcp_pack_context *pack_context) { - cpcp_pack_copy_byte(pack_context, '"'); -} - -//============================ U N P A C K ================================= - -const char *cpon_unpack_skip_insignificant(cpcp_unpack_context *unpack_context) { - while (1) { - const char *p = cpcp_unpack_take_byte(unpack_context); - if (!p) - return p; - if (*p == 0) { - continue; - } - if (*p < 0) { - // skip all characters with ASCII > 128 - // because they cannot appear in CPON delimiters - continue; - } - if (*p == '\n') { - unpack_context->parser_line_no++; - } else if (*p > ' ') { - switch (*p) { - case '/': { - p = cpcp_unpack_take_byte(unpack_context); - if (!p) { - unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; - unpack_context->err_msg = "Unfinished comment"; - } else if (*p == '*') { - // multiline_comment_entered; - while (1) { - p = cpcp_unpack_take_byte(unpack_context); - if (!p) - return p; - if (*p == '\n') - unpack_context->parser_line_no++; - if (*p == '*') { - p = cpcp_unpack_take_byte(unpack_context); - if (*p == '/') - break; - } - } - } else if (*p == '/') { - // to end of line comment entered; - while (1) { - p = cpcp_unpack_take_byte(unpack_context); - if (!p) - return p; - if (*p == '\n') { - unpack_context->parser_line_no++; - break; - } - } - } else { - return NULL; - } - break; - } - case CCPON_C_KEY_DELIM: - case CCPON_C_FIELD_DELIM: - continue; - default: - return p; - } - } - } -} - -static int unpack_int(cpcp_unpack_context *unpack_context, int64_t *p_val) { - int64_t val = 0; - int neg = 0; - int base = 10; - int n = 0; - for (;; n++) { - const char *p = cpcp_unpack_take_byte(unpack_context); - if (!p) - goto eonumb; - uint8_t b = (uint8_t)(*p); - switch (b) { - case '+': - case '-': - if (n != 0) { - unpack_context->current--; - goto eonumb; - } - if (b == '-') - neg = 1; - break; - case 'x': - if (n == 1 && val != 0) { - unpack_context->current--; - goto eonumb; - } - if (n != 1) { - unpack_context->current--; - goto eonumb; - } - base = 16; - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - val *= base; - val += b - '0'; - break; - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - if (base != 16) { - unpack_context->current--; - goto eonumb; - } - val *= base; - val += b - 'a' + 10; - break; - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - if (base != 16) { - unpack_context->current--; - goto eonumb; - } - val *= base; - val += b - 'A' + 10; - break; - default: - unpack_context->current--; - goto eonumb; - } - } -eonumb: - if (neg) - val = -val; - if (p_val) - *p_val = val; - return n; -} - -void cpon_unpack_date_time(cpcp_unpack_context *unpack_context, struct tm *tm, - int *msec, int *utc_offset) { - tm->tm_year = 0; - tm->tm_mon = 0; - tm->tm_mday = 1; - tm->tm_hour = 0; - tm->tm_min = 0; - tm->tm_sec = 0; - tm->tm_isdst = -1; - - *msec = 0; - *utc_offset = 0; - - const char *p; - - int64_t val; - int n = unpack_int(unpack_context, &val); - if (n < 0) { - unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; - unpack_context->err_msg = "Malformed year in DateTime"; - return; - } - tm->tm_year = (int)val - 1900; - - UNPACK_TAKE_BYTE(p); - if (*p != '-') { - unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; - unpack_context->err_msg = "Malformed year-month separator in DateTime"; - return; - } - - n = unpack_int(unpack_context, &val); - if (n <= 0) { - unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; - unpack_context->err_msg = "Malformed month in DateTime"; - return; - } - tm->tm_mon = (int)val - 1; - - UNPACK_TAKE_BYTE(p); - if (*p != '-') { - unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; - unpack_context->err_msg = "Malformed month-day separator in DateTime"; - return; - } - - n = unpack_int(unpack_context, &val); - if (n <= 0) { - unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; - unpack_context->err_msg = "Malformed day in DateTime"; - return; - } - tm->tm_mday = (int)val; - - UNPACK_TAKE_BYTE(p); - if (!(*p == 'T' || *p == ' ')) { - unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; - unpack_context->err_msg = "Malformed date-time separator in DateTime"; - return; - } - - n = unpack_int(unpack_context, &val); - if (n <= 0) { - unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; - unpack_context->err_msg = "Malformed hour in DateTime"; - return; - } - tm->tm_hour = (int)val; - - UNPACK_TAKE_BYTE(p); - - n = unpack_int(unpack_context, &val); - if (n <= 0) { - unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; - unpack_context->err_msg = "Malformed minutes in DateTime"; - return; - } - tm->tm_min = (int)val; - - UNPACK_TAKE_BYTE(p); - - n = unpack_int(unpack_context, &val); - if (n <= 0) { - unpack_context->err_no = CPCP_RC_MALFORMED_INPUT; - unpack_context->err_msg = "Malformed seconds in DateTime"; - return; - } - tm->tm_sec = (int)val; - - p = cpcp_unpack_take_byte(unpack_context); - if (p) { - if (*p == '.') { - n = unpack_int(unpack_context, &val); - if (n < 0) - return; - *msec = (int)val; - p = cpcp_unpack_take_byte(unpack_context); - } - if (p) { - uint8_t b = (uint8_t)(*p); - if (b == 'Z') { - // UTC time - } else if (b == '+' || b == '-') { - // UTC time - n = unpack_int(unpack_context, &val); - if (!(n == 2 || n == 4)) - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, - "Malformed TS offset in DateTime."); - if (n == 2) - *utc_offset = (int)(60 * val); - else if (n == 4) - *utc_offset = (int)(60 * (val / 100) + (val % 100)); - if (b == '-') - *utc_offset = -*utc_offset; - } else { - // unget unused char - unpack_context->current--; - } - } - } - unpack_context->err_no = CPCP_RC_OK; - unpack_context->item.type = CPCP_ITEM_DATE_TIME; - int64_t epoch_sec = cpon_timegm(tm); - epoch_sec -= *utc_offset * 60; - int64_t epoch_msec = epoch_sec * 1000; - cpcp_date_time *it = &unpack_context->item.as.DateTime; - epoch_msec += *msec; - it->msecs_since_epoch = epoch_msec; - it->minutes_from_utc = *utc_offset; -} - -static void cpon_unpack_blob_hex(cpcp_unpack_context *unpack_context) { - if (unpack_context->item.type != CPCP_ITEM_BLOB) - UNPACK_ERROR(CPCP_RC_LOGICAL_ERROR, "Unpack cpon blob internal error."); - - const char *p; - cpcp_string *it = &unpack_context->item.as.String; - if (it->chunk_cnt == 0) { - // must start with '"' - UNPACK_TAKE_BYTE(p); - if (*p != '"') { - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Blob should start with 'x\"' ."); - } - } - for (it->chunk_size = 0; it->chunk_size < it->chunk_buff_len;) { - do { - UNPACK_TAKE_BYTE(p); - } while (*p <= ' '); - if (*p == '"') { - // end of string - it->last_chunk = 1; - break; - } - int b1 = unhex((uint8_t)(*p)); - if (b1 < 0) - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Invalid HEX char, first digit."); - UNPACK_TAKE_BYTE(p); - if (!p) - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, - "Invalid HEX char, second digit missing."); - int b2 = unhex((uint8_t)(*p)); - if (b2 < 0) - UNPACK_ERROR( - CPCP_RC_MALFORMED_INPUT, "Invalid HEX char, second digit."); - (it->chunk_start)[it->chunk_size++] = (char)((uint8_t)(16 * b1 + b2)); - } - it->chunk_cnt++; -} - -static void cpon_unpack_blob_esc(cpcp_unpack_context *unpack_context) { - if (unpack_context->item.type != CPCP_ITEM_BLOB) - UNPACK_ERROR(CPCP_RC_LOGICAL_ERROR, "Unpack cpon blob internal error."); - - const char *p; - cpcp_string *it = &unpack_context->item.as.String; - if (it->chunk_cnt == 0) { - // must start with '"' - UNPACK_TAKE_BYTE(p); - if (*p != '"') { - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Blob should start with 'b\"' ."); - } - } - for (it->chunk_size = 0; it->chunk_size < it->chunk_buff_len;) { - UNPACK_TAKE_BYTE(p); - uint8_t b = (uint8_t)(*p); - if (b == '"') { - // end of string - it->last_chunk = 1; - break; - } - if (b == '\\') { - UNPACK_TAKE_BYTE(p); - switch ((uint8_t)*p) { - case 't': - (it->chunk_start)[it->chunk_size++] = '\t'; - break; - case 'r': - (it->chunk_start)[it->chunk_size++] = '\r'; - break; - case 'n': - (it->chunk_start)[it->chunk_size++] = '\n'; - break; - case '"': - (it->chunk_start)[it->chunk_size++] = '"'; - break; - case '\\': - (it->chunk_start)[it->chunk_size++] = '\\'; - break; - default: { - int hi = unhex((uint8_t)(*p)); - if (hi < 0) - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Invalid HEX char."); - UNPACK_TAKE_BYTE(p); - int lo = unhex((uint8_t)(*p)); - if (lo < 0) - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Invalid HEX char."); - (it->chunk_start)[it->chunk_size++] = - (char)((uint8_t)(16 * hi + lo)); - break; - } - }; - } else if (b < 128) { - (it->chunk_start)[it->chunk_size++] = (char)b; - } else { - UNPACK_ERROR( - CPCP_RC_MALFORMED_INPUT, "Invalid blob char, code >= 128."); - } - } - it->chunk_cnt++; -} - -static void cpon_unpack_string(cpcp_unpack_context *unpack_context) { - if (unpack_context->item.type != CPCP_ITEM_STRING) - UNPACK_ERROR(CPCP_RC_LOGICAL_ERROR, "Unpack cpon string internal error."); - - const char *p; - cpcp_string *it = &unpack_context->item.as.String; - if (it->chunk_cnt == 0) { - // must start with '"' - UNPACK_TAKE_BYTE(p); - if (*p != '"') { - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, - "String should start with '\"' character."); - } - } - for (it->chunk_size = 0; it->chunk_size < it->chunk_buff_len;) { - UNPACK_TAKE_BYTE(p); - if (*p == '\\') { - UNPACK_TAKE_BYTE(p); - if (!p) - return; - switch (*p) { - case '\\': - (it->chunk_start)[it->chunk_size++] = '\\'; - break; - case '"': - (it->chunk_start)[it->chunk_size++] = '"'; - break; - case 'b': - (it->chunk_start)[it->chunk_size++] = '\b'; - break; - case 'f': - (it->chunk_start)[it->chunk_size++] = '\f'; - break; - case 'n': - (it->chunk_start)[it->chunk_size++] = '\n'; - break; - case 'r': - (it->chunk_start)[it->chunk_size++] = '\r'; - break; - case 't': - (it->chunk_start)[it->chunk_size++] = '\t'; - break; - case '0': - (it->chunk_start)[it->chunk_size++] = '\0'; - break; - default: - (it->chunk_start)[it->chunk_size++] = *p; - break; - } - } else { - if (*p == '"') { - // end of string - it->last_chunk = 1; - break; - } - (it->chunk_start)[it->chunk_size++] = *p; - } - } - it->chunk_cnt++; -} - -void cpon_unpack_next(cpcp_unpack_context *unpack_context) { - if (unpack_context->err_no) - return; - - const char *p; - if (unpack_context->item.type == CPCP_ITEM_STRING) { - cpcp_string *str_it = &unpack_context->item.as.String; - if (!str_it->last_chunk) { - cpon_unpack_string(unpack_context); - return; - } - } else if (unpack_context->item.type == CPCP_ITEM_BLOB) { - cpcp_string *str_it = &unpack_context->item.as.String; - if (!str_it->last_chunk) { - if (str_it->blob_hex) - cpon_unpack_blob_hex(unpack_context); - else - cpon_unpack_blob_esc(unpack_context); - return; - } - } - - unpack_context->item.type = CPCP_ITEM_INVALID; - - p = cpon_unpack_skip_insignificant(unpack_context); - if (!p) - return; - - switch (*p) { - case CCPON_C_LIST_END: - case CCPON_C_MAP_END: - case CCPON_C_META_END: { - unpack_context->item.type = CPCP_ITEM_CONTAINER_END; - if (unpack_context->container_stack) { - cpcp_container_state *top_cont_state = - cpcp_unpack_context_top_container_state(unpack_context); - if (!top_cont_state) - UNPACK_ERROR(CPCP_RC_CONTAINER_STACK_UNDERFLOW, - "Container stack underflow.") - } - break; - } - case CCPON_C_META_BEGIN: - unpack_context->item.type = CPCP_ITEM_META; - break; - case CCPON_C_MAP_BEGIN: - unpack_context->item.type = CPCP_ITEM_MAP; - break; - case CCPON_C_LIST_BEGIN: - unpack_context->item.type = CPCP_ITEM_LIST; - break; - case 'i': { - UNPACK_TAKE_BYTE(p); - if (*p != '{') - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "IMap should start with '{'.") - unpack_context->item.type = CPCP_ITEM_IMAP; - break; - } - case 'a': { - UNPACK_TAKE_BYTE(p); - if (*p != '[') - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "List should start with '['.") - // unpack unsupported ARRAY type as list - unpack_context->item.type = CPCP_ITEM_LIST; - break; - } - case 'd': { - UNPACK_TAKE_BYTE(p); - if (!p || *p != '"') - UNPACK_ERROR( - CPCP_RC_MALFORMED_INPUT, "DateTime should start with 'd'.") - struct tm tm; - int msec; - int utc_offset; - cpon_unpack_date_time(unpack_context, &tm, &msec, &utc_offset); - UNPACK_TAKE_BYTE(p); - if (!p || *p != '"') - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, - "DateTime should start with 'd\"'.") - break; - } - case 'n': { - UNPACK_TAKE_BYTE(p); - if (*p == 'u') { - UNPACK_TAKE_BYTE(p); - if (*p == 'l') { - UNPACK_TAKE_BYTE(p); - if (*p == 'l') { - unpack_context->item.type = CPCP_ITEM_NULL; - break; - } - } - } - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Malformed 'null' literal.") - } - case 'f': { - UNPACK_TAKE_BYTE(p); - if (*p == 'a') { - UNPACK_TAKE_BYTE(p); - if (*p == 'l') { - UNPACK_TAKE_BYTE(p); - if (*p == 's') { - UNPACK_TAKE_BYTE(p); - if (*p == 'e') { - unpack_context->item.type = CPCP_ITEM_BOOLEAN; - unpack_context->item.as.Bool = false; - break; - } - } - } - } - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Malformed 'false' literal.") - } - case 't': { - UNPACK_TAKE_BYTE(p); - if (*p == 'r') { - UNPACK_TAKE_BYTE(p); - if (*p == 'u') { - UNPACK_TAKE_BYTE(p); - if (*p == 'e') { - unpack_context->item.type = CPCP_ITEM_BOOLEAN; - unpack_context->item.as.Bool = true; - break; - } - } - } - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Malformed 'true' literal.") - } - case 'x': { - UNPACK_TAKE_BYTE(p); - if (*p != '"') - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, - "HEX string should start with 'x\"'.") - unpack_context->item.type = CPCP_ITEM_BLOB; - cpcp_string *str_it = &unpack_context->item.as.String; - cpcp_string_init(str_it, unpack_context); - str_it->blob_hex = 1; - unpack_context->current--; - cpon_unpack_blob_hex(unpack_context); - break; - } - case 'b': { - UNPACK_TAKE_BYTE(p); - if (*p != '"') - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, - "BLOB string should start with 'b\"'.") - unpack_context->item.type = CPCP_ITEM_BLOB; - cpcp_string *str_it = &unpack_context->item.as.String; - cpcp_string_init(str_it, unpack_context); - str_it->blob_hex = 0; - unpack_context->current--; - cpon_unpack_blob_esc(unpack_context); - break; - } - case '"': { - unpack_context->item.type = CPCP_ITEM_STRING; - cpcp_string *str_it = &unpack_context->item.as.String; - cpcp_string_init(str_it, unpack_context); - unpack_context->current--; - cpon_unpack_string(unpack_context); - break; - } - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '+': - case '-': { - // number - int64_t mantisa = 0; - int64_t exponent = 0; - int64_t decimals = 0; - int dec_cnt = 0; - struct { - uint8_t is_decimal:1; - uint8_t is_uint:1; - uint8_t is_neg:1; - } flags; - flags.is_decimal = 0; - flags.is_uint = 0; - flags.is_neg = 0; - - flags.is_neg = *p == '-'; - if (!flags.is_neg) - unpack_context->current--; - int n = unpack_int(unpack_context, &mantisa); - if (n < 0) - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Malformed number.") - p = cpcp_unpack_take_byte(unpack_context); - while (p) { - if (*p == CCPON_C_UNSIGNED_END) { - flags.is_uint = 1; - break; - } - if (*p == '.') { - flags.is_decimal = 1; - n = unpack_int(unpack_context, &decimals); - if (n < 0) - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, - "Malformed number decimal part.") - dec_cnt = n; - p = cpcp_unpack_take_byte(unpack_context); - if (!p) - break; - } - if (*p == 'e' || *p == 'E') { - flags.is_decimal = 1; - n = unpack_int(unpack_context, &exponent); - if (n < 0) - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, - "Malformed number exponetional part.") - break; - } - if (*p != '.') { - // unget char - unpack_context->current--; - } - break; - } - if (flags.is_decimal) { - int i; - for (i = 0; i < dec_cnt; ++i) - mantisa *= 10; - mantisa += decimals; - unpack_context->item.type = CPCP_ITEM_DECIMAL; - unpack_context->item.as.Decimal.mantisa = - flags.is_neg ? -mantisa : mantisa; - unpack_context->item.as.Decimal.exponent = - (int)(exponent - dec_cnt); - } else if (flags.is_uint) { - unpack_context->item.type = CPCP_ITEM_UINT; - unpack_context->item.as.UInt = (uint64_t)mantisa; - - } else { - unpack_context->item.type = CPCP_ITEM_INT; - unpack_context->item.as.Int = flags.is_neg ? -mantisa : mantisa; - } - unpack_context->err_no = CPCP_RC_OK; - break; - } - default: - UNPACK_ERROR(CPCP_RC_MALFORMED_INPUT, "Invalid character."); - } - - cpcp_item_types current_item_type = unpack_context->item.type; - bool is_container_end = false; - switch (current_item_type) { - case CPCP_ITEM_LIST: - case CPCP_ITEM_MAP: - case CPCP_ITEM_IMAP: - case CPCP_ITEM_META: - cpcp_unpack_context_push_container_state( - unpack_context, current_item_type); - break; - case CPCP_ITEM_CONTAINER_END: - cpcp_unpack_context_pop_container_state(unpack_context); - is_container_end = true; - break; - default: - break; - } - - cpcp_container_state *top_cont_state = - cpcp_unpack_context_top_container_state(unpack_context); - if (top_cont_state && !is_container_end) { - if (top_cont_state->current_item_type != CPCP_ITEM_META) - top_cont_state->item_count++; - top_cont_state->current_item_type = current_item_type; - } -} - -void cpon_pack_field_delim( - cpcp_pack_context *pack_context, bool is_first_field, bool is_oneliner) { - if (!is_first_field) - cpcp_pack_copy_bytes(pack_context, ",", 1); - indent_element(pack_context, is_oneliner, is_first_field); -} - -void cpon_pack_key_val_delim(cpcp_pack_context *pack_context) { - cpcp_pack_copy_bytes(pack_context, ":", 1); -} diff --git a/libshvrpc/libshvrpc.version b/libshvrpc/libshvrpc.version index a907573..36dfa0f 100644 --- a/libshvrpc/libshvrpc.version +++ b/libshvrpc/libshvrpc.version @@ -13,11 +13,7 @@ V0.0 { rpcmsg_pack_error; rpcmsg_pack_ferror; rpcmsg_pack_vferror; - - # shv/rpcurl.h - rpcurl_parse; - rpcurl_free; - rpcurl_str; + rpcmsg_unpack_error; # shv/rpcclient.h rpcclient_connect; @@ -32,6 +28,17 @@ V0.0 { rpcclient_logger_new; rpcclient_logger_destroy; + # shv/rpcclient_impl.h + rpcclient_init; + rpcclient_log_lock; + rpcclient_log_item; + rpcclient_log_unlock; + + # shv/rpcurl.h + rpcurl_parse; + rpcurl_free; + rpcurl_str; + # shv/rpchandler.h rpchandler_destroy; rpchandler_new; @@ -50,10 +57,18 @@ V0.0 { rpchandler_dir_result; rpchandler_dir_name; - # shv/rpcapp.h - rpcapp_destroy; - rpcapp_new; - rpcapp_handler_stage; + # shv/rpchandler_app.h + rpchandler_app_destroy; + rpchandler_app_new; + rpchandler_app_stage; + + # shv/rpchandler_responses.h + rpchandler_responses_new; + rpchandler_responses_destroy; + rpchandler_responses_stage; + rpcresponse_expect; + rpcresponse_waitfor; + rpcresponse_validmsg; local: *; }; diff --git a/libshvrpc/meson.build b/libshvrpc/meson.build index e2913d7..f2c4a28 100644 --- a/libshvrpc/meson.build +++ b/libshvrpc/meson.build @@ -1,6 +1,5 @@ libshvrpc_sources = [ files( - 'rpcapp.c', 'rpcclient_connect.c', 'rpcclient_datagram.c', 'rpcclient_log.c', @@ -8,17 +7,21 @@ libshvrpc_sources = [ 'rpcclient_serial.c', 'rpcclient_stream.c', 'rpchandler.c', + 'rpchandler_app.c', + 'rpchandler_responses.c', 'rpcmsg_access.c', 'rpcmsg_head.c', 'rpcmsg_pack.c', - 'rpcrespond.c', + 'rpcmsg_unpack.c', 'rpcurl.c', + 'strset.c', ), gperf.process('rpcmsg_access.gperf'), gperf.process('rpcurl_query.gperf'), gperf.process('rpcurl_scheme.gperf'), ] libshvrpc_dependencies = [libshvchainpack_dep, openssl, uriparser] +libshvrpc_internal_includes = include_directories('.') libshvrpc = library( 'shvrpc', diff --git a/libshvrpc/rpcclient_log.c b/libshvrpc/rpcclient_log.c index fd44d86..c54b431 100644 --- a/libshvrpc/rpcclient_log.c +++ b/libshvrpc/rpcclient_log.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include diff --git a/libshvrpc/rpcclient_login.c b/libshvrpc/rpcclient_login.c index 6ccb0be..3b0a233 100644 --- a/libshvrpc/rpcclient_login.c +++ b/libshvrpc/rpcclient_login.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #define obstack_chunk_alloc malloc #define obstack_chunk_free free @@ -30,9 +31,11 @@ static void sha1_password(const char *nonce, const char *password, char *res) { } -enum rpcclient_login_res rpcclient_login( - rpcclient_t client, const struct rpclogin_options *opts) { - enum rpcclient_login_res res = RPCCLIENT_LOGIN_ERROR; +bool rpcclient_login( + rpcclient_t client, const struct rpclogin_options *opts, char **errmsg) { + bool res = false; + if (errmsg) + *errmsg = NULL; struct obstack obs; obstack_init(&obs); void *obs_base = obstack_base(&obs); @@ -54,8 +57,7 @@ enum rpcclient_login_res rpcclient_login( meta.request_id == 1)) goto err; obstack_free(&obs, obs_base); - // TODO unpack message beginning - if (!cp_unpack_expect_type(rpcclient_unpack(client), &item, CPITEM_MAP)) + if (cp_unpack_type(rpcclient_unpack(client), &item) != CPITEM_MAP) goto err; for_cp_unpack_map(rpcclient_unpack(client), &item) { char str[7]; @@ -120,18 +122,13 @@ enum rpcclient_login_res rpcclient_login( /* Response to login */ if (!(rpcclient_nextmsg(client) && rpcmsg_head_unpack(rpcclient_unpack(client), &item, &meta, NULL, &obs) && - meta.request_id == 2 && rpcclient_validmsg(client))) + meta.request_id == 2)) goto err; - switch (meta.type) { - case RPCMSG_T_RESPONSE: - res = RPCCLIENT_LOGIN_OK; - break; - case RPCMSG_T_ERROR: - res = RPCCLIENT_LOGIN_INVALID; - break; - default: - break; - } + if (errmsg && meta.type == RPCMSG_T_ERROR) + rpcmsg_unpack_error(rpcclient_unpack(client), &item, NULL, errmsg); + if (!rpcclient_validmsg(client)) + goto err; + res = meta.type == RPCMSG_T_RESPONSE; err: free(nonce); obstack_free(&obs, NULL); diff --git a/libshvrpc/rpchandler.c b/libshvrpc/rpchandler.c index 6543acf..4841dbe 100644 --- a/libshvrpc/rpchandler.c +++ b/libshvrpc/rpchandler.c @@ -1,4 +1,3 @@ -#include "shv/rpcmsg.h" #include #include #include @@ -6,6 +5,7 @@ #include #define obstack_chunk_alloc malloc #define obstack_chunk_free free +#include "strset.h" struct rpchandler { struct rpcreceive recv; @@ -42,6 +42,7 @@ struct rpchandler_x_ctx { struct rpchandler_ls_ctx { struct rpchandler_x_ctx x; + struct strset strset; }; struct rpchandler_dir_ctx { @@ -80,6 +81,7 @@ void rpchandler_destroy(rpchandler_t rpchandler) { static bool parse_ls_dir(struct rpcreceive *recv, struct rpcmsg_meta *meta, struct rpchandler_x_ctx *ctx) { ctx->name = NULL; + ctx->pack = NULL; bool invalid_param = false; if (recv->item.type != CPITEM_CONTAINER_END) { cp_unpack(recv->unpack, &recv->item); @@ -101,32 +103,43 @@ static bool parse_ls_dir(struct rpcreceive *recv, struct rpcmsg_meta *meta, if (!rpcreceive_validmsg(recv)) return true; - ctx->pack = rpcreceive_response_new(recv); + cp_pack_t pack = rpcreceive_response_new(recv); if (invalid_param) { - rpcmsg_pack_error(ctx->pack, meta, RPCMSG_E_INVALID_PARAMS, + rpcmsg_pack_error(pack, meta, RPCMSG_E_INVALID_PARAMS, "Use Null or String with node name"); rpcreceive_response_send(recv); return false; - } + } else + ctx->pack = pack; return true; } static bool handle_ls(const struct rpchandler_stage *stages, struct rpcreceive *recv, struct rpcmsg_meta *meta) { struct rpchandler_ls_ctx ctx; + printf("Wtf\n"); if (!parse_ls_dir(recv, meta, &ctx.x)) return false; + if (ctx.x.pack == NULL) + return true; ctx.x.located = false; - rpcmsg_pack_response(ctx.x.pack, meta); - if (ctx.x.name == NULL) - cp_pack_list_begin(ctx.x.pack); + ctx.strset = (struct strset){}; for (const struct rpchandler_stage *s = stages; s->funcs; s++) if (s->funcs->ls) s->funcs->ls(s->cookie, meta->path, &ctx); - if (ctx.x.name == NULL) + printf("Before response pack\n"); + rpcmsg_pack_response(ctx.x.pack, meta); + printf("Before packing\n"); + if (ctx.x.name == NULL) { + cp_pack_list_begin(ctx.x.pack); + for (size_t i = 0; i < ctx.strset.cnt; i++) { + printf("Packing %s\n", ctx.strset.items[i].str); + cp_pack_str(ctx.x.pack, ctx.strset.items[i].str); + } cp_pack_container_end(ctx.x.pack); - else + shv_strset_free(&ctx.strset); + } else cp_pack_bool(ctx.x.pack, ctx.x.located); cp_pack_container_end(ctx.x.pack); rpcreceive_response_send(recv); @@ -281,15 +294,42 @@ bool rpcreceive_response_drop(struct rpcreceive *receive) { return res; } -bool rpchandler_ls_result(struct rpchandler_ls_ctx *ctx, const char *name) { +void rpchandler_ls_result(struct rpchandler_ls_ctx *ctx, const char *name) { + printf("LS with %s\n", name); if (ctx->x.name) ctx->x.located = !strcmp(ctx->x.name, name); else - cp_pack_str(ctx->x.pack, name); - // TODO error? - return true; + shv_strset_add(&ctx->strset, name); + printf("LS with %s done\n", name); +} + +void rpchandler_ls_const(struct rpchandler_ls_ctx *ctx, const char *name) { + if (ctx->x.name) + ctx->x.located = !strcmp(ctx->x.name, name); + else + shv_strset_add_const(&ctx->strset, name); } +void rpchandler_ls_result_vfmt( + struct rpchandler_ls_ctx *ctx, const char *fmt, va_list args) { + if (ctx->x.name) { + va_list cargs; + va_copy(cargs, args); + int siz = snprintf(NULL, 0, fmt, cargs); + va_end(cargs); + if (siz != strlen(ctx->x.name)) { + va_end(args); + return; + } + char str[siz]; + assert(snprintf(str, siz, fmt, args) == siz); + ctx->x.located = !strcmp(ctx->x.name, str); + } else { + char *str; + assert(vasprintf(&str, fmt, args) > 0); + shv_strset_add_dyn(&ctx->strset, str); + } +} bool rpchandler_dir_result(struct rpchandler_dir_ctx *ctx, const char *name, enum rpcnode_dir_signature signature, int flags, enum rpcmsg_access access, diff --git a/libshvrpc/rpcapp.c b/libshvrpc/rpchandler_app.c similarity index 79% rename from libshvrpc/rpcapp.c rename to libshvrpc/rpchandler_app.c index 168a361..c1e0785 100644 --- a/libshvrpc/rpcapp.c +++ b/libshvrpc/rpchandler_app.c @@ -1,7 +1,7 @@ -#include +#include #include -struct rpcapp { +struct rpchandler_app { const char *name; const char *version; }; @@ -28,7 +28,7 @@ static void rpc_dir(void *cookie, const char *path, struct rpchandler_dir_ctx *c static enum rpchandler_func_res rpc_msg( void *cookie, struct rpcreceive *receive, const struct rpcmsg_meta *meta) { - struct rpcapp *rpcapp = cookie; + struct rpchandler_app *rpchandler_app = cookie; enum methods { UNKNOWN, SHV_VERSION_MAJOR, @@ -61,10 +61,10 @@ static enum rpchandler_func_res rpc_msg( cp_pack_int(pack, 0); break; case APP_NAME: - cp_pack_str(pack, rpcapp->name ?: "shvc"); // TODO + cp_pack_str(pack, rpchandler_app->name ?: "shvc"); // TODO break; case APP_VERSION: - cp_pack_str(pack, rpcapp->version ?: PROJECT_VERSION); // TODO + cp_pack_str(pack, rpchandler_app->version ?: PROJECT_VERSION); // TODO break; case UNKNOWN: abort(); /* Can't happen */ @@ -81,17 +81,17 @@ const struct rpchandler_funcs rpc_funcs = { }; -rpcapp_t rpcapp_new(const char *name, const char *version) { - rpcapp_t res = malloc(sizeof *res); +rpchandler_app_t rpchandler_app_new(const char *name, const char *version) { + rpchandler_app_t res = malloc(sizeof *res); res->name = name; res->version = version; return res; } -void rpcapp_destroy(rpcapp_t rpcapp) { - free(rpcapp); +void rpchandler_app_destroy(rpchandler_app_t rpchandler_app) { + free(rpchandler_app); } -struct rpchandler_stage rpcapp_handler_stage(rpcapp_t rpcapp) { - return (struct rpchandler_stage){.funcs = &rpc_funcs, .cookie = rpcapp}; +struct rpchandler_stage rpchandler_app_stage(rpchandler_app_t rpchandler_app) { + return (struct rpchandler_stage){.funcs = &rpc_funcs, .cookie = rpchandler_app}; } diff --git a/libshvrpc/rpcrespond.c b/libshvrpc/rpchandler_responses.c similarity index 66% rename from libshvrpc/rpcrespond.c rename to libshvrpc/rpchandler_responses.c index 54e2c7c..fb47788 100644 --- a/libshvrpc/rpcrespond.c +++ b/libshvrpc/rpchandler_responses.c @@ -1,28 +1,29 @@ -#include +#include #include +#include -struct rpcresponder { - struct rpcrespond { +struct rpchandler_responses { + struct rpcresponse { int request_id; struct rpcreceive *receive; cp_unpack_t unpack; struct cpitem *item; const struct rpcmsg_meta *meta; sem_t sem, sem_complete; - struct rpcrespond *next; + struct rpcresponse *next; } * resp; pthread_mutex_t lock; }; static enum rpchandler_func_res rpc_msg( void *cookie, struct rpcreceive *receive, const struct rpcmsg_meta *meta) { - struct rpcresponder *resp = cookie; + struct rpchandler_responses *resp = cookie; if (meta->type != RPCMSG_T_RESPONSE && meta->type != RPCMSG_T_ERROR) return RPCHFR_UNHANDLED; enum rpchandler_func_res res = RPCHFR_HANDLED; pthread_mutex_lock(&resp->lock); - rpcrespond_t pr = NULL; - rpcrespond_t r = resp->resp; + rpcresponse_t pr = NULL; + rpcresponse_t r = resp->resp; while (r) { if (r->request_id == meta->request_id) { r->receive = receive; @@ -47,17 +48,17 @@ static enum rpchandler_func_res rpc_msg( static struct rpchandler_funcs rpc_funcs = {.msg = rpc_msg}; -rpcresponder_t rpcresponder_new(void) { - struct rpcresponder *res = malloc(sizeof *res); +rpchandler_responses_t rpchandler_responses_new(void) { + struct rpchandler_responses *res = malloc(sizeof *res); res->resp = NULL; pthread_mutex_init(&res->lock, NULL); return res; } -void rpcresponder_destroy(rpcresponder_t resp) { - rpcrespond_t r = resp->resp; +void rpchandler_responses_destroy(rpchandler_responses_t resp) { + rpcresponse_t r = resp->resp; while (r) { - rpcrespond_t pr = r; + rpcresponse_t pr = r; r = r->next; free(pr); }; @@ -65,20 +66,21 @@ void rpcresponder_destroy(rpcresponder_t resp) { free(resp); } -struct rpchandler_stage rpcresponder_handler_stage(rpcresponder_t responder) { +struct rpchandler_stage rpchandler_responses_stage( + rpchandler_responses_t responder) { return (struct rpchandler_stage){.funcs = &rpc_funcs, .cookie = responder}; } -rpcrespond_t rpcrespond_expect(rpcresponder_t responder, int request_id) { - rpcrespond_t res = malloc(sizeof *res); +rpcresponse_t rpcresponse_expect(rpchandler_responses_t responder, int request_id) { + rpcresponse_t res = malloc(sizeof *res); res->request_id = request_id; res->receive = NULL; sem_init(&res->sem, false, 0); sem_init(&res->sem_complete, false, 0); pthread_mutex_lock(&responder->lock); - rpcrespond_t *r = &responder->resp; + rpcresponse_t *r = &responder->resp; while (*r) r = &(*r)->next; *r = res; @@ -87,14 +89,14 @@ rpcrespond_t rpcrespond_expect(rpcresponder_t responder, int request_id) { return res; } -bool rpcrespond_waitfor(rpcrespond_t respond, struct rpcreceive **receive, +bool rpcresponse_waitfor(rpcresponse_t respond, struct rpcreceive **receive, struct rpcmsg_meta **meta, int timeout) { sem_wait(&respond->sem); // TODO wait return 1; } -bool rpcrespond_validmsg(rpcrespond_t respond) { +bool rpcresponse_validmsg(rpcresponse_t respond) { bool res = rpcreceive_validmsg(respond->receive); sem_post(&respond->sem_complete); return res; diff --git a/libshvrpc/rpcmsg_head.c b/libshvrpc/rpcmsg_head.c index 5b2e9d2..5a2650e 100644 --- a/libshvrpc/rpcmsg_head.c +++ b/libshvrpc/rpcmsg_head.c @@ -154,11 +154,11 @@ bool rpcmsg_head_unpack(cp_unpack_t unpack, struct cpitem *item, break; } } - if (!cp_unpack_expect_type(unpack, item, CPITEM_IMAP)) + if (cp_unpack_type(unpack, item) != CPITEM_IMAP) return false; int key = -1; cp_unpack(unpack, item); - if (item->type != CPITEM_CONTAINER_END && !cp_extract_int(item, key)) + if (item->type != CPITEM_CONTAINER_END && !cpitem_extract_int(item, key)) return false; if (has_rid) { if (valid_method) { diff --git a/libshvrpc/rpcmsg_unpack.c b/libshvrpc/rpcmsg_unpack.c new file mode 100644 index 0000000..30c0490 --- /dev/null +++ b/libshvrpc/rpcmsg_unpack.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include + + +bool rpcmsg_unpack_error(cp_unpack_t unpack, struct cpitem *item, + enum rpcmsg_error *errnum, char **errmsg) { + if (errnum) + *errnum = RPCMSG_E_NO_ERROR; + if (errmsg) + *errmsg = NULL; + + if (cp_unpack_type(unpack, item) != CPITEM_IMAP) + return false; + for_cp_unpack_imap(unpack, item) { + switch (item->as.Int) { + case RPCMSG_ERR_KEY_CODE: + if (cp_unpack_type(unpack, item) != CPITEM_INT) + return false; + if (errnum) + cpitem_extract_uint(item, *errnum); + break; + case RPCMSG_ERR_KEY_MESSAGE: + if (cp_unpack_type(unpack, item) != CPITEM_STRING) + return false; + if (errmsg) + *errmsg = cp_unpack_strdup(unpack, item); + break; + default: /* Just ignore any unknown key */ + break; + } + } + if (item->type != CPITEM_CONTAINER_END) + return false; + if (cp_unpack_type(unpack, item) != CPITEM_CONTAINER_END) + return false; + return true; +} diff --git a/libshvrpc/strset.c b/libshvrpc/strset.c new file mode 100644 index 0000000..c2bf457 --- /dev/null +++ b/libshvrpc/strset.c @@ -0,0 +1,69 @@ +#include "strset.h" +#include +#include +#include + + +static unsigned strhash(const char *str) { + unsigned res = 0; + while (*str != '\0') + res = (res << 8 | res >> ((sizeof(unsigned) - 1) * 8)) ^ *(str++); + return res; +} + +static size_t strpos(const struct strset *set, int hsh) { + if (set->cnt == 0) + return 0; + size_t min = 0; + size_t max = set->cnt - 1; + while (min != max) { + size_t i = (min + max + 1) / 2; + if (set->items[i].hash > hsh) + max = i - 1; + else + min = i; + } + if (set->items[min].hash >= hsh) + return min; + return min + 1; +} + +static void add(struct strset *set, const char *str, bool dyn) { + unsigned hsh = strhash(str); + size_t pos = strpos(set, hsh); + if (pos < set->cnt && set->items[pos].hash == hsh && + !strcmp(set->items[pos].str, str)) + return; /* Already in the set */ + + if (set->cnt >= set->siz) { + set->siz = set->siz * 2 ?: 2; + set->items = realloc(set->items, set->siz * sizeof(*set->items)); + } + if (pos < set->cnt) + memmove(set->items + pos + 1, set->items + pos, + (set->cnt - pos) * sizeof *set->items); + set->items[pos] = (struct strset_item){.hash = hsh, .str = str, .dyn = dyn}; + set->cnt++; +} + + +void shv_strset_free(struct strset *set) { + for (size_t i = 0; i < set->cnt; i++) + if (set->items[i].dyn) + free((void *)set->items[i].str); + free(set->items); + set->cnt = 0; + set->siz = 0; +} + +void shv_strset_add(struct strset *set, const char *str) { + add(set, strdup(str), true); +} + +void shv_strset_add_const(struct strset *set, const char *str) { + add(set, str, false); +} + +void shv_strset_add_dyn(struct strset *set, char *str) { + add(set, str, true); +} diff --git a/libshvrpc/strset.h b/libshvrpc/strset.h new file mode 100644 index 0000000..bf30984 --- /dev/null +++ b/libshvrpc/strset.h @@ -0,0 +1,33 @@ +#ifndef SHV_HASHSET_H +#define SHV_HASHSET_H + +#include +#include + + +struct strset { + struct strset_item { + unsigned hash; + const char *str; + bool dyn; + } * items; + size_t cnt, siz; +}; + +void shv_strset_free(struct strset *set) __attribute__((nonnull)); + +/* Add item. This item is duplicated to be kept. */ +void shv_strset_add(struct strset *set, const char *str) __attribute__((nonnull)); + +/* Add item but it is considered to be constant and thus kept for the duration + * of execution the same. + */ +void shv_strset_add_const(struct strset *set, const char *str) + __attribute__((nonnull)); + +/* Add item that is freed when hash is freed. */ +void shv_strset_add_dyn(struct strset *set, char *str) __attribute__((nonnull)); + + + +#endif diff --git a/meson.build b/meson.build index 8980604..88fc1a4 100644 --- a/meson.build +++ b/meson.build @@ -17,7 +17,6 @@ python = import('python') if not isnuttx add_project_arguments('-D_GNU_SOURCE', language: 'c') endif -add_project_arguments('-fms-extensions', language: 'c') add_project_arguments('-DPROJECT_VERSION="@0@"'.format(meson.project_version()), language: 'c') diff --git a/shvc/main.c b/shvc/main.c index 5ddbd12..04851e2 100644 --- a/shvc/main.c +++ b/shvc/main.c @@ -31,17 +31,17 @@ int main(int argc, char **argv) { logger = rpcclient_logger_new(stderr, conf.verbose); client->logger = logger; } - switch (rpcclient_login(client, &rpcurl->login)) { - case RPCCLIENT_LOGIN_OK: - break; - case RPCCLIENT_LOGIN_INVALID: + char *loginerr; + if (!rpcclient_login(client, &rpcurl->login, &loginerr)) { + if (loginerr) { fprintf(stderr, "Invalid login for connecting to the: %s\n", conf.url); - rpcclient_destroy(client); - rpcurl_free(rpcurl); - return 1; - case RPCCLIENT_LOGIN_ERROR: + fprintf(stderr, "%s\n", loginerr); + } else { fprintf(stderr, "Communication error with server\n"); - return 2; + } + rpcclient_destroy(client); + rpcurl_free(rpcurl); + return 1; } rpcurl_free(rpcurl); diff --git a/tests/unit/libshvchainpack/cp_unpack.c b/tests/unit/libshvchainpack/cp_unpack.c index ab2776f..bbf5b81 100644 --- a/tests/unit/libshvchainpack/cp_unpack.c +++ b/tests/unit/libshvchainpack/cp_unpack.c @@ -18,6 +18,7 @@ TEST(unpack, chainpack_unpack_null) { cp_unpack_t unpack = unpack_chainpack(&b); struct cpitem item = (struct cpitem){}; ck_assert_uint_eq(cp_unpack(unpack, &item), 1); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -26,6 +27,7 @@ TEST(unpack, cpon_unpack_null) { cp_unpack_t unpack = unpack_cpon(str); struct cpitem item = (struct cpitem){}; ck_assert_uint_eq(cp_unpack(unpack, &item), 4); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -45,6 +47,7 @@ TEST(unpack, cpon_unpack_ctx_realloc) { ck_assert_int_eq(item.type, CPITEM_CONTAINER_END); } + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -62,6 +65,7 @@ TEST(unpack, drop_string) { cp_unpack(unpack, &item); ck_assert_item_type(item, CPITEM_INVALID); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -79,6 +83,7 @@ TEST(unpack, skip_int) { cp_unpack(unpack, &item); ck_assert_item_type(item, CPITEM_CONTAINER_END); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -94,6 +99,7 @@ TEST(unpack, skip_string) { cp_unpack(unpack, &item); ck_assert_item_type(item, CPITEM_CONTAINER_END); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -111,6 +117,7 @@ TEST(unpack, skip_string_fin) { cp_unpack(unpack, &item); ck_assert_item_type(item, CPITEM_CONTAINER_END); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -126,6 +133,7 @@ TEST(unpack, skip_containers) { cp_unpack(unpack, &item); ck_assert_item_type(item, CPITEM_CONTAINER_END); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -143,6 +151,7 @@ TEST(unpack, finish_containers) { cp_unpack(unpack, &item); ck_assert_item_type(item, CPITEM_CONTAINER_END); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -155,6 +164,7 @@ TEST(unpack, unpack_strdup) { char *res = cp_unpack_strdup(unpack, &item); ck_assert_str_eq(res, "Some text"); free(res); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -166,6 +176,7 @@ TEST(unpack, unpack_strndup) { char *res = cp_unpack_strndup(unpack, &item, 5); ck_assert_str_eq(res, "Some "); free(res); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -183,6 +194,7 @@ TEST(unpack, unpack_memdup) { ck_assert_mem_eq(res, exp.v, siz); free(res); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -200,6 +212,7 @@ TEST(unpack, unpack_memndup) { ck_assert_mem_eq(res, exp.v, siz); free(res); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -212,6 +225,7 @@ TEST(unpack, unpack_strdupo) { obstack_init(&obstack); char *res = cp_unpack_strdupo(unpack, &item, &obstack); ck_assert_str_eq(res, "Some text"); + ck_assert_uint_eq(item.bufsiz, 0); obstack_free(&obstack, NULL); unpack_free(unpack); } @@ -225,6 +239,7 @@ TEST(unpack, unpack_strndupo) { obstack_init(&obstack); char *res = cp_unpack_strndupo(unpack, &item, 5, &obstack); ck_assert_str_eq(res, "Some "); + ck_assert_uint_eq(item.bufsiz, 0); obstack_free(&obstack, NULL); unpack_free(unpack); } @@ -244,6 +259,7 @@ TEST(unpack, unpack_memdupo) { ck_assert_int_eq(siz, exp.len); ck_assert_mem_eq(res, exp.v, siz); + ck_assert_uint_eq(item.bufsiz, 0); obstack_free(&obstack, NULL); unpack_free(unpack); } @@ -263,6 +279,7 @@ TEST(unpack, unpack_memndupo) { ck_assert_int_eq(siz, exp.len); ck_assert_mem_eq(res, exp.v, siz); + ck_assert_uint_eq(item.bufsiz, 0); obstack_free(&obstack, NULL); unpack_free(unpack); } @@ -274,6 +291,7 @@ TEST(unpack, unpack_strdup_invalid) { struct cpitem item = (struct cpitem){}; ck_assert_ptr_null(cp_unpack_strdup(unpack, &item)); ck_assert_int_eq(item.type, CPITEM_INT); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -291,6 +309,7 @@ TEST(unpack, unpack_strncpy) { ck_assert_int_eq(cp_unpack_strncpy(unpack, &item, buf, 5), 4); ck_assert_str_eq(buf, "text"); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -308,6 +327,7 @@ TEST(unpack, unpack_memcpy) { uint8_t exp2[] = {0x6, 0x7, 0x8, 0x9}; ck_assert_mem_eq(buf, exp2, 4); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -327,6 +347,7 @@ TEST(unpack, unpack_fopen_string) { ck_assert_str_eq(buf, "string"); fclose(f); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST @@ -344,6 +365,7 @@ TEST(unpack, unpack_fopen_blob) { ck_assert_mem_eq(buf, exp, 9); fclose(f); + ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } END_TEST diff --git a/tests/unit/libshvrpc/meson.build b/tests/unit/libshvrpc/meson.build index a3c6efc..f02e61b 100644 --- a/tests/unit/libshvrpc/meson.build +++ b/tests/unit/libshvrpc/meson.build @@ -10,7 +10,7 @@ unittest_libshvrpc = executable( 'rpcurl.c', unittest_utils_src, ], - dependencies: [libshvrpc_dep, check_suite, obstack], + dependencies: [libshvrpc_dep, check_suite], include_directories: [includes, unittest_utils_includes], c_args: unittest_utils_c_args, ) @@ -23,3 +23,27 @@ test( depends: unittest_libshvrpc, timeout: 120, ) + +unittest_libshvrpc_internal = executable( + 'unittest-libshvrpc-internal', + [ + 'strset.c', + libshvrpc_sources, + unittest_utils_src, + ], + dependencies: [libshvrpc_dep, check_suite], + include_directories: [ + includes, + unittest_utils_includes, + libshvrpc_internal_includes, + ], + c_args: unittest_utils_c_args, +) +test( + 'unittest-libshvrpc-internal', + bash, + args: [test_driver, unittest_libshvrpc_internal.full_path()], + env: unittests_env, + protocol: 'tap', + depends: unittest_libshvrpc_internal, +) diff --git a/tests/unit/libshvrpc/rpcclient_links.c b/tests/unit/libshvrpc/rpcclient_links.c index b099810..acf6b87 100644 --- a/tests/unit/libshvrpc/rpcclient_links.c +++ b/tests/unit/libshvrpc/rpcclient_links.c @@ -19,7 +19,7 @@ TEST(all, stream_tcp) { .password = "admin!123", .login_type = RPC_LOGIN_PLAIN, }; - ck_assert_int_eq(rpcclient_login(c, &opts), RPCCLIENT_LOGIN_OK); + ck_assert(rpcclient_login(c, &opts, NULL)); rpcclient_ping_test(c); @@ -35,7 +35,7 @@ TEST(all, stream_unix) { .password = "admin!123", .login_type = RPC_LOGIN_PLAIN, }; - ck_assert_int_eq(rpcclient_login(c, &opts), RPCCLIENT_LOGIN_OK); + ck_assert(rpcclient_login(c, &opts, NULL)); rpcclient_ping_test(c); diff --git a/tests/unit/libshvrpc/rpcclient_login.c b/tests/unit/libshvrpc/rpcclient_login.c index 2b27f14..a30bf06 100644 --- a/tests/unit/libshvrpc/rpcclient_login.c +++ b/tests/unit/libshvrpc/rpcclient_login.c @@ -18,7 +18,7 @@ TEST(all, plain) { .password = "admin!123", .login_type = RPC_LOGIN_PLAIN, }; - ck_assert_int_eq(rpcclient_login(c, &opts), RPCCLIENT_LOGIN_OK); + ck_assert(rpcclient_login(c, &opts, NULL)); rpcclient_ping_test(c); @@ -34,7 +34,7 @@ TEST(all, sha1) { .password = "57a261a7bcb9e6cf1db80df501cdd89cee82957e", .login_type = RPC_LOGIN_SHA1, }; - ck_assert_int_eq(rpcclient_login(c, &opts), RPCCLIENT_LOGIN_OK); + ck_assert(rpcclient_login(c, &opts, NULL)); rpcclient_ping_test(c); @@ -50,7 +50,9 @@ TEST(all, invalid) { .password = "invalid", .login_type = RPC_LOGIN_PLAIN, }; - ck_assert_int_eq(rpcclient_login(c, &opts), RPCCLIENT_LOGIN_INVALID); + char *msg; + ck_assert(!rpcclient_login(c, &opts, &msg)); + ck_assert_str_eq(msg, "Invalid login"); rpcclient_destroy(c); } diff --git a/tests/unit/libshvrpc/rpcmsg_head.c b/tests/unit/libshvrpc/rpcmsg_head.c index ad3ceca..4da8ca2 100644 --- a/tests/unit/libshvrpc/rpcmsg_head.c +++ b/tests/unit/libshvrpc/rpcmsg_head.c @@ -1,5 +1,3 @@ -#include "check.h" -#include "shv/cp.h" #include #include #include diff --git a/tests/unit/libshvrpc/rpcmsg_pack.c b/tests/unit/libshvrpc/rpcmsg_pack.c index 938b27d..557289d 100644 --- a/tests/unit/libshvrpc/rpcmsg_pack.c +++ b/tests/unit/libshvrpc/rpcmsg_pack.c @@ -15,7 +15,7 @@ END_TEST TEST(all, request_void) { rpcmsg_pack_request_void(packstream_pack, ".broker/app", "ping", 42); - ck_assert_packstr("<1:1,8:42,9:\".broker/app\",10:\"ping\">i{1:null}"); + ck_assert_packstr("<1:1,8:42,9:\".broker/app\",10:\"ping\">i{}"); } END_TEST diff --git a/tests/unit/libshvrpc/strset.c b/tests/unit/libshvrpc/strset.c new file mode 100644 index 0000000..9d55a3d --- /dev/null +++ b/tests/unit/libshvrpc/strset.c @@ -0,0 +1,92 @@ +#include +#include +#include + +#define SUITE "strset" +#include + + +TEST_CASE(all) {} + + +#define ck_assert_hash \ + do { \ + int hsh = 0; \ + for (size_t i = 0; i < set.cnt; i++) { \ + ck_assert_int_le(hsh, set.items[i].hash); \ + hsh = set.items[i].hash; \ + } \ + } while (false) + + +TEST(all, single) { + struct strset set = {}; + + shv_strset_add_const(&set, ".app"); + shv_strset_add_const(&set, ".app"); + shv_strset_add_const(&set, ".app"); + + ck_assert_int_eq(set.cnt, 1); + ck_assert_str_eq(set.items[0].str, ".app"); + shv_strset_free(&set); +} +END_TEST + +TEST(all, four) { + struct strset set = {}; + + shv_strset_add_const(&set, ".app"); + shv_strset_add_const(&set, ".broker"); + shv_strset_add_const(&set, ".device"); + shv_strset_add_const(&set, "test"); + + shv_strset_add_const(&set, ".app"); + shv_strset_add_const(&set, ".broker"); + shv_strset_add_const(&set, ".device"); + shv_strset_add_const(&set, "test"); + + ck_assert_int_eq(set.cnt, 4); + ck_assert_hash; + + shv_strset_free(&set); +} +END_TEST + +TEST(all, five) { + struct strset set = {}; + + shv_strset_add_const(&set, "foo"); + shv_strset_add_const(&set, "fee"); + shv_strset_add_const(&set, "faa"); + shv_strset_add_const(&set, "bar"); + shv_strset_add_const(&set, "boooooooooooooooo"); + + shv_strset_add_const(&set, "faa"); + shv_strset_add_const(&set, "bar"); + shv_strset_add_const(&set, "boooooooooooooooo"); + shv_strset_add_const(&set, "fee"); + shv_strset_add_const(&set, "foo"); + + ck_assert_int_eq(set.cnt, 5); + ck_assert_hash; + + shv_strset_free(&set); +} +END_TEST + +TEST(all, alphabet) { + struct strset set = {}; + + for (int i = 0; i < 3; i++) + for (char c = 'a'; c <= 'z'; c++) { + char *str; + ck_assert_int_ne(asprintf(&str, "char:%c", c), -1); + shv_strset_add_dyn(&set, str); + } + + ck_assert_int_eq(set.cnt, 'z' - 'a' + 1); + ck_assert_hash; + + shv_strset_free(&set); +} +END_TEST From 689859b190c782286fd2434bfd382f43daf860ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Wed, 27 Sep 2023 16:07:52 +0200 Subject: [PATCH 30/44] Simplify error handling with generic pack and unpack --- include/shv/cp_pack.h | 120 ++++++++++---------- include/shv/cp_unpack.h | 69 ++++++------ include/shv/rpcmsg.h | 78 +++++++++++-- libshvchainpack/common.c | 2 +- libshvchainpack/cp_pack.c | 8 +- libshvchainpack/cp_unpack.c | 27 ++--- libshvrpc/rpcclient_stream.c | 9 +- libshvrpc/rpcmsg_head.c | 15 ++- libshvrpc/rpcmsg_pack.c | 147 ++++++++++++------------- tests/unit/libshvchainpack/cp_unpack.c | 8 +- tests/unit/libshvrpc/rpcclient_login.c | 1 + tests/unit/libshvrpc/strset.c | 2 + 12 files changed, 259 insertions(+), 227 deletions(-) diff --git a/include/shv/cp_pack.h b/include/shv/cp_pack.h index 670d08f..10da39c 100644 --- a/include/shv/cp_pack.h +++ b/include/shv/cp_pack.h @@ -22,20 +22,19 @@ * @param ptr: Pointer to the context information that is pointer to the @ref * cp_pack_t. * @param item: Item to be packed. - * @returns Number of bytes written. On error it returns value of zero. Note - * that some bytes might have been successfully written before error was - * detected. Number of written bytes in case of an error is irrelevant because - * packed data is invalid anyway. Be aware that some inputs might inevitably - * produce no output (strings and blobs that are not first or last and have - * zero length produce no output). + * @returns `true` in case packing was sucesfull and `false` otherwise. The + * common way to diagnose such error is not provided. If we are talking about + * bare ChainPack and CPON packers it is going to mean either stream error or + * EOF. The generic meaning is that additional packing won't be possible when + * `false` is returned. */ -typedef size_t (*cp_pack_func_t)(void *ptr, const struct cpitem *item); +typedef bool (*cp_pack_func_t)(void *ptr, const struct cpitem *item); /*! Generic packer. * * This is pointer to the function pointer that implements packing. The function * is called by dereferencing this generic packer and the pointer to it is * passed to the function as the first argument. This double pointer provides - * you a way to store any context info side by pointer to the unpack function. + * you a way to store any context info side by pointer to the pack function. * * To understand this you can look into the @ref cp_pack_chainpack and @ref * cp_pack_cpon definitions. They have @ref cp_pack_func_t as a first field and @@ -105,14 +104,9 @@ cp_pack_t cp_pack_cpon_init(struct cp_pack_cpon *pack, FILE *f, * * The calling is described in @ref cp_pack_func_t. * - * @param PACK: Generic packer to be used for unpacking. + * @param PACK: Generic packer to be used for packing. * @param ITEM: Item to be packed. - * @returns Number of bytes written. On error it returns value of zero. Note - * that some bytes might have been successfully written before error was - * detected. Number of written bytes in case of an error is irrelevant because - * packed data is invalid anyway. Be aware that some inputs might inevitably - * produce no output (strings and blobs that are not first or last and have - * zero length produce no output). + * @returns Boolean signaling the pack success or failure. */ #define cp_pack(PACK, ITEM) \ ({ \ @@ -124,9 +118,9 @@ cp_pack_t cp_pack_cpon_init(struct cp_pack_cpon *pack, FILE *f, /*! Pack *Null* to the generic packer. * * @param pack: Generic packer. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_null(cp_pack_t pack) { +static inline bool cp_pack_null(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_NULL; return cp_pack(pack, &i); @@ -136,9 +130,9 @@ static inline size_t cp_pack_null(cp_pack_t pack) { * * @param pack: Generic packer. * @param v: Boolean value to be packed. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_bool(cp_pack_t pack, bool v) { +static inline bool cp_pack_bool(cp_pack_t pack, bool v) { struct cpitem i; i.type = CPITEM_BOOL; i.as.Bool = v; @@ -149,9 +143,9 @@ static inline size_t cp_pack_bool(cp_pack_t pack, bool v) { * * @param pack: Generic packer. * @param v: Integer value to be packed. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_int(cp_pack_t pack, long long v) { +static inline bool cp_pack_int(cp_pack_t pack, long long v) { struct cpitem i; i.type = CPITEM_INT; i.as.Int = v; @@ -162,9 +156,9 @@ static inline size_t cp_pack_int(cp_pack_t pack, long long v) { * * @param pack: Generic packer. * @param v: Uinsigned integer value to be packed. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_uint(cp_pack_t pack, unsigned long long v) { +static inline bool cp_pack_uint(cp_pack_t pack, unsigned long long v) { struct cpitem i; i.type = CPITEM_UINT; i.as.UInt = v; @@ -175,9 +169,9 @@ static inline size_t cp_pack_uint(cp_pack_t pack, unsigned long long v) { * * @param pack: Generic packer. * @param v: Float point number value to be packed. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_double(cp_pack_t pack, double v) { +static inline bool cp_pack_double(cp_pack_t pack, double v) { struct cpitem i; i.type = CPITEM_DOUBLE; i.as.Double = v; @@ -188,9 +182,9 @@ static inline size_t cp_pack_double(cp_pack_t pack, double v) { * * @param pack: Generic packer. * @param v: Decimal number value to be packed. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_decimal(cp_pack_t pack, struct cpdecimal v) { +static inline bool cp_pack_decimal(cp_pack_t pack, struct cpdecimal v) { struct cpitem i; i.type = CPITEM_DECIMAL; i.as.Decimal = v; @@ -201,9 +195,9 @@ static inline size_t cp_pack_decimal(cp_pack_t pack, struct cpdecimal v) { * * @param pack: Generic packer. * @param v: Date and time value to be packed. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_datetime(cp_pack_t pack, struct cpdatetime v) { +static inline bool cp_pack_datetime(cp_pack_t pack, struct cpdatetime v) { struct cpitem i; i.type = CPITEM_DATETIME; i.as.Datetime = v; @@ -215,9 +209,9 @@ static inline size_t cp_pack_datetime(cp_pack_t pack, struct cpdatetime v) { * @param pack: Generic packer. * @param buf: Buffer to be packed as a signle blob. * @param len: Size of the **buf** (valid number of bytes to be packed). - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_blob(cp_pack_t pack, const uint8_t *buf, size_t len) { +static inline bool cp_pack_blob(cp_pack_t pack, const uint8_t *buf, size_t len) { struct cpitem i; i.type = CPITEM_BLOB; i.rbuf = buf; @@ -239,9 +233,9 @@ static inline size_t cp_pack_blob(cp_pack_t pack, const uint8_t *buf, size_t len * @param item: Item that needs to be used with subsequent @ref * cp_pack_blob_data calls. The item doesn't have to be initialized. * @param siz: Number of bytes to be packed in total. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_blob_size( +static inline bool cp_pack_blob_size( cp_pack_t pack, struct cpitem *item, size_t siz) { item->type = CPITEM_BLOB; item->as.Blob = (struct cpbufinfo){ @@ -262,15 +256,15 @@ static inline size_t cp_pack_blob_size( * @param pack: Generic packer. * @param item: Item previously used with @ref cp_pack_blob_size. * @param buf: Buffer to the bytes to be packed to the blob. - * @param siz: Size of the **buf** (valid number of bytes to be packed). It is - * discouraged to use here `0`, because that results in this function - * returining zero. - * @returns Number of bytes written. See @ref cp_pack. + * @param siz: Size of the **buf** (valid number of bytes to be packed). + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_blob_data( +static inline bool cp_pack_blob_data( cp_pack_t pack, struct cpitem *item, const uint8_t *buf, size_t siz) { assert(item->type == CPITEM_BLOB); assert(item->as.Blob.eoff >= siz); + if (siz == 0) + return true; item->rbuf = buf; item->as.Blob.len = siz; item->as.Blob.eoff -= siz; @@ -285,9 +279,9 @@ static inline size_t cp_pack_blob_data( * @param pack: Generic packer. * @param buf: Buffer containing characters to be packed as a signle string. * @param len: Size of the **buf** (valid number of characters to be packed). - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_string(cp_pack_t pack, const char *buf, size_t len) { +static inline bool cp_pack_string(cp_pack_t pack, const char *buf, size_t len) { struct cpitem i; i.type = CPITEM_STRING; i.rchr = buf; @@ -303,9 +297,9 @@ static inline size_t cp_pack_string(cp_pack_t pack, const char *buf, size_t len) * * @param pack: Generic packer. * @param str: C-String (null terminated string) to be packed. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_str(cp_pack_t pack, const char *str) { +static inline bool cp_pack_str(cp_pack_t pack, const char *str) { return cp_pack_string(pack, str, strlen(str)); } @@ -314,9 +308,9 @@ static inline size_t cp_pack_str(cp_pack_t pack, const char *str) { * @param pack: Generic packer. * @param fmt: printf format string. * @param args: list of variadic arguments to be passed to the printf. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_vfstr(cp_pack_t pack, const char *fmt, va_list args) { +static inline bool cp_pack_vfstr(cp_pack_t pack, const char *fmt, va_list args) { va_list cargs; va_copy(cargs, args); int siz = vsnprintf(NULL, 0, fmt, cargs); @@ -330,9 +324,9 @@ static inline size_t cp_pack_vfstr(cp_pack_t pack, const char *fmt, va_list args * * @param pack: Generic packer. * @param fmt: printf format string. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_fstr(cp_pack_t pack, const char *fmt, ...) { +static inline bool cp_pack_fstr(cp_pack_t pack, const char *fmt, ...) { va_list args; va_start(args, fmt); size_t res = cp_pack_vfstr(pack, fmt, args); @@ -350,9 +344,9 @@ static inline size_t cp_pack_fstr(cp_pack_t pack, const char *fmt, ...) { * @param item: Item that needs to be used with subsequent @ref * cp_pack_string_data calls. The item doesn't have to be initialized. * @param siz: Number of bytes to be packed in total. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_string_size( +static inline bool cp_pack_string_size( cp_pack_t pack, struct cpitem *item, size_t siz) { item->type = CPITEM_STRING; item->as.String = (struct cpbufinfo){ @@ -373,15 +367,15 @@ static inline size_t cp_pack_string_size( * @param pack: Generic packer. * @param item: Item previously used with @ref cp_pack_string_size. * @param buf: Buffer to the bytes to be packed to the string. - * @param siz: Size of the **buf** (valid number of bytes to be packed). It is - * discouraged to use here `0`, because that results in this function - * returining zero. - * @returns Number of bytes written. See @ref cp_pack. + * @param siz: Size of the **buf** (valid number of bytes to be packed). + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_string_data( +static inline bool cp_pack_string_data( cp_pack_t pack, struct cpitem *item, const char *buf, size_t siz) { assert(item->type == CPITEM_STRING); assert(item->as.Blob.eoff >= siz); + if (siz == 0) + return true; item->rchr = buf; item->as.String.len = siz; item->as.String.eoff -= siz; @@ -397,9 +391,9 @@ static inline size_t cp_pack_string_data( * to terminate the it with @ref cp_pack_container_end. * * @param pack: Generic packer. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_list_begin(cp_pack_t pack) { +static inline bool cp_pack_list_begin(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_LIST; return cp_pack(pack, &i); @@ -412,9 +406,9 @@ static inline size_t cp_pack_list_begin(cp_pack_t pack) { * cp_pack_container_end. * * @param pack: Generic packer. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_map_begin(cp_pack_t pack) { +static inline bool cp_pack_map_begin(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_MAP; return cp_pack(pack, &i); @@ -427,9 +421,9 @@ static inline size_t cp_pack_map_begin(cp_pack_t pack) { * cp_pack_container_end. * * @param pack: Generic packer. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_imap_begin(cp_pack_t pack) { +static inline bool cp_pack_imap_begin(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_IMAP; return cp_pack(pack, &i); @@ -442,9 +436,9 @@ static inline size_t cp_pack_imap_begin(cp_pack_t pack) { * with @ref cp_pack_container_end and follow it with item that meta is tied to. * * @param pack: Generic packer. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_meta_begin(cp_pack_t pack) { +static inline bool cp_pack_meta_begin(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_META; return cp_pack(pack, &i); @@ -456,9 +450,9 @@ static inline size_t cp_pack_meta_begin(cp_pack_t pack) { * cp_pack_map_begin, @ref cp_pack_imap_begin and @ref cp_pack_meta_begin. * * @param pack: Generic packer. - * @returns Number of bytes written. See @ref cp_pack. + * @returns Boolean signaling the pack success or failure. */ -static inline size_t cp_pack_container_end(cp_pack_t pack) { +static inline bool cp_pack_container_end(cp_pack_t pack) { struct cpitem i; i.type = CPITEM_CONTAINER_END; return cp_pack(pack, &i); diff --git a/include/shv/cp_unpack.h b/include/shv/cp_unpack.h index 7bc3c74..af03a59 100644 --- a/include/shv/cp_unpack.h +++ b/include/shv/cp_unpack.h @@ -21,9 +21,8 @@ * cp_unpack_t. * @param item: Item where info about the unpacked item and its value is placed * to. - * @returns Number of bytes read. */ -typedef size_t (*cp_unpack_func_t)(void *ptr, struct cpitem *item); +typedef void (*cp_unpack_func_t)(void *ptr, struct cpitem *item); /*! Generic unpacker. * * This is pointer to the function pointer that implements unpacking. The @@ -101,12 +100,15 @@ cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *pack, FILE *f) * @param UNPACK: Generic unpacker to be used for unpacking. * @param ITEM: Item where info about the unpacked item and its value is placed * to. - * @returns Number of bytes read. + * @returns Boolean signaling if unpack was successful (non-invalid item was + * unpacked). */ #define cp_unpack(UNPACK, ITEM) \ ({ \ cp_unpack_t __unpack = UNPACK; \ - (*__unpack)(__unpack, (ITEM)); \ + struct cpitem *__item = ITEM; \ + (*__unpack)(__unpack, __item); \ + __item->type != CPITEM_INVALID; \ }) /*! Unpack item with generic unpacker and provide its type. @@ -138,9 +140,8 @@ cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *pack, FILE *f) * @param unpack: Unpack handle. * @param item: Item used for the `cp_unpack` calls and was used in the last * one. - * @returns number of read bytes. */ -size_t cp_unpack_drop(cp_unpack_t unpack, struct cpitem *item) +void cp_unpack_drop(cp_unpack_t unpack, struct cpitem *item) __attribute__((nonnull)); /*! Instead of getting next item this skips it. @@ -155,9 +156,8 @@ size_t cp_unpack_drop(cp_unpack_t unpack, struct cpitem *item) * @param unpack: Unpack handle. * @param item: Item used for the last `cp_unpack` call. This is required * because there might be unfinished string or blob we need to know about. - * @returns number of read bytes. */ -size_t cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) +void cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) __attribute__((nonnull)); /*! Read until the currently opened container is finished. @@ -172,9 +172,8 @@ size_t cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) * @param depth: How many containers we should finish. To finish one pass `1`. * To close two containers pass `2` and so on. The `0` provides the same * functionality as `cp_unpack_skip`. - * @returns number of read bytes. */ -size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item, unsigned depth) +void cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item, unsigned depth) __attribute__((nonnull)); @@ -187,16 +186,14 @@ size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item, unsigned depth) * to. You can use it to identify the real type or error in case of failure. * @param DEST: destination integer variable (not pointer, the variable * directly). - * @returns Number of read bytes or zero or negative number of read bytes in - * case of an error. The error can be either due to the unpack failure or due - * to number being too big to be stored in **DEST**. The real issue can be - * deduced from **ITEM**. + * @returns Boolean signaling if both unpack was successful and value fit the + * destination. The real issue can be deduced from **ITEM**. */ #define cp_unpack_int(UNPACK, ITEM, DEST) \ ({ \ - struct cpitem *__uitem = ITEM; \ - ssize_t res = cp_unpack(UNPACK, __uitem); \ - cpitem_extract_int(__uitem, DEST) ? res : -res; \ + struct cpitem *___item = ITEM; \ + cp_unpack(UNPACK, ___item); \ + cpitem_extract_int(___item, DEST); \ }) /*! Unpack unsigned integer and place it to the destination. @@ -208,16 +205,14 @@ size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item, unsigned depth) * to. You can use it to identify the real type or error in case of failure. * @param DEST: destination integer variable (not pointer, the variable * directly). - * @returns Number of read bytes or zero or negative number of read bytes in - * case of an error. The error can be either due to the unpack failure or due - * to number being too big to be stored in **DEST**. The real issue can be - * deduced from **ITEM**. + * @returns Boolean signaling if both unpack was successful and value fit the + * destination. The real issue can be deduced from **ITEM**. */ #define cp_unpack_uint(UNPACK, ITEM, DEST) \ ({ \ - struct cpitem *__uitem = ITEM; \ - ssize_t res = cp_unpack(UNPACK, __uitem); \ - cpitem_extract_uint(__uitem, DEST) ? res : -res; \ + struct cpitem *___item = ITEM; \ + cp_unpack(UNPACK, ___item); \ + cpitem_extract_uint(___item, DEST); \ }) /*! Unpack a single by from string. @@ -432,9 +427,9 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_memcpy( */ #define for_cp_unpack_list(UNPACK, ITEM) \ while (({ \ - struct cpitem *__item = ITEM; \ - cp_unpack(UNPACK, __item); \ - __item->type != CPITEM_INVALID && __item->type != CPITEM_CONTAINER_END; \ + struct cpitem *___item = ITEM; \ + cp_unpack(UNPACK, ___item); \ + ___item->type != CPITEM_INVALID && ___item->type != CPITEM_CONTAINER_END; \ })) /*! Helper macro for unpacking lists with items counter. @@ -447,10 +442,10 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_memcpy( */ #define for_cp_unpack_ilist(UNPACK, ITEM, CNT) \ for (unsigned CNT = 0; ({ \ - struct cpitem *__item = ITEM; \ - cp_unpack(UNPACK, __item); \ - __item->type != CPITEM_INVALID && \ - __item->type != CPITEM_CONTAINER_END; \ + struct cpitem *___item = ITEM; \ + cp_unpack(UNPACK, ___item); \ + ___item->type != CPITEM_INVALID && \ + ___item->type != CPITEM_CONTAINER_END; \ }); \ CNT++) @@ -465,18 +460,18 @@ __attribute__((nonnull(1, 2))) static inline ssize_t cp_unpack_memcpy( */ #define for_cp_unpack_map(UNPACK, ITEM) \ while (({ \ - struct cpitem *__item = ITEM; \ - cp_unpack(UNPACK, __item); \ - __item->type == CPITEM_STRING; \ + struct cpitem *___item = ITEM; \ + cp_unpack(UNPACK, ___item); \ + ___item->type == CPITEM_STRING; \ })) /*! Helper macro for unpacking integer maps. */ #define for_cp_unpack_imap(UNPACK, ITEM) \ while (({ \ - struct cpitem *__item = ITEM; \ - cp_unpack(UNPACK, __item); \ - __item->type == CPITEM_INT; \ + struct cpitem *___item = ITEM; \ + cp_unpack(UNPACK, ___item); \ + ___item->type == CPITEM_INT; \ })) /*! Open string or blob for reading using stream. diff --git a/include/shv/rpcmsg.h b/include/shv/rpcmsg.h index dc1459c..53a6cd2 100644 --- a/include/shv/rpcmsg.h +++ b/include/shv/rpcmsg.h @@ -39,12 +39,13 @@ enum rpcmsg_error_key { * * @param pack: pack context the meta should be written to. * @param path: SHV path to the node the method we want to request is associated - * with. + * with. * @param method: name of the method we request to call. * @param rid: request identifier. Thanks to this number you can associate - * response with requests. + * response with requests. + * @returns Boolean signaling the pack success or failure. */ -size_t rpcmsg_pack_request(cp_pack_t pack, const char *path, const char *method, +bool rpcmsg_pack_request(cp_pack_t pack, const char *path, const char *method, int rid) __attribute__((nonnull(1, 3))); /*! Pack request message with no parameters. @@ -59,8 +60,9 @@ size_t rpcmsg_pack_request(cp_pack_t pack, const char *path, const char *method, * @param method: name of the method we request to call. * @param rid: request identifier. Thanks to this number you can associate * response with requests. + * @returns Boolean signaling the pack success or failure. */ -size_t rpcmsg_pack_request_void(cp_pack_t pack, const char *path, +bool rpcmsg_pack_request_void(cp_pack_t pack, const char *path, const char *method, int rid) __attribute__((nonnull(1, 3))); /*! Pack signal message meta and open imap. The followup packed data are @@ -70,8 +72,9 @@ size_t rpcmsg_pack_request_void(cp_pack_t pack, const char *path, * @param pack: pack context the meta should be written to. * @param path: SHV path to the node method is associated with. * @param method: name of the method signal is raised for. + * @returns Boolean signaling the pack success or failure. */ -size_t rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) +bool rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) __attribute__((nonnull(1, 3))); /*! Pack value change signal message meta and open imap. The followup packed @@ -83,8 +86,9 @@ size_t rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) * * @param pack: pack context the meta should be written to. * @param path: SHV path the value change signal is associated with. + * @returns Boolean signaling the pack success or failure. */ -__attribute__((nonnull(1))) static inline size_t rpcmsg_pack_chng( +__attribute__((nonnull(1))) static inline bool rpcmsg_pack_chng( cp_pack_t pack, const char *path) { return rpcmsg_pack_signal(pack, path, "chng"); } @@ -264,10 +268,26 @@ bool rpcmsg_head_unpack(cp_unpack_t unpack, struct cpitem *item, struct obstack *obstack); -size_t rpcmsg_pack_response(cp_pack_t, const struct rpcmsg_meta *meta) +/*! Pack response message meta based on the provided meta. + * + * @param pack: Pack context the meta should be written to. + * @param meta: Meta with info for previously received request. + * @returns Boolean signaling the pack success or failure. + */ +bool rpcmsg_pack_response(cp_pack_t pack, const struct rpcmsg_meta *meta) __attribute__((nonnull)); -size_t rpcmsg_pack_response_void(cp_pack_t, const struct rpcmsg_meta *meta) +/*! Pack response message based on the provided meta without any value. + * + * This is helper to pack the complete message at once when response is *void* + * (only confirm without any real value returned). After this you can directly + * send the message. + * + * @param pack: Pack context the meta should be written to. + * @param meta: Meta with info for previously received request. + * @returns Boolean signaling the pack success or failure. + */ +bool rpcmsg_pack_response_void(cp_pack_t, const struct rpcmsg_meta *meta) __attribute__((nonnull)); enum rpcmsg_error { @@ -284,13 +304,49 @@ enum rpcmsg_error { RPCMSG_E_USER_CODE = 32, }; -size_t rpcmsg_pack_error(cp_pack_t, const struct rpcmsg_meta *meta, +/*! Pack error response message based on the provided meta. + * + * This is helper to pack the whole error message at once. After this you can + * direcly send the message. + * + * @param pack: Pack context the meta should be written to. + * @param meta: Meta with info for previously received request. + * @param error: Error code to be reported as response to the request. + * @param msg: Optional message describing the error details. + * @returns Boolean signaling the pack success or failure. + */ +bool rpcmsg_pack_error(cp_pack_t, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *msg) __attribute__((nonnull(1, 2))); -size_t rpcmsg_pack_ferror(cp_pack_t, const struct rpcmsg_meta *meta, +/*! Pack error response message based on the provided meta. + * + * This is variant of @ref rpcmsg_pack_error that allows you to use printf + * format string to generate the error message. This function packs complete + * message and thus you can immediately send it. + * + * @param pack: Pack context the meta should be written to. + * @param meta: Meta with info for previously received request. + * @param error: Error code to be reported as response to the request. + * @param fmt: Format string used to generate the error message. + * @returns Boolean signaling the pack success or failure. + */ +bool rpcmsg_pack_ferror(cp_pack_t, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, ...) __attribute__((nonnull(1, 2))); -size_t rpcmsg_pack_vferror(cp_pack_t, const struct rpcmsg_meta *meta, +/*! Pack error response message based on the provided meta. + * + * This is variant of @ref rpcmsg_pack_error that allows you to use printf + * format string to generate the error message. This function packs complete + * message and thus you can immediately send it. + * + * @param pack: Pack context the meta should be written to. + * @param meta: Meta with info for previously received request. + * @param error: Error code to be reported as response to the request. + * @param fmt: Format string used to generate the error message. + * @param args: Variable list of arguments to be used with **fmt**. + * @returns Boolean signaling the pack success or failure. + */ +bool rpcmsg_pack_vferror(cp_pack_t, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, va_list args) __attribute__((nonnull(1, 2))); diff --git a/libshvchainpack/common.c b/libshvchainpack/common.c index 5887ba7..aaf054f 100644 --- a/libshvchainpack/common.c +++ b/libshvchainpack/common.c @@ -30,7 +30,7 @@ bool common_unpack(size_t *res, FILE *f, struct cpitem *item) { } bool common_pack(size_t *res, FILE *f, const struct cpitem *item) { - if (ferror(f)) /* No reason to write to file in error */ + if (f && ferror(f)) /* No reason to write to file in error */ return true; switch (item->type) { case CPITEM_INVALID: diff --git a/libshvchainpack/cp_pack.c b/libshvchainpack/cp_pack.c index 2f1fc94..92e97c1 100644 --- a/libshvchainpack/cp_pack.c +++ b/libshvchainpack/cp_pack.c @@ -1,9 +1,9 @@ #include #include -static size_t cp_pack_chainpack_func(void *ptr, const struct cpitem *item) { +static bool cp_pack_chainpack_func(void *ptr, const struct cpitem *item) { struct cp_pack_chainpack *p = ptr; - return chainpack_pack(p->f, item); + return chainpack_pack(p->f, item) > 0; } cp_pack_t cp_pack_chainpack_init(struct cp_pack_chainpack *pack, FILE *f) { @@ -19,9 +19,9 @@ static void cpon_state_realloc(struct cpon_state *state) { state->ctx = realloc(state->ctx, state->cnt * sizeof *state->ctx); } -static size_t cp_pack_cpon_func(void *ptr, const struct cpitem *item) { +static bool cp_pack_cpon_func(void *ptr, const struct cpitem *item) { struct cp_pack_cpon *p = ptr; - return cpon_pack(p->f, &p->state, item); + return cpon_pack(p->f, &p->state, item) > 0; } cp_pack_t cp_pack_cpon_init(struct cp_pack_cpon *pack, FILE *f, const char *indent) { diff --git a/libshvchainpack/cp_unpack.c b/libshvchainpack/cp_unpack.c index c9f6e19..207c22e 100644 --- a/libshvchainpack/cp_unpack.c +++ b/libshvchainpack/cp_unpack.c @@ -4,9 +4,9 @@ #include #include -static size_t cp_unpack_chainpack_func(void *ptr, struct cpitem *item) { +static void cp_unpack_chainpack_func(void *ptr, struct cpitem *item) { struct cp_unpack_chainpack *p = ptr; - return chainpack_unpack(p->f, item); + chainpack_unpack(p->f, item); } cp_unpack_t cp_unpack_chainpack_init(struct cp_unpack_chainpack *unpack, FILE *f) { @@ -22,9 +22,9 @@ static void cpon_state_realloc(struct cpon_state *state) { state->ctx = realloc(state->ctx, state->cnt * sizeof *state->ctx); } -static size_t cp_unpack_cpon_func(void *ptr, struct cpitem *item) { +static void cp_unpack_cpon_func(void *ptr, struct cpitem *item) { struct cp_unpack_cpon *p = ptr; - return cpon_unpack(p->f, &p->state, item); + cpon_unpack(p->f, &p->state, item); } cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *unpack, FILE *f) { @@ -41,27 +41,24 @@ cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *unpack, FILE *f) { return &unpack->func; } -size_t cp_unpack_drop(cp_unpack_t unpack, struct cpitem *item) { - size_t res = 0; +void cp_unpack_drop(cp_unpack_t unpack, struct cpitem *item) { item->buf = NULL; item->bufsiz = SIZE_MAX; while ((item->type == CPITEM_STRING || item->type == CPITEM_BLOB) && !(item->as.Blob.flags & CPBI_F_LAST)) - res += cp_unpack(unpack, item); + cp_unpack(unpack, item); item->bufsiz = 0; - return res; } -size_t cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) { - return cp_unpack_finish(unpack, item, 0); +void cp_unpack_skip(cp_unpack_t unpack, struct cpitem *item) { + cp_unpack_finish(unpack, item, 0); } -size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item, unsigned depth) { - size_t res = 0; +void cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item, unsigned depth) { cp_unpack_drop(unpack, item); item->bufsiz = SIZE_MAX; do { - res += cp_unpack(unpack, item); + cp_unpack(unpack, item); switch (item->type) { case CPITEM_BLOB: case CPITEM_STRING: @@ -80,13 +77,13 @@ size_t cp_unpack_finish(cp_unpack_t unpack, struct cpitem *item, unsigned depth) depth--; break; case CPITEM_INVALID: - return false; + item->bufsiz = 0; + return; default: break; } } while (depth); item->bufsiz = 0; - return res; } static void *cp_unpack_dup( diff --git a/libshvrpc/rpcclient_stream.c b/libshvrpc/rpcclient_stream.c index 2245f94..b875e90 100644 --- a/libshvrpc/rpcclient_stream.c +++ b/libshvrpc/rpcclient_stream.c @@ -53,11 +53,10 @@ static ssize_t cookie_read(void *cookie, char *buf, size_t size) { return i; } -static size_t cp_unpack_stream(void *ptr, struct cpitem *item) { +static void cp_unpack_stream(void *ptr, struct cpitem *item) { struct rpcclient_stream *c = ptr - offsetof(struct rpcclient_stream, c.unpack); - size_t res = chainpack_unpack(c->rf, item); + chainpack_unpack(c->rf, item); rpcclient_log_item(c->c.logger, item); - return res; } static ssize_t read_size(int fd) { @@ -101,12 +100,12 @@ static bool msgfetch_stream(struct rpcclient *client, bool newmsg) { return ok; } -static size_t cp_pack_stream(void *ptr, const struct cpitem *item) { +static bool cp_pack_stream(void *ptr, const struct cpitem *item) { struct rpcclient_stream *c = ptr - offsetof(struct rpcclient_stream, c.pack); if (ftell(c->fbuf) == 0) rpcclient_log_lock(c->c.logger, false); rpcclient_log_item(c->c.logger, item); - return chainpack_pack(c->fbuf, item); + return chainpack_pack(c->fbuf, item) > 0; } static bool write_size(int fd, size_t len) { diff --git a/libshvrpc/rpcmsg_head.c b/libshvrpc/rpcmsg_head.c index 5a2650e..796e2a9 100644 --- a/libshvrpc/rpcmsg_head.c +++ b/libshvrpc/rpcmsg_head.c @@ -46,8 +46,8 @@ static bool repack(cp_unpack_t unpack, struct cpitem *item, bool res = true; unsigned depth = 0; do { - cp_unpack(unpack, item); - if (item->type == CPITEM_INVALID || chainpack_pack(f, item) == -1) { + if (cp_unpack_type(unpack, item) == CPITEM_INVALID || + chainpack_pack(f, item) == 0) { /* Note: chainpack_pack Can fail only due to not enough space in the * buffer. */ @@ -86,7 +86,7 @@ static bool unpack_extra(cp_unpack_t unpack, struct cpitem *item, struct rpcmsg_meta_extra **extra, struct obstack *obstack) { while (*extra != NULL) extra = &(*extra)->next; - *extra = malloc(sizeof **extra); + *extra = obstack_alloc(obstack, sizeof **extra); (*extra)->key = item->as.Int; (*extra)->next = NULL; return repack(unpack, item, &(*extra)->ptr, -1, obstack); @@ -96,8 +96,7 @@ bool rpcmsg_head_unpack(cp_unpack_t unpack, struct cpitem *item, struct rpcmsg_meta *meta, struct rpcmsg_meta_limits *limits, struct obstack *obstack) { meta->type = RPCMSG_T_INVALID; - cp_unpack(unpack, item); - if (item->type != CPITEM_META) + if (!cp_unpack(unpack, item)) return false; meta->request_id = INT64_MIN; @@ -128,7 +127,7 @@ bool rpcmsg_head_unpack(cp_unpack_t unpack, struct cpitem *item, break; case RPCMSG_TAG_REQUEST_ID: has_rid = true; - if (cp_unpack_int(unpack, item, meta->request_id) <= 0) + if (!cp_unpack_int(unpack, item, meta->request_id)) return false; break; case RPCMSG_TAG_SHV_PATH: @@ -157,8 +156,8 @@ bool rpcmsg_head_unpack(cp_unpack_t unpack, struct cpitem *item, if (cp_unpack_type(unpack, item) != CPITEM_IMAP) return false; int key = -1; - cp_unpack(unpack, item); - if (item->type != CPITEM_CONTAINER_END && !cpitem_extract_int(item, key)) + if (cp_unpack_type(unpack, item) != CPITEM_CONTAINER_END && + !cpitem_extract_int(item, key)) return false; if (has_rid) { if (valid_method) { diff --git a/libshvrpc/rpcmsg_pack.c b/libshvrpc/rpcmsg_pack.c index bfaaba5..fb13969 100644 --- a/libshvrpc/rpcmsg_pack.c +++ b/libshvrpc/rpcmsg_pack.c @@ -3,80 +3,72 @@ #include #include -#define RES(V) \ +#define G(V) \ do { \ - size_t __res = V; \ - if (__res == 0) \ - return 0; \ - res += __res; \ + if (!(V)) \ + return false; \ } while (false) -static inline size_t meta_begin(cp_pack_t pack) { - size_t res = 0; - RES(cp_pack_meta_begin(pack)); - RES(cp_pack_int(pack, RPCMSG_TAG_META_TYPE_ID)); - RES(cp_pack_int(pack, 1)); - return res; +static inline bool meta_begin(cp_pack_t pack) { + G(cp_pack_meta_begin(pack)); + G(cp_pack_int(pack, RPCMSG_TAG_META_TYPE_ID)); + G(cp_pack_int(pack, 1)); + return true; } -size_t _rpcmsg_pack_request( +bool _rpcmsg_pack_request( cp_pack_t pack, const char *path, const char *method, int rid) { - size_t res = 0; - RES(meta_begin(pack)); - RES(cp_pack_int(pack, RPCMSG_TAG_REQUEST_ID)); - RES(cp_pack_int(pack, rid)); + G(meta_begin(pack)); + G(cp_pack_int(pack, RPCMSG_TAG_REQUEST_ID)); + G(cp_pack_int(pack, rid)); if (path) { - RES(cp_pack_int(pack, RPCMSG_TAG_SHV_PATH)); - RES(cp_pack_str(pack, path)); + G(cp_pack_int(pack, RPCMSG_TAG_SHV_PATH)); + G(cp_pack_str(pack, path)); } - RES(cp_pack_int(pack, RPCMSG_TAG_METHOD)); - RES(cp_pack_str(pack, method)); - RES(cp_pack_container_end(pack)); + G(cp_pack_int(pack, RPCMSG_TAG_METHOD)); + G(cp_pack_str(pack, method)); + G(cp_pack_container_end(pack)); - RES(cp_pack_imap_begin(pack)); - return res; + G(cp_pack_imap_begin(pack)); + return true; } -size_t rpcmsg_pack_request( +bool rpcmsg_pack_request( cp_pack_t pack, const char *path, const char *method, int rid) { - size_t res = 0; - RES(_rpcmsg_pack_request(pack, path, method, rid)); - RES(cp_pack_int(pack, RPCMSG_KEY_PARAMS)); - return res; + G(_rpcmsg_pack_request(pack, path, method, rid)); + G(cp_pack_int(pack, RPCMSG_KEY_PARAMS)); + return true; } -size_t rpcmsg_pack_request_void( +bool rpcmsg_pack_request_void( cp_pack_t pack, const char *path, const char *method, int rid) { - size_t res = 0; - RES(_rpcmsg_pack_request(pack, path, method, rid)); - RES(cp_pack_container_end(pack)); - return res; + G(_rpcmsg_pack_request(pack, path, method, rid)); + G(cp_pack_container_end(pack)); + return true; } -size_t rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) { - size_t res = 0; - RES(meta_begin(pack)); - RES(cp_pack_int(pack, RPCMSG_TAG_SHV_PATH)); +bool rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) { + G(meta_begin(pack)); + G(cp_pack_int(pack, RPCMSG_TAG_SHV_PATH)); if (path) { - RES(cp_pack_str(pack, path)); - RES(cp_pack_int(pack, RPCMSG_TAG_METHOD)); + G(cp_pack_str(pack, path)); + G(cp_pack_int(pack, RPCMSG_TAG_METHOD)); } - RES(cp_pack_str(pack, method)); - RES(cp_pack_container_end(pack)); + G(cp_pack_str(pack, method)); + G(cp_pack_container_end(pack)); - RES(cp_pack_imap_begin(pack)); - RES(cp_pack_int(pack, RPCMSG_KEY_PARAMS)); - return res; + G(cp_pack_imap_begin(pack)); + G(cp_pack_int(pack, RPCMSG_KEY_PARAMS)); + return true; } -static size_t _pack_response(cp_pack_t pack, const struct rpcmsg_meta *meta) { - size_t res = 0; - RES(meta_begin(pack)); - RES(cp_pack_int(pack, RPCMSG_TAG_REQUEST_ID)); - RES(cp_pack_int(pack, meta->request_id)); +static bool _pack_response(cp_pack_t pack, const struct rpcmsg_meta *meta) { + G(meta_begin(pack)); + G(cp_pack_int(pack, RPCMSG_TAG_REQUEST_ID)); + G(cp_pack_int(pack, meta->request_id)); if (meta->cids.siz) { - RES(cp_pack_int(pack, RPCMSG_TAG_CALLER_IDS)); + G(cp_pack_int(pack, RPCMSG_TAG_CALLER_IDS)); FILE *f = fmemopen(meta->cids.ptr, meta->cids.siz, "r"); uint8_t buf[BUFSIZ]; struct cpitem item = (struct cpitem){.buf = buf, .bufsiz = BUFSIZ}; @@ -85,55 +77,52 @@ static size_t _pack_response(cp_pack_t pack, const struct rpcmsg_meta *meta) { assert(chainpack_unpack(f, &item) != -1); if (item.type == CPITEM_INVALID) break; - RES(cp_pack(pack, &item)); + G(cp_pack(pack, &item)); } fclose(f); } - RES(cp_pack_container_end(pack)); + G(cp_pack_container_end(pack)); - RES(cp_pack_imap_begin(pack)); - return res; + G(cp_pack_imap_begin(pack)); + return true; } -size_t rpcmsg_pack_response(cp_pack_t pack, const struct rpcmsg_meta *meta) { - size_t res = 0; - RES(_pack_response(pack, meta)); - RES(cp_pack_int(pack, RPCMSG_KEY_RESULT)); - return res; +bool rpcmsg_pack_response(cp_pack_t pack, const struct rpcmsg_meta *meta) { + G(_pack_response(pack, meta)); + G(cp_pack_int(pack, RPCMSG_KEY_RESULT)); + return true; } -size_t rpcmsg_pack_response_void(cp_pack_t pack, const struct rpcmsg_meta *meta) { - size_t res = 0; - RES(_pack_response(pack, meta)); - RES(cp_pack_container_end(pack)); - return res; +bool rpcmsg_pack_response_void(cp_pack_t pack, const struct rpcmsg_meta *meta) { + G(_pack_response(pack, meta)); + G(cp_pack_container_end(pack)); + return true; } -size_t rpcmsg_pack_error(cp_pack_t pack, const struct rpcmsg_meta *meta, +bool rpcmsg_pack_error(cp_pack_t pack, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *msg) { - size_t res = 0; - RES(_pack_response(pack, meta)); - RES(cp_pack_int(pack, RPCMSG_KEY_ERROR)); - RES(cp_pack_imap_begin(pack)); - RES(cp_pack_int(pack, RPCMSG_ERR_KEY_CODE)); - RES(cp_pack_int(pack, error)); - RES(cp_pack_int(pack, RPCMSG_ERR_KEY_MESSAGE)); - RES(cp_pack_str(pack, msg)); - RES(cp_pack_container_end(pack)); - RES(cp_pack_container_end(pack)); - return res; + G(_pack_response(pack, meta)); + G(cp_pack_int(pack, RPCMSG_KEY_ERROR)); + G(cp_pack_imap_begin(pack)); + G(cp_pack_int(pack, RPCMSG_ERR_KEY_CODE)); + G(cp_pack_int(pack, error)); + G(cp_pack_int(pack, RPCMSG_ERR_KEY_MESSAGE)); + G(cp_pack_str(pack, msg)); + G(cp_pack_container_end(pack)); + G(cp_pack_container_end(pack)); + return true; } -size_t rpcmsg_pack_ferror(cp_pack_t pack, const struct rpcmsg_meta *meta, +bool rpcmsg_pack_ferror(cp_pack_t pack, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, ...) { va_list args; va_start(args, fmt); - size_t res = rpcmsg_pack_vferror(pack, meta, error, fmt, args); + bool res = rpcmsg_pack_vferror(pack, meta, error, fmt, args); va_end(args); return res; } -size_t rpcmsg_pack_vferror(cp_pack_t pack, const struct rpcmsg_meta *meta, +bool rpcmsg_pack_vferror(cp_pack_t pack, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, va_list args) { va_list argsdup; va_copy(argsdup, args); diff --git a/tests/unit/libshvchainpack/cp_unpack.c b/tests/unit/libshvchainpack/cp_unpack.c index bbf5b81..4f602a7 100644 --- a/tests/unit/libshvchainpack/cp_unpack.c +++ b/tests/unit/libshvchainpack/cp_unpack.c @@ -17,7 +17,7 @@ TEST(unpack, chainpack_unpack_null) { struct bdata b = B(CPS_Null); cp_unpack_t unpack = unpack_chainpack(&b); struct cpitem item = (struct cpitem){}; - ck_assert_uint_eq(cp_unpack(unpack, &item), 1); + cp_unpack(unpack, &item); ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } @@ -26,7 +26,7 @@ TEST(unpack, cpon_unpack_null) { const char *str = "null"; cp_unpack_t unpack = unpack_cpon(str); struct cpitem item = (struct cpitem){}; - ck_assert_uint_eq(cp_unpack(unpack, &item), 4); + cp_unpack(unpack, &item); ck_assert_uint_eq(item.bufsiz, 0); unpack_free(unpack); } @@ -39,11 +39,11 @@ TEST(unpack, cpon_unpack_ctx_realloc) { struct cpitem item = (struct cpitem){}; for (int i = 0; i < 3; i++) { - ck_assert_uint_eq(cp_unpack(unpack, &item), 1); + cp_unpack(unpack, &item); ck_assert_int_eq(item.type, CPITEM_LIST); } for (int i = 0; i < 3; i++) { - ck_assert_uint_eq(cp_unpack(unpack, &item), 1); + cp_unpack(unpack, &item); ck_assert_int_eq(item.type, CPITEM_CONTAINER_END); } diff --git a/tests/unit/libshvrpc/rpcclient_login.c b/tests/unit/libshvrpc/rpcclient_login.c index a30bf06..8d1954e 100644 --- a/tests/unit/libshvrpc/rpcclient_login.c +++ b/tests/unit/libshvrpc/rpcclient_login.c @@ -54,6 +54,7 @@ TEST(all, invalid) { ck_assert(!rpcclient_login(c, &opts, &msg)); ck_assert_str_eq(msg, "Invalid login"); + free(msg); rpcclient_destroy(c); } END_TEST diff --git a/tests/unit/libshvrpc/strset.c b/tests/unit/libshvrpc/strset.c index 9d55a3d..36162d0 100644 --- a/tests/unit/libshvrpc/strset.c +++ b/tests/unit/libshvrpc/strset.c @@ -82,6 +82,8 @@ TEST(all, alphabet) { char *str; ck_assert_int_ne(asprintf(&str, "char:%c", c), -1); shv_strset_add_dyn(&set, str); + if (i > 0) + free(str); } ck_assert_int_eq(set.cnt, 'z' - 'a' + 1); From f63a93fc49502217c38ed2ee0e8d7c9f9d6d3afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Sat, 30 Sep 2023 18:16:59 +0200 Subject: [PATCH 31/44] Document rpchandler.h and tweak its API --- demo/device_handler.c | 12 +- flake.lock | 81 +++---- flake.nix | 2 +- include/shv/chainpack.h | 1 + include/shv/cp.h | 2 + include/shv/rpcclient.h | 224 +++++++++++++++++- include/shv/rpcclient_impl.h | 2 +- include/shv/rpchandler.h | 322 +++++++++++++++++++++++++- include/shv/rpcmsg.h | 139 +++++++---- include/shv/rpcurl.h | 1 + libshvrpc/libshvrpc.version | 3 +- libshvrpc/meson.build | 1 - libshvrpc/rpcclient_connect.c | 4 +- libshvrpc/rpcclient_datagram.c | 11 - libshvrpc/rpcclient_serial.c | 7 +- libshvrpc/rpcclient_stream.c | 4 +- libshvrpc/rpchandler.c | 61 ++--- libshvrpc/rpchandler_app.c | 9 +- libshvrpc/rpchandler_responses.c | 7 +- libshvrpc/rpcmsg_head.c | 2 +- tests/unit/libshvrpc/rpcclient_ping.c | 2 +- 21 files changed, 724 insertions(+), 173 deletions(-) delete mode 100644 libshvrpc/rpcclient_datagram.c diff --git a/demo/device_handler.c b/demo/device_handler.c index 16c7228..46c9ec8 100644 --- a/demo/device_handler.c +++ b/demo/device_handler.c @@ -36,14 +36,14 @@ static void rpc_dir(void *cookie, const char *path, struct rpchandler_dir_ctx *c } } -static enum rpchandler_func_res rpc_msg( +static bool rpc_msg( void *cookie, struct rpcreceive *receive, const struct rpcmsg_meta *meta) { struct device_state *state = cookie; int tid = trackid(meta->path); if (tid != -1) { if (!strcmp(meta->method, "get")) { if (!rpcreceive_validmsg(receive)) - return RPCHFR_RECV_ERR; + return true; cp_pack_t pack = rpcreceive_response_new(receive); rpcmsg_pack_response(pack, meta); cp_pack_list_begin(pack); @@ -52,7 +52,7 @@ static enum rpchandler_func_res rpc_msg( cp_pack_container_end(pack); cp_pack_container_end(pack); rpcreceive_response_send(receive); - return RPCHFR_HANDLED; + return true; } else if (!strcmp(meta->method, "set")) { size_t siz = 2; size_t cnt = 0; @@ -75,7 +75,7 @@ static enum rpchandler_func_res rpc_msg( } else invalid_param = true; if (!rpcreceive_validmsg(receive)) - return RPCHFR_RECV_ERR; + return true; cp_pack_t pack = rpcreceive_response_new(receive); if (invalid_param) @@ -88,10 +88,10 @@ static enum rpchandler_func_res rpc_msg( rpcmsg_pack_response_void(pack, meta); } rpcreceive_response_send(receive); - return RPCHFR_HANDLED; + return true; } } - return RPCHFR_UNHANDLED; + return false; } diff --git a/flake.lock b/flake.lock index 2eb12ba..f063d14 100644 --- a/flake.lock +++ b/flake.lock @@ -75,11 +75,11 @@ "systems": "systems_4" }, "locked": { - "lastModified": 1681202837, - "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", "owner": "numtide", "repo": "flake-utils", - "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", "type": "github" }, "original": { @@ -87,6 +87,27 @@ "type": "indirect" } }, + "libshv": { + "inputs": { + "flake-utils": "flake-utils_4", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1695376977, + "narHash": "sha256-EX5xv33ck0hIhy/0TNmusNV9iiSxbDWE+mWEiPYNSYs=", + "ref": "refs/heads/master", + "rev": "94b6fe9c8acc091e16a4fa8b189b6d437ff97fbb", + "revCount": 2254, + "submodules": true, + "type": "git", + "url": "https://github.com/silicon-heaven/libshv.git" + }, + "original": { + "submodules": true, + "type": "git", + "url": "https://github.com/silicon-heaven/libshv.git" + } + }, "nixpkgs": { "locked": { "lastModified": 1689752456, @@ -103,11 +124,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1694593561, - "narHash": "sha256-WSaIQZ5s9N9bDFkEMTw6P9eaZ9bv39ZhsiW12GtTNM0=", + "lastModified": 1695806987, + "narHash": "sha256-fX5kGs66NZIxCMcpAGIpxuftajHL8Hil1vjHmjjl118=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "1697b7d480449b01111e352021f46e5879e47643", + "rev": "f3dab3509afca932f3f4fd0908957709bb1c1f57", "type": "github" }, "original": { @@ -117,11 +138,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1694032533, - "narHash": "sha256-I8cfCV/4JNJJ8KHOTxTU1EphKT8ARSb4s9pq99prYV0=", + "lastModified": 1694948089, + "narHash": "sha256-d2B282GmQ9o8klc22/Rbbbj6r99EnELQpOQjWMyv0rU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "efd23a1c9ae8c574e2ca923c2b2dc336797f4cc4", + "rev": "5148520bfab61f99fd25fb9ff7bfbb50dad3c9db", "type": "github" }, "original": { @@ -131,11 +152,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1682109806, - "narHash": "sha256-d9g7RKNShMLboTWwukM+RObDWWpHKaqTYXB48clBWXI=", + "lastModified": 1694032533, + "narHash": "sha256-I8cfCV/4JNJJ8KHOTxTU1EphKT8ARSb4s9pq99prYV0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "2362848adf8def2866fabbffc50462e929d7fffb", + "rev": "efd23a1c9ae8c574e2ca923c2b2dc336797f4cc4", "type": "github" }, "original": { @@ -146,19 +167,20 @@ "pyshv": { "inputs": { "flake-utils": "flake-utils_3", - "nixpkgs": "nixpkgs_3", - "shvapp": "shvapp" + "libshv": "libshv", + "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1694672975, - "narHash": "sha256-itjeksAIk2faG+UDJ0jB/Q2LiA1FHKXrGDlGTHpoOis=", - "ref": "refs/heads/master", - "rev": "5a4961a735cc02f11388b3f149fd43b3523545bd", - "revCount": 98, + "lastModified": 1695928422, + "narHash": "sha256-1Y1yLY6e6CAE+cwmxyq8up/iV/KOswYDKoftpWB1GoU=", + "ref": "shv3", + "rev": "0f1fa8347bb783c71693a808cb177d8a9ac3d857", + "revCount": 108, "type": "git", "url": "https://gitlab.com/elektroline-predator/pyshv.git" }, "original": { + "ref": "shv3", "type": "git", "url": "https://gitlab.com/elektroline-predator/pyshv.git" } @@ -171,27 +193,6 @@ "pyshv": "pyshv" } }, - "shvapp": { - "inputs": { - "flake-utils": "flake-utils_4", - "nixpkgs": "nixpkgs_4" - }, - "locked": { - "lastModified": 1691081147, - "narHash": "sha256-K19rIrLxuB5xVy/ZEbNHeHnPapyyeMvH9bOv3pGuvn8=", - "ref": "refs/heads/master", - "rev": "e3154552a55d59be7d5c2426b92cd758e088444e", - "revCount": 2096, - "submodules": true, - "type": "git", - "url": "https://github.com/silicon-heaven/shvapp.git" - }, - "original": { - "submodules": true, - "type": "git", - "url": "https://github.com/silicon-heaven/shvapp.git" - } - }, "systems": { "locked": { "lastModified": 1681028828, diff --git a/flake.nix b/flake.nix index 0659b4f..2ebb650 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,7 @@ inputs = { check-suite.url = "github:cynerd/check-suite"; - pyshv.url = "git+https://gitlab.com/elektroline-predator/pyshv.git"; + pyshv.url = "git+https://gitlab.com/elektroline-predator/pyshv.git?ref=shv3"; }; outputs = { diff --git a/include/shv/chainpack.h b/include/shv/chainpack.h index 26ca7cf..eb9f915 100644 --- a/include/shv/chainpack.h +++ b/include/shv/chainpack.h @@ -16,6 +16,7 @@ _Static_assert(sizeof(unsigned long long) <= 17, "Limitation of the ChainPack"); +/*! Scheme bytes used in ChainPack. */ enum chainpack_scheme { CPS_Null = 128, CPS_UInt, diff --git a/include/shv/cp.h b/include/shv/cp.h index 1fc7cb2..bc959a5 100644 --- a/include/shv/cp.h +++ b/include/shv/cp.h @@ -365,7 +365,9 @@ struct cpitem { * This is aliased with @ref buf, @ref rbuf and @ref chr with union! */ const char *rchr; + // @cond }; + // @endcond /*! Size of the @ref buf (@ref rbuf, @ref chr and @ref rchr). This is used * only by unpacking functions to know the limit of the buffer. * diff --git a/include/shv/rpcclient.h b/include/shv/rpcclient.h index 725ed59..164a1de 100644 --- a/include/shv/rpcclient.h +++ b/include/shv/rpcclient.h @@ -30,46 +30,258 @@ typedef struct rpcclient *rpcclient_t; */ rpcclient_t rpcclient_connect(const struct rpcurl *url); +/*! Perform login on the newly established connection. + * + * @param client: The RPC client object. + * @param opts: RPC login options. It is common to get this directly from the + * RPC URL. + * @param errmsg: Pointer to the string pointer to which we optionally place the + * error message sent by the SHV Broker. Otherwise the pointer to the string + * is set to `NULL`. The error message is allocated using `malloc` and you + * must free it after use. You can pass `NULL` if you are not interested in + * error message at all. + * @returns `true` if login is successful and `false` otherwise. The login can + * fail either due to the communication error or because of SHV Broker + * declining the login for any reason. The difference can be detected with + * **errmsg** and @ref rpcclient_connected. + */ bool rpcclient_login(rpcclient_t client, const struct rpclogin_options *opts, char **errmsg) __attribute__((nonnull(1, 2))); +/*! Create new RPC Client with Stream transport layer. + * + * The transport layer description can be seen in the [official SHV + * documentation](https://silicon-heaven.github.io/shv-doc/rpctransportlayer.html#tcp--stream). + * + * @param readfd: File descriptor used for reading / receiving. + * @param writefd: File descriptor used for writing / sending. + * @returns New RPC Client object. To free it you need to use @ref + * rpcclient_destroy. + */ rpcclient_t rpcclient_stream_new(int readfd, int writefd); -rpcclient_t rpcclient_stream_tcp_connect(const char *location, int port); -rpcclient_t rpcclient_stream_unix_connect(const char *location); -rpcclient_t rpcclient_datagram_new(void); -rpcclient_t rpcclient_datagram_udp_connect(const char *location, int port); +/*! Establish a new TCP connection that uses Stream transport layer. + * + * @param location: Location of the TCP server. + * @param port: Port of the TCP server (commonly 3755). + * @returns New RPC Client object or `NULL` in case connection couldn't have + * been established. You can investigate the `errno` to identify why that + * might have been the case. + */ +rpcclient_t rpcclient_stream_tcp_connect(const char *location, int port) + __attribute__((nonnull)); +/*! Establish a new Unix socket connection that uses Stream transport layer. + * + * @param location: Location of the Unix socket. + * @returns New RPC Client object or `NULL` in case connection couldn't have + * been established. You can investigate the `errno` to identify why that + * might have been the case. + */ +rpcclient_t rpcclient_stream_unix_connect(const char *location) + __attribute__((nonnull)); + +/*! Create new RPC Client with Serial transport layer. + * + * The transport layer description can be seen in the [official SHV + * documentation](https://silicon-heaven.github.io/shv-doc/rpctransportlayer.html#rs232--serial). + * + * @param fd: File descriptor used to both read / receive and write / send + * messages. + * @returns New RPC Client object. To free it you need to use @ref + * rpcclient_destroy. + */ rpcclient_t rpcclient_serial_new(int fd); -rpcclient_t rpcclient_serial_connect(const char *path); + +/*! Establish a new connection over terminal device that uses Serial transport + * layer. + * + * This provides an easy way to open serial console and set it speed. The code + * changes the mode of the opened terminal device to raw and configures flow + * control. You can open terminal device on your own if you need a different + * configuration on the terminal device. + * + * @param path: Path to the terminal device. + * @param speed: Speed to be specified as the communication speed on the + * terminal device. + * @returns New RPC Client object or `NULL` in case terminal device open or + * configuration failed. You can investigate the `errno` to identify why that + * might have been the case. + */ +rpcclient_t rpcclient_serial_tty_connect(const char *path, unsigned speed) + __attribute__((nonnull)); + +/*! Establish a new Unix socket connection that uses Serial transport layer. + * + * The advantage of the Serial transport layer is that it sends data before they + * are all generated (because it doesn't have to know the complete message size + * to do so). That is beneficial in inter-process communication. Once sending + * process fills socket connection buffer the system can plan the receiving + * process without anyone of them actually having to keep the whole message in + * the memory. Operation systems do a good job to set an appropriate size of + * those buffers to either reduce the memory consumption or to increase the + * performance. + * + * @param location: Location of the Unix socket. + * @returns New RPC Client object or `NULL` in case connection couldn't have + * been established. You can investigate the `errno` to identify why that + * might have been the case. + */ +rpcclient_t rpcclient_serial_unix_connect(const char *location) + __attribute__((nonnull)); -#define rpcclient_destroy(CLIENT) ((CLIENT)->disconnect(CLIENT)) +/*! Perform disconnect and destroy the RPC client object. + * + * @param CLIENT: Pointer to the RPC client object. + */ +#define rpcclient_destroy(CLIENT) ((CLIENT)->destroy(CLIENT)) +/*! Check that client is still connected. + * + * This doesn't really check if connection is established. It instead checks if + * RPC client object thinks it is. No communication check happens here. + * + * @param CLIENT: The RPC client object. + * @returns `true` in case client seems to be connected and `false` otherwise. + */ #define rpcclient_connected(CLIENT) ((CLIENT)->rfd != -1) +/*! Wait for the next message to be ready for reading. + * + * Note that this only says that start of the new message was received. The + * unpack of the message can still block, because the complete message might + * not be received yet. This is done to allow stream processing of big messages. + * + * @param CLIENT: The RPC client object. + * @returns `true` if next message is received and `false` if client is no + * longer connected. + */ #define rpcclient_nextmsg(CLIENT) ((CLIENT)->msgfetch(CLIENT, true)) +/*! Finish reading and validate the received message. + * + * This should be called every time at the end of the message receive to + * validate the received data. Some transport layers validate messages only + * after they receive the complete message, not during the transport. The + * received bytes can be invalid but still be a valid packing format, or other + * side can decide to drop the already almost sent message and thus you should + * always parse the whole message and validate it before you proceed to act on + * the data received. + * + * You won't be able to unpack any more items from the message after you call + * this! + * + * @param CLIENT: The RPC client object. + * @returns `true` if received message was valid and `false` otherwise. Note + * that `false` can also mean client disconnect not just invalid message. + */ #define rpcclient_validmsg(CLIENT) ((CLIENT)->msgfetch(CLIENT, false)) +/*! Provides access to the general unpack handle for this client and message. + * + * The unpack is allowed to be changed by @ref rpcclient_nextmsg and @ref + * rpcclient_validmsg and thus make sure that you always refresh your own + * reference after you call those functions. + * + * Unpack always unpacks only a single message. The @ref CPERR_EOF error signals + * end of the message not end of the connection. + * + * @param CLIENT: The RPC client object. + * @returns @ref cp_unpack_t that can be used to unpack the received message. + */ #define rpcclient_unpack(CLIENT) (&(CLIENT)->unpack) +/*! Provides access to the general pack handle for this client. + * + * The pack is allowed to be changed by @ref rpcclient_sendmsg and @ref + * rpcclient_dropmsg, make sure that you always refresh your references after + * you call those functions. + * + * You can start packing a new message without any preliminary function. To + * really send message you need to call @ref rpcclient_sendmsg. + * + * @param CLIENT: The RPC client object. + * @returns @ref cp_pack_t that can be used to pack a message to be sent. + */ #define rpcclient_pack(CLIENT) (&(CLIENT)->pack) +/*! Send packed message. + * + * Based on the link layer implementation it is possible that some data were + * already sent and thus this is not really the complete send operation. It + * rather only confirms that sent data were correct. Without calling this the + * other side won't validate the message successfully, but it can already be + * processing it. You can consider this as a other side of the @ref + * rpcclient_validmsg. + * + * @param CLIENT: The RPC client object. + * @returns `true` if message sending was successful, `false` otherwise. The + * sending can fail only due to the disconnect. + */ #define rpcclient_sendmsg(CLIENT) ((CLIENT)->msgflush(CLIENT, true)) +/*! Drop packed message. + * + * This drops not yet sent message and invalidates partially sent one. You can + * use it instead of @ref rpcclient_sendmsg so you can start sending a different + * message instead. The primary use case is to replace the existing message with + * error that was detected when that message was being packed. + * + * Based on the transport layer this causes failure reported by @ref + * rpcclient_validmsg or it prevents from message to be even sent. + * + * @param CLIENT: The RPC client object. + * @returns `true` if message dropping was successful, `false` otherwise. The + * drop might need to send some bytes to inform other side about end of the + * message and this operation can as side effect detect the client disconnect. + */ #define rpcclient_dropmsg(CLIENT) ((CLIENT)->msgflush(CLIENT, false)) +/*! This provides access to the underlying file descriptor used for reading. + * + * This should be used only in poll operations. Do not use other operations on + * it because that might break internal state consistency of the RPC client. + * + * @param CLIENT: The RPC client object. + * @returns Integer with file descriptor or `-1` if client provides none or lost + * connection. + */ #define rpcclient_pollfd(CLIENT) ((CLIENT)->rfd) +/*! Provides access to the logger for this RPC client. + * + * This is implemented as macro and allows not only access but also change of + * the logger with syntax `rpcclient_logger(client) = logger;`. + * + * Be aware that log implementation is based on the locks and thus all logged + * clients under the same logger are serialized and only one client runs at a + * time. If you have a bit complex setup deadlocks can be easily encountered. + * + * @param CLIENT: The RPC client object. + * @returns @ref rpcclient_logger for the client. + */ #define rpcclient_logger(CLIENT) ((CLIENT)->logger) +/*! RPC Client logger handle. */ typedef struct rpcclient_logger *rpcclient_logger_t; +/*! Destroy the existing logger handle. + * + * @param logger: Logger handle. + */ void rpcclient_logger_destroy(rpcclient_logger_t logger); +/*! Create a new RPC Client logger handle. + * + * @param f: Stream used to output log lines. + * @param maxdepth: The output is in CPON format and this allows you to limit + * the maximum depth of containers you want to see in the logs. By specifying + * low enough number the logger can skip unnecessary data and still show you + * enough info about the message so you can recognize it. + */ rpcclient_logger_t rpcclient_logger_new(FILE *f, unsigned maxdepth) __attribute__((nonnull, malloc, malloc(rpcclient_logger_destroy))); diff --git a/include/shv/rpcclient_impl.h b/include/shv/rpcclient_impl.h index 9a00a5e..662e8eb 100644 --- a/include/shv/rpcclient_impl.h +++ b/include/shv/rpcclient_impl.h @@ -24,7 +24,7 @@ struct rpcclient { bool (*msgflush)(struct rpcclient *, bool send); struct timespec last_send; - void (*disconnect)(struct rpcclient *); + void (*destroy)(struct rpcclient *); struct rpcclient_logger *logger; }; diff --git a/include/shv/rpchandler.h b/include/shv/rpchandler.h index fba9e96..e4dac98 100644 --- a/include/shv/rpchandler.h +++ b/include/shv/rpchandler.h @@ -1,7 +1,12 @@ /* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCHANDLER_H #define SHV_RPCHANDLER_H +/* @file + * The RPC message handler that wraps RPC Client and allows asynchronous access + * for sending and receiving messages. + */ +#include "shv/cp.h" #include #include #include @@ -10,82 +15,334 @@ #include +/*! Handle passed to the message handling functions @ref rpchandler_funcs.msg. + * + * It provides access to functionality needed to receive message and optionally + * also to send single message back. The full access is intentionally limited + * to reduce the time it takes to receive message. If you need to pass some + * handle the message handling function then you might not be doing what you + * should. + */ struct rpcreceive { + /*! Unpack handle for reading parameter of the received message. */ cp_unpack_t unpack; + /*! Item to be used to read parameter of the received message. */ struct cpitem item; + /*! Obstack instance used to store values in **meta**. You are free to use + * it in the message handling function. All that is allocated in it will be + * freed after message is fully handled. + */ struct obstack obstack; }; -enum rpchandler_func_res { - RPCHFR_UNHANDLED, - RPCHFR_HANDLED, - RPCHFR_RECV_ERR, - RPCHFR_SEND_ERR, -}; - +/*! Handle passed to the ls handling functions @ref rpchandler_funcs.ls. */ struct rpchandler_ls_ctx; +/*! Handle passed to the dir handling functions @ref rpchandler_funcs.dir. */ struct rpchandler_dir_ctx; +/*! Pointers to the functions called by RPC Handler. + * + * This is set of functions needed by handler to handle messages. It is common + * that implementations define these as constant and provide pointer to it in + * @ref rpchandler_stage. + */ struct rpchandler_funcs { - enum rpchandler_func_res (*msg)(void *cookie, struct rpcreceive *receive, + /*! This is the primary function that is called to handle generic message. + * + * RPC Handler calls this for received requests in sequence given by stages + * array until some function returns `true`. + * + * This function must investigate **meta** to see if it should handle that + * message. It can immediately return `false` or it can proceed to handle + * it. It is prohibited to touch the **receive** if it returns `false`. + * + * The handling of the message consist of receiving the message parameters + * and validating the message. Handler is allowed to pack response or it + * needs to copy **meta** for sending response later on. It is not allowed + * to pack any other message to the RPC Client this handler manages to + * minimize the time handler is blocked. + * + * The message parameters are parsed with @ref rpcreceive.unpack and @ref + * rpcreceive.item. Before you invoke first @ref cp_unpack you must make + * sure that there is actually parameter to unpack with @ref + * rpcreceive_has_param (otherwise you can get unpack error even for valid + * message). Next you can use unpack functions to unpack parameters any way + * you wish. + * + * The received message needs to be validated after unpack with @ref + * rpcreceive_validmsg (internally uses @ref rpcclient_validmsg). + * + * If you encounter error on receive or sending then you need to still + * return `true`. The invalid messages are dropped this way (unpack error + * due to the invalid data) and communication error is recorded in RPC + * Client that RPC Handler uses. + */ + bool (*msg)(void *cookie, struct rpcreceive *receive, const struct rpcmsg_meta *meta); + /*! ls method implementation. + * + * RPC Handler handles all calls to the *ls* methods, It only needs to know + * nodes that are present on given path. This function serves that purpose. + * It is called every time ls request is being processed to fetch the nodes + * for the given path. + * + * The implementation needs to call @ref rpchandler_ls_result and variants + * of that function for every node in the requested path. + */ void (*ls)(void *cookie, const char *path, struct rpchandler_ls_ctx *ctx); + /*! dir method implementation. + * + * @ref rpchandler_destroy + * + * RPC Handler handles all calls to the *dir* methods, It only needs to know + * methods that are associated with node on the given path. This function + * serves that purpose. It is called every time dir request is being + * processed to fetch the method descriptions for the given path. + * + * The implementation needs to call @ref rpchandler_dir_result and variants + * of that function for every method on the requested path. + */ void (*dir)(void *cookie, const char *path, struct rpchandler_dir_ctx *ctx); }; +/*! Single stage in the RPC Handler. + * + * RPC Handler works with array of these stages and handler implementations + * commonly provide function returning this structure. + */ struct rpchandler_stage { + /*! Pointer to the @ref rpchandler_funcs with pointer to the function + * implementing the specific functionality. + */ const struct rpchandler_funcs *funcs; + /*! Cookie passed to the functions when they are called. It allows access to + * some context and thus function structure can be generic for multiple + * instances where only cookies are different. + */ void *cookie; }; +/*! RPC Handler object. + * + * Handler provides a wrapper for receiving messages from the connected peer and + * process them. + */ typedef struct rpchandler *rpchandler_t; +/*! Free all resources occupied by @ref rpchandler_t object. + * + * This is destructor for the object created by @ref rpchandler_new. + * + * The RPC Client that Handle manages is not destroyed nor disconnected. + * + * @param rpchandler: RPC Handler object. + */ void rpchandler_destroy(rpchandler_t rpchandler); +/*! Create new RPC message handle. + * + * @param client: RPC client to wrap in the handler. Make sure that you destroy + * the RPC handler before you destroy this client instance. + * @param stages: Handling stages that are used to manage messages received from + * this client. It is an array that is `NULL` terminated (the @ref + * rpchandler_stage.funcs is zero). Stages are referenced as they are and thus + * you need to keep this pointer valid for the duration of object existence + * (or you can replace it with @ref rpchandler_change_stages). + * @param limits: Limits used for parsing message heads. This is referenced as + * it is and this you need to keep this pointer valid for the duration of + * object existence. + * @returns New RPC handler instance. + */ rpchandler_t rpchandler_new(rpcclient_t client, const struct rpchandler_stage *stages, const struct rpcmsg_meta_limits *limits) - __attribute__((nonnull(1), malloc, malloc(rpchandler_destroy, 1))); - + __attribute__((nonnull(1, 2), malloc, malloc(rpchandler_destroy, 1))); + +/*! This allows you to change the current array of stages. + * + * You can change stages anytime you want but it will have no effect on the + * currently handled messages. The primary issue in multithreading with this is + * that you can't just free the previous stages array, you must wait for the + * current message to be processed and with @ref rpchandler_spawn_thread that is + * not easily known. If you are using loop with @ref rpchandler_next, then you + * can be sure that between calls to that function there is no message being + * processed. + * + * @param handler: RPC Handler instance. + * @param stages: Stages array to be used for now on with this handler. + */ void rpchandler_change_stages(rpchandler_t handler, const struct rpchandler_stage *stages) __attribute__((nonnull)); +/*! Handle next message. + * + * This blocks until the next message is received and fully handled. + * + * @param rpchandler: RPC Handler instance. + * @returns `true` if message handled (even by dropping) and `false` if + * connection error encountered in RPC Client. `false` pretty much means that + * loop calling repeatedly this should should terminate because we are no + * longer able to read messages. + */ bool rpchandler_next(rpchandler_t rpchandler) __attribute__((nonnull)); +/*! Spawn thread that repeatedly calls @ref rpchandler_next. + * + * This is the most easier way to get RPC Handler up and running. It is the + * suggested way unless you plan to use some poll based loop and multiple + * handlers. The single handler can live pretty easily in it this thread. + * + * @param rpchandler: RPC Handler instance. + * @param thread: Pointer to the variable where handle for the pthread is + * stored. You can use this to control thread. + * @param attr: Pointer to the pthread attributes or `NULL` for the inherited + * defaults. + * @returns Integer value returned from `pthread_create`. + */ int rpchandler_spawn_thread(rpchandler_t rpchandler, pthread_t *restrict thread, const pthread_attr_t *restrict attr) __attribute__((nonnull(1, 2))); +/*! Get next unused request ID. + * + * @param rpchandler: RPC Handler instance. + * @returns New request ID. + */ int rpchandler_next_request_id(rpchandler_t rpchandler) __attribute__((nonnull)); +/*! Start sending new message. + * + * This is used to send messages from multiple threads. It ensures that lock is + * taken and thus only one thread is packing message at the time. The lock is + * released either with @ref rpchandler_msg_send or with @ref + * rpchandler_msg_drop. + * + * @param rpchandler: RPC Handler instance. + * @returns Packer you need to use to pack message. + */ cp_pack_t rpchandler_msg_new(rpchandler_t rpchandler) __attribute__((nonnull)); +/*! Send the packed message. + * + * This calls @ref rpcclient_send under the hood and releases the lock taken by + * @ref rpchandler_msg_new. + * + * @param rpchandler: RPC Handler instance. + * @returns `true` if send was successful and `false` otherwise. + */ bool rpchandler_msg_send(rpchandler_t rpchandler) __attribute__((nonnull)); +/*! Drop the packed message. + * + * This calls @ref rpcclient_drop under the hood and releases the lock taken by + * @ref rpchandler_msg_new. + * + * @param rpchandler: RPC Handler instance. + * @returns `true` if send was successful and `false` otherwise. + */ bool rpchandler_msg_drop(rpchandler_t rpchandler) __attribute__((nonnull)); +/*! Query if received message has parameter to unpack. + * + * You should use this right before you start unpacking parameters to ensure + * that there are some. + * + * @param receive: Receive handle passed to @ref rpchandler_funcs.msg. + * @returns `true` if there is parameter to unpack and `false` if there are not. + */ +__attribute__((nonnull)) static inline bool rpcreceive_has_param( + struct rpcreceive *receive) { + return receive->item.type != CPITEM_CONTAINER_END; +} + +/*! Validate the received message. + * + * This calls @ref rpcclient_validmsg under the hood. You must call it to + * validate that message is valid. Do not act on the received data if this + * function returns `false`. + * + * @param receive: Receive handle passed to @ref rpchandler_funcs.msg. + * @returns `true` if message is valid and `false` otherwise. + */ bool rpcreceive_validmsg(struct rpcreceive *receive) __attribute__((nonnull)); +/*! Start packing response. + * + * Make sure that you call this only if you received request and only after you + * called @ref rpcreceive_validmsg. + * + * @param receive: Receive handle passed to @ref rpchandler_funcs.msg. + * @returns Packer used to pack response message. + */ cp_pack_t rpcreceive_response_new(struct rpcreceive *receive) __attribute__((nonnull)); +/*! Send packed response message. + * + * @param receive: Receive handle passed to @ref rpchandler_funcs.msg. + * @returns `true` if send is successful and `false` otherwise. + */ bool rpcreceive_response_send(struct rpcreceive *receive) __attribute__((nonnull)); +/*! Drop packed response message. + * + * You can pack a different message instead. + * + * @param receive: Receive handle passed to @ref rpchandler_funcs.msg. + * @returns `true` if send is successful and `false` otherwise. + */ bool rpcreceive_response_drop(struct rpcreceive *receive) __attribute__((nonnull)); +/*! Add result of `ls`. + * + * This is intended to be called only from @ref rpchandler_funcs.ls functions. + * + * The same **name** can be specified multiple times but it is added only once. + * The reason for this is because in the tree the same node might belong to + * multiple handlers and thus multiple handlers would report it and because they + * can't know about each other we just need to prevent from duplicates to be + * added. + * + * @param context: Context passed to the @ref rpchandler_funcs.ls. + * @param name: Name of the node. + */ void rpchandler_ls_result(struct rpchandler_ls_ctx *context, const char *name) __attribute__((nonnull)); +/*! The variant of the @ref rpchandler_ls_result with constant string. + * + * @ref rpchandler_dir_result needs to duplicate **name** to filter out + * duplicates. That is not required if string is constant or can be considered + * such for the time of ls method handling. This function simply takes pointer + * to the **name** and uses it internally in RPC Handler without copying it. + * + * @param context: Context passed to the @ref rpchandler_funcs.ls. + * @param name: Name of the node (must be pointer to memory that is kept valid + * for the duration of ls method handler). + */ void rpchandler_ls_result_const(struct rpchandler_ls_ctx *context, const char *name) __attribute__((nonnull)); +/*! The variant of the @ref rpchandler_ls_result with name generated from format + * string. + * + * @param context: Context passed to the @ref rpchandler_funcs.ls. + * @param fmt: Format string used to generate node name. + * @param args: List of variable arguments used in format string. + */ void rpchandler_ls_result_vfmt(struct rpchandler_ls_ctx *context, const char *fmt, va_list args) __attribute__((nonnull)); +/*! The variant of the @ref rpchandler_ls_result with name generated from format + * string. + * + * @param context: Context passed to the @ref rpchandler_funcs.ls. + * @param fmt: Format string used to generate node name. + */ __attribute__((nonnull)) static inline void rpchandler_ls_result_fmt( struct rpchandler_ls_ctx *context, const char *fmt, ...) { va_list args; @@ -94,13 +351,56 @@ __attribute__((nonnull)) static inline void rpchandler_ls_result_fmt( va_end(args); } +/*! Access name of the node ls should validate that it exists. + * + * There are two uses for call of *ls* method. One when all nodes on given path + * are requested and second where you use it to validate existence of the node + * in some path. The second usage can sometimes be optimised because you might + * not need to call @ref rpchandler_ls_result for every single node name when + * we are interested only in one of them. This function provides you that name. + * + * Note that if you plan to use it to compare against names one at a time then + * you can just use @ref rpchandler_ls_result that does exactly that. On the + * other hand if you have some more efficient way to detecting if node exists + * than you can speed up the process by using this function. + * + * @param context: Context passed to the @ref rpchandler_funcs.ls. + * @returns Pointer to the node name or `NULL` if all nodes should be listed. + */ const char *rpchandler_ls_name(struct rpchandler_ls_ctx *context) __attribute__((nonnull)); -bool rpchandler_dir_result(struct rpchandler_dir_ctx *context, const char *name, +/*! Add result of `dir`. + * + * This is intended to be called only from @ref rpchandler_funcs.dir functions. + * + * Compared to the @ref rpchandler_ls_result is not filtering duplicates, + * because handlers should not have duplicate methods between each other. There + * is also no need for function like @ref rpchandler_ls_result_const because + * **name** is used immediately without need to preserve it. + * + * @param context: Context passed to the @ref rpchandler_funcs.dir. + * @param name: Name of the method. + * @param signature: Signature the method has. + * @param flags: Combination of flags for the method. + * @param access: Minimal access level needed to access this method. + * @param description: Optional description of the method. + */ +void rpchandler_dir_result(struct rpchandler_dir_ctx *context, const char *name, enum rpcnode_dir_signature signature, int flags, enum rpcmsg_access access, const char *description) __attribute__((nonnull(1, 2))); +/*! Access name of the method dir should query info about. + * + * The same case as in @ref rpchandler_ls_name but instead of validating dir + * with method name is used to query info about a specific method. The + * effective use is the same: You can implement more efficient algorithm for + * searching for method based on the name and that way increase performance. + * + * @param context: Context passed to the @ref rpchandler_funcs.ls. + * @returns Pointer to the method name or `NULL` if all methods should be + * listed. + */ const char *rpchandler_dir_name(struct rpchandler_ls_ctx *context) __attribute__((nonnull)); diff --git a/include/shv/rpcmsg.h b/include/shv/rpcmsg.h index 53a6cd2..763a273 100644 --- a/include/shv/rpcmsg.h +++ b/include/shv/rpcmsg.h @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCMSG_H #define SHV_RPCMSG_H +/*! @file + * Functions to pack and unpack RPC messages. + */ #include #include @@ -9,33 +12,49 @@ #include #include +/*! Keys used in RPC message meta. */ enum rpcmsg_tags { - RPCMSG_TAG_UNKNOWN = 0, - RPCMSG_TAG_META_TYPE_ID, + /*! Identifier of the message type. */ + RPCMSG_TAG_META_TYPE_ID = 1, + /*! Identifier for the message namespace. */ RPCMSG_TAG_META_TYPE_NAMESPACE_ID, + /*! Request identifier used for request and response messages. */ RPCMSG_TAG_REQUEST_ID = 8, + /*! Path in the SHV tree to the specific node. */ RPCMSG_TAG_SHV_PATH, + /*! Method name associated with node on SHV path. */ RPCMSG_TAG_METHOD, + /*! Identifiers for callers. */ RPCMSG_TAG_CALLER_IDS, + /*! Identifiers for the reverse callers. */ RPCMSG_TAG_REV_CALLER_IDS, - RPCMSG_TAG_ACCESS_GRANT, - RPCMSG_TAG_USER_ID, + /*! Access level associated with this message. */ + RPCMSG_TAG_ACCESS, }; +/*! Keys used in RPC top level *IMap*. */ enum rpcmsg_keys { + /*! Parameter used for requests and signals. */ RPCMSG_KEY_PARAMS = 1, + /*! Result used for successful responses. */ RPCMSG_KEY_RESULT, + /*! Result used for failed responses. */ RPCMSG_KEY_ERROR, }; +/*! Keys used in RPC error *IMap*, that follows @ref RPCMSG_KEY_ERROR. */ enum rpcmsg_error_key { + /*! Must be followed by *Int* value specifying the error code (see @ref + * rpcmsg_error). + */ RPCMSG_ERR_KEY_CODE = 1, + /*! Message describing cause of the error. */ RPCMSG_ERR_KEY_MESSAGE, }; /*! Pack request message meta and open imap. The followup packed data are * parameters provided to the method call request. The message needs to be - * terminated with container end (`chainpack_pack_container_end`). + * terminated with container end (@ref cp_pack_container_end). * * @param pack: pack context the meta should be written to. * @param path: SHV path to the node the method we want to request is associated @@ -51,8 +70,8 @@ bool rpcmsg_pack_request(cp_pack_t pack, const char *path, const char *method, /*! Pack request message with no parameters. * * This provides an easy way to just pack message without arguments. It packs - * the same head as `rpcmsg_pack_request` plus additional `CONTAINER_END` to - * complete the message. Such message can be immediately sent. + * the same head as @ref rpcmsg_pack_request plus additional container end to + * complete the message. Such message can be immediately send. * * @param pack: pack context the meta should be written to. * @param path: SHV path to the node the method we want to request is associated @@ -67,7 +86,7 @@ bool rpcmsg_pack_request_void(cp_pack_t pack, const char *path, /*! Pack signal message meta and open imap. The followup packed data are * signaled values. The message needs to be terminated with container end - * (`chainpack_pack_container_end`). + * (@ref cp_pack_container_end). * * @param pack: pack context the meta should be written to. * @param path: SHV path to the node method is associated with. @@ -79,9 +98,9 @@ bool rpcmsg_pack_signal(cp_pack_t pack, const char *path, const char *method) /*! Pack value change signal message meta and open imap. The followup packed * data are signaled values. The message needs to be terminated with container - * end (`chainpack_pack_container_end`). + * end (@ref cp_pack_container_end). * - * This is actually just a convenient way to call `rpcmsg_pack_signal` for + * This is actually just a convenient way to call @ref rpcmsg_pack_signal for * "chng" methods. * * @param pack: pack context the meta should be written to. @@ -127,8 +146,8 @@ enum rpcmsg_access rpcmsg_access_extract(const char *str); * You can detect unpack error by checking `item->type != CP_ITEM_INVALID`. * * @param unpack: Unpack handle. - * @param item: Item used for the `cp_unpack` calls and was used in the last - * one. + * @param item: Item used for the @ref cp_unpack calls and was used in the last + * one. * @returns Access level. */ enum rpcmsg_access rpcmsg_access_unpack(cp_unpack_t unpack, struct cpitem *item); @@ -151,13 +170,15 @@ enum rpcmsg_type { /*! Pointer to data (`ptr`) with `siz` number of valid bytes. */ struct rpcmsg_ptr { + /*! Pointer to the data. */ void *ptr; + /*! Number of valid bytes in @ref ptr */ size_t siz; }; /*! Parsed content of RPC Message Meta. * - * Parsing is performed with `rpcmsg_meta_unpack`. + * Parsing is performed with @ref rpcmsg_head_unpack. */ struct rpcmsg_meta { /*! Type of the message. */ @@ -173,29 +194,35 @@ struct rpcmsg_meta { /*! Client IDs in Chainpack if message is request. */ struct rpcmsg_ptr cids; + /*! Additional extra fields found in meta as linked list. */ struct rpcmsg_meta_extra { + /*! Integer key this value had in meta. */ int key; + /*! Pointer and size of the value. */ struct rpcmsg_ptr ptr; + /*! Next meta or `NULL` if this is the last one. */ struct rpcmsg_meta_extra *next; - } * extra; + } + /*! Pointer to the first extra meta or `NULL`. */ + * extra; }; -/*! Limits imposed on `struct rpcmsg_meta` fields. +/*! Limits imposed on @ref rpcmsg_meta fields. * - * This is used by `rpcmsg_meta_unpack` in two modes: It provides size of the - * buffers pointed in `struct rpcmsg_meta`; It provides limit on dynamically + * This is used by @ref rpcmsg_head_unpack in two modes: It provides size of the + * buffers pointed in @ref rpcmsg_meta; It provides limit on dynamically * allocated buffers. * * The dynamic allocation can be constrained or unconstrained. The default - * behavior if you pass no limits to the `rpcmsg_meta_unpack` is to use as much - * space as required. Not only that this results in possible crash if message - * with too big fields is received, it is primarily not necessary. The devices - * commonly know how long paths and methods they support and anything longer is - * invalid anyway and thus in most cases it servers no purpose to store actually - * the whole value. + * behavior if you pass no limits to the @ref rpcmsg_head_unpack is to use as + * much space as required. Not only that this results in possible crash if + * message with too big fields is received, it is primarily not necessary. The + * devices commonly know how long paths and methods they support and anything + * longer is invalid anyway and thus in most cases it servers no purpose to + * store actually the whole value. * * To allow constrained allocation for some of the fields but not for other you - * can use `-1` as limit which informs `rpcmsg_meta_unpack` that there is no + * can use `-1` as limit which informs @ref rpcmsg_head_unpack that there is no * limit for that field. */ struct rpcmsg_meta_limits { @@ -234,28 +261,28 @@ struct rpcmsg_meta_limits { * * The unpacking can be performed in two different modes. You can either provide * obstack and in such case data would be pushed to it (highly suggested). But - * if you know all limits upfront you can also just pass `NULL` to `obstack` and - * in such case we expect that provided `meta` is already seeded with buffers of - * sizes specified in `limits`. + * if you know all limits upfront you can also just pass `NULL` to **obstack** + * and in such case we expect that provided **meta** is already seeded with + * buffers of sizes specified in **limits**. * - * WARNING: The limits when `obstack` is provided are not enforced right now. + * WARNING: The limits when **obstack** is provided are not enforced right now. * This is missing implementation not intended state! * * @param unpack: Unpack handle. - * @param item: Item used for the `cp_unpack` calls and was used in the last + * @param item: Item used for the @ref cp_unpack calls and was used in the last + * one. * @param meta: Pointer to the structure where unpacked data will be placed. In * case of integers the value is directly stored. For strings and byte arrays * the data are stored on obstack, if provided, and meta contains pointers to - * this data. If you do not provide `obstack` then pointers in the meta need - * to point to valid buffers. + * this data. If you do not provide **obstack** then pointers in the meta need + * to point to valid buffers. * @param limits: Optional pointer to the limits imposed on the meta attributes. * This is used for two purposes: It specifies size of the buffers if you do - * not provide `obstack; It limits maximal length for data copied to the - * obstack. Please see `struct rpcmsg_meta_limits` documentation for - * explanation. - * @param obstack: pointer to the obstack used to allocate space for received + * not provide **obstack** It limits maximal length for data copied to the + * obstack. Please see @ref rpcmsg_meta_limits for explanation. + * @param obstack: Pointer to the obstack used to allocate space for received * strings and byte arrays. You can pass `NULL` but in such case you need to - * provide your own buffers in `meta`. + * provide your own buffers in **meta**. * @returns `false` in case unpack reports error, if meta contains invalid value * for supported key, or when there is not enough space to store caller IDs. * In other cases `true` is returned. In short this return primarily signals @@ -287,20 +314,36 @@ bool rpcmsg_pack_response(cp_pack_t pack, const struct rpcmsg_meta *meta) * @param meta: Meta with info for previously received request. * @returns Boolean signaling the pack success or failure. */ -bool rpcmsg_pack_response_void(cp_pack_t, const struct rpcmsg_meta *meta) +bool rpcmsg_pack_response_void(cp_pack_t pack, const struct rpcmsg_meta *meta) __attribute__((nonnull)); +/*! Known error codes defined in SHV. */ enum rpcmsg_error { + /*! No error that is mostly internally used to identify no errors. */ RPCMSG_E_NO_ERROR, + /*! Sent request is invalid in its format. */ RPCMSG_E_INVALID_REQUEST, + /*! Method is can't be found or you do not have access to it. */ RPCMSG_E_METHOD_NOT_FOUND, + /*! Request received but with invalid parameters. */ RPCMSG_E_INVALID_PARAMS, + /*! Request can't be processes due to the internal error. */ RPCMSG_E_INTERNAL_ERR, + /*! Message content can't be parsed correctly. */ RPCMSG_E_PARSE_ERR, + /*! Request timed out without response. */ RPCMSG_E_METHOD_CALL_TIMEOUT, + /*! Request got canceled. */ RPCMSG_E_METHOD_CALL_CANCELLED, + /*! Request was received successfully but issue was encountered when it was + * being acted upon it. + */ RPCMSG_E_METHOD_CALL_EXCEPTION, + /*! Generic unknown error assigned when we are not aware what happened. */ RPCMSG_E_UNKNOWN, + /*! The first user defined error code. The lower values are reserved for the + * SHV defined errors. + */ RPCMSG_E_USER_CODE = 32, }; @@ -315,7 +358,7 @@ enum rpcmsg_error { * @param msg: Optional message describing the error details. * @returns Boolean signaling the pack success or failure. */ -bool rpcmsg_pack_error(cp_pack_t, const struct rpcmsg_meta *meta, +bool rpcmsg_pack_error(cp_pack_t pack, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *msg) __attribute__((nonnull(1, 2))); /*! Pack error response message based on the provided meta. @@ -330,7 +373,7 @@ bool rpcmsg_pack_error(cp_pack_t, const struct rpcmsg_meta *meta, * @param fmt: Format string used to generate the error message. * @returns Boolean signaling the pack success or failure. */ -bool rpcmsg_pack_ferror(cp_pack_t, const struct rpcmsg_meta *meta, +bool rpcmsg_pack_ferror(cp_pack_t pack, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, ...) __attribute__((nonnull(1, 2))); /*! Pack error response message based on the provided meta. @@ -346,10 +389,26 @@ bool rpcmsg_pack_ferror(cp_pack_t, const struct rpcmsg_meta *meta, * @param args: Variable list of arguments to be used with **fmt**. * @returns Boolean signaling the pack success or failure. */ -bool rpcmsg_pack_vferror(cp_pack_t, const struct rpcmsg_meta *meta, +bool rpcmsg_pack_vferror(cp_pack_t pack, const struct rpcmsg_meta *meta, enum rpcmsg_error error, const char *fmt, va_list args) __attribute__((nonnull(1, 2))); +/*! Unpack error. + * + * This must be called right after @ref rpcmsg_head_unpack to correctly unpack + * error. + * + * @param unpack: Unpack handle. + * @param item: Item used for the @ref cp_unpack calls and was used in the last + * one. + * @param errnum: Pointer to the variable where error number is placed. Can be + * `NULL` if you are not interested in the error code. + * @param errmsg: Pointer to the variable where error message is placed. Error + * message is copied to the memory allocated using malloc and thus do not + * forget to free it. You can pass `NULL` if you are not interested in the + * error message. + * @returns `true` if error was unpacked correctly, `false` otherwise. + */ bool rpcmsg_unpack_error(cp_unpack_t unpack, struct cpitem *item, enum rpcmsg_error *errnum, char **errmsg) __attribute__((nonnull(1, 2))); diff --git a/include/shv/rpcurl.h b/include/shv/rpcurl.h index 192b0e6..8c2d292 100644 --- a/include/shv/rpcurl.h +++ b/include/shv/rpcurl.h @@ -38,6 +38,7 @@ enum rpclogin_type { RPC_LOGIN_SHA1, }; +/*! Options used for login to the RPC Broker. */ struct rpclogin_options { /*! User name used to login client connection to the server */ const char *username; diff --git a/libshvrpc/libshvrpc.version b/libshvrpc/libshvrpc.version index 36dfa0f..4c2625c 100644 --- a/libshvrpc/libshvrpc.version +++ b/libshvrpc/libshvrpc.version @@ -24,7 +24,8 @@ V0.0 { rpcclient_datagram_new; rpcclient_datagram_udp_connect; rpcclient_serial_new; - rpcclient_serial_connect; + rpcclient_serial_tty_connect; + rpcclient_serial_unix_connect; rpcclient_logger_new; rpcclient_logger_destroy; diff --git a/libshvrpc/meson.build b/libshvrpc/meson.build index f2c4a28..34aa08f 100644 --- a/libshvrpc/meson.build +++ b/libshvrpc/meson.build @@ -1,7 +1,6 @@ libshvrpc_sources = [ files( 'rpcclient_connect.c', - 'rpcclient_datagram.c', 'rpcclient_log.c', 'rpcclient_login.c', 'rpcclient_serial.c', diff --git a/libshvrpc/rpcclient_connect.c b/libshvrpc/rpcclient_connect.c index 75a96e0..dc4ed5e 100644 --- a/libshvrpc/rpcclient_connect.c +++ b/libshvrpc/rpcclient_connect.c @@ -8,10 +8,8 @@ struct rpcclient *rpcclient_connect(const struct rpcurl *url) { return rpcclient_stream_tcp_connect(url->location, url->port); case RPC_PROTOCOL_LOCAL_SOCKET: return rpcclient_stream_unix_connect(url->location); - case RPC_PROTOCOL_UDP: - return rpcclient_datagram_udp_connect(url->location, url->port); case RPC_PROTOCOL_SERIAL_PORT: - return rpcclient_serial_connect(url->location); + return rpcclient_serial_tty_connect(url->location, 115200); default: abort(); }; diff --git a/libshvrpc/rpcclient_datagram.c b/libshvrpc/rpcclient_datagram.c deleted file mode 100644 index c71c482..0000000 --- a/libshvrpc/rpcclient_datagram.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -struct rpcclient *rpcclient_datagram_new(void) { - // TODO - return NULL; -} - -rpcclient_t rpcclient_datagram_udp_connect(const char *location, int port) { - // TODO - return NULL; -} diff --git a/libshvrpc/rpcclient_serial.c b/libshvrpc/rpcclient_serial.c index 271abbe..10a95f1 100644 --- a/libshvrpc/rpcclient_serial.c +++ b/libshvrpc/rpcclient_serial.c @@ -6,7 +6,12 @@ struct rpcclient *rpcclient_new_serial(int fd) { return NULL; } -rpcclient_t rpcclient_serial_connect(const char *path) { +rpcclient_t rpcclient_serial_tty_connect(const char *path, unsigned speed) { + // TODO + return NULL; +} + +rpcclient_t rpcclient_serial_unix_connect(const char *location) { // TODO return NULL; } diff --git a/libshvrpc/rpcclient_stream.c b/libshvrpc/rpcclient_stream.c index b875e90..b6d88e5 100644 --- a/libshvrpc/rpcclient_stream.c +++ b/libshvrpc/rpcclient_stream.c @@ -152,7 +152,7 @@ static bool msgflush_stream(struct rpcclient *client, bool send) { return true; } -static void disconnect(rpcclient_t client) { +static void destroy(rpcclient_t client) { struct rpcclient_stream *c = (struct rpcclient_stream *)client; fclose(c->rf); fclose(c->fbuf); @@ -176,7 +176,7 @@ rpcclient_t rpcclient_stream_new(int readfd, int writefd) { .msgfetch = msgfetch_stream, .pack = cp_pack_stream, .msgflush = msgflush_stream, - .disconnect = disconnect, + .destroy = destroy, .logger = NULL, }, .rf = f, diff --git a/libshvrpc/rpchandler.c b/libshvrpc/rpchandler.c index 4841dbe..4141798 100644 --- a/libshvrpc/rpchandler.c +++ b/libshvrpc/rpchandler.c @@ -101,7 +101,7 @@ static bool parse_ls_dir(struct rpcreceive *recv, struct rpcmsg_meta *meta, if (recv->item.type == CPITEM_INVALID && recv->item.as.Error == CPERR_IO) return false; if (!rpcreceive_validmsg(recv)) - return true; + return false; cp_pack_t pack = rpcreceive_response_new(recv); if (invalid_param) { @@ -114,27 +114,22 @@ static bool parse_ls_dir(struct rpcreceive *recv, struct rpcmsg_meta *meta, return true; } -static bool handle_ls(const struct rpchandler_stage *stages, +static void handle_ls(const struct rpchandler_stage *stages, struct rpcreceive *recv, struct rpcmsg_meta *meta) { struct rpchandler_ls_ctx ctx; - printf("Wtf\n"); if (!parse_ls_dir(recv, meta, &ctx.x)) - return false; - if (ctx.x.pack == NULL) - return true; + return; ctx.x.located = false; ctx.strset = (struct strset){}; for (const struct rpchandler_stage *s = stages; s->funcs; s++) if (s->funcs->ls) s->funcs->ls(s->cookie, meta->path, &ctx); - printf("Before response pack\n"); + // TODO when empty we need to validate that path exists rpcmsg_pack_response(ctx.x.pack, meta); - printf("Before packing\n"); if (ctx.x.name == NULL) { cp_pack_list_begin(ctx.x.pack); for (size_t i = 0; i < ctx.strset.cnt; i++) { - printf("Packing %s\n", ctx.strset.items[i].str); cp_pack_str(ctx.x.pack, ctx.strset.items[i].str); } cp_pack_container_end(ctx.x.pack); @@ -143,14 +138,13 @@ static bool handle_ls(const struct rpchandler_stage *stages, cp_pack_bool(ctx.x.pack, ctx.x.located); cp_pack_container_end(ctx.x.pack); rpcreceive_response_send(recv); - return true; } -static bool handle_dir(const struct rpchandler_stage *stages, +static void handle_dir(const struct rpchandler_stage *stages, struct rpcreceive *recv, struct rpcmsg_meta *meta) { struct rpchandler_dir_ctx ctx; if (!parse_ls_dir(recv, meta, &ctx.x)) - return false; + return; ctx.x.located = false; rpcmsg_pack_response(ctx.x.pack, meta); @@ -169,35 +163,28 @@ static bool handle_dir(const struct rpchandler_stage *stages, cp_pack_null(ctx.x.pack); cp_pack_container_end(ctx.x.pack); rpcreceive_response_send(recv); - return true; } -static bool handle_msg(const struct rpchandler_stage *stages, +static void handle_msg(const struct rpchandler_stage *stages, struct rpcreceive *recv, struct rpcmsg_meta *meta) { - enum rpchandler_func_res fres = RPCHFR_UNHANDLED; - for (const struct rpchandler_stage *s = stages; - s->funcs && fres == RPCHFR_UNHANDLED; s++) - fres = s->funcs->msg(s->cookie, recv, meta); - if (fres == RPCHFR_UNHANDLED) { + const struct rpchandler_stage *s = stages; + while (s->funcs && !s->funcs->msg(s->cookie, recv, meta)) + s++; + if (s->funcs == NULL && meta->type == RPCMSG_T_REQUEST) { /* Default handler for requests, other types are dropped. */ if (!rpcreceive_validmsg(recv)) - return false; - if (meta->type == RPCMSG_T_REQUEST) { - cp_pack_t pack = rpcreceive_response_new(recv); - rpcmsg_pack_ferror(pack, meta, RPCMSG_E_METHOD_NOT_FOUND, - "No such method '%s' on path '%s'", meta->method, meta->path); - rpcreceive_response_send(recv); - } + return; + cp_pack_t pack = rpcreceive_response_new(recv); + rpcmsg_pack_ferror(pack, meta, RPCMSG_E_METHOD_NOT_FOUND, + "No such method '%s' on path '%s'", meta->method, meta->path); + rpcreceive_response_send(recv); } - // TODO handle other fres - return true; } bool rpchandler_next(struct rpchandler *rpchandler) { if (!(rpcclient_nextmsg(rpchandler->client))) return false; - bool res = false; void *obs_base = obstack_base(&rpchandler->recv.obstack); cpitem_unpack_init(&rpchandler->recv.item); struct rpcmsg_meta meta; @@ -206,15 +193,15 @@ bool rpchandler_next(struct rpchandler *rpchandler) { rpchandler->recv.unpack = rpcclient_unpack(rpchandler->client); rpchandler->can_respond = meta.type == RPCMSG_T_REQUEST; if (!strcmp(meta.method, "ls")) - res = handle_ls(rpchandler->stages, &rpchandler->recv, &meta); + handle_ls(rpchandler->stages, &rpchandler->recv, &meta); else if (!strcmp(meta.method, "dir")) - res = handle_dir(rpchandler->stages, &rpchandler->recv, &meta); + handle_dir(rpchandler->stages, &rpchandler->recv, &meta); else - res = handle_msg(rpchandler->stages, &rpchandler->recv, &meta); + handle_msg(rpchandler->stages, &rpchandler->recv, &meta); } obstack_free(&rpchandler->recv.obstack, obs_base); - return res; + return rpcclient_connected(rpchandler->client); } static void *thread_loop(void *ctx) { @@ -295,12 +282,10 @@ bool rpcreceive_response_drop(struct rpcreceive *receive) { } void rpchandler_ls_result(struct rpchandler_ls_ctx *ctx, const char *name) { - printf("LS with %s\n", name); if (ctx->x.name) ctx->x.located = !strcmp(ctx->x.name, name); else shv_strset_add(&ctx->strset, name); - printf("LS with %s done\n", name); } void rpchandler_ls_const(struct rpchandler_ls_ctx *ctx, const char *name) { @@ -331,12 +316,12 @@ void rpchandler_ls_result_vfmt( } } -bool rpchandler_dir_result(struct rpchandler_dir_ctx *ctx, const char *name, +void rpchandler_dir_result(struct rpchandler_dir_ctx *ctx, const char *name, enum rpcnode_dir_signature signature, int flags, enum rpcmsg_access access, const char *description) { if (ctx->x.name) { if (strcmp(ctx->x.name, name)) - return true; + return; ctx->x.located = true; } cp_pack_map_begin(ctx->x.pack); @@ -353,6 +338,4 @@ bool rpchandler_dir_result(struct rpchandler_dir_ctx *ctx, const char *name, cp_pack_str(ctx->x.pack, description); } cp_pack_container_end(ctx->x.pack); - // TODO error? - return true; } diff --git a/libshvrpc/rpchandler_app.c b/libshvrpc/rpchandler_app.c index c1e0785..30de8f8 100644 --- a/libshvrpc/rpchandler_app.c +++ b/libshvrpc/rpchandler_app.c @@ -26,7 +26,7 @@ static void rpc_dir(void *cookie, const char *path, struct rpchandler_dir_ctx *c } } -static enum rpchandler_func_res rpc_msg( +static bool rpc_msg( void *cookie, struct rpcreceive *receive, const struct rpcmsg_meta *meta) { struct rpchandler_app *rpchandler_app = cookie; enum methods { @@ -47,10 +47,11 @@ static enum rpchandler_func_res rpc_msg( method = APP_VERSION; } if (method == UNKNOWN) - return RPCHFR_UNHANDLED; + return false; + // TODO possibly be pedantic about not having parameter if (!rpcreceive_validmsg(receive)) - return RPCHFR_RECV_ERR; + return true; cp_pack_t pack = rpcreceive_response_new(receive); rpcmsg_pack_response(pack, meta); switch (method) { @@ -71,7 +72,7 @@ static enum rpchandler_func_res rpc_msg( } cp_pack_container_end(pack); rpcreceive_response_send(receive); - return RPCHFR_HANDLED; + return true; } const struct rpchandler_funcs rpc_funcs = { diff --git a/libshvrpc/rpchandler_responses.c b/libshvrpc/rpchandler_responses.c index fb47788..63f24bc 100644 --- a/libshvrpc/rpchandler_responses.c +++ b/libshvrpc/rpchandler_responses.c @@ -15,12 +15,11 @@ struct rpchandler_responses { pthread_mutex_t lock; }; -static enum rpchandler_func_res rpc_msg( +static bool rpc_msg( void *cookie, struct rpcreceive *receive, const struct rpcmsg_meta *meta) { struct rpchandler_responses *resp = cookie; if (meta->type != RPCMSG_T_RESPONSE && meta->type != RPCMSG_T_ERROR) - return RPCHFR_UNHANDLED; - enum rpchandler_func_res res = RPCHFR_HANDLED; + return false; pthread_mutex_lock(&resp->lock); rpcresponse_t pr = NULL; rpcresponse_t r = resp->resp; @@ -43,7 +42,7 @@ static enum rpchandler_func_res rpc_msg( r = r->next; }; pthread_mutex_unlock(&resp->lock); - return res; + return true; } static struct rpchandler_funcs rpc_funcs = {.msg = rpc_msg}; diff --git a/libshvrpc/rpcmsg_head.c b/libshvrpc/rpcmsg_head.c index 796e2a9..2bad740 100644 --- a/libshvrpc/rpcmsg_head.c +++ b/libshvrpc/rpcmsg_head.c @@ -142,7 +142,7 @@ bool rpcmsg_head_unpack(cp_unpack_t unpack, struct cpitem *item, valid_cids = unpack_cids(unpack, item, &meta->cids, limits ? limits->cids : -1, obstack); break; - case RPCMSG_TAG_ACCESS_GRANT: + case RPCMSG_TAG_ACCESS: meta->access_grant = rpcmsg_access_unpack(unpack, item); break; default: diff --git a/tests/unit/libshvrpc/rpcclient_ping.c b/tests/unit/libshvrpc/rpcclient_ping.c index 0058f6d..0794f56 100644 --- a/tests/unit/libshvrpc/rpcclient_ping.c +++ b/tests/unit/libshvrpc/rpcclient_ping.c @@ -7,7 +7,7 @@ #include void rpcclient_ping_test(rpcclient_t c) { - rpcmsg_pack_request_void(rpcclient_pack(c), ".broker/app", "ping", 3); + rpcmsg_pack_request_void(rpcclient_pack(c), ".app", "ping", 3); ck_assert(rpcclient_sendmsg(c)); struct obstack obs; From e82358e6938c0e924d408102e92d5a6f1a153bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karel=20Ko=C4=8D=C3=AD?= Date: Tue, 3 Oct 2023 14:49:52 +0200 Subject: [PATCH 32/44] Change how RPC Client works and better integrate to RPC Handler --- demo/main.c | 42 ++--- docs/api/libshvrpc.md | 1 + docs/api/rpcclient.md | 11 -- docs/api/rpchandler.md | 2 +- docs/api/rpclogger.md | 8 + include/meson.build | 2 +- include/shv/cp_unpack.h | 8 +- include/shv/rpcbroker.h | 15 +- include/shv/rpcclient.h | 182 ++++++++++++++------- include/shv/rpcclient_impl.h | 49 ------ include/shv/rpchandler.h | 76 ++++++--- include/shv/rpchandler_app.h | 46 +++++- include/shv/rpchandler_responses.h | 70 +++++++- include/shv/rpclogger.h | 76 +++++++++ libshvrpc/libshvrpc.version | 14 +- libshvrpc/meson.build | 2 +- libshvrpc/rpcclient_stream.c | 170 +++++++++++-------- libshvrpc/rpchandler.c | 106 +++++++++--- libshvrpc/{rpcclient_log.c => rpclogger.c} | 18 +- shvc/main.c | 101 +++++++++++- shvc/opts.c | 27 +-- shvc/opts.h | 1 + 22 files changed, 717 insertions(+), 310 deletions(-) create mode 100644 docs/api/rpclogger.md delete mode 100644 include/shv/rpcclient_impl.h create mode 100644 include/shv/rpclogger.h rename libshvrpc/{rpcclient_log.c => rpclogger.c} (79%) diff --git a/demo/main.c b/demo/main.c index 4ae348b..26e1f3a 100644 --- a/demo/main.c +++ b/demo/main.c @@ -30,9 +30,9 @@ int main(int argc, char **argv) { rpcurl_free(rpcurl); return 1; } - rpcclient_logger_t logger = NULL; + rpclogger_t logger = NULL; if (conf.verbose > 0) { - logger = rpcclient_logger_new(stderr, conf.verbose); + logger = rpclogger_new(stderr, conf.verbose); client->logger = logger; } char *loginerr; @@ -53,40 +53,22 @@ int main(int argc, char **argv) { rpchandler_app_t app = rpchandler_app_new("demo-device", PROJECT_VERSION); - struct rpchandler_stage stages[4]; - stages[0] = rpchandler_app_stage(app); - stages[1] = (struct rpchandler_stage){ - .funcs = &device_handler_funcs, .cookie = state}; - stages[2] = (struct rpchandler_stage){}; - + const struct rpchandler_stage stages[] = { + rpchandler_app_stage(app), + (struct rpchandler_stage){.funcs = &device_handler_funcs, .cookie = state}, + {}, + }; rpchandler_t handler = rpchandler_new(client, stages, NULL); assert(handler); - int res = 0; - struct pollfd pfd = {.fd = client->rfd, .events = POLLIN | POLLHUP}; - while (true) { - int pr = poll( - &pfd, 1, rpcclient_maxsleep(client, RPC_DEFAULT_IDLE_TIME) * 1000); - if (pr == 0) { - cp_pack_t pack = rpchandler_msg_new(handler); - rpcmsg_pack_request_void(pack, ".app", "ping", 0); - rpchandler_msg_send(handler); - } else if (pr == -1 || pfd.revents & POLLERR) { - fprintf(stderr, "Poll error: %s\n", strerror(errno)); - res = 1; - break; - } else if (pfd.revents & POLLIN) { - rpchandler_next(handler); - } else if (pfd.revents & POLLHUP) { - fprintf(stderr, "Disconnected from the broker\n"); - break; - } - } + rpchandler_run(handler, NULL); + printf("Terminating due to: %s\n", strerror(rpcclient_errno(client))); rpchandler_destroy(handler); rpchandler_app_destroy(app); device_state_free(state); rpcclient_destroy(client); - rpcclient_logger_destroy(logger); - return res; + if (conf.verbose > 0) + rpclogger_destroy(logger); + return 0; } diff --git a/docs/api/libshvrpc.md b/docs/api/libshvrpc.md index 15da854..52d6a03 100644 --- a/docs/api/libshvrpc.md +++ b/docs/api/libshvrpc.md @@ -7,6 +7,7 @@ This is API provided by libshvrpc library. rpcclient rpcserver +rpclogger rpcurl rpcmsg rpchandler diff --git a/docs/api/rpcclient.md b/docs/api/rpcclient.md index f69c87b..37d969d 100644 --- a/docs/api/rpcclient.md +++ b/docs/api/rpcclient.md @@ -1,19 +1,8 @@ # RPC Client -## Usage - ```c #include ``` ```{autodoxygenfile} shv/rpcclient.h ``` - -## Implementation - -```c -#include -``` - -```{autodoxygenfile} shv/rpcclient_impl.h -``` diff --git a/docs/api/rpchandler.md b/docs/api/rpchandler.md index e07597e..e340a55 100644 --- a/docs/api/rpchandler.md +++ b/docs/api/rpchandler.md @@ -12,6 +12,6 @@ For the RPC Handler there are some prepared handlers you want to use: ```{toctree} :maxdepth: 2 -rpchandler_responses rpchandler_app +rpchandler_responses ``` diff --git a/docs/api/rpclogger.md b/docs/api/rpclogger.md new file mode 100644 index 0000000..47e5807 --- /dev/null +++ b/docs/api/rpclogger.md @@ -0,0 +1,8 @@ +# RPC Logger + +```c +#include +``` + +```{autodoxygenfile} shv/rpclogger.h +``` diff --git a/include/meson.build b/include/meson.build index d7a3052..a55d2e0 100644 --- a/include/meson.build +++ b/include/meson.build @@ -8,10 +8,10 @@ libshvchainpack_headers = files( ) libshvrpc_headers = files( 'shv/rpcclient.h', - 'shv/rpcclient_impl.h', 'shv/rpchandler.h', 'shv/rpchandler_app.h', 'shv/rpchandler_responses.h', + 'shv/rpclogger.h', 'shv/rpcmsg.h', 'shv/rpcnode.h', 'shv/rpcserver.h', diff --git a/include/shv/cp_unpack.h b/include/shv/cp_unpack.h index af03a59..ab847c5 100644 --- a/include/shv/cp_unpack.h +++ b/include/shv/cp_unpack.h @@ -52,11 +52,11 @@ struct cp_unpack_chainpack { * cp_unpack_chainpack.f to the **f** passed to it. There is no need for a * special resource deallocation afterward. * - * @param pack: Pointer to the handle to be initialized. + * @param unpack: Pointer to the handle to be initialized. * @param f: File used to read ChainPack bytes. * @returns Generic unpacker. */ -cp_unpack_t cp_unpack_chainpack_init(struct cp_unpack_chainpack *pack, FILE *f) +cp_unpack_t cp_unpack_chainpack_init(struct cp_unpack_chainpack *unpack, FILE *f) __attribute__((nonnull)); /*! Handle for the CPON generic unpacker. */ @@ -84,11 +84,11 @@ struct cp_unpack_cpon { * * Remember to release resources allocated by @ref cpon_state.ctx! * - * @param pack: Pointer to the handle to be initialized. + * @param unpack: Pointer to the handle to be initialized. * @param f: File used to read CPON bytes. * @returns Generic unpacker. */ -cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *pack, FILE *f) +cp_unpack_t cp_unpack_cpon_init(struct cp_unpack_cpon *unpack, FILE *f) __attribute__((nonnull)); diff --git a/include/shv/rpcbroker.h b/include/shv/rpcbroker.h index d5d1ade..4c27e41 100644 --- a/include/shv/rpcbroker.h +++ b/include/shv/rpcbroker.h @@ -1,9 +1,22 @@ /* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCBROKER_H #define SHV_RPCBROKER_H +/*! @file + * Implementaion of RPC Broker based on RPC Handler. + */ #include -#include +#include + +typedef struct rpcbroker *rpcbroker_t; + + +void rpcbroker_destroy(rpcbroker_t rpcbroker); + +rpcbroker_t rpcbroker_new(void); + + +struct rpchandler_stage rpcbroker_handler_stage(rpcbroker_t rpcbroker); diff --git a/include/shv/rpcclient.h b/include/shv/rpcclient.h index 164a1de..b2f4978 100644 --- a/include/shv/rpcclient.h +++ b/include/shv/rpcclient.h @@ -8,14 +8,82 @@ #include #include #include +#include #include -#include +#include +#include +#include /*! Number of seconds that is default idle time before brokers disconnect * clients for inactivity. */ #define RPC_DEFAULT_IDLE_TIME 180 +/*! Operations performed by control function for RPC Client. + * + * To reduce the size of the @ref rpcclient structure we use only one function + * pointer and pass operation as argument. We define macros to hide this but + * also because control function must be called with pointer to the RPC Client + * and thus using macros makes is safer and more easy to read. + */ +enum rpcclient_ctlop { + /*! @ref rpcclient_destroy */ + RPCC_CTRLOP_DESTROY, + /*! @ref rpcclient_errno and @ref rpcclient_connected */ + RPCC_CTRLOP_ERRNO, + /*! @ref rpcclient_nextmsg */ + RPCC_CTRLOP_NEXTMSG, + /*! @ref rpcclient_validmsg */ + RPCC_CTRLOP_VALIDMSG, + /*! @ref rpcclient_sendmsg */ + RPCC_CTRLOP_SENDMSG, + /*! @ref rpcclient_dropmsg */ + RPCC_CTRLOP_DROPMSG, + /*! @ref rpcclient_pollfd */ + RPCC_CTRLOP_POLLFD, +}; + +/*! Public definition of RPC Client object. + * + * It provides abstraction on top of multiple different protocols providing + * transfer of SHV RPC messages. + */ +struct rpcclient { + /*! Control function. Do not use directly. */ + int (*ctl)(struct rpcclient *, enum rpcclient_ctlop); + + /*! Unpack function. Please use @ref rpcclient_unpack instead. */ + cp_unpack_func_t unpack; + /*! The last time we received message. + * + * This is used by RPC Broker to detect inactive clients. + */ + struct timespec last_receive; + + /*! Pack function. Please use @ref rpcclient_pack instead. */ + cp_pack_func_t pack; + /*! Last time we sent message. + * + * This is used by RPC clients to detect that thay should perform some + * activity to stay connected to the RPC Broker. + */ + struct timespec last_send; + + /*! Logger used to log communication happenning with this client. + * + * Be aware that log implementation is based on the locks and thus all + * logged clients under the same logger are serialized and only one client + * runs at a time. If you have a bit complex setup deadlocks can be easily + * encountered. + * + * You can change logger while client is handling some message but you must + * ensure that previous logger stays valid for some time so the running + * function can finish with the old log. Thew new log will take effect only + * with next message processed. + */ + rpclogger_t logger; +}; + /*! Handle used to manage SHV RPC client. */ typedef struct rpcclient *rpcclient_t; @@ -136,7 +204,7 @@ rpcclient_t rpcclient_serial_unix_connect(const char *location) * * @param CLIENT: Pointer to the RPC client object. */ -#define rpcclient_destroy(CLIENT) ((CLIENT)->destroy(CLIENT)) +#define rpcclient_destroy(CLIENT) ((CLIENT)->ctl(CLIENT, RPCC_CTRLOP_DESTROY)) /*! Check that client is still connected. * @@ -146,7 +214,18 @@ rpcclient_t rpcclient_serial_unix_connect(const char *location) * @param CLIENT: The RPC client object. * @returns `true` in case client seems to be connected and `false` otherwise. */ -#define rpcclient_connected(CLIENT) ((CLIENT)->rfd != -1) +#define rpcclient_connected(CLIENT) \ + ((CLIENT)->ctl(CLIENT, RPCC_CTRLOP_ERRNO) == 0) + +/*! Get errno recorded for error in RPC Client. + * + * This can be read or write error, they are recorded over each other. In any + * case it should given you an idea of what caused the failure. + * + * @param CLIENT: The RPC client object. + * @returns Standard error number recorded in reading or writing in RPC Client. + */ +#define rpcclient_errno(CLIENT) ((CLIENT)->ctl(CLIENT, RPCC_CTRLOP_ERRNO)) /*! Wait for the next message to be ready for reading. * @@ -158,7 +237,21 @@ rpcclient_t rpcclient_serial_unix_connect(const char *location) * @returns `true` if next message is received and `false` if client is no * longer connected. */ -#define rpcclient_nextmsg(CLIENT) ((CLIENT)->msgfetch(CLIENT, true)) +#define rpcclient_nextmsg(CLIENT) ((CLIENT)->ctl(CLIENT, RPCC_CTRLOP_NEXTMSG)) + +/*! Provides access to the general unpack handle for this client and message. + * + * The unpack is allowed to be changed by @ref rpcclient_nextmsg and @ref + * rpcclient_validmsg and thus make sure that you always refresh your own + * reference after you call those functions. + * + * Unpack always unpacks only a single message. The @ref CPERR_EOF error signals + * end of the message not end of the connection. + * + * @param CLIENT: The RPC client object. + * @returns @ref cp_unpack_t that can be used to unpack the received message. + */ +#define rpcclient_unpack(CLIENT) (&(CLIENT)->unpack) /*! Finish reading and validate the received message. * @@ -177,21 +270,7 @@ rpcclient_t rpcclient_serial_unix_connect(const char *location) * @returns `true` if received message was valid and `false` otherwise. Note * that `false` can also mean client disconnect not just invalid message. */ -#define rpcclient_validmsg(CLIENT) ((CLIENT)->msgfetch(CLIENT, false)) - -/*! Provides access to the general unpack handle for this client and message. - * - * The unpack is allowed to be changed by @ref rpcclient_nextmsg and @ref - * rpcclient_validmsg and thus make sure that you always refresh your own - * reference after you call those functions. - * - * Unpack always unpacks only a single message. The @ref CPERR_EOF error signals - * end of the message not end of the connection. - * - * @param CLIENT: The RPC client object. - * @returns @ref cp_unpack_t that can be used to unpack the received message. - */ -#define rpcclient_unpack(CLIENT) (&(CLIENT)->unpack) +#define rpcclient_validmsg(CLIENT) ((CLIENT)->ctl(CLIENT, RPCC_CTRLOP_VALIDMSG)) /*! Provides access to the general pack handle for this client. * @@ -220,7 +299,7 @@ rpcclient_t rpcclient_serial_unix_connect(const char *location) * @returns `true` if message sending was successful, `false` otherwise. The * sending can fail only due to the disconnect. */ -#define rpcclient_sendmsg(CLIENT) ((CLIENT)->msgflush(CLIENT, true)) +#define rpcclient_sendmsg(CLIENT) ((CLIENT)->ctl(CLIENT, RPCC_CTRLOP_SENDMSG)) /*! Drop packed message. * @@ -237,7 +316,7 @@ rpcclient_t rpcclient_serial_unix_connect(const char *location) * drop might need to send some bytes to inform other side about end of the * message and this operation can as side effect detect the client disconnect. */ -#define rpcclient_dropmsg(CLIENT) ((CLIENT)->msgflush(CLIENT, false)) +#define rpcclient_dropmsg(CLIENT) ((CLIENT)->ctl(CLIENT, RPCC_CTRLOP_DROPMSG)) /*! This provides access to the underlying file descriptor used for reading. * @@ -248,42 +327,7 @@ rpcclient_t rpcclient_serial_unix_connect(const char *location) * @returns Integer with file descriptor or `-1` if client provides none or lost * connection. */ -#define rpcclient_pollfd(CLIENT) ((CLIENT)->rfd) - -/*! Provides access to the logger for this RPC client. - * - * This is implemented as macro and allows not only access but also change of - * the logger with syntax `rpcclient_logger(client) = logger;`. - * - * Be aware that log implementation is based on the locks and thus all logged - * clients under the same logger are serialized and only one client runs at a - * time. If you have a bit complex setup deadlocks can be easily encountered. - * - * @param CLIENT: The RPC client object. - * @returns @ref rpcclient_logger for the client. - */ -#define rpcclient_logger(CLIENT) ((CLIENT)->logger) - - -/*! RPC Client logger handle. */ -typedef struct rpcclient_logger *rpcclient_logger_t; - -/*! Destroy the existing logger handle. - * - * @param logger: Logger handle. - */ -void rpcclient_logger_destroy(rpcclient_logger_t logger); - -/*! Create a new RPC Client logger handle. - * - * @param f: Stream used to output log lines. - * @param maxdepth: The output is in CPON format and this allows you to limit - * the maximum depth of containers you want to see in the logs. By specifying - * low enough number the logger can skip unnecessary data and still show you - * enough info about the message so you can recognize it. - */ -rpcclient_logger_t rpcclient_logger_new(FILE *f, unsigned maxdepth) - __attribute__((nonnull, malloc, malloc(rpcclient_logger_destroy))); +#define rpcclient_pollfd(CLIENT) ((CLIENT)->ctl(CLIENT, RPCC_CTRLOP_POLLFD)) /*! Calculate maximum sleep before some message needs to be sent. * @@ -303,4 +347,28 @@ static inline int rpcclient_maxsleep(rpcclient_t client, int idle_time) { return period - t.tv_sec + client->last_send.tv_sec; } +/*! Update @ref rpcclient.last_receive to current time. + * + * This is available mostly for implementation of additional RPC Clients. + * Existing implementations are already calling this internally in the + * appropriate places and thus you do not need to call it on your own. + * + * @param client: Client handle. + */ +static inline void rpcclient_last_receive_update(struct rpcclient *client) { + clock_gettime(CLOCK_MONOTONIC, &client->last_receive); +} + +/*! Update @ref rpcclient.last_send to current time. + * + * This is available mostly for implementation of additional RPC Clients. + * Existing implementations are already calling this internally in the + * appropriate places and thus you do not need to call it on your own. + * + * @param client: Client handle. + */ +static inline void rpcclient_last_send_update(struct rpcclient *client) { + clock_gettime(CLOCK_MONOTONIC, &client->last_send); +} + #endif diff --git a/include/shv/rpcclient_impl.h b/include/shv/rpcclient_impl.h deleted file mode 100644 index 662e8eb..0000000 --- a/include/shv/rpcclient_impl.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: MIT */ -#ifndef SHV_RPCCLIENT_IMPL_H -#define SHV_RPCCLIENT_IMPL_H -/*! @file - * Implementation of the SHV RPC client handle. The implementation is defined - * publically to allow possibly anyone to provide custom implementation but at - * the same time it is split to clearly differentiate that direct access from - * the users to the handle is not desirable. - */ - -#include -#include -#include - -// TODO possibly hide this and just provide function to allocate it - -struct rpcclient { - int rfd; - cp_unpack_func_t unpack; - bool (*msgfetch)(struct rpcclient *, bool newmsg); - struct timespec last_receive; - - cp_pack_func_t pack; - bool (*msgflush)(struct rpcclient *, bool send); - struct timespec last_send; - - void (*destroy)(struct rpcclient *); - - struct rpcclient_logger *logger; -}; - -void rpcclient_init(struct rpcclient *client) __attribute__((nonnull)); - -void rpcclient_log_lock(struct rpcclient_logger *, bool in); - -void rpcclient_log_item(struct rpcclient_logger *, const struct cpitem *) - __attribute__((nonnull(2))); - -void rpcclient_log_unlock(struct rpcclient_logger *); - -static inline void rpcclient_last_receive_update(struct rpcclient *client) { - clock_gettime(CLOCK_MONOTONIC, &client->last_receive); -} - -static inline void rpcclient_last_send_update(struct rpcclient *client) { - clock_gettime(CLOCK_MONOTONIC, &client->last_send); -} - -#endif diff --git a/include/shv/rpchandler.h b/include/shv/rpchandler.h index e4dac98..18314a6 100644 --- a/include/shv/rpchandler.h +++ b/include/shv/rpchandler.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCHANDLER_H #define SHV_RPCHANDLER_H -/* @file +/*! @file * The RPC message handler that wraps RPC Client and allows asynchronous access * for sending and receiving messages. */ @@ -20,7 +20,7 @@ * It provides access to functionality needed to receive message and optionally * also to send single message back. The full access is intentionally limited * to reduce the time it takes to receive message. If you need to pass some - * handle the message handling function then you might not be doing what you + * handle to the message handling function then you might not be doing what you * should. */ struct rpcreceive { @@ -131,16 +131,6 @@ struct rpchandler_stage { typedef struct rpchandler *rpchandler_t; -/*! Free all resources occupied by @ref rpchandler_t object. - * - * This is destructor for the object created by @ref rpchandler_new. - * - * The RPC Client that Handle manages is not destroyed nor disconnected. - * - * @param rpchandler: RPC Handler object. - */ -void rpchandler_destroy(rpchandler_t rpchandler); - /*! Create new RPC message handle. * * @param client: RPC client to wrap in the handler. Make sure that you destroy @@ -157,7 +147,17 @@ void rpchandler_destroy(rpchandler_t rpchandler); */ rpchandler_t rpchandler_new(rpcclient_t client, const struct rpchandler_stage *stages, const struct rpcmsg_meta_limits *limits) - __attribute__((nonnull(1, 2), malloc, malloc(rpchandler_destroy, 1))); + __attribute__((nonnull(1, 2), malloc)); + +/*! Free all resources occupied by @ref rpchandler_t object. + * + * This is destructor for the object created by @ref rpchandler_new. + * + * The RPC Client that Handle manages is not destroyed nor disconnected. + * + * @param rpchandler: RPC Handler object. + */ +void rpchandler_destroy(rpchandler_t rpchandler); /*! This allows you to change the current array of stages. * @@ -187,21 +187,57 @@ void rpchandler_change_stages(rpchandler_t handler, */ bool rpchandler_next(rpchandler_t rpchandler) __attribute__((nonnull)); -/*! Spawn thread that repeatedly calls @ref rpchandler_next. +/*! RPC Handler errors */ +enum rpchandler_error { + /*! Disconnect is reported by RPC Client. */ + RPCHANDLER_DISCONNECT, + /*! Detected too long inactivity. */ + RPCHANDLER_TIMEOUT, +}; + +/*! Run the RPC Handler loop. * * This is the most easier way to get RPC Handler up and running. It is the * suggested way unless you plan to use some poll based loop and multiple * handlers. The single handler can live pretty easily in it this thread. * + * Loop performs the following actions: + * * Wait for data to be read from client and calls @ref rpchandler_next. + * * On inactivity longer than half of the @ref RPC_DEFAULT_IDLE_TIME it calls + * **onerr** with @ref RPCHANDLER_TIMEOUT. Then it continues executing (fail + * was either resolved or this will lead to disconnect by other side). + * * On disconnect it calls **onerr** with @ref RPCHANDLER_DISCONNECT. It + * continues only if client is connected afterwards, otherwise it terminates the + * loop. + * + * @param rpchandler: RPC Handler instance. + * @param onerr: Callback called when some error is detected. It allows you to + * act on that error and possibly clear it before return from callback to + * continue the loop. Callback gets the RPC Handler object and RPC Client + * object it wraps. You can pass `NULL` in which case default function is used + * that ignores disconnects and sends ping requests on timeout. + */ +void rpchandler_run(rpchandler_t rpchandler, + void (*onerr)(rpchandler_t, rpcclient_t, enum rpchandler_error)) + __attribute__((nonnull(1))); + +/*! Spawn thread that runs @ref rpchandler_run. + * + * It is common to run handler in separate thread and perform work on primary + * one. This simplifies this setup by spawning that thread for you. + * * @param rpchandler: RPC Handler instance. + * @param onerr: Passed to @ref rpchandler_run. * @param thread: Pointer to the variable where handle for the pthread is * stored. You can use this to control thread. * @param attr: Pointer to the pthread attributes or `NULL` for the inherited * defaults. * @returns Integer value returned from `pthread_create`. */ -int rpchandler_spawn_thread(rpchandler_t rpchandler, pthread_t *restrict thread, - const pthread_attr_t *restrict attr) __attribute__((nonnull(1, 2))); +int rpchandler_spawn_thread(rpchandler_t rpchandler, + void (*onerr)(rpchandler_t, rpcclient_t, enum rpchandler_error), + pthread_t *restrict thread, const pthread_attr_t *restrict attr) + __attribute__((nonnull(1, 2))); /*! Get next unused request ID. @@ -226,8 +262,8 @@ cp_pack_t rpchandler_msg_new(rpchandler_t rpchandler) __attribute__((nonnull)); /*! Send the packed message. * - * This calls @ref rpcclient_send under the hood and releases the lock taken by - * @ref rpchandler_msg_new. + * This calls @ref rpcclient_sendmsg under the hood and releases the lock taken + * by @ref rpchandler_msg_new. * * @param rpchandler: RPC Handler instance. * @returns `true` if send was successful and `false` otherwise. @@ -236,8 +272,8 @@ bool rpchandler_msg_send(rpchandler_t rpchandler) __attribute__((nonnull)); /*! Drop the packed message. * - * This calls @ref rpcclient_drop under the hood and releases the lock taken by - * @ref rpchandler_msg_new. + * This calls @ref rpcclient_dropmsg under the hood and releases the lock taken + * by @ref rpchandler_msg_new. * * @param rpchandler: RPC Handler instance. * @returns `true` if send was successful and `false` otherwise. diff --git a/include/shv/rpchandler_app.h b/include/shv/rpchandler_app.h index a4101bd..3ed24a5 100644 --- a/include/shv/rpchandler_app.h +++ b/include/shv/rpchandler_app.h @@ -1,17 +1,55 @@ /* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCHANDLER_APP_H #define SHV_RPCHANDLER_APP_H -/*! @file */ +/*! @file + * The Application SHV API is requires by standard to be present for devices and + * optional for other clients. This handler implements it for easy inclusion. + */ #include +/*! Object representing SHV RPC Application Handle. + * + * It provides application API functionality as defined by SHV standard. That + * include methods: + * * `.app:shvVersionMajor` + * * `.app:shvVersionMinor` + * * `.app appName` + * * `.app:appVersion` + * * `.app:ping` + */ typedef struct rpchandler_app *rpchandler_app_t; -void rpchandler_app_destroy(rpchandler_app_t rpchandler_app); - +/*! Create new RPC Application Handle. + * + * @param name: Name of the application or `NULL` to report `shvc`. The string + * must stay valid for the duration of the object existence as it is not + * copied. + * @param version: Version of the application or `NULL` to report SHVC version. + * The must stay valid for the duration of the object existence as it is not + * copied. You should use both **name** and **version** or none to prevent + * confusion. + * @returns A new RPC Application Handler object. + */ rpchandler_app_t rpchandler_app_new(const char *name, const char *version) - __attribute__((malloc, malloc(rpchandler_app_destroy, 1))); + __attribute__((malloc)); + +/*! Free all resources occupied by @ref rpchandler_app_t object. + * + * This is destructor for the object created by @ref rpchandler_app_new. + * + * @param rpchandler_app: RPC Application Handler object. + */ +void rpchandler_app_destroy(rpchandler_app_t rpchandler_app); +/*! Get the RPC Handler stage for this Application Handler. + * + * This provides you with stage to be used in list of stages for the RPC + * handler. + * + * @param rpchandler_app: RPC Application Handler object. + * @returns Stage to be used in array of stages for RPC Handler. + */ struct rpchandler_stage rpchandler_app_stage(rpchandler_app_t rpchandler_app) __attribute__((nonnull)); diff --git a/include/shv/rpchandler_responses.h b/include/shv/rpchandler_responses.h index 531ff1d..434dd59 100644 --- a/include/shv/rpchandler_responses.h +++ b/include/shv/rpchandler_responses.h @@ -1,27 +1,87 @@ +/* SPDX-License-Identifier: MIT */ #ifndef SHV_RPCHANDLER_RESPONSES_H #define SHV_RPCHANDLER_RESPONSES_H +/*! @file + * RPC Handler that delivers responses to the thread waiting for them. + * + * RPC Handler is implemented to handle all received messages in the primary + * thread. If you want get data on some other thread then you must synchronize + * these two threads and pass all parameters needed to receive the message. This + * is potentially a common action and for that reason this handler stage is + * provided to give you this functionality. + */ #include +/*! Object representing RPC Responses Handler. */ typedef struct rpchandler_responses *rpchandler_responses_t; -void rpchandler_responses_destroy(rpchandler_responses_t responder); +/*! Create new RPC REsponses Handler. + * + * @returns RPC Responses Handler object. + */ +rpchandler_responses_t rpchandler_responses_new(void) __attribute__((malloc)); -rpchandler_responses_t rpchandler_responses_new(void) - __attribute__((malloc, malloc(rpchandler_responses_destroy, 1))); +/*! Free all resources occupied by @ref rpchandler_responses_t. + * + * This is destructor for the object created by @ref rpchandler_responses_new. + * + * @param responses: RPC Responses Handler object. + */ +void rpchandler_responses_destroy(rpchandler_responses_t responses); +/*! Get RPC Handler stage for this Responses Handler. + * + * This provides you with stage to be used in list of stages for the RPC + * handler. + * + * @param responses: RPC Responses Handler object. + * @returns Stage to be used in array of stages for RPC Handler. + */ struct rpchandler_stage rpchandler_responses_stage( - rpchandler_responses_t responder) __attribute__((nonnull)); + rpchandler_responses_t responses) __attribute__((nonnull)); +/*! Object representing a single response in RPC Responses Handler. */ typedef struct rpcresponse *rpcresponse_t; -rpcresponse_t rpcresponse_expect(rpchandler_responses_t responder, int request_id) +/*! Register that new response will be received. + * + * You should call this right before you send (ref rpchandler_msg_send) the + * message to prevent race condition where response might have been already + * received. + * + * @param responses: RPC Responses Handler object. + * @param request_id: Request ID of response to be waited for. + * @returns Object you need to use to reference to this response. + */ +rpcresponse_t rpcresponse_expect(rpchandler_responses_t responses, int request_id) __attribute__((nonnull)); +/*! Wait for the response to be received. + * + * This blocks execution of the thread for up to the given timeout. + * + * @param respond: Respond object to wait for. + * @param receive: Pointer to the variable where pointer to the receive + * structure is placed in. This structure provides you with reference you need + * to receive message just like in @ref rpchandler_funcs.msg. + * @param meta: Pointer to the variable where pointer to the meta structure is + * placed in. This structure contains info about received message such as its + * type or if it caries an error. + * @param timeout: Number of seconds we wait before we stop waiting. + * @returns `true` if response received or `false` otherwise. + */ bool rpcresponse_waitfor(rpcresponse_t respond, struct rpcreceive **receive, struct rpcmsg_meta **meta, int timeout) __attribute__((nonnull)); +/*! Validate the response message. + * + * This also frees the RPC Response from RPC Responses Handler. + * + * @param respond: Respond object to wait for. + * @returns `true` if message is valid and `false` otherwise. + */ bool rpcresponse_validmsg(rpcresponse_t respond) __attribute__((nonnull)); diff --git a/include/shv/rpclogger.h b/include/shv/rpclogger.h new file mode 100644 index 0000000..9091259 --- /dev/null +++ b/include/shv/rpclogger.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef SHV_RPCLOGGER_H +#define SHV_RPCLOGGER_H +/*! @file + * Logger used with RPC Client to trace the communication. + */ + + +#include +#include +#include + +/*! RPC Client logger handle. */ +typedef struct rpclogger *rpclogger_t; + +/*! Create a new RPC Client logger handle. + * + * @param f: Stream used to output log lines. + * @param maxdepth: The output is in CPON format and this allows you to limit + * the maximum depth of containers you want to see in the logs. By specifying + * low enough number the logger can skip unnecessary data and still show you + * enough info about the message so you can recognize it. + */ +rpclogger_t rpclogger_new(FILE *f, unsigned maxdepth) + __attribute__((nonnull, malloc)); + +/*! Destroy the existing logger handle. + * + * @param logger: Logger handle. + */ +void rpclogger_destroy(rpclogger_t logger); + + +/*! Lock log for the current thread. + * + * This is API intended to be called by RPC Client implementations. It is not + * desirable to call this outside of that context. + * + * This must be called before you start using @ref rpclogger_log_item! + * + * @param logger: Logger handle. + * @param in: If message is input (`true`) or output (`false`) message. + */ +void rpclogger_log_lock(rpclogger_t logger, bool in); + +/*! Log single item into the logger. + * + * This is API intended to be called by RPC Client implementations. It is not + * desirable to call this outside of that context. + * + * Do not forget to use @ref rpclogger_log_lock before you start calling this + * fuinction. + * + * For consistency you need to log every single item you unpack otherwise log + * might be invalid. + * + * @param logger: Logger handle. + * @param item: RPC item to be logged. + */ +void rpclogger_log_item(rpclogger_t logger, const struct cpitem *item) + __attribute__((nonnull(2))); + +/*! Release log lock held by current thread. + * + * This unlocks lock locked by @ref rpclogger_log_lock. + * + * This is API intended to be called by RPC Client implementations. It is not + * desirable to call this outside of that context. + * + * After this you can't no longer call @ref rpclogger_log_item. + * + * @param logger: Logger handle. + */ +void rpclogger_log_unlock(rpclogger_t logger); + +#endif diff --git a/libshvrpc/libshvrpc.version b/libshvrpc/libshvrpc.version index 4c2625c..068f6a2 100644 --- a/libshvrpc/libshvrpc.version +++ b/libshvrpc/libshvrpc.version @@ -26,14 +26,13 @@ V0.0 { rpcclient_serial_new; rpcclient_serial_tty_connect; rpcclient_serial_unix_connect; - rpcclient_logger_new; - rpcclient_logger_destroy; - # shv/rpcclient_impl.h - rpcclient_init; - rpcclient_log_lock; - rpcclient_log_item; - rpcclient_log_unlock; + # shv/rpclogger.h + rpclogger_new; + rpclogger_destroy; + rpclogger_log_lock; + rpclogger_log_item; + rpclogger_log_unlock; # shv/rpcurl.h rpcurl_parse; @@ -44,6 +43,7 @@ V0.0 { rpchandler_destroy; rpchandler_new; rpchandler_next; + rpchandler_run; rpchandler_spawn_thread; rpchandler_next_request_id; rpchandler_msg_new; diff --git a/libshvrpc/meson.build b/libshvrpc/meson.build index 34aa08f..18acd74 100644 --- a/libshvrpc/meson.build +++ b/libshvrpc/meson.build @@ -1,13 +1,13 @@ libshvrpc_sources = [ files( 'rpcclient_connect.c', - 'rpcclient_log.c', 'rpcclient_login.c', 'rpcclient_serial.c', 'rpcclient_stream.c', 'rpchandler.c', 'rpchandler_app.c', 'rpchandler_responses.c', + 'rpclogger.c', 'rpcmsg_access.c', 'rpcmsg_head.c', 'rpcmsg_pack.c', diff --git a/libshvrpc/rpcclient_stream.c b/libshvrpc/rpcclient_stream.c index b6d88e5..dfe9d21 100644 --- a/libshvrpc/rpcclient_stream.c +++ b/libshvrpc/rpcclient_stream.c @@ -16,7 +16,9 @@ struct rpcclient_stream { struct rpcclient c; + int errnum; /* Read */ + int rfd; FILE *rf; size_t rmsgoff; /* Write */ @@ -27,27 +29,48 @@ struct rpcclient_stream { }; -static ssize_t xread(int fd, char *buf, size_t siz) { +static ssize_t xread(struct rpcclient_stream *c, char *buf, size_t siz) { ssize_t i; do - i = read(fd, buf, siz); + i = read(c->rfd, buf, siz); while (i == -1 && errno == EINTR); + if (i == -1) + c->errnum = errno; return i; } -static int readc(int fd) { +static int readc(struct rpcclient_stream *c) { uint8_t v; - if (xread(fd, (void *)&v, 1) < 0) + if (xread(c, (void *)&v, 1) != 1) { + c->errnum = ENODATA; return -1; + } return v; } +static bool xwrite(struct rpcclient_stream *c, const void *buf, size_t siz) { + uint8_t *data = (uint8_t *)buf; + while (siz > 0) { + ssize_t i = write(c->wfd, data, siz); + if (i == -1) { + if (errno != EINTR) { + c->errnum = errno; + return false; + } + } else { + data += i; + siz -= i; + } + } + return true; +} + static ssize_t cookie_read(void *cookie, char *buf, size_t size) { struct rpcclient_stream *c = (struct rpcclient_stream *)cookie; if (c->rmsgoff < size) /* Read up to the end of the message and then signal EOF by reading 0. */ size = c->rmsgoff; - ssize_t i = xread(c->c.rfd, buf, size); + ssize_t i = xread(c, buf, size); if (i > 0) c->rmsgoff -= i; return i; @@ -56,97 +79,84 @@ static ssize_t cookie_read(void *cookie, char *buf, size_t size) { static void cp_unpack_stream(void *ptr, struct cpitem *item) { struct rpcclient_stream *c = ptr - offsetof(struct rpcclient_stream, c.unpack); chainpack_unpack(c->rf, item); - rpcclient_log_item(c->c.logger, item); + rpclogger_log_item(c->c.logger, item); } -static ssize_t read_size(int fd) { - int c; - if ((c = readc(fd)) == -1) +static ssize_t read_size(struct rpcclient_stream *c) { + int v; + if ((v = readc(c)) == -1) return -1; - unsigned bytes = chainpack_int_bytes(c); - size_t res = chainpack_uint_value1(c, bytes); + unsigned bytes = chainpack_int_bytes(v); + size_t res = chainpack_uint_value1(v, bytes); for (unsigned i = 1; i < bytes; i++) { - if ((c = readc(fd)) == -1) + if ((v = readc(c)) == -1) return -1; - res = (res << 8) | c; + res = (res << 8) | v; } return res; } -static bool msgfetch_stream(struct rpcclient *client, bool newmsg) { - struct rpcclient_stream *c = (struct rpcclient_stream *)client; +static void flushmsg(struct rpcclient_stream *c) { + /* Flush the rest of the message. Read up to the EOF. */ + char buf[BUFSIZ]; + while (fread(buf, 1, BUFSIZ, c->rf) > 0) {} +} + +static bool nextmsg_stream(struct rpcclient_stream *c) { bool ok = true; - if (newmsg) { - ssize_t msgsiz = read_size(c->c.rfd); - if (msgsiz == -1) - return false; - c->rmsgoff = msgsiz; - clearerr(c->rf); - if (ok) { - if (fgetc(c->rf) != CP_ChainPack) - ok = false; - else - rpcclient_log_lock(c->c.logger, true); - } - rpcclient_last_receive_update(client); - } else { - rpcclient_last_receive_update(client); - rpcclient_log_unlock(c->c.logger); - /* Flush the rest of the message */ - char buf[BUFSIZ]; - while (fread(buf, 1, BUFSIZ, c->rf) > 0) - ; + ssize_t msgsiz = read_size(c); + if (msgsiz == -1) + return false; + c->rmsgoff = msgsiz; + clearerr(c->rf); + if (fgetc(c->rf) != CP_ChainPack) { + flushmsg(c); + rpcclient_last_receive_update(&c->c); /* Still valid receive */ + return false; } + rpclogger_log_lock(c->c.logger, true); + rpcclient_last_receive_update(&c->c); return ok; } +static bool validmsg_stream(struct rpcclient_stream *c) { + rpcclient_last_receive_update(&c->c); + rpclogger_log_unlock(c->c.logger); + flushmsg(c); + rpcclient_last_receive_update(&c->c); + return true; /* Always valid for stream as we relly on lower layer */ +} + static bool cp_pack_stream(void *ptr, const struct cpitem *item) { struct rpcclient_stream *c = ptr - offsetof(struct rpcclient_stream, c.pack); if (ftell(c->fbuf) == 0) - rpcclient_log_lock(c->c.logger, false); - rpcclient_log_item(c->c.logger, item); + rpclogger_log_lock(c->c.logger, false); + rpclogger_log_item(c->c.logger, item); return chainpack_pack(c->fbuf, item) > 0; } -static bool write_size(int fd, size_t len) { +static bool write_size(struct rpcclient_stream *c, size_t len) { unsigned bytes = chainpack_w_uint_bytes(len); for (unsigned i = 0; i < bytes; i++) { - uint8_t c = i == 0 ? chainpack_w_uint_value1(len, bytes) + uint8_t v = i == 0 ? chainpack_w_uint_value1(len, bytes) : 0xff & (len >> (8 * (bytes - i - 1))); - int res; - do - res = write(fd, &c, 1); - while (res == -1 && errno == EINTR); - if (res == -1) + if (!xwrite(c, &v, 1)) return false; } return true; } -static bool msgflush_stream(struct rpcclient *client, bool send) { - struct rpcclient_stream *c = (struct rpcclient_stream *)client; - rpcclient_log_unlock(c->c.logger); +static bool msgflush_stream(struct rpcclient_stream *c, bool send) { + rpclogger_log_unlock(c->c.logger); fflush(c->fbuf); if (send) { size_t len = ftell(c->fbuf); - if (!write_size(c->wfd, len + 1)) + if (!write_size(c, len + 1)) return false; - int res; uint8_t cpf = CP_ChainPack; - do - res = write(c->wfd, &cpf, 1); - while (res == -1 && errno == EINTR); - ssize_t i = 0; - while (i < len) { - ssize_t r = write(c->wfd, c->buf, len); - if (r == -1) { - if (errno == EINTR) - continue; - return false; - } - i += r; - } - rpcclient_last_send_update(client); + if (!xwrite(c, &cpf, 1) || !xwrite(c, c->buf, len)) + return false; + rpcclient_last_send_update(&c->c); } fseek(c->fbuf, 0, SEEK_SET); return true; @@ -157,12 +167,35 @@ static void destroy(rpcclient_t client) { fclose(c->rf); fclose(c->fbuf); free(c->buf); - close(c->c.rfd); - if (c->c.rfd != c->wfd) + close(c->rfd); + if (c->rfd != c->wfd) close(c->wfd); free(c); } +static int ctl_stream(struct rpcclient *client, enum rpcclient_ctlop op) { + struct rpcclient_stream *c = (struct rpcclient_stream *)client; + switch (op) { + case RPCC_CTRLOP_DESTROY: + destroy(client); + return true; + case RPCC_CTRLOP_ERRNO: + return c->errnum; + case RPCC_CTRLOP_NEXTMSG: + return nextmsg_stream(c); + case RPCC_CTRLOP_VALIDMSG: + return validmsg_stream(c); + case RPCC_CTRLOP_SENDMSG: + return msgflush_stream(c, true); + case RPCC_CTRLOP_DROPMSG: + return msgflush_stream(c, false); + case RPCC_CTRLOP_POLLFD: + return c->rfd; + } + abort(); /* This should not happen -> implementation error */ +} + + rpcclient_t rpcclient_stream_new(int readfd, int writefd) { struct rpcclient_stream *res = malloc(sizeof *res); assert(res); @@ -171,14 +204,12 @@ rpcclient_t rpcclient_stream_new(int readfd, int writefd) { *res = (struct rpcclient_stream){ .c = (struct rpcclient){ - .rfd = readfd, + .ctl = ctl_stream, .unpack = cp_unpack_stream, - .msgfetch = msgfetch_stream, .pack = cp_pack_stream, - .msgflush = msgflush_stream, - .destroy = destroy, .logger = NULL, }, + .rfd = readfd, .rf = f, .rmsgoff = 0, .wfd = writefd, @@ -189,6 +220,7 @@ rpcclient_t rpcclient_stream_new(int readfd, int writefd) { return &res->c; } + rpcclient_t rpcclient_stream_tcp_connect(const char *location, int port) { struct addrinfo hints; hints.ai_family = AF_UNSPEC; diff --git a/libshvrpc/rpchandler.c b/libshvrpc/rpchandler.c index 4141798..4386ca0 100644 --- a/libshvrpc/rpchandler.c +++ b/libshvrpc/rpchandler.c @@ -2,6 +2,7 @@ #include #include #include +#include #include #define obstack_chunk_alloc malloc #define obstack_chunk_free free @@ -78,6 +79,27 @@ void rpchandler_destroy(rpchandler_t rpchandler) { free(rpchandler); } +static void priority_lock(rpchandler_t rpchandler) { + pthread_mutex_lock(&rpchandler->pmutex); + pthread_mutex_lock(&rpchandler->mutex); + pthread_mutex_unlock(&rpchandler->pmutex); +} + +static void lock(rpchandler_t rpchandler) { + while (true) { + pthread_mutex_lock(&rpchandler->mutex); + if (pthread_mutex_trylock(&rpchandler->pmutex) == 0) { + pthread_mutex_unlock(&rpchandler->pmutex); + break; + } else + pthread_mutex_unlock(&rpchandler->mutex); + } +} + +static void unlock(rpchandler_t rpchandler) { + pthread_mutex_unlock(&rpchandler->mutex); +} + static bool parse_ls_dir(struct rpcreceive *recv, struct rpcmsg_meta *meta, struct rpchandler_x_ctx *ctx) { ctx->name = NULL; @@ -204,15 +226,64 @@ bool rpchandler_next(struct rpchandler *rpchandler) { return rpcclient_connected(rpchandler->client); } -static void *thread_loop(void *ctx) { - rpchandler_t rpchandler = ctx; - while (rpchandler_next(rpchandler)) {} +void rpchandler_run(rpchandler_t rpchandler, + void (*onerr)(rpchandler_t, rpcclient_t, enum rpchandler_error)) { + while (rpcclient_connected(rpchandler->client)) { + while (true) { + struct pollfd pfd = { + .fd = rpcclient_pollfd(rpchandler->client), + .events = POLLIN | POLLHUP, + }; + int pr = poll(&pfd, 1, + rpcclient_maxsleep(rpchandler->client, RPC_DEFAULT_IDLE_TIME) * + 1000); + if (pr == 0) { /* Timeout */ + priority_lock(rpchandler); + if (onerr) + onerr(rpchandler, rpchandler->client, RPCHANDLER_TIMEOUT); + else { + rpcmsg_pack_request_void( + rpcclient_pack(rpchandler->client), ".app", "ping", 0); + rpcclient_sendmsg(rpchandler->client); + } + unlock(rpchandler); + } else if (pr == -1 || pfd.revents & POLLERR) { + fprintf(stderr, "Poll error: %s\n", strerror(errno)); + abort(); // TODO + } else if (pfd.revents & (POLLIN | POLLHUP)) { + /* Even on pollhup we request so RPC Client actually detest the + * disconnect not just us. + */ + if (!rpchandler_next(rpchandler)) + break; + } + } + /*printf("Calling onerr\n");*/ + if (onerr) + onerr(rpchandler, rpchandler->client, RPCHANDLER_DISCONNECT); + } +} + +struct thread_ctx { + rpchandler_t handler; + void (*onerr)(rpchandler_t, rpcclient_t, enum rpchandler_error); +}; + +static void *thread_loop(void *_ctx) { + struct thread_ctx *ctx = _ctx; + rpchandler_t handler = ctx->handler; + void (*onerr)(rpchandler_t, rpcclient_t, enum rpchandler_error) = ctx->onerr; + free(ctx); + rpchandler_run(handler, onerr); return NULL; } -int rpchandler_spawn_thread(rpchandler_t rpchandler, pthread_t *restrict thread, - const pthread_attr_t *restrict attr) { - return pthread_create(thread, attr, thread_loop, rpchandler); +int rpchandler_spawn_thread(rpchandler_t rpchandler, + void (*onerr)(rpchandler_t, rpcclient_t, enum rpchandler_error), + pthread_t *restrict thread, const pthread_attr_t *restrict attr) { + struct thread_ctx *ctx = malloc(sizeof *ctx); + *ctx = (struct thread_ctx){.handler = rpchandler, .onerr = onerr}; + return pthread_create(thread, attr, thread_loop, ctx); } int rpchandler_next_request_id(rpchandler_t rpchandler) { @@ -221,26 +292,19 @@ int rpchandler_next_request_id(rpchandler_t rpchandler) { cp_pack_t rpchandler_msg_new(rpchandler_t rpchandler) { - while (true) { - pthread_mutex_lock(&rpchandler->mutex); - if (pthread_mutex_trylock(&rpchandler->pmutex) == 0) { - pthread_mutex_unlock(&rpchandler->pmutex); - break; - } else - pthread_mutex_unlock(&rpchandler->mutex); - } + lock(rpchandler); return rpcclient_pack(rpchandler->client); } bool rpchandler_msg_send(rpchandler_t rpchandler) { bool res = rpcclient_sendmsg(rpchandler->client); - pthread_mutex_unlock(&rpchandler->mutex); + unlock(rpchandler); return res; } bool rpchandler_msg_drop(rpchandler_t rpchandler) { bool res = rpcclient_dropmsg(rpchandler->client); - pthread_mutex_unlock(&rpchandler->mutex); + unlock(rpchandler); return res; } @@ -253,14 +317,12 @@ bool rpcreceive_validmsg(struct rpcreceive *receive) { cp_pack_t rpcreceive_response_new(struct rpcreceive *receive) { struct rpchandler *rpchandler = (struct rpchandler *)receive; - /* DUe to the logging the message needs to be first read to release the lock + /* Due to the logging the message needs to be first read to release the lock * for the logging before we start writing. */ assert(receive->unpack == NULL); assert(rpchandler->can_respond); - pthread_mutex_lock(&rpchandler->pmutex); - pthread_mutex_lock(&rpchandler->mutex); - pthread_mutex_unlock(&rpchandler->pmutex); + priority_lock(rpchandler); return rpcclient_pack(rpchandler->client); } @@ -269,7 +331,7 @@ bool rpcreceive_response_send(struct rpcreceive *receive) { assert(rpchandler->can_respond); bool res = rpcclient_sendmsg(rpchandler->client); rpchandler->can_respond = false; - pthread_mutex_unlock(&rpchandler->mutex); + unlock(rpchandler); return res; } @@ -277,7 +339,7 @@ bool rpcreceive_response_drop(struct rpcreceive *receive) { struct rpchandler *rpchandler = (struct rpchandler *)receive; assert(rpchandler->can_respond); bool res = rpcclient_dropmsg(rpchandler->client); - pthread_mutex_unlock(&rpchandler->mutex); + unlock(rpchandler); return res; } diff --git a/libshvrpc/rpcclient_log.c b/libshvrpc/rpclogger.c similarity index 79% rename from libshvrpc/rpcclient_log.c rename to libshvrpc/rpclogger.c index c54b431..3b64373 100644 --- a/libshvrpc/rpcclient_log.c +++ b/libshvrpc/rpclogger.c @@ -8,7 +8,7 @@ #include #include -struct rpcclient_logger { +struct rpclogger { FILE *f; bool ellipsis; struct cpon_state cpon_state; @@ -17,7 +17,7 @@ struct rpcclient_logger { }; -void rpcclient_log_lock(struct rpcclient_logger *logger, bool in) { +void rpclogger_log_lock(struct rpclogger *logger, bool in) { if (logger == NULL) return; // TODO signal interrupt @@ -25,7 +25,7 @@ void rpcclient_log_lock(struct rpcclient_logger *logger, bool in) { fputs(in ? "<= " : "=> ", logger->f); } -void rpcclient_log_item(struct rpcclient_logger *logger, const struct cpitem *item) { +void rpclogger_log_item(struct rpclogger *logger, const struct cpitem *item) { if (logger == NULL) return; int semval; @@ -52,7 +52,7 @@ void rpcclient_log_item(struct rpcclient_logger *logger, const struct cpitem *it } } -void rpcclient_log_unlock(struct rpcclient_logger *logger) { +void rpclogger_log_unlock(struct rpclogger *logger) { if (logger == NULL) return; if (logger->cpon_state.depth > 0) { @@ -68,8 +68,8 @@ void rpcclient_log_unlock(struct rpcclient_logger *logger) { static void cpon_state_realloc(struct cpon_state *state) { - const struct rpcclient_logger *logger = - (void *)state - offsetof(struct rpcclient_logger, cpon_state); + const struct rpclogger *logger = + (void *)state - offsetof(struct rpclogger, cpon_state); size_t newcnt = state->cnt ? state->cnt * 2 : 1; if (newcnt > logger->maxdepth) newcnt = logger->maxdepth; @@ -79,8 +79,8 @@ static void cpon_state_realloc(struct cpon_state *state) { } } -rpcclient_logger_t rpcclient_logger_new(FILE *f, unsigned maxdepth) { - struct rpcclient_logger *res = malloc(sizeof *res); +rpclogger_t rpclogger_new(FILE *f, unsigned maxdepth) { + struct rpclogger *res = malloc(sizeof *res); res->f = f; res->ellipsis = false; res->cpon_state = (struct cpon_state){.realloc = cpon_state_realloc}; @@ -89,7 +89,7 @@ rpcclient_logger_t rpcclient_logger_new(FILE *f, unsigned maxdepth) { return res; } -void rpcclient_logger_destroy(rpcclient_logger_t logger) { +void rpclogger_destroy(rpclogger_t logger) { free(logger->cpon_state.ctx); sem_destroy(&logger->semaphore); free(logger); diff --git a/shvc/main.c b/shvc/main.c index 04851e2..433f48d 100644 --- a/shvc/main.c +++ b/shvc/main.c @@ -5,7 +5,14 @@ #include #include #include +#include +#include +#include #include "opts.h" +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +#define TIMEOUT (-1) int main(int argc, char **argv) { @@ -26,9 +33,9 @@ int main(int argc, char **argv) { rpcurl_free(rpcurl); return 1; } - rpcclient_logger_t logger = NULL; + rpclogger_t logger = NULL; if (conf.verbose > 0) { - logger = rpcclient_logger_new(stderr, conf.verbose); + logger = rpclogger_new(stderr, conf.verbose); client->logger = logger; } char *loginerr; @@ -45,14 +52,90 @@ int main(int argc, char **argv) { } rpcurl_free(rpcurl); - // TODO error handling - rpcmsg_pack_request(rpcclient_pack(client), conf.path, conf.method, 3); - // TODO pack params - rpcclient_sendmsg(client); + rpchandler_app_t app = rpchandler_app_new("shvc", PROJECT_VERSION); + rpchandler_responses_t resps = rpchandler_responses_new(); + + const struct rpchandler_stage stages[] = { + rpchandler_app_stage(app), + rpchandler_responses_stage(resps), + {}, + }; + rpchandler_t handler = rpchandler_new(client, stages, NULL); + assert(handler); + + int ec = 1; + + cp_pack_t pack = rpchandler_msg_new(handler); + int rid = rpchandler_next_request_id(handler); + if (conf.param) { + rpcmsg_pack_request(pack, conf.path, conf.method, rid); + + FILE *f = fmemopen((void *)conf.param, strlen(conf.param), "r"); + struct cp_unpack_cpon cp_cpon_unpack; + cp_unpack_t unpack = cp_unpack_cpon_init(&cp_cpon_unpack, f); + struct cpitem item; + cpitem_unpack_init(&item); + while (cp_unpack(unpack, &item)) + cp_pack(pack, &item); + switch (item.as.Error) { + case CPERR_EOF: + break; /* THis is expected exit */ + case CPERR_INVALID: + fprintf(stderr, "Invalid CPON passed as parameter on byte %ld!", + ftell(f)); + goto err; + case CPERR_OVERFLOW: + /* Some value is outside of our capabilities */ + fprintf(stderr, "The CPON parameter can't be handled by SHVC\n"); + goto err; + case CPERR_IO: + case CPERR_NONE: + abort(); /* This should not happen */ + } + free(cp_cpon_unpack.state.ctx); + fclose(f); + + cp_pack_container_end(pack); + } else + rpcmsg_pack_request_void( + rpcclient_pack(client), conf.path, conf.method, rid); - // TODO read response + rpcresponse_t resp = rpcresponse_expect(resps, rid); + if (!rpchandler_msg_send(handler)) { + fprintf(stderr, "Failed to send message: %s\n", strerror(errno)); + goto err; + } + + struct rpcreceive *receive; + struct rpcmsg_meta *meta; + if (!rpcresponse_waitfor(resp, &receive, &meta, TIMEOUT)) { + fprintf(stderr, "Request timed out\n"); + goto err; + } + + struct obstack obs; + obstack_init(&obs); + void *obs_base = obstack_base(&obs); + while (rpcclient_nextmsg(client)) { + obstack_free(&obs, obs_base); + struct cpitem item; + cpitem_unpack_init(&item); + struct rpcmsg_meta meta = (struct rpcmsg_meta){}; + if (!rpcmsg_head_unpack(rpcclient_unpack(client), &item, &meta, NULL, &obs)) + continue; + switch (meta.type) { + case RPCMSG_T_RESPONSE: + break; + } + } + obstack_free(&obs, NULL); +err: + rpchandler_destroy(handler); + rpchandler_app_destroy(app); + rpchandler_responses_destroy(resps); rpcclient_destroy(client); - rpcclient_logger_destroy(logger); - return 0; + if (conf.verbose > 0) + rpclogger_destroy(logger); + return ec; } diff --git a/shvc/opts.c b/shvc/opts.c index 3ca5992..cf1d528 100644 --- a/shvc/opts.c +++ b/shvc/opts.c @@ -4,19 +4,21 @@ #include #include +static const char *default_url = + "tcp://test@localhost?password=test&devmount=test/device"; + static void print_usage(const char *argv0) { - fprintf(stderr, "%s [-vqd] [URL] [PATH] [METHOD] [PARAM]\n", argv0); + fprintf(stderr, "%s [-ujvqdVh] [PATH] [METHOD] [PARAM]\n", argv0); } static void print_help(const char *argv0) { print_usage(argv0); - fprintf(stderr, "Convert between Chainpack and human readable Cpon.\n"); - fprintf(stderr, - "The conversion direction is can be autodetected but it is highly " - "advised to specify the direction if possible.\n"); + fprintf(stderr, "SHV RPC client.\n"); fprintf(stderr, "\n"); fprintf(stderr, "Arguments:\n"); + fprintf(stderr, " -u URL Where to connect to (default %s)\n", default_url); + fprintf(stderr, " -j Output in JSON format instead of CPON\n"); fprintf(stderr, " -v Increase logging level of the communication\n"); fprintf(stderr, " -q Decrease logging level of the communication\n"); fprintf(stderr, " -d Set maximul logging level of the communication\n"); @@ -26,16 +28,23 @@ static void print_help(const char *argv0) { void parse_opts(int argc, char **argv, struct conf *conf) { *conf = (struct conf){ - .url = "tcp://localhost", + .url = default_url, .path = NULL, .method = "dir", - .param = "null", + .param = NULL, .verbose = 0, + .json = false, }; int c; - while ((c = getopt(argc, argv, "vqdVh")) != -1) { + while ((c = getopt(argc, argv, "ujvqdVh")) != -1) { switch (c) { + case 'u': + conf->url = argv[optind]; + break; + case 'j': + conf->json = true; + break; case 'v': if (conf->verbose < UINT_MAX) conf->verbose++; @@ -59,8 +68,6 @@ void parse_opts(int argc, char **argv, struct conf *conf) { exit(2); } } - if (optind < argc) - conf->url = argv[optind++]; if (optind < argc) conf->path = argv[optind++]; if (optind < argc) diff --git a/shvc/opts.h b/shvc/opts.h index 226fba2..d253617 100644 --- a/shvc/opts.h +++ b/shvc/opts.h @@ -8,6 +8,7 @@ struct conf { const char *method; const char *param; unsigned verbose; + bool json; }; /* Parse arguments. */ From c62976d8fee5c864302a9fad9d181389e86f2433 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Tue, 7 Nov 2023 14:36:00 +0100 Subject: [PATCH 33/44] beginning of the broker --- flake.lock | 11 ++- flake.nix | 2 +- shvcbroker/config.c | 206 +++++++++++++++++++++++++++++++++++++++++++- shvcbroker/config.h | 62 +++++++++++-- 4 files changed, 265 insertions(+), 16 deletions(-) diff --git a/flake.lock b/flake.lock index f063d14..aa9a567 100644 --- a/flake.lock +++ b/flake.lock @@ -171,16 +171,15 @@ "nixpkgs": "nixpkgs_4" }, "locked": { - "lastModified": 1695928422, - "narHash": "sha256-1Y1yLY6e6CAE+cwmxyq8up/iV/KOswYDKoftpWB1GoU=", - "ref": "shv3", - "rev": "0f1fa8347bb783c71693a808cb177d8a9ac3d857", - "revCount": 108, + "lastModified": 1698398594, + "narHash": "sha256-KlaNMlOzVtqo1U8xev1VFS+1pHIxQUnPf/JyccRl8LE=", + "ref": "refs/heads/master", + "rev": "8ff380fef35980a3fc24c4a3a37b5bedc38af5eb", + "revCount": 148, "type": "git", "url": "https://gitlab.com/elektroline-predator/pyshv.git" }, "original": { - "ref": "shv3", "type": "git", "url": "https://gitlab.com/elektroline-predator/pyshv.git" } diff --git a/flake.nix b/flake.nix index 2ebb650..0659b4f 100644 --- a/flake.nix +++ b/flake.nix @@ -3,7 +3,7 @@ inputs = { check-suite.url = "github:cynerd/check-suite"; - pyshv.url = "git+https://gitlab.com/elektroline-predator/pyshv.git?ref=shv3"; + pyshv.url = "git+https://gitlab.com/elektroline-predator/pyshv.git"; }; outputs = { diff --git a/shvcbroker/config.c b/shvcbroker/config.c index e037bd6..dbbda03 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -2,7 +2,109 @@ #include #include #include +#include +#include +//TODO reallocating +static int handler(void* user, const char* section, const char* name, + const char* value) { + if(!strlen(section) || !strlen(name) || !strlen(value)) + return 0; + // printf("%s....%s....%s\n",section,name,value); + struct config* conf = (struct config*)user; + + if(!strcmp(section,"listen")){ + if(!config_listen(conf,name, value)) { //TODO Error message and exit + return 0; + } + } else if(!strncmp(section,"users.",6)){ + if(conf->num_users>0 ? strcmp(section+6,conf->users[conf->num_users-1].name ) : true){ + prepare_user(conf,section); + } + + if(!config_users(conf, section, name, value)) { + return 0; + } + } + else if(!strncmp(section,"roles",5)){ + if(conf->num_roles>0 ? strcmp(section+6,conf->roles[conf->num_roles-1].name ) : true){ + prepare_role(conf, section); + } + + if(!config_role(conf,section,name, value)){ + return 0; + } + + } + + + return 1; +} + +bool config_listen(struct config* conf, const char* name, const char* value) { + if(!strcmp(name,"internet")){ + conf->internet= strdup(value); + return 1; + } + if(!strcmp(name,"unix")){ + conf->unixx= strdup(value); + return 1; + } + + return 0; +} + +bool config_users(struct config* conf, const char* section, const char* name, const char* value) { + + if(!strcmp(name,"password")){ + conf->users[conf->num_users-1].password= strdup(value); + return 1; + } + if(!strcmp(name,"sha1pass")){ + conf->users[conf->num_users-1].sha1pass= strdup(value); + printf("%s\n\n",conf->users[conf->num_users-1].sha1pass); + return 1; + } + if(!strcmp(name,"roles")){ + conf->users[conf->num_users-1].role=strdup(value); + return 1; + } + return 0; +} + +bool config_role(struct config* conf, const char* section, + const char* name, const char* value){ + + if(!strcmp(name,"roles")){ + conf->roles[conf->num_roles-1].other_roles=strdup(value); + return 1; + } + if(!strcmp(name,"methods")){ + conf->roles[conf->num_roles-1].txt_methods=strdup(value); + return 1; + } + if(!strcmp(name,"access")){ + conf->roles[conf->num_roles-1].access=strdup(value); + return 1; + } + return 0; +} + +void prepare_user(struct config* conf, const char* section) { + conf->num_users++; + conf->users[conf->num_users-1].name= strdup(section+6); + conf->users[conf->num_users-1].role=NULL; + conf->users[conf->num_users-1].password=NULL; + conf->users[conf->num_users-1].sha1pass=NULL; +} + +void prepare_role(struct config* conf, const char* section) { + conf->num_roles++; + conf->roles[conf->num_roles-1].name= strdup(section+6); + conf->roles[conf->num_roles-1].access=NULL; + conf->roles[conf->num_roles-1].txt_methods=NULL; + conf->roles[conf->num_roles-1].other_roles=NULL; +} static void print_usage(const char *argv0) { fprintf(stderr, "%s [-Vh] [-c FILE]\n", argv0); @@ -42,9 +144,109 @@ static void parse_opts(const char **confpath, int argc, char **argv) { } } - void load_config(int argc, char **argv) { const char *confpath = NULL; + struct config *conf = malloc(sizeof(struct config)); + conf->users = malloc(10*sizeof(struct user)); + conf->roles = malloc(10*sizeof(struct role)); + conf->num_users=0; + conf->num_roles=0; parse_opts(&confpath, argc, argv); - // TODO load config + const char* config_file = argv[2]; + + if (argc != 3) { + return ; + } + + if (ini_parse(config_file, handler, conf) < 0) { + printf("Can't load '%s'\n", config_file); + return ; + } + // printf("%s\n%s",conf->internet, conf->unixx); + // printf("\n-----------\n"); + // for(int i=0; inum_users;i++){ + // printf("%s--%s--%s--%s\n",conf->users[i].name,conf->users[i].password,conf->users[i].sha1pass,conf->users[i].role); + // } + // printf("\n-----------\n"); + // for(int i=0; inum_roles;i++){ + // printf("%s--%s--%s--%s\n",conf->roles[i].name,conf->roles[i].access, conf->roles[i].other_roles, conf->roles[i].txt_methods); + // } + // printf("\n-----------\n"); + // printf("\n-----------\n"); + // printf("\n-----------\n"); + config_method(conf); + // printf("\n-----------\n"); + // printf("\n-----------\n"); + // printf("\n-----------\n"); + for(int s=0;snum_roles;s++){ + for(int i=0;iroles[s].num_methods;i++){ + printf("%s - %s\n",conf->roles[s].methods[i]->path,conf->roles[s].methods[i]->name); + } + printf("\n----------\n"); + + } + free_config(conf); +} + +void free_config(struct config* conf) { //TODO finish + for (size_t i = 0; i < conf->num_users; i++) { + free(conf->users[i].name); + free(conf->users[i].password); + free(conf->users[i].sha1pass); + free(conf->users[i].role); + + } + free(conf->users); + + for (size_t i = 0; i < conf->num_roles; ++i) { + free(conf->roles[i].name); + + free(conf->roles[i].other_roles); + free(conf->roles[i].access); + free(conf->roles[i].txt_methods); + } + free(conf->roles); + + free(conf->internet); + free(conf->unixx); + free(conf); +} + +bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter + conf->methods = malloc(10 * sizeof(struct method)); + conf->num_methods = 0; + + for (int i = 0; i < conf->num_roles; i++) { + conf->roles[i].num_methods=0; + conf->roles[i].methods= malloc(10*sizeof (struct method*)); + char* ptr = strdup(conf->roles[i].txt_methods); + if(ptr[0]==':') { //TODO THERE SHOULD BE EVERY METHOD THERE IScd + conf->roles[i].methods=&conf->methods; + continue; + } + char* token_1 = strtok(ptr, " "); + while (token_1 != NULL) { + conf->num_methods++; + conf->roles[i].num_methods++; + config_method_path(token_1,&conf->methods[conf->num_methods-1]); + conf->roles[i].methods[conf->roles[i].num_methods-1]=&conf->methods[conf->num_methods-1]; + token_1= strtok(NULL," "); + } + free(ptr); + } +} + +void config_method_path(const char* meth_name, struct method* method){ + int path_len; + for(path_len=0;path_lenpath= strndup(meth_name,path_len); + if(meth_name[path_len]==':' && strlen(meth_name)==path_len+1){//TODO idk bad if + method->name=NULL; + return ; + } + meth_name +=path_len+1; + method->name= strdup(meth_name); } diff --git a/shvcbroker/config.h b/shvcbroker/config.h index 6f44de4..0d4b503 100644 --- a/shvcbroker/config.h +++ b/shvcbroker/config.h @@ -1,16 +1,64 @@ /***************************************************************************** - * Copyright (C) 2022 Elektroline Inc. All rights reserved. - * K Ladvi 1805/20 - * Prague, 184 00 - * info@elektroline.cz (+420 284 021 111) - *****************************************************************************/ +* Copyright (C) 2022 Elektroline Inc. All rights reserved. +* K Ladvi 1805/20 +* Prague, 184 00 +* info@elektroline.cz (+420 284 021 111) +*****************************************************************************/ #ifndef _FOO_CONFIG_H_ #define _FOO_CONFIG_H_ - +#include +#include /* Parse arguments and load configuration file - * Returned pointer is to statically allocated (do not call free on it). +* Returned pointer is to statically allocated (do not call free on it). */ + +struct user { + char* name; + char* password; + char* sha1pass; + char* role; +}; + +struct method { + char* name; + char* path; +}; + +struct role { + char* name; + size_t num_methods; + char* other_roles; + char* access; + char* txt_methods; + struct method** methods; +}; + +struct config { + char* internet; + char* unixx; + struct user* users; + struct role* roles; + size_t num_users; + size_t num_roles; + struct method* methods; + size_t num_methods; +}; + void load_config(int argc, char **argv) __attribute__((nonnull)); +static int handler(void* user, const char* section, const char* name, + const char* value); +bool config_listen(struct config* conf, const char* section, + const char* name); +bool config_users(struct config* conf, const char* section, + const char* name, const char* value); +bool config_role(struct config* conf, const char* section, + const char* name, const char* value); +bool config_method(struct config* conf); +void config_method_path(const char*meth_name, struct method *method); +void prepare_user(struct config* conf, const char* section); +void prepare_role(struct config* conf, const char* section); +void free_config(struct config* conf); #endif + From 9ed0b20522701e03110714740da186509156c6ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Tue, 7 Nov 2023 14:47:14 +0100 Subject: [PATCH 34/44] mby gettint somewhere --- shvcbroker/config.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index dbbda03..7620540 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -214,6 +214,10 @@ void free_config(struct config* conf) { //TODO finish bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter conf->methods = malloc(10 * sizeof(struct method)); + struct role** all_methods_role = malloc(10 * sizeof(struct role*)); + struct role** all_path_role = malloc(10 * sizeof(struct role*)); + size_t all_methods_cnt=0; + size_t all_path_cnt=0; conf->num_methods = 0; for (int i = 0; i < conf->num_roles; i++) { @@ -221,7 +225,8 @@ bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter conf->roles[i].methods= malloc(10*sizeof (struct method*)); char* ptr = strdup(conf->roles[i].txt_methods); if(ptr[0]==':') { //TODO THERE SHOULD BE EVERY METHOD THERE IScd - conf->roles[i].methods=&conf->methods; + all_methods_cnt++; + all_methods_role[all_methods_cnt-1]=&conf->roles[i]; continue; } char* token_1 = strtok(ptr, " "); From a0535464a92e18b433b8d436c48998d214fca92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Tue, 7 Nov 2023 15:34:16 +0100 Subject: [PATCH 35/44] sss --- shvcbroker/config.c | 7 ++++--- shvcbroker/config.h | 8 +++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index 7620540..4d79858 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -215,7 +215,7 @@ void free_config(struct config* conf) { //TODO finish bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter conf->methods = malloc(10 * sizeof(struct method)); struct role** all_methods_role = malloc(10 * sizeof(struct role*)); - struct role** all_path_role = malloc(10 * sizeof(struct role*)); + struct path_role* all_path_role = malloc(10 * sizeof(struct path_role)); size_t all_methods_cnt=0; size_t all_path_cnt=0; conf->num_methods = 0; @@ -233,7 +233,7 @@ bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter while (token_1 != NULL) { conf->num_methods++; conf->roles[i].num_methods++; - config_method_path(token_1,&conf->methods[conf->num_methods-1]); + config_method_path(token_1,&conf->methods[conf->num_methods-1],all_path_role,all_path_cnt); conf->roles[i].methods[conf->roles[i].num_methods-1]=&conf->methods[conf->num_methods-1]; token_1= strtok(NULL," "); } @@ -241,8 +241,9 @@ bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter } } -void config_method_path(const char* meth_name, struct method* method){ +void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t all_path_cnt){ int path_len; + printf("%s-----------------------\n",meth_name); for(path_len=0;path_len Date: Fri, 10 Nov 2023 10:51:48 +0100 Subject: [PATCH 36/44] to be sure --- shvcbroker/config.c | 20 +++++++++++++------- shvcbroker/config.h | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index 4d79858..0961aea 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -224,7 +224,7 @@ bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter conf->roles[i].num_methods=0; conf->roles[i].methods= malloc(10*sizeof (struct method*)); char* ptr = strdup(conf->roles[i].txt_methods); - if(ptr[0]==':') { //TODO THERE SHOULD BE EVERY METHOD THERE IScd + if(ptr[0]==':') { all_methods_cnt++; all_methods_role[all_methods_cnt-1]=&conf->roles[i]; continue; @@ -233,24 +233,30 @@ bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter while (token_1 != NULL) { conf->num_methods++; conf->roles[i].num_methods++; - config_method_path(token_1,&conf->methods[conf->num_methods-1],all_path_role,all_path_cnt); + config_method_path(token_1,&conf->methods[conf->num_methods-1],all_path_role,&all_path_cnt,&conf->roles[i]); conf->roles[i].methods[conf->roles[i].num_methods-1]=&conf->methods[conf->num_methods-1]; - token_1= strtok(NULL," "); + token_1=strtok(NULL," "); } free(ptr); } + for(int i=0;iname); + } } -void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t all_path_cnt){ +void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt,struct role* role){ int path_len; - printf("%s-----------------------\n",meth_name); for(path_len=0;path_lenpath= strndup(meth_name,path_len); - if(meth_name[path_len]==':' && strlen(meth_name)==path_len+1){//TODO idk bad if - method->name=NULL; + if(meth_name[path_len]==':' && strlen(meth_name)==path_len+1){ + (*all_path_cnt)++; + path_role[*all_path_cnt-1].path=strdup(meth_name); + path_role[*all_path_cnt-1].role=role; + printf("HEIL HITLER \n \n"); + printf("%s\n",role->name); return ; } meth_name +=path_len+1; diff --git a/shvcbroker/config.h b/shvcbroker/config.h index dda27c3..d9f6c9f 100644 --- a/shvcbroker/config.h +++ b/shvcbroker/config.h @@ -61,7 +61,7 @@ bool config_users(struct config* conf, const char* section, bool config_role(struct config* conf, const char* section, const char* name, const char* value); bool config_method(struct config* conf); -void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t all_path_cnt); +void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt, struct role* role); void prepare_user(struct config* conf, const char* section); void prepare_role(struct config* conf, const char* section); void free_config(struct config* conf); From fe53648b7f13d10a75379d2ebd4c1763e208a7f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Fri, 10 Nov 2023 11:54:51 +0100 Subject: [PATCH 37/44] keep going --- shvcbroker/config.c | 1 + 1 file changed, 1 insertion(+) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index 0961aea..92db6f7 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -252,6 +252,7 @@ void config_method_path(const char* meth_name, struct method* method, struct pat } method->path= strndup(meth_name,path_len); if(meth_name[path_len]==':' && strlen(meth_name)==path_len+1){ + method->name=NULL; (*all_path_cnt)++; path_role[*all_path_cnt-1].path=strdup(meth_name); path_role[*all_path_cnt-1].role=role; From f9e5260c404d838abafd04d6ae4fd49bba75077d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Fri, 10 Nov 2023 14:46:36 +0100 Subject: [PATCH 38/44] blej --- shvcbroker/config.c | 41 +++++++++++++++++++++++++++-------------- shvcbroker/config.h | 1 + 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index 92db6f7..2839ce9 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -178,12 +178,15 @@ void load_config(int argc, char **argv) { // printf("\n-----------\n"); // printf("\n-----------\n"); // printf("\n-----------\n"); - for(int s=0;snum_roles;s++){ - for(int i=0;iroles[s].num_methods;i++){ - printf("%s - %s\n",conf->roles[s].methods[i]->path,conf->roles[s].methods[i]->name); - } - printf("\n----------\n"); - +// for(int s=0;snum_roles;s++){ +// for(int i=0;iroles[s].num_methods;i++){ +// printf("%s - %s\n",conf->roles[s].methods[i]->path,conf->roles[s].methods[i]->name); +// } +// printf("\n----------\n"); +// +// } + for(int i=0;inum_methods;i++){ + printf("%s\n",conf->methods[i].name); } free_config(conf); } @@ -239,27 +242,37 @@ bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter } free(ptr); } - for(int i=0;iname); - } +// for(int i=0;iname); +// } + config_path_method_all(all_methods_role,all_path_role,all_path_cnt,all_methods_cnt); } void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt,struct role* role){ int path_len; for(path_len=0;path_lenpath= strndup(meth_name,path_len); if(meth_name[path_len]==':' && strlen(meth_name)==path_len+1){ - method->name=NULL; + method->name=NULL; (*all_path_cnt)++; path_role[*all_path_cnt-1].path=strdup(meth_name); path_role[*all_path_cnt-1].role=role; - printf("HEIL HITLER \n \n"); - printf("%s\n",role->name); - return ; + return; } meth_name +=path_len+1; method->name= strdup(meth_name); } +void config_path_method_all(struct role** roles_all, struct path_role* path_role, size_t all_path_cnt, size_t all_methods_cnt){ + for(int i=0;iname); + } + printf("---------------\n"); + for(int i=0;iname); + } +} + diff --git a/shvcbroker/config.h b/shvcbroker/config.h index d9f6c9f..42eb922 100644 --- a/shvcbroker/config.h +++ b/shvcbroker/config.h @@ -62,6 +62,7 @@ bool config_role(struct config* conf, const char* section, const char* name, const char* value); bool config_method(struct config* conf); void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt, struct role* role); +void config_path_method_all(struct role** roles_all, struct path_role* path_role, size_t all_path_cnt, size_t all_methods_cnt); void prepare_user(struct config* conf, const char* section); void prepare_role(struct config* conf, const char* section); void free_config(struct config* conf); From 931d8fb0f616e647b655ddc9bce14e0b8d3d4417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Fri, 10 Nov 2023 15:30:18 +0100 Subject: [PATCH 39/44] sss --- shvcbroker/config.c | 28 +++++++++++++++------------- shvcbroker/config.h | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index 2839ce9..bc920a3 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -185,9 +185,9 @@ void load_config(int argc, char **argv) { // printf("\n----------\n"); // // } - for(int i=0;inum_methods;i++){ - printf("%s\n",conf->methods[i].name); - } +// for(int i=0;inum_methods;i++){ +// printf("%s \n",conf->methods[i].name); +// } free_config(conf); } @@ -245,7 +245,7 @@ bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter // for(int i=0;iname); // } - config_path_method_all(all_methods_role,all_path_role,all_path_cnt,all_methods_cnt); + config_path_method_all(all_methods_role,all_path_role,all_path_cnt,all_methods_cnt,conf); } void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt,struct role* role){ @@ -257,7 +257,7 @@ void config_method_path(const char* meth_name, struct method* method, struct pat } method->path= strndup(meth_name,path_len); if(meth_name[path_len]==':' && strlen(meth_name)==path_len+1){ - method->name=NULL; + method->name=NULL; (*all_path_cnt)++; path_role[*all_path_cnt-1].path=strdup(meth_name); path_role[*all_path_cnt-1].role=role; @@ -266,13 +266,15 @@ void config_method_path(const char* meth_name, struct method* method, struct pat meth_name +=path_len+1; method->name= strdup(meth_name); } -void config_path_method_all(struct role** roles_all, struct path_role* path_role, size_t all_path_cnt, size_t all_methods_cnt){ - for(int i=0;iname); - } - printf("---------------\n"); - for(int i=0;iname); - } +void config_path_method_all(struct role** roles_all, struct path_role* path_role, size_t all_path_cnt, size_t all_methods_cnt, struct config* conf){ +// for(int i=0;iname); +// } +// for(int i=0;imethods=&conf->methods; +// printf("%s",roles_all[i]->methods[0]->name); +// } + roles_all[0]->methods=&conf->methods; + printf("----%s\n",roles_all[0]->methods[0]->name); } diff --git a/shvcbroker/config.h b/shvcbroker/config.h index 42eb922..ae76630 100644 --- a/shvcbroker/config.h +++ b/shvcbroker/config.h @@ -62,7 +62,7 @@ bool config_role(struct config* conf, const char* section, const char* name, const char* value); bool config_method(struct config* conf); void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt, struct role* role); -void config_path_method_all(struct role** roles_all, struct path_role* path_role, size_t all_path_cnt, size_t all_methods_cnt); +void config_path_method_all(struct role** roles_all, struct path_role* path_role, size_t all_path_cnt, size_t all_methods_cnt, struct config* conf); void prepare_user(struct config* conf, const char* section); void prepare_role(struct config* conf, const char* section); void free_config(struct config* conf); From b408a1224140ec402c05439a1dd7726e35532036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Fri, 10 Nov 2023 15:40:38 +0100 Subject: [PATCH 40/44] s --- shvcbroker/config.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index bc920a3..22362a9 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -217,7 +217,7 @@ void free_config(struct config* conf) { //TODO finish bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter conf->methods = malloc(10 * sizeof(struct method)); - struct role** all_methods_role = malloc(10 * sizeof(struct role*)); + struct role** all_methods_role = malloc(10 * sizeof(struct role)); struct path_role* all_path_role = malloc(10 * sizeof(struct path_role)); size_t all_methods_cnt=0; size_t all_path_cnt=0; @@ -274,7 +274,9 @@ void config_path_method_all(struct role** roles_all, struct path_role* path_role // roles_all[i]->methods=&conf->methods; // printf("%s",roles_all[i]->methods[0]->name); // } - roles_all[0]->methods=&conf->methods; - printf("----%s\n",roles_all[0]->methods[0]->name); + roles_all[0]->methods=conf->methods; + printf("----%s\n",roles_all[0]->methods[1]->name); + struct method* lol=conf->methods; +// printf("%s\n",lol[1].name); } From b3e058f00b7097190fc710ef07fd579127448ad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Tue, 21 Nov 2023 13:56:43 +0100 Subject: [PATCH 41/44] big change --- shvcbroker/config.c | 54 +++++++++++++-------------------------------- shvcbroker/config.h | 7 +++--- 2 files changed, 18 insertions(+), 43 deletions(-) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index 22362a9..bcc2099 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -10,11 +10,10 @@ static int handler(void* user, const char* section, const char* name, const char* value) { if(!strlen(section) || !strlen(name) || !strlen(value)) return 0; - // printf("%s....%s....%s\n",section,name,value); struct config* conf = (struct config*)user; if(!strcmp(section,"listen")){ - if(!config_listen(conf,name, value)) { //TODO Error message and exit + if(!add_listen(conf,name, value)) { //TODO Error message and exit return 0; } } else if(!strncmp(section,"users.",6)){ @@ -22,7 +21,7 @@ static int handler(void* user, const char* section, const char* name, prepare_user(conf,section); } - if(!config_users(conf, section, name, value)) { + if(!add_user(conf, section, name, value)) { return 0; } } @@ -31,7 +30,7 @@ static int handler(void* user, const char* section, const char* name, prepare_role(conf, section); } - if(!config_role(conf,section,name, value)){ + if(!add_role(conf,section,name, value)){ return 0; } @@ -41,7 +40,7 @@ static int handler(void* user, const char* section, const char* name, return 1; } -bool config_listen(struct config* conf, const char* name, const char* value) { +bool add_listen(struct config* conf, const char* name, const char* value) { if(!strcmp(name,"internet")){ conf->internet= strdup(value); return 1; @@ -54,7 +53,7 @@ bool config_listen(struct config* conf, const char* name, const char* value) { return 0; } -bool config_users(struct config* conf, const char* section, const char* name, const char* value) { +bool add_user(struct config* conf, const char* section, const char* name, const char* value) { if(!strcmp(name,"password")){ conf->users[conf->num_users-1].password= strdup(value); @@ -72,7 +71,7 @@ bool config_users(struct config* conf, const char* section, const char* name, co return 0; } -bool config_role(struct config* conf, const char* section, +bool add_role(struct config* conf, const char* section, const char* name, const char* value){ if(!strcmp(name,"roles")){ @@ -162,15 +161,13 @@ void load_config(int argc, char **argv) { printf("Can't load '%s'\n", config_file); return ; } - // printf("%s\n%s",conf->internet, conf->unixx); - // printf("\n-----------\n"); - // for(int i=0; inum_users;i++){ - // printf("%s--%s--%s--%s\n",conf->users[i].name,conf->users[i].password,conf->users[i].sha1pass,conf->users[i].role); - // } - // printf("\n-----------\n"); - // for(int i=0; inum_roles;i++){ - // printf("%s--%s--%s--%s\n",conf->roles[i].name,conf->roles[i].access, conf->roles[i].other_roles, conf->roles[i].txt_methods); - // } +// for(int i=0; inum_users;i++){ +// printf("%s--%s--%s--%s\n",conf->users[i].name,conf->users[i].password,conf->users[i].sha1pass,conf->users[i].role); +// } +// printf("\n-----------\n"); +// for(int i=0; inum_roles;i++){ +// printf("%s--%s--%s--%s\n",conf->roles[i].name,conf->roles[i].access, conf->roles[i].other_roles, conf->roles[i].txt_methods); +// } // printf("\n-----------\n"); // printf("\n-----------\n"); // printf("\n-----------\n"); @@ -224,17 +221,13 @@ bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter conf->num_methods = 0; for (int i = 0; i < conf->num_roles; i++) { - conf->roles[i].num_methods=0; - conf->roles[i].methods= malloc(10*sizeof (struct method*)); + char* ptr = strdup(conf->roles[i].txt_methods); if(ptr[0]==':') { - all_methods_cnt++; - all_methods_role[all_methods_cnt-1]=&conf->roles[i]; continue; } char* token_1 = strtok(ptr, " "); while (token_1 != NULL) { - conf->num_methods++; conf->roles[i].num_methods++; config_method_path(token_1,&conf->methods[conf->num_methods-1],all_path_role,&all_path_cnt,&conf->roles[i]); conf->roles[i].methods[conf->roles[i].num_methods-1]=&conf->methods[conf->num_methods-1]; @@ -242,10 +235,7 @@ bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter } free(ptr); } -// for(int i=0;iname); -// } - config_path_method_all(all_methods_role,all_path_role,all_path_cnt,all_methods_cnt,conf); + } void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt,struct role* role){ @@ -266,17 +256,3 @@ void config_method_path(const char* meth_name, struct method* method, struct pat meth_name +=path_len+1; method->name= strdup(meth_name); } -void config_path_method_all(struct role** roles_all, struct path_role* path_role, size_t all_path_cnt, size_t all_methods_cnt, struct config* conf){ -// for(int i=0;iname); -// } -// for(int i=0;imethods=&conf->methods; -// printf("%s",roles_all[i]->methods[0]->name); -// } - roles_all[0]->methods=conf->methods; - printf("----%s\n",roles_all[0]->methods[1]->name); - struct method* lol=conf->methods; -// printf("%s\n",lol[1].name); -} - diff --git a/shvcbroker/config.h b/shvcbroker/config.h index ae76630..9d08e89 100644 --- a/shvcbroker/config.h +++ b/shvcbroker/config.h @@ -54,15 +54,14 @@ struct config { void load_config(int argc, char **argv) __attribute__((nonnull)); static int handler(void* user, const char* section, const char* name, const char* value); -bool config_listen(struct config* conf, const char* section, +bool add_listen(struct config* conf, const char* section, const char* name); -bool config_users(struct config* conf, const char* section, +bool add_user(struct config* conf, const char* section, const char* name, const char* value); -bool config_role(struct config* conf, const char* section, +bool add_role(struct config* conf, const char* section, const char* name, const char* value); bool config_method(struct config* conf); void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt, struct role* role); -void config_path_method_all(struct role** roles_all, struct path_role* path_role, size_t all_path_cnt, size_t all_methods_cnt, struct config* conf); void prepare_user(struct config* conf, const char* section); void prepare_role(struct config* conf, const char* section); void free_config(struct config* conf); From e38f6fdd190bd2bc42273a03d7198ae343be990b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Tue, 21 Nov 2023 14:05:11 +0100 Subject: [PATCH 42/44] big change --- shvcbroker/config.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index bcc2099..ccfe98e 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -186,6 +186,7 @@ void load_config(int argc, char **argv) { // printf("%s \n",conf->methods[i].name); // } free_config(conf); + } void free_config(struct config* conf) { //TODO finish @@ -213,12 +214,6 @@ void free_config(struct config* conf) { //TODO finish } bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter - conf->methods = malloc(10 * sizeof(struct method)); - struct role** all_methods_role = malloc(10 * sizeof(struct role)); - struct path_role* all_path_role = malloc(10 * sizeof(struct path_role)); - size_t all_methods_cnt=0; - size_t all_path_cnt=0; - conf->num_methods = 0; for (int i = 0; i < conf->num_roles; i++) { From 298547c2647b974a7eedd69502d5299a8849d204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Tue, 21 Nov 2023 16:08:20 +0100 Subject: [PATCH 43/44] pomoooooooooooooooooooc --- shvcbroker/config.c | 84 +++++++++++++++++++++++++++++---------------- shvcbroker/config.h | 6 ++-- 2 files changed, 59 insertions(+), 31 deletions(-) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index ccfe98e..6f64f57 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -171,7 +171,7 @@ void load_config(int argc, char **argv) { // printf("\n-----------\n"); // printf("\n-----------\n"); // printf("\n-----------\n"); - config_method(conf); + add_method(conf); // printf("\n-----------\n"); // printf("\n-----------\n"); // printf("\n-----------\n"); @@ -189,43 +189,24 @@ void load_config(int argc, char **argv) { } -void free_config(struct config* conf) { //TODO finish - for (size_t i = 0; i < conf->num_users; i++) { - free(conf->users[i].name); - free(conf->users[i].password); - free(conf->users[i].sha1pass); - free(conf->users[i].role); - - } - free(conf->users); - - for (size_t i = 0; i < conf->num_roles; ++i) { - free(conf->roles[i].name); - - free(conf->roles[i].other_roles); - free(conf->roles[i].access); - free(conf->roles[i].txt_methods); - } - free(conf->roles); - - free(conf->internet); - free(conf->unixx); - free(conf); -} - -bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter +bool add_method(struct config* conf) { // TODO jenom ":" pomocí counter for (int i = 0; i < conf->num_roles; i++) { - + conf->roles[i].methods=malloc(10 * sizeof(struct method)); char* ptr = strdup(conf->roles[i].txt_methods); + printf("----%s\n",conf->roles[i].txt_methods); if(ptr[0]==':') { + free(ptr); + free_method(conf->roles[i].methods,&conf->roles[i].num_methods); + conf->roles[i].num_methods++; + conf->roles[i].methods[0].name=NULL; + conf->roles[i].methods[0].path=NULL; continue; } char* token_1 = strtok(ptr, " "); while (token_1 != NULL) { conf->roles[i].num_methods++; - config_method_path(token_1,&conf->methods[conf->num_methods-1],all_path_role,&all_path_cnt,&conf->roles[i]); - conf->roles[i].methods[conf->roles[i].num_methods-1]=&conf->methods[conf->num_methods-1]; + config_method(token_1,&conf->roles[i].methods[conf->roles[i].num_methods-1]); token_1=strtok(NULL," "); } free(ptr); @@ -233,6 +214,23 @@ bool config_method(struct config* conf) { // TODO jenom ":" pomocí counter } +void config_method(const char* meth_name, struct method* method){ + int path_len; + for(path_len=0;path_lenpath= strndup(meth_name,path_len); + + if(meth_name[path_len]==':' && strlen(meth_name)==path_len+1){ + method->path= strdup(meth_name); + method->name=NULL; + return ; + } + +} + void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt,struct role* role){ int path_len; for(path_len=0;path_lenname= strdup(meth_name); } + +void free_config(struct config* conf) { //TODO finish + for (size_t i = 0; i < conf->num_users; i++) { + free(conf->users[i].name); + free(conf->users[i].password); + free(conf->users[i].sha1pass); + free(conf->users[i].role); + + } + free(conf->users); + + for (size_t i = 0; i < conf->num_roles; ++i) { + free(conf->roles[i].name); + + free(conf->roles[i].other_roles); + free(conf->roles[i].access); + free(conf->roles[i].txt_methods); + } + free(conf->roles); + + free(conf->internet); + free(conf->unixx); + free(conf); +} + +void free_method(struct method* methods,size_t* num_methods){ + num_methods=0; +} diff --git a/shvcbroker/config.h b/shvcbroker/config.h index 9d08e89..1bad394 100644 --- a/shvcbroker/config.h +++ b/shvcbroker/config.h @@ -37,7 +37,7 @@ struct role { char* other_roles; char* access; char* txt_methods; - struct method** methods; + struct method* methods; }; struct config { @@ -60,11 +60,13 @@ bool add_user(struct config* conf, const char* section, const char* name, const char* value); bool add_role(struct config* conf, const char* section, const char* name, const char* value); -bool config_method(struct config* conf); +bool add_method(struct config* conf); void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt, struct role* role); +void config_method(const char* meth_name, struct method* method); void prepare_user(struct config* conf, const char* section); void prepare_role(struct config* conf, const char* section); void free_config(struct config* conf); +void free_method(struct method* methods,size_t* num_methods); #endif From 6777b4822cc8bf6b210e6ba906137f6266a0c1db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Bart=C3=A1k?= Date: Fri, 24 Nov 2023 11:27:51 +0100 Subject: [PATCH 44/44] ss --- shvcbroker/config.c | 55 +++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/shvcbroker/config.c b/shvcbroker/config.c index 6f64f57..a9d5183 100644 --- a/shvcbroker/config.c +++ b/shvcbroker/config.c @@ -61,7 +61,6 @@ bool add_user(struct config* conf, const char* section, const char* name, const } if(!strcmp(name,"sha1pass")){ conf->users[conf->num_users-1].sha1pass= strdup(value); - printf("%s\n\n",conf->users[conf->num_users-1].sha1pass); return 1; } if(!strcmp(name,"roles")){ @@ -161,30 +160,20 @@ void load_config(int argc, char **argv) { printf("Can't load '%s'\n", config_file); return ; } -// for(int i=0; inum_users;i++){ -// printf("%s--%s--%s--%s\n",conf->users[i].name,conf->users[i].password,conf->users[i].sha1pass,conf->users[i].role); -// } -// printf("\n-----------\n"); -// for(int i=0; inum_roles;i++){ -// printf("%s--%s--%s--%s\n",conf->roles[i].name,conf->roles[i].access, conf->roles[i].other_roles, conf->roles[i].txt_methods); -// } - // printf("\n-----------\n"); - // printf("\n-----------\n"); - // printf("\n-----------\n"); + add_method(conf); - // printf("\n-----------\n"); - // printf("\n-----------\n"); - // printf("\n-----------\n"); -// for(int s=0;snum_roles;s++){ -// for(int i=0;iroles[s].num_methods;i++){ -// printf("%s - %s\n",conf->roles[s].methods[i]->path,conf->roles[s].methods[i]->name); -// } -// printf("\n----------\n"); -// -// } -// for(int i=0;inum_methods;i++){ -// printf("%s \n",conf->methods[i].name); -// } + + for(int s=0;snum_roles;s++){ + printf("***%s***\n",conf->roles[s].name); + for(int i=0;iroles[s].num_methods;i++){ + printf("%s - %s\n",conf->roles[s].methods[i].path,conf->roles[s].methods[i].name); + } +// free_method(conf->roles[s].methods,&conf->roles[s].num_methods); + printf("\n----------\n"); + + } + + free_config(conf); } @@ -192,9 +181,9 @@ void load_config(int argc, char **argv) { bool add_method(struct config* conf) { // TODO jenom ":" pomocí counter for (int i = 0; i < conf->num_roles; i++) { + if(conf->roles[i].num_methods) conf->roles[i].methods=malloc(10 * sizeof(struct method)); char* ptr = strdup(conf->roles[i].txt_methods); - printf("----%s\n",conf->roles[i].txt_methods); if(ptr[0]==':') { free(ptr); free_method(conf->roles[i].methods,&conf->roles[i].num_methods); @@ -228,7 +217,8 @@ void config_method(const char* meth_name, struct method* method){ method->name=NULL; return ; } - + method->path= strndup(meth_name,path_len); + method->name= strdup(meth_name +=path_len+1); } void config_method_path(const char* meth_name, struct method* method, struct path_role* path_role, size_t* all_path_cnt,struct role* role){ @@ -250,6 +240,15 @@ void config_method_path(const char* meth_name, struct method* method, struct pat method->name= strdup(meth_name); } +void free_method(struct method* methods,size_t* num_methods){ + for(int i=0;i<*num_methods;i++){ + free(methods[i].name); + free(methods[i].path); + } + *num_methods=0; + +} + void free_config(struct config* conf) { //TODO finish for (size_t i = 0; i < conf->num_users; i++) { free(conf->users[i].name); @@ -273,7 +272,3 @@ void free_config(struct config* conf) { //TODO finish free(conf->unixx); free(conf); } - -void free_method(struct method* methods,size_t* num_methods){ - num_methods=0; -}