From 74cd5fabdcbf937be6af5bfcae509ce861e927c9 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Sun, 25 Sep 2022 21:28:28 -0400 Subject: [PATCH 1/3] desubmodule AntelopeIO/fc@6f9fc55 Co-authored-by: Kevin Heifner Co-authored-by: Brian Johnson Co-authored-by: Areg Hayrapetian Co-authored-by: Daniel Larimer Co-authored-by: Bart Wyatt Co-authored-by: Matias Romeo Co-authored-by: Timothy Banks Co-authored-by: Huang-Ming Huang Co-authored-by: Anton Perkov Co-authored-by: Bucky Kittinger Co-authored-by: Allen Han Co-authored-by: Steven Watanabe Co-authored-by: Nathan Hourt Co-authored-by: Jonathan Giszczak Co-authored-by: Jeeyong Um Co-authored-by: Matthieu Vachon Co-authored-by: Clayton Calabrese Co-authored-by: Khaled Al-Hassanieh Co-authored-by: johndebord Co-authored-by: Todd Fleming Co-authored-by: Peter Oschwald Co-authored-by: Paul Calabrese Co-authored-by: littlekfc <309999096@qq.com> Co-authored-by: Lin Huang Co-authored-by: Jeffrey Smith II Co-authored-by: Adam Mitz Co-authored-by: Thiago Souza Co-authored-by: Chris Gundlach Co-authored-by: zaratustra Co-authored-by: Yue Han Co-authored-by: Ted Cahall @ EOS Network Foundation Co-authored-by: Tadas Co-authored-by: Steven Strand Co-authored-by: Spartucus Co-authored-by: Phil Mesnier Co-authored-by: Pete Cheyne Co-authored-by: Patrick Raphael Co-authored-by: Kayan Co-authored-by: Harry Wong Co-authored-by: anr09 Co-authored-by: Andrianto Lie --- .gitmodules | 12 +- CMakeLists.txt | 18 +- libraries/CMakeLists.txt | 2 +- libraries/fc | 1 - libraries/libfc/CMakeLists.txt | 228 +++ .../libfc/CMakeModules/ArgumentParser.cmake | 74 + .../libfc/CMakeModules/ParseLibraryList.cmake | 79 + .../CMakeModules/SetupTargetMacros.cmake | 369 ++++ .../libfc/CMakeModules/UseLibraryMacros.cmake | 72 + .../libfc/CMakeModules/VersionMacros.cmake | 244 +++ .../GetGitRevisionDescription.cmake.in | 38 + libraries/libfc/LICENSE.txt | 27 + libraries/libfc/include/fc/actor.hpp | 65 + libraries/libfc/include/fc/aligned.hpp | 14 + libraries/libfc/include/fc/any.hpp | 7 + libraries/libfc/include/fc/api.hpp | 134 ++ libraries/libfc/include/fc/array.hpp | 145 ++ libraries/libfc/include/fc/bitutil.hpp | 28 + libraries/libfc/include/fc/bloom_filter.hpp | 621 ++++++ libraries/libfc/include/fc/compress/smaz.hpp | 9 + libraries/libfc/include/fc/compress/zlib.hpp | 10 + .../include/fc/container/container_detail.hpp | 160 ++ .../libfc/include/fc/container/deque.hpp | 12 + .../libfc/include/fc/container/deque_fwd.hpp | 13 + libraries/libfc/include/fc/container/flat.hpp | 186 ++ .../libfc/include/fc/container/flat_fwd.hpp | 45 + .../include/fc/container/tracked_storage.hpp | 124 ++ libraries/libfc/include/fc/crypto/aes.hpp | 54 + .../libfc/include/fc/crypto/alt_bn128.hpp | 25 + libraries/libfc/include/fc/crypto/base32.hpp | 10 + libraries/libfc/include/fc/crypto/base36.hpp | 10 + libraries/libfc/include/fc/crypto/base58.hpp | 11 + libraries/libfc/include/fc/crypto/base64.hpp | 14 + libraries/libfc/include/fc/crypto/bigint.hpp | 76 + libraries/libfc/include/fc/crypto/blake2.hpp | 42 + .../libfc/include/fc/crypto/blowfish.hpp | 178 ++ libraries/libfc/include/fc/crypto/city.hpp | 76 + libraries/libfc/include/fc/crypto/common.hpp | 198 ++ libraries/libfc/include/fc/crypto/dh.hpp | 26 + libraries/libfc/include/fc/crypto/digest.hpp | 15 + .../libfc/include/fc/crypto/elliptic.hpp | 239 +++ .../libfc/include/fc/crypto/elliptic_r1.hpp | 228 +++ .../include/fc/crypto/elliptic_webauthn.hpp | 148 ++ .../libfc/include/fc/crypto/equihash.hpp | 21 + libraries/libfc/include/fc/crypto/hex.hpp | 15 + libraries/libfc/include/fc/crypto/hmac.hpp | 63 + .../libfc/include/fc/crypto/k1_recover.hpp | 20 + .../include/fc/crypto/modular_arithmetic.hpp | 15 + libraries/libfc/include/fc/crypto/openssl.hpp | 60 + libraries/libfc/include/fc/crypto/pke.hpp | 113 ++ .../libfc/include/fc/crypto/private_key.hpp | 73 + .../libfc/include/fc/crypto/public_key.hpp | 64 + libraries/libfc/include/fc/crypto/rand.hpp | 8 + .../libfc/include/fc/crypto/ripemd160.hpp | 95 + libraries/libfc/include/fc/crypto/sha1.hpp | 85 + libraries/libfc/include/fc/crypto/sha224.hpp | 91 + libraries/libfc/include/fc/crypto/sha256.hpp | 138 ++ libraries/libfc/include/fc/crypto/sha3.hpp | 115 ++ libraries/libfc/include/fc/crypto/sha512.hpp | 79 + .../libfc/include/fc/crypto/signature.hpp | 71 + .../libfc/include/fc/crypto/webauthn_json | 1 + .../libfc/include/fc/exception/exception.hpp | 549 +++++ libraries/libfc/include/fc/filesystem.hpp | 264 +++ libraries/libfc/include/fc/fixed_string.hpp | 171 ++ libraries/libfc/include/fc/fwd.hpp | 44 + libraries/libfc/include/fc/fwd_impl.hpp | 121 ++ libraries/libfc/include/fc/git_revision.hpp | 9 + .../include/fc/interprocess/container.hpp | 99 + .../include/fc/interprocess/file_mapping.hpp | 42 + .../include/fc/interprocess/iprocess.hpp | 67 + .../include/fc/interprocess/mmap_struct.hpp | 59 + .../libfc/include/fc/interprocess/process.hpp | 37 + .../include/fc/io/bio_device_adaptor.hpp | 32 + .../libfc/include/fc/io/buffered_iostream.hpp | 73 + libraries/libfc/include/fc/io/cfile.hpp | 278 +++ libraries/libfc/include/fc/io/console.hpp | 8 + libraries/libfc/include/fc/io/datastream.hpp | 313 +++ libraries/libfc/include/fc/io/enum_type.hpp | 82 + libraries/libfc/include/fc/io/fstream.hpp | 11 + libraries/libfc/include/fc/io/incbin.h | 478 +++++ libraries/libfc/include/fc/io/iobuffer.hpp | 81 + libraries/libfc/include/fc/io/json.hpp | 90 + .../libfc/include/fc/io/json_relaxed.hpp | 753 +++++++ .../libfc/include/fc/io/persistence_util.hpp | 75 + libraries/libfc/include/fc/io/raw.hpp | 809 ++++++++ libraries/libfc/include/fc/io/raw_fwd.hpp | 131 ++ .../libfc/include/fc/io/raw_unpack_file.hpp | 24 + libraries/libfc/include/fc/io/raw_variant.hpp | 159 ++ libraries/libfc/include/fc/io/sstream.hpp | 35 + libraries/libfc/include/fc/io/varint.hpp | 98 + libraries/libfc/include/fc/log/appender.hpp | 42 + .../libfc/include/fc/log/console_appender.hpp | 73 + .../libfc/include/fc/log/dmlog_appender.hpp | 34 + .../libfc/include/fc/log/gelf_appender.hpp | 45 + .../libfc/include/fc/log/log_message.hpp | 167 ++ libraries/libfc/include/fc/log/logger.hpp | 179 ++ .../libfc/include/fc/log/logger_config.hpp | 83 + libraries/libfc/include/fc/log/trace.hpp | 44 + libraries/libfc/include/fc/log/zipkin.hpp | 197 ++ libraries/libfc/include/fc/make_fused.hpp | 26 + libraries/libfc/include/fc/mock_time.hpp | 47 + .../include/fc/network/http/http_client.hpp | 36 + libraries/libfc/include/fc/network/ip.hpp | 128 ++ .../include/fc/network/message_buffer.hpp | 352 ++++ .../include/fc/network/platform_root_ca.hpp | 13 + .../libfc/include/fc/network/resolve.hpp | 9 + .../libfc/include/fc/network/udp_socket.hpp | 39 + libraries/libfc/include/fc/network/url.hpp | 62 + libraries/libfc/include/fc/noncopyable.hpp | 14 + .../include/fc/platform_independence.hpp | 15 + libraries/libfc/include/fc/real128.hpp | 52 + .../libfc/include/fc/reflect/reflect.hpp | 323 +++ .../libfc/include/fc/reflect/typename.hpp | 80 + .../libfc/include/fc/reflect/variant.hpp | 109 + .../libfc/include/fc/rpc/api_connection.hpp | 516 +++++ .../include/fc/rpc/binary_api_connection.hpp | 530 +++++ libraries/libfc/include/fc/rpc/cli.hpp | 41 + libraries/libfc/include/fc/rpc/http_api.hpp | 35 + .../include/fc/rpc/variant_connection.hpp | 140 ++ .../libfc/include/fc/rpc/variant_stream.hpp | 36 + .../libfc/include/fc/rpc/websocket_api.hpp | 36 + libraries/libfc/include/fc/safe.hpp | 230 +++ libraries/libfc/include/fc/scoped_exit.hpp | 39 + libraries/libfc/include/fc/static_variant.hpp | 93 + libraries/libfc/include/fc/string.hpp | 152 ++ libraries/libfc/include/fc/time.hpp | 143 ++ libraries/libfc/include/fc/tuple.hpp | 136 ++ libraries/libfc/include/fc/uint128.hpp | 160 ++ libraries/libfc/include/fc/unique_ptr.hpp | 60 + libraries/libfc/include/fc/utf8.hpp | 39 + libraries/libfc/include/fc/utility.hpp | 218 ++ libraries/libfc/include/fc/variant.hpp | 712 +++++++ libraries/libfc/include/fc/variant_object.hpp | 245 +++ libraries/libfc/include/fc/vector.hpp | 2 + libraries/libfc/include/fc/vector_fwd.hpp | 11 + libraries/libfc/libraries/ff | 1 + libraries/libfc/secp256k1/CMakeLists.txt | 40 + .../secp256k1/config/libsecp256k1-config.h | 11 + libraries/libfc/secp256k1/secp256k1 | 1 + libraries/libfc/src/byteswap.hpp | 14 + libraries/libfc/src/compress/smaz.cpp | 223 ++ libraries/libfc/src/compress/zlib.cpp | 21 + libraries/libfc/src/crypto/_digest_common.cpp | 53 + libraries/libfc/src/crypto/_digest_common.hpp | 8 + .../libfc/src/crypto/_elliptic_impl_priv.hpp | 25 + .../libfc/src/crypto/_elliptic_impl_pub.hpp | 33 + libraries/libfc/src/crypto/aes.cpp | 367 ++++ libraries/libfc/src/crypto/alt_bn128.cpp | 230 +++ libraries/libfc/src/crypto/base32.cpp | 29 + libraries/libfc/src/crypto/base36.cpp | 82 + libraries/libfc/src/crypto/base58.cpp | 650 ++++++ libraries/libfc/src/crypto/base64.cpp | 161 ++ libraries/libfc/src/crypto/bigint.cpp | 229 +++ libraries/libfc/src/crypto/blake2.cpp | 137 ++ libraries/libfc/src/crypto/blowfish.cpp | 624 ++++++ libraries/libfc/src/crypto/city.cpp | 683 +++++++ libraries/libfc/src/crypto/crc.cpp | 627 ++++++ libraries/libfc/src/crypto/dh.cpp | 90 + .../libfc/src/crypto/elliptic_common.cpp | 240 +++ .../libfc/src/crypto/elliptic_impl_priv.cpp | 108 + .../libfc/src/crypto/elliptic_impl_pub.cpp | 358 ++++ libraries/libfc/src/crypto/elliptic_mixed.cpp | 38 + .../libfc/src/crypto/elliptic_openssl.cpp | 250 +++ libraries/libfc/src/crypto/elliptic_r1.cpp | 644 ++++++ .../libfc/src/crypto/elliptic_secp256k1.cpp | 163 ++ .../libfc/src/crypto/elliptic_webauthn.cpp | 250 +++ libraries/libfc/src/crypto/equihash.cpp | 47 + libraries/libfc/src/crypto/hex.cpp | 49 + libraries/libfc/src/crypto/k1_recover.cpp | 40 + .../libfc/src/crypto/modular_arithmetic.cpp | 47 + libraries/libfc/src/crypto/pke.cpp | 364 ++++ libraries/libfc/src/crypto/private_key.cpp | 154 ++ libraries/libfc/src/crypto/public_key.cpp | 118 ++ libraries/libfc/src/crypto/rand.cpp | 21 + libraries/libfc/src/crypto/ripemd160.cpp | 118 ++ libraries/libfc/src/crypto/sha1.cpp | 104 + libraries/libfc/src/crypto/sha224.cpp | 101 + libraries/libfc/src/crypto/sha256.cpp | 229 +++ libraries/libfc/src/crypto/sha3.cpp | 287 +++ libraries/libfc/src/crypto/sha512.cpp | 108 + libraries/libfc/src/crypto/signature.cpp | 97 + libraries/libfc/src/exception.cpp | 357 ++++ libraries/libfc/src/filesystem.cpp | 624 ++++++ libraries/libfc/src/git_revision.cpp.in | 11 + .../libfc/src/interprocess/file_mapping.cpp | 41 + .../libfc/src/interprocess/mmap_struct.cpp | 43 + libraries/libfc/src/interprocess/process.cpp | 190 ++ libraries/libfc/src/io/buffered_iostream.cpp | 209 ++ libraries/libfc/src/io/console.cpp | 46 + libraries/libfc/src/io/datastream.cpp | 7 + libraries/libfc/src/io/fstream.cpp | 26 + libraries/libfc/src/io/json.cpp | 861 ++++++++ libraries/libfc/src/io/sstream.cpp | 115 ++ libraries/libfc/src/io/varint.cpp | 10 + libraries/libfc/src/log/appender.cpp | 15 + libraries/libfc/src/log/console_appender.cpp | 175 ++ libraries/libfc/src/log/console_defines.h | 561 ++++++ libraries/libfc/src/log/dmlog_appender.cpp | 97 + libraries/libfc/src/log/gelf_appender.cpp | 251 +++ libraries/libfc/src/log/log_message.cpp | 238 +++ libraries/libfc/src/log/logger.cpp | 109 + libraries/libfc/src/log/logger_config.cpp | 159 ++ libraries/libfc/src/log/zipkin.cpp | 208 ++ libraries/libfc/src/mock_time.cpp | 33 + libraries/libfc/src/network/LICENSE.go | 27 + libraries/libfc/src/network/gntp.cpp | 291 +++ .../libfc/src/network/http/http_client.cpp | 454 +++++ libraries/libfc/src/network/ip.cpp | 158 ++ libraries/libfc/src/network/ntp.cpp | 265 +++ .../libfc/src/network/platform_root_ca.cpp | 126 ++ libraries/libfc/src/network/rate_limiting.cpp | 547 +++++ libraries/libfc/src/network/resolve.cpp | 33 + libraries/libfc/src/network/tcp_socket.cpp | 344 ++++ libraries/libfc/src/network/udp_socket.cpp | 111 + libraries/libfc/src/network/udt_socket.cpp | 413 ++++ libraries/libfc/src/network/url.cpp | 202 ++ libraries/libfc/src/real128.cpp | 130 ++ libraries/libfc/src/rpc/bstate.cpp | 69 + libraries/libfc/src/rpc/cli.cpp | 233 +++ libraries/libfc/src/rpc/http_api.cpp | 183 ++ libraries/libfc/src/rpc/state.cpp | 69 + libraries/libfc/src/rpc/websocket_api.cpp | 177 ++ libraries/libfc/src/string.cpp | 177 ++ .../libfc/src/thread/asio/detail/yield.hpp | 302 +++ .../libfc/src/thread/asio/round_robin.hpp | 185 ++ libraries/libfc/src/thread/asio/yield.hpp | 63 + libraries/libfc/src/time.cpp | 169 ++ libraries/libfc/src/uint128.cpp | 405 ++++ libraries/libfc/src/utf8.cpp | 85 + libraries/libfc/src/utf8/ReleaseNotes | 12 + libraries/libfc/src/utf8/checked.h | 334 +++ libraries/libfc/src/utf8/core.h | 329 +++ libraries/libfc/src/utf8/unchecked.h | 234 +++ libraries/libfc/src/utf8/utf8cpp.html | 1789 +++++++++++++++++ libraries/libfc/src/variant.cpp | 1035 ++++++++++ libraries/libfc/src/variant_object.cpp | 424 ++++ libraries/libfc/test/CMakeLists.txt | 17 + libraries/libfc/test/crypto/CMakeLists.txt | 28 + .../libfc/test/crypto/test_alt_bn128.cpp | 270 +++ libraries/libfc/test/crypto/test_blake2.cpp | 119 ++ .../libfc/test/crypto/test_cypher_suites.cpp | 82 + .../libfc/test/crypto/test_hash_functions.cpp | 70 + .../libfc/test/crypto/test_k1_recover.cpp | 71 + .../test/crypto/test_modular_arithmetic.cpp | 302 +++ libraries/libfc/test/crypto/test_utils.hpp | 11 + libraries/libfc/test/crypto/test_webauthn.cpp | 420 ++++ libraries/libfc/test/io/CMakeLists.txt | 14 + libraries/libfc/test/io/test_cfile.cpp | 160 ++ libraries/libfc/test/io/test_json.cpp | 225 +++ .../libfc/test/io/test_tracked_storage.cpp | 222 ++ libraries/libfc/test/network/CMakeLists.txt | 4 + .../test/network/test_message_buffer.cpp | 380 ++++ .../libfc/test/scoped_exit/CMakeLists.txt | 4 + .../test/scoped_exit/test_scoped_exit.cpp | 56 + .../libfc/test/static_variant/CMakeLists.txt | 4 + .../static_variant/test_static_variant.cpp | 56 + libraries/libfc/test/test_base64.cpp | 55 + libraries/libfc/test/test_filesystem.cpp | 95 + libraries/libfc/test/variant/CMakeLists.txt | 4 + libraries/libfc/test/variant/test_variant.cpp | 98 + .../variant_estimated_size/CMakeLists.txt | 4 + .../test_variant_estimated_size.cpp | 157 ++ 262 files changed, 40898 insertions(+), 14 deletions(-) delete mode 160000 libraries/fc create mode 100644 libraries/libfc/CMakeLists.txt create mode 100644 libraries/libfc/CMakeModules/ArgumentParser.cmake create mode 100644 libraries/libfc/CMakeModules/ParseLibraryList.cmake create mode 100644 libraries/libfc/CMakeModules/SetupTargetMacros.cmake create mode 100644 libraries/libfc/CMakeModules/UseLibraryMacros.cmake create mode 100644 libraries/libfc/CMakeModules/VersionMacros.cmake create mode 100644 libraries/libfc/GitVersionGen/GetGitRevisionDescription.cmake.in create mode 100644 libraries/libfc/LICENSE.txt create mode 100644 libraries/libfc/include/fc/actor.hpp create mode 100644 libraries/libfc/include/fc/aligned.hpp create mode 100644 libraries/libfc/include/fc/any.hpp create mode 100644 libraries/libfc/include/fc/api.hpp create mode 100644 libraries/libfc/include/fc/array.hpp create mode 100644 libraries/libfc/include/fc/bitutil.hpp create mode 100644 libraries/libfc/include/fc/bloom_filter.hpp create mode 100644 libraries/libfc/include/fc/compress/smaz.hpp create mode 100644 libraries/libfc/include/fc/compress/zlib.hpp create mode 100644 libraries/libfc/include/fc/container/container_detail.hpp create mode 100644 libraries/libfc/include/fc/container/deque.hpp create mode 100644 libraries/libfc/include/fc/container/deque_fwd.hpp create mode 100644 libraries/libfc/include/fc/container/flat.hpp create mode 100644 libraries/libfc/include/fc/container/flat_fwd.hpp create mode 100644 libraries/libfc/include/fc/container/tracked_storage.hpp create mode 100644 libraries/libfc/include/fc/crypto/aes.hpp create mode 100644 libraries/libfc/include/fc/crypto/alt_bn128.hpp create mode 100644 libraries/libfc/include/fc/crypto/base32.hpp create mode 100644 libraries/libfc/include/fc/crypto/base36.hpp create mode 100644 libraries/libfc/include/fc/crypto/base58.hpp create mode 100644 libraries/libfc/include/fc/crypto/base64.hpp create mode 100644 libraries/libfc/include/fc/crypto/bigint.hpp create mode 100644 libraries/libfc/include/fc/crypto/blake2.hpp create mode 100644 libraries/libfc/include/fc/crypto/blowfish.hpp create mode 100644 libraries/libfc/include/fc/crypto/city.hpp create mode 100644 libraries/libfc/include/fc/crypto/common.hpp create mode 100644 libraries/libfc/include/fc/crypto/dh.hpp create mode 100644 libraries/libfc/include/fc/crypto/digest.hpp create mode 100644 libraries/libfc/include/fc/crypto/elliptic.hpp create mode 100644 libraries/libfc/include/fc/crypto/elliptic_r1.hpp create mode 100644 libraries/libfc/include/fc/crypto/elliptic_webauthn.hpp create mode 100644 libraries/libfc/include/fc/crypto/equihash.hpp create mode 100644 libraries/libfc/include/fc/crypto/hex.hpp create mode 100644 libraries/libfc/include/fc/crypto/hmac.hpp create mode 100644 libraries/libfc/include/fc/crypto/k1_recover.hpp create mode 100644 libraries/libfc/include/fc/crypto/modular_arithmetic.hpp create mode 100644 libraries/libfc/include/fc/crypto/openssl.hpp create mode 100644 libraries/libfc/include/fc/crypto/pke.hpp create mode 100644 libraries/libfc/include/fc/crypto/private_key.hpp create mode 100644 libraries/libfc/include/fc/crypto/public_key.hpp create mode 100644 libraries/libfc/include/fc/crypto/rand.hpp create mode 100644 libraries/libfc/include/fc/crypto/ripemd160.hpp create mode 100644 libraries/libfc/include/fc/crypto/sha1.hpp create mode 100644 libraries/libfc/include/fc/crypto/sha224.hpp create mode 100644 libraries/libfc/include/fc/crypto/sha256.hpp create mode 100644 libraries/libfc/include/fc/crypto/sha3.hpp create mode 100644 libraries/libfc/include/fc/crypto/sha512.hpp create mode 100644 libraries/libfc/include/fc/crypto/signature.hpp create mode 160000 libraries/libfc/include/fc/crypto/webauthn_json create mode 100644 libraries/libfc/include/fc/exception/exception.hpp create mode 100644 libraries/libfc/include/fc/filesystem.hpp create mode 100644 libraries/libfc/include/fc/fixed_string.hpp create mode 100644 libraries/libfc/include/fc/fwd.hpp create mode 100644 libraries/libfc/include/fc/fwd_impl.hpp create mode 100644 libraries/libfc/include/fc/git_revision.hpp create mode 100644 libraries/libfc/include/fc/interprocess/container.hpp create mode 100644 libraries/libfc/include/fc/interprocess/file_mapping.hpp create mode 100644 libraries/libfc/include/fc/interprocess/iprocess.hpp create mode 100644 libraries/libfc/include/fc/interprocess/mmap_struct.hpp create mode 100644 libraries/libfc/include/fc/interprocess/process.hpp create mode 100644 libraries/libfc/include/fc/io/bio_device_adaptor.hpp create mode 100644 libraries/libfc/include/fc/io/buffered_iostream.hpp create mode 100644 libraries/libfc/include/fc/io/cfile.hpp create mode 100644 libraries/libfc/include/fc/io/console.hpp create mode 100644 libraries/libfc/include/fc/io/datastream.hpp create mode 100644 libraries/libfc/include/fc/io/enum_type.hpp create mode 100644 libraries/libfc/include/fc/io/fstream.hpp create mode 100644 libraries/libfc/include/fc/io/incbin.h create mode 100644 libraries/libfc/include/fc/io/iobuffer.hpp create mode 100644 libraries/libfc/include/fc/io/json.hpp create mode 100644 libraries/libfc/include/fc/io/json_relaxed.hpp create mode 100644 libraries/libfc/include/fc/io/persistence_util.hpp create mode 100644 libraries/libfc/include/fc/io/raw.hpp create mode 100644 libraries/libfc/include/fc/io/raw_fwd.hpp create mode 100644 libraries/libfc/include/fc/io/raw_unpack_file.hpp create mode 100644 libraries/libfc/include/fc/io/raw_variant.hpp create mode 100644 libraries/libfc/include/fc/io/sstream.hpp create mode 100644 libraries/libfc/include/fc/io/varint.hpp create mode 100644 libraries/libfc/include/fc/log/appender.hpp create mode 100644 libraries/libfc/include/fc/log/console_appender.hpp create mode 100644 libraries/libfc/include/fc/log/dmlog_appender.hpp create mode 100644 libraries/libfc/include/fc/log/gelf_appender.hpp create mode 100644 libraries/libfc/include/fc/log/log_message.hpp create mode 100644 libraries/libfc/include/fc/log/logger.hpp create mode 100644 libraries/libfc/include/fc/log/logger_config.hpp create mode 100644 libraries/libfc/include/fc/log/trace.hpp create mode 100644 libraries/libfc/include/fc/log/zipkin.hpp create mode 100644 libraries/libfc/include/fc/make_fused.hpp create mode 100644 libraries/libfc/include/fc/mock_time.hpp create mode 100644 libraries/libfc/include/fc/network/http/http_client.hpp create mode 100644 libraries/libfc/include/fc/network/ip.hpp create mode 100644 libraries/libfc/include/fc/network/message_buffer.hpp create mode 100644 libraries/libfc/include/fc/network/platform_root_ca.hpp create mode 100644 libraries/libfc/include/fc/network/resolve.hpp create mode 100644 libraries/libfc/include/fc/network/udp_socket.hpp create mode 100644 libraries/libfc/include/fc/network/url.hpp create mode 100644 libraries/libfc/include/fc/noncopyable.hpp create mode 100644 libraries/libfc/include/fc/platform_independence.hpp create mode 100644 libraries/libfc/include/fc/real128.hpp create mode 100644 libraries/libfc/include/fc/reflect/reflect.hpp create mode 100644 libraries/libfc/include/fc/reflect/typename.hpp create mode 100644 libraries/libfc/include/fc/reflect/variant.hpp create mode 100644 libraries/libfc/include/fc/rpc/api_connection.hpp create mode 100644 libraries/libfc/include/fc/rpc/binary_api_connection.hpp create mode 100644 libraries/libfc/include/fc/rpc/cli.hpp create mode 100644 libraries/libfc/include/fc/rpc/http_api.hpp create mode 100644 libraries/libfc/include/fc/rpc/variant_connection.hpp create mode 100644 libraries/libfc/include/fc/rpc/variant_stream.hpp create mode 100644 libraries/libfc/include/fc/rpc/websocket_api.hpp create mode 100644 libraries/libfc/include/fc/safe.hpp create mode 100644 libraries/libfc/include/fc/scoped_exit.hpp create mode 100644 libraries/libfc/include/fc/static_variant.hpp create mode 100644 libraries/libfc/include/fc/string.hpp create mode 100644 libraries/libfc/include/fc/time.hpp create mode 100644 libraries/libfc/include/fc/tuple.hpp create mode 100644 libraries/libfc/include/fc/uint128.hpp create mode 100644 libraries/libfc/include/fc/unique_ptr.hpp create mode 100644 libraries/libfc/include/fc/utf8.hpp create mode 100644 libraries/libfc/include/fc/utility.hpp create mode 100644 libraries/libfc/include/fc/variant.hpp create mode 100644 libraries/libfc/include/fc/variant_object.hpp create mode 100644 libraries/libfc/include/fc/vector.hpp create mode 100644 libraries/libfc/include/fc/vector_fwd.hpp create mode 160000 libraries/libfc/libraries/ff create mode 100644 libraries/libfc/secp256k1/CMakeLists.txt create mode 100644 libraries/libfc/secp256k1/config/libsecp256k1-config.h create mode 160000 libraries/libfc/secp256k1/secp256k1 create mode 100644 libraries/libfc/src/byteswap.hpp create mode 100644 libraries/libfc/src/compress/smaz.cpp create mode 100644 libraries/libfc/src/compress/zlib.cpp create mode 100644 libraries/libfc/src/crypto/_digest_common.cpp create mode 100644 libraries/libfc/src/crypto/_digest_common.hpp create mode 100644 libraries/libfc/src/crypto/_elliptic_impl_priv.hpp create mode 100644 libraries/libfc/src/crypto/_elliptic_impl_pub.hpp create mode 100644 libraries/libfc/src/crypto/aes.cpp create mode 100644 libraries/libfc/src/crypto/alt_bn128.cpp create mode 100644 libraries/libfc/src/crypto/base32.cpp create mode 100644 libraries/libfc/src/crypto/base36.cpp create mode 100644 libraries/libfc/src/crypto/base58.cpp create mode 100644 libraries/libfc/src/crypto/base64.cpp create mode 100644 libraries/libfc/src/crypto/bigint.cpp create mode 100644 libraries/libfc/src/crypto/blake2.cpp create mode 100644 libraries/libfc/src/crypto/blowfish.cpp create mode 100644 libraries/libfc/src/crypto/city.cpp create mode 100644 libraries/libfc/src/crypto/crc.cpp create mode 100644 libraries/libfc/src/crypto/dh.cpp create mode 100644 libraries/libfc/src/crypto/elliptic_common.cpp create mode 100644 libraries/libfc/src/crypto/elliptic_impl_priv.cpp create mode 100644 libraries/libfc/src/crypto/elliptic_impl_pub.cpp create mode 100644 libraries/libfc/src/crypto/elliptic_mixed.cpp create mode 100644 libraries/libfc/src/crypto/elliptic_openssl.cpp create mode 100644 libraries/libfc/src/crypto/elliptic_r1.cpp create mode 100644 libraries/libfc/src/crypto/elliptic_secp256k1.cpp create mode 100644 libraries/libfc/src/crypto/elliptic_webauthn.cpp create mode 100644 libraries/libfc/src/crypto/equihash.cpp create mode 100644 libraries/libfc/src/crypto/hex.cpp create mode 100644 libraries/libfc/src/crypto/k1_recover.cpp create mode 100644 libraries/libfc/src/crypto/modular_arithmetic.cpp create mode 100644 libraries/libfc/src/crypto/pke.cpp create mode 100644 libraries/libfc/src/crypto/private_key.cpp create mode 100644 libraries/libfc/src/crypto/public_key.cpp create mode 100644 libraries/libfc/src/crypto/rand.cpp create mode 100644 libraries/libfc/src/crypto/ripemd160.cpp create mode 100644 libraries/libfc/src/crypto/sha1.cpp create mode 100644 libraries/libfc/src/crypto/sha224.cpp create mode 100644 libraries/libfc/src/crypto/sha256.cpp create mode 100644 libraries/libfc/src/crypto/sha3.cpp create mode 100644 libraries/libfc/src/crypto/sha512.cpp create mode 100644 libraries/libfc/src/crypto/signature.cpp create mode 100644 libraries/libfc/src/exception.cpp create mode 100644 libraries/libfc/src/filesystem.cpp create mode 100644 libraries/libfc/src/git_revision.cpp.in create mode 100644 libraries/libfc/src/interprocess/file_mapping.cpp create mode 100644 libraries/libfc/src/interprocess/mmap_struct.cpp create mode 100644 libraries/libfc/src/interprocess/process.cpp create mode 100644 libraries/libfc/src/io/buffered_iostream.cpp create mode 100644 libraries/libfc/src/io/console.cpp create mode 100644 libraries/libfc/src/io/datastream.cpp create mode 100644 libraries/libfc/src/io/fstream.cpp create mode 100644 libraries/libfc/src/io/json.cpp create mode 100644 libraries/libfc/src/io/sstream.cpp create mode 100644 libraries/libfc/src/io/varint.cpp create mode 100644 libraries/libfc/src/log/appender.cpp create mode 100644 libraries/libfc/src/log/console_appender.cpp create mode 100644 libraries/libfc/src/log/console_defines.h create mode 100644 libraries/libfc/src/log/dmlog_appender.cpp create mode 100644 libraries/libfc/src/log/gelf_appender.cpp create mode 100644 libraries/libfc/src/log/log_message.cpp create mode 100644 libraries/libfc/src/log/logger.cpp create mode 100644 libraries/libfc/src/log/logger_config.cpp create mode 100644 libraries/libfc/src/log/zipkin.cpp create mode 100644 libraries/libfc/src/mock_time.cpp create mode 100644 libraries/libfc/src/network/LICENSE.go create mode 100644 libraries/libfc/src/network/gntp.cpp create mode 100644 libraries/libfc/src/network/http/http_client.cpp create mode 100644 libraries/libfc/src/network/ip.cpp create mode 100644 libraries/libfc/src/network/ntp.cpp create mode 100644 libraries/libfc/src/network/platform_root_ca.cpp create mode 100644 libraries/libfc/src/network/rate_limiting.cpp create mode 100644 libraries/libfc/src/network/resolve.cpp create mode 100644 libraries/libfc/src/network/tcp_socket.cpp create mode 100644 libraries/libfc/src/network/udp_socket.cpp create mode 100644 libraries/libfc/src/network/udt_socket.cpp create mode 100644 libraries/libfc/src/network/url.cpp create mode 100644 libraries/libfc/src/real128.cpp create mode 100644 libraries/libfc/src/rpc/bstate.cpp create mode 100644 libraries/libfc/src/rpc/cli.cpp create mode 100644 libraries/libfc/src/rpc/http_api.cpp create mode 100644 libraries/libfc/src/rpc/state.cpp create mode 100644 libraries/libfc/src/rpc/websocket_api.cpp create mode 100644 libraries/libfc/src/string.cpp create mode 100644 libraries/libfc/src/thread/asio/detail/yield.hpp create mode 100644 libraries/libfc/src/thread/asio/round_robin.hpp create mode 100644 libraries/libfc/src/thread/asio/yield.hpp create mode 100644 libraries/libfc/src/time.cpp create mode 100644 libraries/libfc/src/uint128.cpp create mode 100644 libraries/libfc/src/utf8.cpp create mode 100644 libraries/libfc/src/utf8/ReleaseNotes create mode 100644 libraries/libfc/src/utf8/checked.h create mode 100644 libraries/libfc/src/utf8/core.h create mode 100644 libraries/libfc/src/utf8/unchecked.h create mode 100644 libraries/libfc/src/utf8/utf8cpp.html create mode 100644 libraries/libfc/src/variant.cpp create mode 100644 libraries/libfc/src/variant_object.cpp create mode 100644 libraries/libfc/test/CMakeLists.txt create mode 100644 libraries/libfc/test/crypto/CMakeLists.txt create mode 100644 libraries/libfc/test/crypto/test_alt_bn128.cpp create mode 100644 libraries/libfc/test/crypto/test_blake2.cpp create mode 100644 libraries/libfc/test/crypto/test_cypher_suites.cpp create mode 100644 libraries/libfc/test/crypto/test_hash_functions.cpp create mode 100644 libraries/libfc/test/crypto/test_k1_recover.cpp create mode 100644 libraries/libfc/test/crypto/test_modular_arithmetic.cpp create mode 100644 libraries/libfc/test/crypto/test_utils.hpp create mode 100644 libraries/libfc/test/crypto/test_webauthn.cpp create mode 100644 libraries/libfc/test/io/CMakeLists.txt create mode 100644 libraries/libfc/test/io/test_cfile.cpp create mode 100644 libraries/libfc/test/io/test_json.cpp create mode 100644 libraries/libfc/test/io/test_tracked_storage.cpp create mode 100644 libraries/libfc/test/network/CMakeLists.txt create mode 100644 libraries/libfc/test/network/test_message_buffer.cpp create mode 100644 libraries/libfc/test/scoped_exit/CMakeLists.txt create mode 100644 libraries/libfc/test/scoped_exit/test_scoped_exit.cpp create mode 100644 libraries/libfc/test/static_variant/CMakeLists.txt create mode 100644 libraries/libfc/test/static_variant/test_static_variant.cpp create mode 100644 libraries/libfc/test/test_base64.cpp create mode 100644 libraries/libfc/test/test_filesystem.cpp create mode 100644 libraries/libfc/test/variant/CMakeLists.txt create mode 100644 libraries/libfc/test/variant/test_variant.cpp create mode 100644 libraries/libfc/test/variant_estimated_size/CMakeLists.txt create mode 100644 libraries/libfc/test/variant_estimated_size/test_variant_estimated_size.cpp diff --git a/.gitmodules b/.gitmodules index 59ab4da36d..57a2ae46a7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,12 +7,18 @@ [submodule "libraries/eos-vm"] path = libraries/eos-vm url = https://github.com/AntelopeIO/eos-vm -[submodule "libraries/fc"] - path = libraries/fc - url = https://github.com/AntelopeIO/fc [submodule "libraries/softfloat"] path = libraries/softfloat url = https://github.com/AntelopeIO/berkeley-softfloat-3 [submodule "tests/abieos"] path = tests/abieos url = https://github.com/AntelopeIO/abieos +[submodule "libraries/libfc/include/fc/crypto/webauthn_json"] + path = libraries/libfc/include/fc/crypto/webauthn_json + url = https://github.com/Tencent/rapidjson/ +[submodule "libraries/libfc/secp256k1/secp256k1"] + path = libraries/libfc/secp256k1/secp256k1 + url = https://github.com/bitcoin-core/secp256k1 +[submodule "libraries/libfc/libraries/ff"] + path = libraries/libfc/libraries/ff + url = https://github.com/AntelopeIO/libff diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f4ca658bd..b3c2c02c70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,15 +214,15 @@ install(FILES ${CMAKE_BINARY_DIR}/modules/leap-config.cmake DESTINATION ${CMAKE_ install(FILES ${CMAKE_BINARY_DIR}/modules/EosioTester.cmake DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/leap COMPONENT dev EXCLUDE_FROM_ALL) install(FILES ${CMAKE_SOURCE_DIR}/CMakeModules/EosioCheckVersion.cmake DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/leap COMPONENT dev EXCLUDE_FROM_ALL) -configure_file(LICENSE licenses/leap/LICENSE COPYONLY) -configure_file(libraries/softfloat/COPYING.txt licenses/leap/LICENSE.softfloat COPYONLY) -configure_file(libraries/wasm-jit/LICENSE licenses/leap/LICENSE.wavm COPYONLY) -configure_file(libraries/fc/secp256k1/secp256k1/COPYING licenses/leap/LICENSE.secp256k1 COPYONLY) -configure_file(libraries/fc/include/fc/crypto/webauthn_json/license.txt licenses/leap/LICENSE.rapidjson COPYONLY) -configure_file(libraries/fc/src/network/LICENSE.go licenses/leap/LICENSE.go COPYONLY) -configure_file(libraries/eos-vm/LICENSE licenses/leap/LICENSE.eos-vm COPYONLY) -configure_file(libraries/fc/libraries/ff/LICENSE licenses/leap/LICENSE.libff COPYONLY) -configure_file(programs/cleos/LICENSE.CLI11 licenses/leap/LICENSE.CLI11 COPYONLY) +configure_file(LICENSE licenses/leap/LICENSE COPYONLY) +configure_file(libraries/softfloat/COPYING.txt licenses/leap/LICENSE.softfloat COPYONLY) +configure_file(libraries/wasm-jit/LICENSE licenses/leap/LICENSE.wavm COPYONLY) +configure_file(libraries/libfc/secp256k1/secp256k1/COPYING licenses/leap/LICENSE.secp256k1 COPYONLY) +configure_file(libraries/libfc/include/fc/crypto/webauthn_json/license.txt licenses/leap/LICENSE.rapidjson COPYONLY) +configure_file(libraries/libfc/src/network/LICENSE.go licenses/leap/LICENSE.go COPYONLY) +configure_file(libraries/eos-vm/LICENSE licenses/leap/LICENSE.eos-vm COPYONLY) +configure_file(libraries/libfc/libraries/ff/LICENSE licenses/leap/LICENSE.libff COPYONLY) +configure_file(programs/cleos/LICENSE.CLI11 licenses/leap/LICENSE.CLI11 COPYONLY) install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/licenses/leap" DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/licenses/" COMPONENT base) diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 1f74f8db08..60c594ea09 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -4,7 +4,7 @@ set(APPBASE_INSTALL_COMPONENT "dev") set(SOFTFLOAT_INSTALL_COMPONENT "dev") set(EOSVM_INSTALL_COMPONENT "dev") -add_subdirectory( fc ) +add_subdirectory( libfc ) add_subdirectory( builtins ) # Suppress warnings on 3rdParty Library diff --git a/libraries/fc b/libraries/fc deleted file mode 160000 index 6f9fc55105..0000000000 --- a/libraries/fc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6f9fc55105733b8581496b81b4392d44f3c08c1a diff --git a/libraries/libfc/CMakeLists.txt b/libraries/libfc/CMakeLists.txt new file mode 100644 index 0000000000..fed4ab9ea5 --- /dev/null +++ b/libraries/libfc/CMakeLists.txt @@ -0,0 +1,228 @@ +project( fc ) +cmake_minimum_required( VERSION 3.8 ) + +SET( DEFAULT_HEADER_INSTALL_DIR usr/include/${target} ) +SET( DEFAULT_LIBRARY_INSTALL_DIR usr/lib ) +SET( DEFAULT_EXECUTABLE_INSTALL_DIR usr/bin ) +SET( CMAKE_DEBUG_POSTFIX _debug ) +SET( BUILD_SHARED_LIBS NO ) +SET( ECC_IMPL secp256k1 CACHE STRING "secp256k1 or openssl or mixed" ) + +set(platformBitness 32) +if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(platformBitness 64) +endif() + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") + +include( GNUInstallDirs ) +include( SetupTargetMacros ) + +if(CMAKE_CXX_STANDARD EQUAL 98 OR CMAKE_CXX_STANDARD LESS 17) + message(FATAL_ERROR "fc requires c++17 or newer") +elseif(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) +endif() + +if(FC_INSTALL_COMPONENT) + set(INSTALL_COMPONENT_ARGS COMPONENT ${FC_INSTALL_COMPONENT} EXCLUDE_FROM_ALL) +endif() + +add_subdirectory( secp256k1 ) + +SET( WITH_PROCPS OFF CACHE BOOL "" FORCE) +SET( CURVE "ALT_BN128" CACHE STRING "" FORCE) +SET( USE_ASM OFF CACHE BOOL "" FORCE) +SET( IS_LIBFF_PARENT OFF CACHE BOOL "" FORCE) +SET( FF_INSTALL_COMPONENT "${FC_INSTALL_COMPONENT}") +add_subdirectory( libraries/ff ) + +IF( ECC_IMPL STREQUAL openssl ) + SET( ECC_REST src/crypto/elliptic_impl_pub.cpp ) +ELSE( ECC_IMPL STREQUAL openssl ) + SET( ECC_LIB secp256k1 ) + IF( ECC_IMPL STREQUAL mixed ) + SET( ECC_REST src/crypto/elliptic_impl_priv.cpp src/crypto/elliptic_impl_pub.cpp ) + ELSE( ECC_IMPL STREQUAL mixed ) + SET( ECC_REST src/crypto/elliptic_impl_priv.cpp ) + ENDIF( ECC_IMPL STREQUAL mixed ) +ENDIF( ECC_IMPL STREQUAL openssl ) + +MESSAGE(STATUS "Configuring fc to build on Unix/Apple") + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +set(THREADS_PREFER_PTHREAD_FLAG TRUE) +find_package(Threads) + +IF(NOT "$ENV{OPENSSL_ROOT_DIR}" STREQUAL "") + set(OPENSSL_ROOT_DIR $ENV{OPENSSL_ROOT_DIR} ) + set(OPENSSL_INCLUDE_DIR ${OPENSSL_ROOT_DIR}/include) + message(STATUS "Setting up OpenSSL root and include vars to ${OPENSSL_ROOT_DIR}, ${OPENSSL_INCLUDE_DIR}") +ENDIF() + +find_package(OpenSSL REQUIRED) + +set( fc_sources + src/uint128.cpp + src/real128.cpp + src/variant.cpp + src/exception.cpp + src/variant_object.cpp + src/string.cpp + src/time.cpp + src/mock_time.cpp + src/utf8.cpp + src/io/datastream.cpp + src/io/json.cpp + src/io/varint.cpp + src/io/fstream.cpp + src/io/console.cpp + src/filesystem.cpp + src/interprocess/file_mapping.cpp + src/interprocess/mmap_struct.cpp + src/log/log_message.cpp + src/log/logger.cpp + src/log/appender.cpp + src/log/console_appender.cpp + src/log/dmlog_appender.cpp + src/log/logger_config.cpp + src/crypto/_digest_common.cpp + src/crypto/aes.cpp + src/crypto/crc.cpp + src/crypto/city.cpp +# src/crypto/base32.cpp + src/crypto/base36.cpp + src/crypto/base58.cpp + src/crypto/base64.cpp + src/crypto/bigint.cpp + src/crypto/hex.cpp + src/crypto/sha1.cpp + src/crypto/sha3.cpp + src/crypto/ripemd160.cpp + src/crypto/sha256.cpp + src/crypto/sha224.cpp + src/crypto/sha512.cpp + src/crypto/dh.cpp + src/crypto/blowfish.cpp + src/crypto/elliptic_common.cpp + ${ECC_REST} + src/crypto/elliptic_${ECC_IMPL}.cpp + src/crypto/elliptic_r1.cpp + src/crypto/elliptic_webauthn.cpp + src/crypto/rand.cpp + src/crypto/public_key.cpp + src/crypto/private_key.cpp + src/crypto/signature.cpp + src/crypto/alt_bn128.cpp + src/crypto/modular_arithmetic.cpp + src/crypto/blake2.cpp + src/crypto/k1_recover.cpp + src/network/ip.cpp + src/network/platform_root_ca.cpp + src/network/resolve.cpp + src/network/udp_socket.cpp + src/network/url.cpp + src/network/http/http_client.cpp + src/compress/smaz.cpp + src/compress/zlib.cpp + src/log/gelf_appender.cpp + src/log/zipkin.cpp + ) + +file( GLOB_RECURSE fc_headers ${CMAKE_CURRENT_SOURCE_DIR} *.hpp *.h ) + +set( sources + ${fc_sources} +) + +list(APPEND sources ${fc_headers}) + +setup_library( fc SOURCES ${sources} LIBRARY_TYPE STATIC DONT_INSTALL_LIBRARY ) + +function(detect_thread_name) + include(CheckSymbolExists) + list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) + list(APPEND CMAKE_REQUIRED_LIBRARIES "-pthread") + check_symbol_exists(pthread_setname_np pthread.h HAVE_PTHREAD_SETNAME_NP) + if(HAVE_PTHREAD_SETNAME_NP) + set_source_files_properties(src/log/logger_config.cpp PROPERTIES COMPILE_DEFINITIONS FC_USE_PTHREAD_NAME_NP) + endif() +endfunction() +if(CMAKE_SYSTEM_NAME MATCHES "Linux") + detect_thread_name() +endif() + +find_package(Boost 1.66 REQUIRED COMPONENTS + date_time + filesystem + chrono + unit_test_framework + iostreams) + +IF(APPLE) + # As of 10.10 yosemite, the OpenSSL static libraries shipped with os x have a dependency + # on zlib, so any time you link in openssl you also need to link zlib. . We really want to detect whether openssl was configured with the --no-zlib + # option or not when it was built, but that's difficult to do in practice, so we + # just always try to link it in on mac. + find_package( ZLIB REQUIRED ) +ELSE(APPLE) + find_package( ZLIB ) +ENDIF(APPLE) + +if( ZLIB_FOUND ) + MESSAGE( STATUS "zlib found" ) + add_definitions( -DHAS_ZLIB ) +else() + MESSAGE( STATUS "zlib not found" ) + set( ZLIB_LIBRARIES "" ) +endif( ZLIB_FOUND ) + +target_include_directories(fc + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include + ${OPENSSL_INCLUDE_DIR} + ) + +# try and make this very clear that this json parser is intended only for webauthn parsing.. +if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.11.0) + set_source_files_properties(src/crypto/elliptic_webauthn.cpp PROPERTIES INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/include/fc/crypto/webauthn_json/include) +else() + set_source_files_properties(src/crypto/elliptic_webauthn.cpp PROPERTIES COMPILE_FLAGS "-I${CMAKE_CURRENT_SOURCE_DIR}/include/fc/crypto/webauthn_json/include") +endif() + +IF(NOT WIN32) + set(LINK_USR_LOCAL_LIB -L/usr/local/lib) +ENDIF() + +IF(WIN32) + target_link_libraries( fc PUBLIC ws2_32 mswsock userenv ) +ENDIF() + +IF(APPLE) + find_library(security_framework Security) + find_library(corefoundation_framework CoreFoundation) +ENDIF() +target_link_libraries( fc PUBLIC ff ${LINK_USR_LOCAL_LIB} + Boost::date_time Boost::filesystem Boost::chrono Boost::iostreams Threads::Threads + ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${readline_libraries} ${ECC_LIB} ${security_framework} ${corefoundation_framework} ) + +# Critically, this ensures that OpenSSL 1.1 & 3.0 both have a variant of BN_zero() with void return value. But it also allows access +# to some obsoleted AES functions in 3.0 too, since 3.0's API_COMPAT is effectively 3.0 by default +target_compile_definitions(fc PUBLIC "OPENSSL_API_COMPAT=0x10100000L" "OPENSSL_NO_DEPRECATED") + +SET(OPENSSL_CONF_TARGET ) +IF(DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY) + SET (OPENSSL_CONF_TARGET ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) +ELSE() + SET (OPENSSL_CONF_TARGET ${CMAKE_CURRENT_BINARY_DIR}) +ENDIF() + +IF(NOT DEFINED SKIP_FC_TESTS) + add_subdirectory( test ) +ENDIF() + +install(TARGETS fc + LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} ${INSTALL_COMPONENT_ARGS} + ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} ${INSTALL_COMPONENT_ARGS} ) +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/fc DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR} ${INSTALL_COMPONENT_ARGS} ) + diff --git a/libraries/libfc/CMakeModules/ArgumentParser.cmake b/libraries/libfc/CMakeModules/ArgumentParser.cmake new file mode 100644 index 0000000000..a6eb49b1f1 --- /dev/null +++ b/libraries/libfc/CMakeModules/ArgumentParser.cmake @@ -0,0 +1,74 @@ +# This module defines the ARGUMENT_PARSER macro for parsing macro arguments. + +# ARGUMENT_PARSER Macro +# This macro parses a mixed list of arguments and headers into lists and boolean +# variables. The output lists and boolean variables are stored using +# tolower( header ) variable names. All non-header arguments will be added to +# the output list that corresponds to the header that they follow (or to the +# default list if no header has been parsed yet). If a boolean header is passed, +# then its corresponding output variable is set to YES. +# +# Usage: +# ARGUMENT_PARSER( default_list lists bools ARGN ) +# +# Parameters: +# default_list The name of the variable that list values should be added +# to before any list headers have been reached. You may +# pass "" to disregard premature list values. +# lists The list headers (semicolon-separated string). +# bools The boolean headers (semicolon-separated string). +# ARGN The arguments to parse. +MACRO( ARGUMENT_PARSER default_list lists bools ) + + # Start using the default list. + SET( dest "${default_list}" ) + IF( NOT dest ) + SET( dest tmp ) + ENDIF( NOT dest ) + + # Clear all of the lists. + FOREACH( list_itr ${lists} ) + STRING( TOLOWER ${list_itr} lower ) + SET( ${lower} "" ) + ENDFOREACH( list_itr ) + + # Set all boolean variables to NO. + FOREACH( bool_itr ${bools} ) + STRING( TOLOWER ${bool_itr} lower ) + SET( ${lower} NO ) + ENDFOREACH( bool_itr ) + + # For all arguments. + FOREACH( arg_itr ${ARGN} ) + + SET( done NO ) + + # For each of the list headers, if the current argument matches a list + # header, then set the destination to the header. + FOREACH( list_itr ${lists} ) + IF( ${arg_itr} STREQUAL ${list_itr} ) + STRING( TOLOWER ${arg_itr} lower ) + SET( dest ${lower} ) + SET( done YES ) + ENDIF( ${arg_itr} STREQUAL ${list_itr} ) + ENDFOREACH( list_itr ) + + # For each of the boolean headers, if the current argument matches a + # boolean header, then set the boolean variable to true. + FOREACH( bool_itr ${bools} ) + IF( ${arg_itr} STREQUAL ${bool_itr} ) + STRING( TOLOWER ${arg_itr} lower ) + SET( ${lower} YES ) + SET( done YES ) + ENDIF( ${arg_itr} STREQUAL ${bool_itr} ) + ENDFOREACH( bool_itr ) + + # If the current argument is not a header, then add it to the current + # destination list. + IF( NOT done ) + SET( ${dest} ${${dest}} ${arg_itr} ) + ENDIF( NOT done ) + + ENDFOREACH( arg_itr ) + +ENDMACRO( ARGUMENT_PARSER ) diff --git a/libraries/libfc/CMakeModules/ParseLibraryList.cmake b/libraries/libfc/CMakeModules/ParseLibraryList.cmake new file mode 100644 index 0000000000..e559b9d539 --- /dev/null +++ b/libraries/libfc/CMakeModules/ParseLibraryList.cmake @@ -0,0 +1,79 @@ +# -*- mode: cmake -*- + +# +# Shamelessly stolen from MSTK who shamelessly stole from Amanzi open source code https://software.lanl.gov/ascem/trac) +# +# PARSE_LIBRARY_LIST( +# DEBUG +# OPT +# GENERAL ) + +# CMake module +include(CMakeParseArguments) + +function(PARSE_LIBRARY_LIST) + + # Macro: _print_usage + macro(_print_usage) + message("PARSE_LIBRARY_LIST \n" + " FOUND \n" + " DEBUG \n" + " OPT \n" + " GENERAL \n" + "lib_list string to parse\n" + "FOUND flag to indicate if keywords were found\n" + "DEBUG variable containing debug libraries\n" + "OPT variable containing optimized libraries\n" + "GENERAL variable containing debug libraries\n") + + endmacro() + + # Read in args + cmake_parse_arguments(PARSE_ARGS "" "FOUND;DEBUG;OPT;GENERAL" "" ${ARGN}) + set(_parse_list "${PARSE_ARGS_UNPARSED_ARGUMENTS}") + if ( (NOT PARSE_ARGS_FOUND) OR + (NOT PARSE_ARGS_DEBUG) OR + (NOT PARSE_ARGS_OPT) OR + (NOT PARSE_ARGS_GENERAL) OR + (NOT _parse_list ) + ) + _print_usage() + message(FATAL_ERROR "Invalid arguments") + endif() + + # Now split the list + set(_debug_libs "") + set(_opt_libs "") + set(_gen_libs "") + foreach( item ${_parse_list} ) + if( ${item} MATCHES debug OR + ${item} MATCHES optimized OR + ${item} MATCHES general ) + + if( ${item} STREQUAL "debug" ) + set( mylist "_debug_libs" ) + elseif( ${item} STREQUAL "optimized" ) + set( mylist "_opt_libs" ) + elseif( ${item} STREQUAL "general" ) + set( mylist "_gen_libs" ) + endif() + else() + list( APPEND ${mylist} ${item} ) + endif() + endforeach() + + + # Now set output vairables + set(${PARSE_ARGS_DEBUG} "${_debug_libs}" PARENT_SCOPE) + set(${PARSE_ARGS_OPT} "${_opt_libs}" PARENT_SCOPE) + set(${PARSE_ARGS_GENERAL} "${_gen_libs}" PARENT_SCOPE) + + # If any of the lib lists are defined set flag to TRUE + if ( (_debug_libs) OR (_opt_libs) OR (_gen_libs) ) + set(${PARSE_ARGS_FOUND} TRUE PARENT_SCOPE) + else() + set(${PARSE_ARGS_FOUND} FALSE PARENT_SCOPE) + endif() + +endfunction(PARSE_LIBRARY_LIST) + diff --git a/libraries/libfc/CMakeModules/SetupTargetMacros.cmake b/libraries/libfc/CMakeModules/SetupTargetMacros.cmake new file mode 100644 index 0000000000..776ed7d7a1 --- /dev/null +++ b/libraries/libfc/CMakeModules/SetupTargetMacros.cmake @@ -0,0 +1,369 @@ +# This module defines several macros that are useful for setting up library, +# plugin, and executable targets. + + +INCLUDE( ArgumentParser ) + +function(enable_unity_build UB_SUFFIX SOURCE_VARIABLE_NAME) + set(files ${${SOURCE_VARIABLE_NAME}}) + # Generate a unique filename for the unity build translation unit + set(unit_build_file ${CMAKE_CURRENT_BINARY_DIR}/ub_${UB_SUFFIX}.cpp) + # Exclude all translation units from compilation + set_source_files_properties(${files} PROPERTIES HEADER_FILE_ONLY true) + # Open the ub file + FILE(WRITE ${unit_build_file} "// Unity Build generated by CMake\n") + # Add include statement for each translation unit + foreach(source_file ${files} ) + FILE( APPEND ${unit_build_file} "#include <${CMAKE_CURRENT_SOURCE_DIR}/${source_file}>\n") + endforeach(source_file) + # Complement list of translation units with the name of ub + set(${SOURCE_VARIABLE_NAME} ${${SOURCE_VARIABLE_NAME}} ${unit_build_file} PARENT_SCOPE) +endfunction(enable_unity_build) + +# SETUP_LIBRARY Macro +# Sets up to build a library target. The macro uses the following global +# variables to define default values (you may change these variables to change +# the defaults: +# DEFAULT_HEADER_INSTALL_DIR +# DEFAULT_LIBRARY_INSTALL_DIR +# +# Usage: +# SETUP_LIBRARY( target +# SOURCES source1 [source2...] +# MOC_HEADERS header1 [header2...] +# LIBRARIES library1 [library2...] +# INSTALL_HEADERS header1 [header2...] +# HEADER_INSTALL_DIR dir +# LIBRARY_INSTALL_DIR dir +# DEBUG_POSTFIX string +# LIBRARY_TYPE string +# AUTO_INSTALL_HEADERS +# DONT_INSTALL_LIBRARY ) +# +# Parameters: +# target The target library. +# SOURCES Follow with the sources to compile. +# MOC_HEADERS Follow with the headers to moc (Requires Qt). +# LIBRARIES Follow with the libraries to link. +# INSTALL_HEADERS Follow with the headers to install. +# HEADER_INSTALL_DIR Follow with the directory to install the headers +# in (${DEFAULT_HEADER_INSTALL_DIR} by default). +# LIBRARY_INSTALL_DIR Follow with the directory to install the library +# in (${DEFAULT_LIBRARY_INSTALL_DIR} by default). +# DEBUG_POSTFIX Follow with the postfix to use when building in +# debug mode (${CMAKE_DEBUG_POSTFIX} by default). +# LIBRARY_TYPE Follow with the type of library to build: SHARED, +# STATIC, or MODULE (if not passed, then the +# behavior is defined by BUILD_SHARED_LIBS). +# AUTO_INSTALL_HEADERS If passed, all *.h files in the current directory +# will be installed. +# DONT_INSTALL_LIBRARY If passed, the library will not be installed. +MACRO( SETUP_LIBRARY target ) + + # Setup the list headers. + SET( list_headers + SOURCES + MOC_HEADERS + LIBRARIES + INSTALL_HEADERS + HEADER_INSTALL_DIR + LIBRARY_INSTALL_DIR + DEBUG_POSTFIX + LIBRARY_TYPE + ) + + # Setup the boolean headers. + SET( bool_headers + AUTO_INSTALL_HEADERS + DONT_INSTALL_LIBRARY + ) + + # Parse the arguments into variables. + ARGUMENT_PARSER( "" "${list_headers}" "${bool_headers}" ${ARGN} ) + + # Set the default values for the header_install_dir, library_install_dir, + # and debug_postfix. + IF( NOT "${ARGN}" MATCHES "(^|;)HEADER_INSTALL_DIR($|;)" ) + SET( header_install_dir ${DEFAULT_HEADER_INSTALL_DIR} ) + ENDIF( NOT "${ARGN}" MATCHES "(^|;)HEADER_INSTALL_DIR($|;)" ) + + IF( NOT "${ARGN}" MATCHES "(^|;)LIBRARY_INSTALL_DIR($|;)" ) + SET( library_install_dir ${DEFAULT_LIBRARY_INSTALL_DIR} ) + ENDIF( NOT "${ARGN}" MATCHES "(^|;)LIBRARY_INSTALL_DIR($|;)" ) + + IF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) + SET( debug_postfix ${CMAKE_DEBUG_POSTFIX} ) + ENDIF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) + + # Configure the header_install_dir and library_install_dir so that ${target} + # may be used in them. Setting target to itself is REQUIRED for the + # configuration to work. + SET( target "${target}" ) + STRING( CONFIGURE "${header_install_dir}" header_install_dir ) + STRING( CONFIGURE "${library_install_dir}" library_install_dir ) + + # Setup the library_type. + IF( NOT library_type ) + SET( library_type STATIC ) + IF( BUILD_SHARED_LIBS ) + SET( library_type SHARED ) + ENDIF( BUILD_SHARED_LIBS ) + ENDIF( NOT library_type ) + + # Clear the moc_sources. + SET( moc_sources "" ) + + # If Qt is being used... + IF( QT_FOUND AND QT_LIBRARIES ) + # Setup QT to build a shared library. + IF( library_type MATCHES SHARED ) + ADD_DEFINITIONS( -DQT_SHARED ) + ENDIF( library_type MATCHES SHARED ) + + # Setup the moc sources. + IF( moc_headers ) + QT4_WRAP_CPP( moc_sources ${moc_headers} ) + ENDIF( moc_headers ) + ENDIF( QT_FOUND AND QT_LIBRARIES ) + + # Fatal error if moc_headers given but moc_sources not created. + IF( moc_headers AND NOT moc_sources ) + MESSAGE( FATAL_ERROR "Calling SETUP_LIBRARY() with MOC_HEADERS failed. " + "Make sure that you included \${QT_USE_FILE} prior to calling " + "SETUP_LIBRARY()." ) + ENDIF( moc_headers AND NOT moc_sources ) + + + IF( UNITY_BUILD ) + enable_unity_build( ${target} sources ) + ENDIF( UNITY_BUILD ) + + # Add the library. + ADD_LIBRARY( "${target}" ${library_type} ${sources} ${moc_sources} ) + + # Setup the debug_postfix. + SET_TARGET_PROPERTIES ( "${target}" PROPERTIES + DEBUG_POSTFIX "${debug_postfix}" ) + + # Link in the dependency libraries. + TARGET_LINK_LIBRARIES( "${target}" ${libraries} ) + + # If auto_install_headers, then set the headers to all .h files in the + # directory. + IF( auto_install_headers ) + FILE( GLOB install_headers *.h ) + ENDIF( auto_install_headers ) + + # Install the headers. + IF( install_headers ) + INSTALL( FILES ${install_headers} DESTINATION "${header_install_dir}" ) + ENDIF( install_headers ) + + # Install the library. + IF( NOT dont_install_library ) + INSTALL( TARGETS "${target}" + LIBRARY DESTINATION "${library_install_dir}" + ARCHIVE DESTINATION "${library_install_dir}" ) + ENDIF( NOT dont_install_library ) + +ENDMACRO( SETUP_LIBRARY ) + + +# SETUP_MODULE Macro +# Sets up to build a module (also setup as a Qt plugin if using Qt). A module is +# built as a shared library; however, modules are typically loaded dynamically +# rather than linked against. Therefore, this macro does not install header +# files and uses its own default install directory. The macro uses the following +# global variables to define default values (you may change these variables to +# change the defaults: +# DEFAULT_MODULE_INSTALL_DIR +# +# Usage: +# SETUP_MODULE( target +# SOURCES source1 [source2...] +# MOC_HEADERS header1 [header2...] +# LIBRARIES library1 [library2...] +# MODULE_INSTALL_DIR dir +# DEBUG_POSTFIX string +# DONT_INSTALL_MODULE ) +# +# Parameters: +# target The target module (built as a shared library). +# SOURCES Follow with the sources to compile. +# MOC_HEADERS Follow with the headers to moc (Requires Qt). +# LIBRARIES Follow with the libraries to link. +# MODULE_INSTALL_DIR Follow with the directory to install the module in +# (${DEFAULT_MODULE_INSTALL_DIR} by default). +# DEBUG_POSTFIX Follow with the postfix to use when building in +# debug mode (${CMAKE_DEBUG_POSTFIX} by default). +# DONT_INSTALL_MODULE If passed, the module will not be installed. +MACRO( SETUP_MODULE target ) + + # Setup the list headers. + SET( list_headers + SOURCES + MOC_HEADERS + LIBRARIES + MODULE_INSTALL_DIR + DEBUG_POSTFIX + ) + + # Setup the boolean headers. + SET( bool_headers + DONT_INSTALL_MODULE + ) + + # Parse the arguments into variables. + ARGUMENT_PARSER( "" "${list_headers}" "${bool_headers}" ${ARGN} ) + + # Set the default values for the module_install_dir and debug postfix. + IF( NOT "${ARGN}" MATCHES "(^|;)MODULE_INSTALL_DIR($|;)" ) + SET( module_install_dir ${DEFAULT_MODULE_INSTALL_DIR} ) + ENDIF( NOT "${ARGN}" MATCHES "(^|;)MODULE_INSTALL_DIR($|;)" ) + + IF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) + SET( debug_postfix ${CMAKE_DEBUG_POSTFIX} ) + ENDIF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) + + # Configure the module_install_dir so that ${target} may be used in it. + # Setting target to itself is REQUIRED for the configuration to work. + SET( target "${target}" ) + STRING( CONFIGURE "${module_install_dir}" module_install_dir ) + + # Clear the moc_sources. + SET( moc_sources "" ) + + # If Qt is being used... + IF( QT_FOUND AND QT_LIBRARIES ) + ADD_DEFINITIONS( -DQT_PLUGIN ) + + # Setup the moc sources. + IF( moc_headers ) + QT4_WRAP_CPP( moc_sources ${moc_headers} ) + ENDIF( moc_headers ) + ENDIF( QT_FOUND AND QT_LIBRARIES ) + + # Fatal error if moc_headers given but moc_sources not created. + IF( moc_headers AND NOT moc_sources ) + MESSAGE( FATAL_ERROR "Calling SETUP_MODULE() with MOC_HEADERS failed. " + "Make sure that you included \${QT_USE_FILE} prior to calling " + "SETUP_MODULE()." ) + ENDIF( moc_headers AND NOT moc_sources ) + + # Add the module (built as a shared library). + ADD_LIBRARY( "${target}" SHARED ${sources} ${moc_sources} ) + + # Setup the debug postfix. + SET_TARGET_PROPERTIES ( "${target}" PROPERTIES + DEBUG_POSTFIX "${debug_postfix}" ) + + # Link in the dependency libraries. + TARGET_LINK_LIBRARIES( "${target}" ${libraries} ) + + # Install the module. + IF( NOT dont_install_module ) + INSTALL( TARGETS "${target}" + LIBRARY DESTINATION "${module_install_dir}" ) + ENDIF( NOT dont_install_module ) + +ENDMACRO( SETUP_MODULE ) + + +# SETUP_EXECUTABLE Macro +# Sets up to build an executable target. The macro uses the following global +# variables to define default values (you may change these variables to change +# the defaults: +# DEFAULT_EXECUTABLE_INSTALL_DIR +# +# Usage: +# SETUP_EXECUTABLE( target +# SOURCES source1 [source2...] +# MOC_HEADERS header1 [header2...] +# LIBRARIES library1 [library2...] +# EXECUTABLE_INSTALL_DIR dir +# DEBUG_POSTFIX string +# DONT_INSTALL_EXECUTABLE ) +# +# Parameters: +# target The target executable. +# SOURCES Follow with the sources to compile. +# MOC_HEADERS Follow with the headers to moc (Requires Qt). +# LIBRARIES Follow with the libraries to link. +# EXECUTABLE_INSTALL_DIR Follow with the directory to install the +# executable in +# (${DEFAULT_EXECUTABLE_INSTALL_DIR} by default). +# DEBUG_POSTFIX Follow with the postfix to use when building in +# debug mode (${CMAKE_DEBUG_POSTFIX} by +# default). +# DONT_INSTALL_EXECUTABLE If passed, the executable will not be +# installed. +MACRO( SETUP_EXECUTABLE target ) + + # Setup the list headers. + SET( list_headers + SOURCES + MOC_HEADERS + LIBRARIES + EXECUTABLE_INSTALL_DIR + DEBUG_POSTFIX + ) + + # Setup the boolean headers. + SET( bool_headers + DONT_INSTALL_EXECUTABLE + ) + + # Parse the arguments into variables. + ARGUMENT_PARSER( "" "${list_headers}" "${bool_headers}" ${ARGN} ) + + # Set the default values for the executable_install_dir and debug postfix. + IF( NOT "${ARGN}" MATCHES "(^|;)EXECUTABLE_INSTALL_DIR($|;)" ) + SET( executable_install_dir ${DEFAULT_EXECUTABLE_INSTALL_DIR} ) + ENDIF( NOT "${ARGN}" MATCHES "(^|;)EXECUTABLE_INSTALL_DIR($|;)" ) + + IF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) + SET( debug_postfix ${CMAKE_DEBUG_POSTFIX} ) + ENDIF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) + + # Configure the executable_install_dir so that ${target} may be used in it. + # Setting target to itself is REQUIRED for the configuration to work. + SET( target "${target}" ) + STRING( CONFIGURE "${executable_install_dir}" executable_install_dir ) + + # Clear the moc_sources. + SET( moc_sources "" ) + + # If Qt is being used... + IF( QT_FOUND AND QT_LIBRARIES ) + ADD_DEFINITIONS( -DQT_SHARED ) + + # Setup the moc sources. + IF( moc_headers ) + QT4_WRAP_CPP( moc_sources ${moc_headers} ) + ENDIF( moc_headers ) + ENDIF( QT_FOUND AND QT_LIBRARIES ) + + # Fatal error if moc_headers given but moc_sources not created. + IF( moc_headers AND NOT moc_sources ) + MESSAGE( FATAL_ERROR "Calling SETUP_EXECUTABLE() with MOC_HEADERS failed. " + "Make sure that you included \${QT_USE_FILE} prior to calling " + "SETUP_EXECUTABLE()." ) + ENDIF( moc_headers AND NOT moc_sources ) + + # Add the executable. + ADD_EXECUTABLE( "${target}" ${sources} ${moc_sources} ) + + # Setup the debug postfix. + SET_TARGET_PROPERTIES ( "${target}" PROPERTIES + DEBUG_POSTFIX "${debug_postfix}" ) + + # Link in the dependency libraries. + TARGET_LINK_LIBRARIES( "${target}" ${libraries} ) + + # Install the executable. + IF( NOT dont_install_executable ) + INSTALL( TARGETS "${target}" RUNTIME DESTINATION + "${executable_install_dir}" ) + ENDIF( NOT dont_install_executable ) + +ENDMACRO( SETUP_EXECUTABLE ) diff --git a/libraries/libfc/CMakeModules/UseLibraryMacros.cmake b/libraries/libfc/CMakeModules/UseLibraryMacros.cmake new file mode 100644 index 0000000000..ed4ccd7908 --- /dev/null +++ b/libraries/libfc/CMakeModules/UseLibraryMacros.cmake @@ -0,0 +1,72 @@ +# This module defines macros that are useful for using libraries in a build. The +# macros in this module are typically used along with the FindDependencyMacros. + +# ADD_LIBRARY_TO_LIST Macro +# Adds a library to a list of libraries if it is found. Otherwise, reports an +# error. +# +# Usage: +# ADD_LIBRARY_TO_LIST( libraries found lib lib_name ) +# +# Parameters: +# libraries The list of libraries to add the library to. +# found Whether or not the library to add was found. +# lib The library to add to the list. +# lib_name The name of the library to add to the list. +MACRO( ADD_LIBRARY_TO_LIST libraries found lib lib_name ) + + # Setting found to itself is necessary for the conditional to work. + SET( found ${found} ) + + # IF found, then add the library to the list, else report an error. + IF( found ) + LIST( REMOVE_ITEM ${libraries} ${lib} ) + SET( ${libraries} ${${libraries}} ${lib} ) + ENDIF( found ) + IF( NOT found ) + MESSAGE( "Using ${lib_name} failed." ) + ENDIF( NOT found ) + +ENDMACRO( ADD_LIBRARY_TO_LIST ) + + +# USE_LIBRARY_GLOBALS Macro +# If ${prefix}_USE_${LIB} is true, then ${prefix}_${LIB}_LIBRARY will be added +# to ${prefix}_LIBRARIES (assuming the library was correctly found). All of the +# dependencies will also be added to ${prefix}_LIBRARIES. +# +# Usage: +# USE_LIBRARY_GLOBALS( prefix lib +# DEPS dependency1 [dependency2...] ) +# +# Parameters: +# prefix The prefix for the global variables. +# lib The library to try to use. +# DEPS Follow with the list of dependencies that should be added with +# the given library. +MACRO( USE_LIBRARY_GLOBALS prefix lib ) + + STRING( TOUPPER ${lib} upper ) + + # If the library should be used... + IF( ${prefix}_USE_${upper} ) + + # Parse the arguments into variables. + ARGUMENT_PARSER( "" "DEPS" "" ${ARGN} ) + + # Add the library to the list. + ADD_LIBRARY_TO_LIST( ${prefix}_LIBRARIES "${${prefix}_${upper}_FOUND}" + "${${prefix}_${upper}_LIBRARY}" ${lib} ) + + # For each of the library's dependencies. + FOREACH( dep_itr ${deps} ) + STRING( TOUPPER ${dep_itr} upper ) + + # Add the dependency to the list. + ADD_LIBRARY_TO_LIST( ${prefix}_LIBRARIES + "${${prefix}_${upper}_FOUND}" + "${${prefix}_${upper}_LIBRARY}" ${dep_itr} ) + ENDFOREACH( dep_itr ) + ENDIF( ${prefix}_USE_${upper} ) + +ENDMACRO( USE_LIBRARY_GLOBALS ) diff --git a/libraries/libfc/CMakeModules/VersionMacros.cmake b/libraries/libfc/CMakeModules/VersionMacros.cmake new file mode 100644 index 0000000000..6dd32c26a9 --- /dev/null +++ b/libraries/libfc/CMakeModules/VersionMacros.cmake @@ -0,0 +1,244 @@ +# This module defines several macros that are useful for handling version +# information. These macros work for version strings of format "#.#.#" +# representing major, minor, and patch integer components. + + +INCLUDE( ArgumentParser ) + + +# PARSE_VERSION_STR Macro +# This macro parses the version string information from a string. The macro +# parses the string for the given definitions followed by whitespace (or by ':' +# or '"' characters) and then version information. For example, passing +# "MyVersion" as a definition would properly retrieve the version from a string +# "containing the line "def MyVersion: 1.2.3". +# +# Usage: +# PARSE_VERSION_STR( version string definition [definition2...] ) +# +# Parameters: +# version The variable to store the version string in. +# string The string to parse. +# definition The definition(s) that may preceed the version string +# information. +MACRO( PARSE_VERSION_STR version string ) + + # Parse the arguments into variables. + ARGUMENT_PARSER( definitions "" "" ${ARGN} ) + + # For each of the given definitions... + FOREACH( def_itr ${definitions} ) + # If the version has not been found, then attempt to parse it. + IF( NOT ${version} ) + # Parse the version string. + STRING( REGEX MATCH "${def_itr}[ \t\":]+[0-9]+(.[0-9]+)?(.[0-9]+)?" + ${version} ${string} ) + + STRING( REGEX MATCH "[0-9]+(.[0-9]+)?(.[0-9]+)?" ${version} + "${${version}}" ) + + CORRECT_VERSION_STR( ${version} "${${version}}" ) + ENDIF( NOT ${version} ) + ENDFOREACH( def_itr ) + +ENDMACRO( PARSE_VERSION_STR ) + + +# PARSE_VERSION_INT Macro +# This macro parses the version integer component information from a string. The +# macro parses the string for the given definitions followed by whitespace (or +# by ':' or '"' characters) and then version information. For example, passing +# "MyVersionMajor" as a definition would properly retrieve the version from a +# string "containing the line "def MyVersionMajor: 1". +# +# Usage: +# PARSE_VERSION_INT( version string definition [definition2...] ) +# +# Parameters: +# version The variable to store the version integer component in. +# string The string to parse. +# definition The definition(s) that may preceed the version integer +# component information. +MACRO( PARSE_VERSION_INT version string ) + + # Parse the arguments into variables. + ARGUMENT_PARSER( definitions "" "" ${ARGN} ) + + # For each of the given definitions... + FOREACH( def_itr ${definitions} ) + # If the version has not been found, then attempt to parse it. + IF( NOT ${version} ) + # Parse the version string. + STRING( REGEX MATCH "${def_itr}[ \t\":]+[0-9]+" ${version} + ${string} ) + + STRING( REGEX MATCH "[0-9]+" ${version} "${${version}}" ) + ENDIF( NOT ${version} ) + ENDFOREACH( def_itr ) + +ENDMACRO( PARSE_VERSION_INT ) + + +# VERSION_STR_TO_INTS Macro +# This macro converts a version string into its three integer components. +# +# Usage: +# VERSION_STR_TO_INTS( major minor patch version ) +# +# Parameters: +# major The variable to store the major integer component in. +# minor The variable to store the minor integer component in. +# patch The variable to store the patch integer component in. +# version The version string to convert ("#.#.#" format). +MACRO( VERSION_STR_TO_INTS major minor patch version ) + + STRING( REGEX REPLACE "([0-9]+).[0-9]+.[0-9]+" "\\1" ${major} ${version} ) + STRING( REGEX REPLACE "[0-9]+.([0-9]+).[0-9]+" "\\1" ${minor} ${version} ) + STRING( REGEX REPLACE "[0-9]+.[0-9]+.([0-9]+)" "\\1" ${patch} ${version} ) + +ENDMACRO( VERSION_STR_TO_INTS ) + + +# VERSION_INTS_TO_STR Macro +# This macro converts three version integer components into a version string. +# +# Usage: +# VERSION_INTS_TO_STR( version major minor patch ) +# +# Parameters: +# version The variable to store the version string in. +# major The major version integer. +# minor The minor version integer. +# patch The patch version integer. +MACRO( VERSION_INTS_TO_STR version major minor patch ) + + SET( ${version} "${major}.${minor}.${patch}" ) + CORRECT_VERSION_STR( ${version} ${${version}} ) + +ENDMACRO( VERSION_INTS_TO_STR version major minor patch ) + + +# COMPARE_VERSION_STR Macro +# This macro compares two version strings to each other. The macro sets the +# result variable to -1 if lhs < rhs, 0 if lhs == rhs, and 1 if lhs > rhs. +# +# Usage: +# COMPARE_VERSION_STR( result lhs rhs ) +# +# Parameters: +# result The variable to store the result of the comparison in. +# lhs The version of the left hand side ("#.#.#" format). +# rhs The version of the right hand side ("#.#.#" format). +MACRO( COMPARE_VERSION_STR result lhs rhs ) + + VERSION_STR_TO_INTS( lhs_major lhs_minor lhs_patch ${lhs} ) + VERSION_STR_TO_INTS( rhs_major rhs_minor rhs_patch ${rhs} ) + + COMPARE_VERSION_INTS( ${result} + ${lhs_major} ${lhs_minor} ${lhs_patch} + ${rhs_major} ${rhs_minor} ${rhs_patch} ) + +ENDMACRO( COMPARE_VERSION_STR result lhs rhs ) + + +# COMPARE_VERSION_INTS Macro +# This macro compares two versions to each other using their integer components. +# The macro sets the result variable to -1 if lhs < rhs, 0 if lhs == rhs, and 1 +# if lhs > rhs. +# +# Usage: +# COMPARE_VERSION_INTS( result +# lhs_major lhs_minor lhs_patch +# rhs_major rhs_minor rhs_patch ) +# +# Parameters: +# result The variable to store the result of the comparison in. +# lhs_major The major integer component for the left hand side. +# lhs_minor The minor integer component for the left hand side. +# lhs_patch The patch integer component for the left hand side. +# rhs_major The major integer component for the right hand side. +# rhs_minor The minor integer component for the right hand side. +# rhs_patch The patch integer component for the right hand side. +MACRO( COMPARE_VERSION_INTS result lhs_major lhs_minor lhs_patch + rhs_major rhs_minor rhs_patch ) + + SET( ${result} 0 ) + IF( NOT ${result} AND ${lhs_major} LESS ${rhs_major} ) + SET( ${result} -1 ) + ENDIF( NOT ${result} AND ${lhs_major} LESS ${rhs_major} ) + IF( NOT ${result} AND ${lhs_major} GREATER ${rhs_major} ) + SET( ${result} 1 ) + ENDIF( NOT ${result} AND ${lhs_major} GREATER ${rhs_major} ) + + IF( NOT ${result} AND ${lhs_minor} LESS ${rhs_minor} ) + SET( ${result} -1 ) + ENDIF( NOT ${result} AND ${lhs_minor} LESS ${rhs_minor} ) + IF( NOT ${result} AND ${lhs_minor} GREATER ${rhs_minor} ) + SET( ${result} 1 ) + ENDIF( NOT ${result} AND ${lhs_minor} GREATER ${rhs_minor} ) + + IF( NOT ${result} AND ${lhs_patch} LESS ${rhs_patch} ) + SET( ${result} -1 ) + ENDIF( NOT ${result} AND ${lhs_patch} LESS ${rhs_patch} ) + IF( NOT ${result} AND ${lhs_patch} GREATER ${rhs_patch} ) + SET( ${result} 1 ) + ENDIF( NOT ${result} AND ${lhs_patch} GREATER ${rhs_patch} ) + +ENDMACRO( COMPARE_VERSION_INTS result lhs_major lhs_minor lhs_patch + rhs_major rhs_minor rhs_patch ) + + +# CORRECT_VERSION_STR Macro +# This macro corrects the version_str and stores the result in the version +# variable. If the version_str contains a version string in "#" or "#.#" format, +# then ".0" will be appended to the string to convert it to "#.#.#" format. If +# the version_str is invalid, then version will be set to "". +# +# Usage: +# CORRECT_VERSION_STR( version version_str ) +# +# Parameters: +# version The variable to store the corrected version string in. +# version_str The version string to correct. +MACRO( CORRECT_VERSION_STR version version_str ) + + SET( ${version} "${version_str}" ) + + # Add ".0" to the end of the version string in case a full "#.#.#" string + # was not given. + FOREACH( itr RANGE 2 ) + IF( NOT ${version} MATCHES "[0-9]+.[0-9]+.[0-9]+" ) + SET( ${version} "${${version}}.0" ) + ENDIF( NOT ${version} MATCHES "[0-9]+.[0-9]+.[0-9]+" ) + ENDFOREACH( itr ) + + # If the version string is not correct, then set it to "". + IF( NOT ${version} MATCHES "^[0-9]+.[0-9]+.[0-9]+$" ) + SET( ${version} "" ) + ENDIF( NOT ${version} MATCHES "^[0-9]+.[0-9]+.[0-9]+$" ) + +ENDMACRO( CORRECT_VERSION_STR ) + + + +# CORRECT_VERSION_Int Macro +# This macro corrects the version_int and stores the result in the version +# variable. If the version_int is invalid, then version will be set to "". +# +# Usage: +# CORRECT_VERSION_Int( version version_int ) +# +# Parameters: +# version The variable to store the corrected version integer +# component in. +# version_INT The version integer component to correct. +MACRO( CORRECT_VERSION_INT version version_int ) + + SET( ${version} "${version_int}" ) + + # If the version is not an integer, then set it to "". + IF( NOT ${version} MATCHES "^[0-9]+$" ) + SET( ${version} "" ) + ENDIF( NOT ${version} MATCHES "^[0-9]+$" ) + +ENDMACRO( CORRECT_VERSION_INT ) diff --git a/libraries/libfc/GitVersionGen/GetGitRevisionDescription.cmake.in b/libraries/libfc/GitVersionGen/GetGitRevisionDescription.cmake.in new file mode 100644 index 0000000000..888ce13aab --- /dev/null +++ b/libraries/libfc/GitVersionGen/GetGitRevisionDescription.cmake.in @@ -0,0 +1,38 @@ +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") + configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + set(HEAD_HASH "${HEAD_REF}") + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/libraries/libfc/LICENSE.txt b/libraries/libfc/LICENSE.txt new file mode 100644 index 0000000000..3689c742a7 --- /dev/null +++ b/libraries/libfc/LICENSE.txt @@ -0,0 +1,27 @@ +AntelopeIO/fc + +Copyright (c) 2021-2022 EOS Network Foundation (ENF) and its contributors. All rights reserved. +This ENF software is based upon: + +EOSIO/fc +Copyright (c) 2018, Respective Authors all rights reserved. + +The MIT License + +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/libraries/libfc/include/fc/actor.hpp b/libraries/libfc/include/fc/actor.hpp new file mode 100644 index 0000000000..920d742765 --- /dev/null +++ b/libraries/libfc/include/fc/actor.hpp @@ -0,0 +1,65 @@ +#pragma once +#include +#include + +namespace fc { + + namespace detail { + struct actor_member { + #if 1 // BOOST_NO_VARIADIC_TEMPLATES + #define RPC_MEMBER_FUNCTOR(z,n,IS_CONST) \ + template \ + static std::function( BOOST_PP_ENUM_PARAMS(n,A) ) > \ + functor( P p, R (C::*mem_func)(BOOST_PP_ENUM_PARAMS(n,A)) IS_CONST, fc::thread* c = 0) { \ + return [=](BOOST_PP_ENUM_BINARY_PARAMS(n,A,a))->fc::future{ \ + return c->async( [=](){ return (p->*mem_func)(BOOST_PP_ENUM_PARAMS(n,a)); } ); }; \ + } + BOOST_PP_REPEAT( 8, RPC_MEMBER_FUNCTOR, const ) + BOOST_PP_REPEAT( 8, RPC_MEMBER_FUNCTOR, BOOST_PP_EMPTY() ) + #undef RPC_MEMBER_FUNCTOR + + #else // g++ has a bug that prevents lambdas and varidic templates from working together (G++ Bug 41933) + + template + static std::function(Args...)> functor( P&& p, R (C::*mem_func)(Args...), fc::thread* c ) { + return [=](Args... args)->fc::future{ c->async( [=]()->R { return p->*mem_func( fc::forward(args)... ); } ) }; + } + template + static std::function(Args...)> functor( P&& p, R (C::*mem_func)(Args...)const, fc::thread* c ){ + return [=](Args... args)->fc::future{ c->async( [=]()->R { return p->*mem_func( fc::forward(args)... ); } ) }; + } + #endif + }; + + template + struct actor_vtable_visitor { + template + actor_vtable_visitor( fc::thread* t, U&& u ):_thread(t),_this( fc::forward(u) ){} + + template + void operator()( const char* name, Function& memb, MemberPtr m )const { + memb = actor_member::functor( _this, m, _thread ); + } + fc::thread* _thread; + ThisPtr _this; + }; + } + + /** + * Posts all method calls to another thread and + * returns a future. + */ + template + class actor : public api { + public: + actor(){} + + template + actor( InterfaceType* p, fc::thread* t = &fc::thread::current() ) + { + this->_vtable.reset(new detail::vtable() ); + this->_vtable->template visit( detail::actor_vtable_visitor(t, p) ); + } + }; + +} // namespace fc diff --git a/libraries/libfc/include/fc/aligned.hpp b/libraries/libfc/include/fc/aligned.hpp new file mode 100644 index 0000000000..b107ed15c9 --- /dev/null +++ b/libraries/libfc/include/fc/aligned.hpp @@ -0,0 +1,14 @@ +#pragma once +namespace fc { + + template + struct aligned { + union { + T _align; + char _data[S]; + } _store; + operator char*() { return _store._data; } + operator const char*()const { return _store._data; } + }; + +} diff --git a/libraries/libfc/include/fc/any.hpp b/libraries/libfc/include/fc/any.hpp new file mode 100644 index 0000000000..022d29eee8 --- /dev/null +++ b/libraries/libfc/include/fc/any.hpp @@ -0,0 +1,7 @@ +#pragma once +#include + +namespace fc { + // TODO: define this without using boost + typedef boost::any any; +} diff --git a/libraries/libfc/include/fc/api.hpp b/libraries/libfc/include/fc/api.hpp new file mode 100644 index 0000000000..06414b1148 --- /dev/null +++ b/libraries/libfc/include/fc/api.hpp @@ -0,0 +1,134 @@ +#pragma once +#include +#include +#include +#include + +// ms visual c++ (as of 2013) doesn't accept the standard syntax for calling a +// templated member function (foo->template bar();) +#ifdef _MSC_VER +# define FC_CALL_MEMBER_TEMPLATE_KEYWORD +#else +# define FC_CALL_MEMBER_TEMPLATE_KEYWORD template +#endif + +namespace fc { + struct identity_member { + template + static std::function functor( P&& p, R (C::*mem_func)(Args...) ); + template + static std::function functor( P&& p, R (C::*mem_func)(Args...)const ); + }; + + template< typename Interface, typename Transform > + struct vtable : public std::enable_shared_from_this> + { private: vtable(); }; + + template + struct vtable_copy_visitor { + typedef OtherType other_type; + + vtable_copy_visitor( OtherType& s):_source( s ){} + + template + void operator()( const char* name, std::function& memb, MemberPtr m )const { + OtherType* src = &_source; + memb = [src,m]( Args... args ){ return (src->*m)(args...); }; + } + OtherType& _source; + }; + + template + class api; + + class api_connection; + + typedef uint32_t api_id_type; + + class api_base + { + public: + api_base() {} + virtual ~api_base() {} + + virtual uint64_t get_handle()const = 0; + + virtual api_id_type register_api( api_connection& conn )const = 0; + + // defined in api_connection.hpp + template< typename T > + api as(); + }; + typedef std::shared_ptr< api_base > api_ptr; + + class api_connection; + + template + class api : public api_base { + public: + typedef vtable vtable_type; + + api():_vtable( std::make_shared() ) {} + + /** T is anything with pointer semantics */ + template + api( const T& p ) + :_vtable( std::make_shared() ) + { + _data = std::make_shared(p); + T& ptr = boost::any_cast(*_data); + auto& pointed_at = *ptr; + typedef typename std::remove_reference::type source_vtable_type; + _vtable->FC_CALL_MEMBER_TEMPLATE_KEYWORD visit_other( vtable_copy_visitor(pointed_at) ); + } + + api( const api& cpy ):_vtable(cpy._vtable),_data(cpy._data) {} + virtual ~api() {} + + friend bool operator == ( const api& a, const api& b ) { return a._data == b._data && a._vtable == b._vtable; } + friend bool operator != ( const api& a, const api& b ) { return !(a._data == b._data && a._vtable == b._vtable); } + virtual uint64_t get_handle()const override { return uint64_t(_data.get()); } + virtual api_id_type register_api( api_connection& conn )const override; // defined in api_connection.hpp + + vtable_type& operator*()const { FC_ASSERT( _vtable ); return *_vtable; } + vtable_type* operator->()const { FC_ASSERT( _vtable ); return _vtable.get(); } + + protected: + std::shared_ptr _vtable; + std::shared_ptr _data; + }; + +} // namespace fc + +#include +#include +#include +#include +#include +#include +#include + +#define FC_API_VTABLE_DEFINE_MEMBER( r, data, elem ) \ + decltype(Transform::functor( (data*)nullptr, &data::elem)) elem; +#define FC_API_VTABLE_DEFINE_VISIT_OTHER( r, data, elem ) \ + { typedef typename Visitor::other_type OtherType; \ + v( BOOST_PP_STRINGIZE(elem), elem, &OtherType::elem ); } +#define FC_API_VTABLE_DEFINE_VISIT( r, data, elem ) \ + v( BOOST_PP_STRINGIZE(elem), elem ); + +#define FC_API( CLASS, METHODS ) \ +namespace fc { \ + template \ + struct vtable : public std::enable_shared_from_this> { \ + BOOST_PP_SEQ_FOR_EACH( FC_API_VTABLE_DEFINE_MEMBER, CLASS, METHODS ) \ + template \ + void visit_other( Visitor&& v ){ \ + BOOST_PP_SEQ_FOR_EACH( FC_API_VTABLE_DEFINE_VISIT_OTHER, CLASS, METHODS ) \ + } \ + template \ + void visit( Visitor&& v ){ \ + BOOST_PP_SEQ_FOR_EACH( FC_API_VTABLE_DEFINE_VISIT, CLASS, METHODS ) \ + } \ + }; \ +} + diff --git a/libraries/libfc/include/fc/array.hpp b/libraries/libfc/include/fc/array.hpp new file mode 100644 index 0000000000..8106bfd098 --- /dev/null +++ b/libraries/libfc/include/fc/array.hpp @@ -0,0 +1,145 @@ +#pragma once +#include +#include +#include +#include + +namespace fc { + + /** + * Provides a fixed size array that is easier for templates to specialize + * against or overload than T[N]. + */ + template + class array { + public: + /** + * Checked indexing (when in debug build) that also simplifies dereferencing + * when you have an array*. + */ + ///@{ + T& at( size_t pos ) { FC_ASSERT( pos < N, "array out-of-bounds" ); return data[pos]; } + const T& at( size_t pos )const { FC_ASSERT( pos < N, "array out-of-bounds" ); return data[pos]; } + ///@} + + T& operator[]( size_t pos ) { FC_ASSERT( pos < N, "array out-of-bounds" ); return data[pos]; } + const T& operator[]( size_t pos )const { FC_ASSERT( pos < N, "array out-of-bounds" ); return data[pos]; } + + + const T* begin()const { return &data[0]; } + const T* end()const { return &data[N]; } + + T* begin() { return &data[0]; } + T* end() { return &data[N]; } + + size_t size()const { return N; } + + T data[N]; + }; + + /** provided for default 0 init */ + template + class array + { + public: + typedef unsigned char T; + array(){ memset( data, 0, sizeof(data) ); } + /** + * Checked indexing (when in debug build) that also simplifies dereferencing + * when you have an array*. + */ + ///@{ + T& at( size_t pos ) { FC_ASSERT( pos < N, "array out-of-bounds" ); return data[pos]; } + const T& at( size_t pos )const { FC_ASSERT( pos < N, "array out-of-bounds" ); return data[pos]; } + ///@} + + T* begin() { return &data[0]; } + const T* begin()const { return &data[0]; } + const T* end()const { return &data[N]; } + + size_t size()const { return N; } + + T data[N]; + }; + + /** provided for default 0 init */ + template + class array + { + public: + typedef char T; + array(){ memset( data, 0, sizeof(data) ); } + /** + * Checked indexing (when in debug build) that also simplifies dereferencing + * when you have an array*. + */ + ///@{ + T& at( size_t pos ) { FC_ASSERT( pos < N, "array out-of-bounds" ); return data[pos]; } + const T& at( size_t pos )const { FC_ASSERT( pos < N, "array out-of-bounds" ); return data[pos]; } + ///@} + + T* begin() { return &data[0]; } + const T* begin()const { return &data[0]; } + const T* end()const { return &data[N]; } + + size_t size()const { return N; } + + T data[N]; + }; + + template + bool operator == ( const array& a, const array& b ) + { return 0 == memcmp( a.data, b.data, N*sizeof(T) ); } + template + bool operator < ( const array& a, const array& b ) + { return memcmp( a.data, b.data, N*sizeof(T) ) < 0 ; } + + template + bool operator > ( const array& a, const array& b ) + { return memcmp( a.data, b.data, N*sizeof(T) ) > 0 ; } + + template + bool operator != ( const array& a, const array& b ) + { return 0 != memcmp( a.data, b.data, N*sizeof(T) ); } + + template + void to_variant( const array& bi, variant& v ) + { + v = std::vector( (const char*)&bi, ((const char*)&bi) + sizeof(bi) ); + } + template + void from_variant( const variant& v, array& bi ) + { + std::vector ve = v.as< std::vector >(); + if( ve.size() ) + { + memcpy(bi.begin(), ve.data(), fc::min(ve.size(),sizeof(bi)) ); + } + else + memset( bi.begin(), char(0), sizeof(bi) ); + } + + + template struct get_typename< fc::array > + { + static const char* name() + { + static std::string _name = std::string("fc::array<")+std::string(fc::get_typename::name())+","+ fc::to_string(N) + ">"; + return _name.c_str(); + } + }; +} + +#include +#include +namespace std +{ + template + struct hash > + { + size_t operator()( const fc::array& e )const + { + return fc::city_hash_size_t( (char*)&e, sizeof(e) ); + } + }; +} diff --git a/libraries/libfc/include/fc/bitutil.hpp b/libraries/libfc/include/fc/bitutil.hpp new file mode 100644 index 0000000000..4d6c3ab698 --- /dev/null +++ b/libraries/libfc/include/fc/bitutil.hpp @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace fc { + +inline uint64_t endian_reverse_u64( uint64_t x ) +{ + return (((x >> 0x38) & 0xFF) ) + | (((x >> 0x30) & 0xFF) << 0x08) + | (((x >> 0x28) & 0xFF) << 0x10) + | (((x >> 0x20) & 0xFF) << 0x18) + | (((x >> 0x18) & 0xFF) << 0x20) + | (((x >> 0x10) & 0xFF) << 0x28) + | (((x >> 0x08) & 0xFF) << 0x30) + | (((x ) & 0xFF) << 0x38) + ; +} + +inline uint32_t endian_reverse_u32( uint32_t x ) +{ + return (((x >> 0x18) & 0xFF) ) + | (((x >> 0x10) & 0xFF) << 0x08) + | (((x >> 0x08) & 0xFF) << 0x10) + | (((x ) & 0xFF) << 0x18) + ; +} + +} // namespace fc diff --git a/libraries/libfc/include/fc/bloom_filter.hpp b/libraries/libfc/include/fc/bloom_filter.hpp new file mode 100644 index 0000000000..67aa5cb03e --- /dev/null +++ b/libraries/libfc/include/fc/bloom_filter.hpp @@ -0,0 +1,621 @@ +#pragma once + +/* + ********************************************************************* + * * + * Open Bloom Filter * + * * + * Author: Arash Partow - 2000 * + * URL: http://www.partow.net * + * URL: http://www.partow.net/programming/hashfunctions/index.html * + * * + * Copyright notice: * + * Free use of the Open Bloom Filter Library is permitted under the * + * guidelines and in accordance with the most current version of the * + * Common Public License. * + * http://www.opensource.org/licenses/cpl1.0.php * + * * + ********************************************************************* +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace fc { + + +static const std::size_t bits_per_char = 0x08; // 8 bits in 1 char(unsigned) +static const unsigned char bit_mask[bits_per_char] = { + 0x01, //00000001 + 0x02, //00000010 + 0x04, //00000100 + 0x08, //00001000 + 0x10, //00010000 + 0x20, //00100000 + 0x40, //01000000 + 0x80 //10000000 + }; + +class bloom_parameters +{ +public: + + bloom_parameters() + : minimum_size(1), + maximum_size(std::numeric_limits::max()), + minimum_number_of_hashes(1), + maximum_number_of_hashes(std::numeric_limits::max()), + projected_element_count(10000), + false_positive_probability(1.0 / projected_element_count), + random_seed(0xA5A5A5A55A5A5A5AULL) + {} + + virtual ~bloom_parameters() + {} + + inline bool operator!() + { + return (minimum_size > maximum_size) || + (minimum_number_of_hashes > maximum_number_of_hashes) || + (minimum_number_of_hashes < 1) || + (0 == maximum_number_of_hashes) || + (0 == projected_element_count) || + (false_positive_probability < 0.0) || + (std::numeric_limits::infinity() == std::abs(false_positive_probability)) || + (0 == random_seed) || + (0xFFFFFFFFFFFFFFFFULL == random_seed); + } + + //Allowed min/max size of the bloom filter in bits + unsigned long long int minimum_size; + unsigned long long int maximum_size; + + //Allowed min/max number of hash functions + unsigned int minimum_number_of_hashes; + unsigned int maximum_number_of_hashes; + + //The approximate number of elements to be inserted + //into the bloom filter, should be within one order + //of magnitude. The default is 10000. + unsigned long long int projected_element_count; + + //The approximate false positive probability expected + //from the bloom filter. The default is the reciprocal + //of the projected_element_count. + double false_positive_probability; + + unsigned long long int random_seed; + + struct optimal_parameters_t + { + optimal_parameters_t() + : number_of_hashes(0), + table_size(0) + {} + + unsigned int number_of_hashes; + unsigned long long int table_size; + }; + + optimal_parameters_t optimal_parameters; + + virtual bool compute_optimal_parameters() + { + /* + Note: + The following will attempt to find the number of hash functions + and minimum amount of storage bits required to construct a bloom + filter consistent with the user defined false positive probability + and estimated element insertion count. + */ + + if (!(*this)) + return false; + + double min_m = std::numeric_limits::infinity(); + double min_k = 0.0; + double curr_m = 0.0; + double k = 1.0; + + while (k < 1000.0) + { + double numerator = (- k * projected_element_count); + double denominator = std::log(1.0 - std::pow(false_positive_probability, 1.0 / k)); + curr_m = numerator / denominator; + if (curr_m < min_m) + { + min_m = curr_m; + min_k = k; + } + k += 1.0; + } + + optimal_parameters_t& optp = optimal_parameters; + + optp.number_of_hashes = static_cast(min_k); + optp.table_size = static_cast(min_m); + optp.table_size += (((optp.table_size % bits_per_char) != 0) ? (bits_per_char - (optp.table_size % bits_per_char)) : 0); + + if (optp.number_of_hashes < minimum_number_of_hashes) + optp.number_of_hashes = minimum_number_of_hashes; + else if (optp.number_of_hashes > maximum_number_of_hashes) + optp.number_of_hashes = maximum_number_of_hashes; + + if (optp.table_size < minimum_size) + optp.table_size = minimum_size; + else if (optp.table_size > maximum_size) + optp.table_size = maximum_size; + + return true; + } + +}; + +class bloom_filter +{ +protected: + + typedef unsigned int bloom_type; + typedef unsigned char cell_type; + +public: + + bloom_filter() + : salt_count_(0), + table_size_(0), + raw_table_size_(0), + projected_element_count_(0), + inserted_element_count_(0), + random_seed_(0), + desired_false_positive_probability_(0.0) + {} + + bloom_filter(const bloom_parameters& p) + : projected_element_count_(p.projected_element_count), + inserted_element_count_(0), + random_seed_((p.random_seed * 0xA5A5A5A5) + 1), + desired_false_positive_probability_(p.false_positive_probability) + { + salt_count_ = p.optimal_parameters.number_of_hashes; + table_size_ = p.optimal_parameters.table_size; + generate_unique_salt(); + raw_table_size_ = table_size_ / bits_per_char; + + bit_table_.resize( static_cast(raw_table_size_) ); + //bit_table_ = new cell_type[static_cast(raw_table_size_)]; + std::fill_n(bit_table_.data(),raw_table_size_,0x00); + } + + bloom_filter(const bloom_filter& filter) + { + this->operator=(filter); + } + + inline bool operator == (const bloom_filter& f) const + { + if (this != &f) + { + return + (salt_count_ == f.salt_count_) && + (table_size_ == f.table_size_) && + (raw_table_size_ == f.raw_table_size_) && + (projected_element_count_ == f.projected_element_count_) && + (inserted_element_count_ == f.inserted_element_count_) && + (random_seed_ == f.random_seed_) && + (desired_false_positive_probability_ == f.desired_false_positive_probability_) && + (salt_ == f.salt_) && + std::equal(f.bit_table_.data(),f.bit_table_.data() + raw_table_size_,bit_table_.data()); + } + else + return true; + } + + inline bool operator != (const bloom_filter& f) const + { + return !operator==(f); + } + + inline bloom_filter& operator = (const bloom_filter& f) + { + if (this != &f) + { + salt_count_ = f.salt_count_; + table_size_ = f.table_size_; + raw_table_size_ = f.raw_table_size_; + projected_element_count_ = f.projected_element_count_; + inserted_element_count_ = f.inserted_element_count_; + random_seed_ = f.random_seed_; + desired_false_positive_probability_ = f.desired_false_positive_probability_; + bit_table_.resize( raw_table_size_ ); + std::copy(f.bit_table_.data(),f.bit_table_.data() + raw_table_size_,bit_table_.data()); + salt_ = f.salt_; + } + return *this; + } + + virtual ~bloom_filter() + { + } + + inline bool operator!() const + { + return (0 == table_size_); + } + + inline void clear() + { + std::fill_n(bit_table_.data(),raw_table_size_,0x00); + inserted_element_count_ = 0; + } + + inline void insert(const unsigned char* key_begin, const std::size_t& length) + { + std::size_t bit_index = 0; + std::size_t bit = 0; + for (std::size_t i = 0; i < salt_.size(); ++i) + { + compute_indices(hash_ap(key_begin,length,salt_[i]),bit_index,bit); + bit_table_[bit_index / bits_per_char] |= bit_mask[bit]; + } + ++inserted_element_count_; + } + + template + inline void insert(const T& t) + { + // Note: T must be a C++ POD type. + insert(reinterpret_cast(&t),sizeof(T)); + } + + inline void insert(const std::string& key) + { + insert(reinterpret_cast(key.c_str()),key.size()); + } + + inline void insert(const char* data, const std::size_t& length) + { + insert(reinterpret_cast(data),length); + } + + template + inline void insert(const InputIterator begin, const InputIterator end) + { + InputIterator itr = begin; + while (end != itr) + { + insert(*(itr++)); + } + } + + inline virtual bool contains(const unsigned char* key_begin, const std::size_t length) const + { + std::size_t bit_index = 0; + std::size_t bit = 0; + for (std::size_t i = 0; i < salt_.size(); ++i) + { + compute_indices(hash_ap(key_begin,length,salt_[i]),bit_index,bit); + if ((bit_table_[bit_index / bits_per_char] & bit_mask[bit]) != bit_mask[bit]) + { + return false; + } + } + return true; + } + + template + inline bool contains(const T& t) const + { + return contains(reinterpret_cast(&t),static_cast(sizeof(T))); + } + + inline bool contains(const std::string& key) const + { + return contains(reinterpret_cast(key.c_str()),key.size()); + } + + inline bool contains(const char* data, const std::size_t& length) const + { + return contains(reinterpret_cast(data),length); + } + + template + inline InputIterator contains_all(const InputIterator begin, const InputIterator end) const + { + InputIterator itr = begin; + while (end != itr) + { + if (!contains(*itr)) + { + return itr; + } + ++itr; + } + return end; + } + + template + inline InputIterator contains_none(const InputIterator begin, const InputIterator end) const + { + InputIterator itr = begin; + while (end != itr) + { + if (contains(*itr)) + { + return itr; + } + ++itr; + } + return end; + } + + inline virtual unsigned long long int size() const + { + return table_size_; + } + + inline std::size_t element_count() const + { + return inserted_element_count_; + } + + inline double effective_fpp() const + { + /* + Note: + The effective false positive probability is calculated using the + designated table size and hash function count in conjunction with + the current number of inserted elements - not the user defined + predicated/expected number of inserted elements. + */ + return std::pow(1.0 - std::exp(-1.0 * salt_.size() * inserted_element_count_ / size()), 1.0 * salt_.size()); + } + + inline bloom_filter& operator &= (const bloom_filter& f) + { + /* intersection */ + if ( + (salt_count_ == f.salt_count_) && + (table_size_ == f.table_size_) && + (random_seed_ == f.random_seed_) + ) + { + for (std::size_t i = 0; i < raw_table_size_; ++i) + { + bit_table_[i] &= f.bit_table_[i]; + } + } + return *this; + } + + inline bloom_filter& operator |= (const bloom_filter& f) + { + /* union */ + if ( + (salt_count_ == f.salt_count_) && + (table_size_ == f.table_size_) && + (random_seed_ == f.random_seed_) + ) + { + for (std::size_t i = 0; i < raw_table_size_; ++i) + { + bit_table_[i] |= f.bit_table_[i]; + } + } + return *this; + } + + inline bloom_filter& operator ^= (const bloom_filter& f) + { + /* difference */ + if ( + (salt_count_ == f.salt_count_) && + (table_size_ == f.table_size_) && + (random_seed_ == f.random_seed_) + ) + { + for (std::size_t i = 0; i < raw_table_size_; ++i) + { + bit_table_[i] ^= f.bit_table_[i]; + } + } + return *this; + } + + inline const cell_type* table() const + { + return bit_table_.data(); + } + + inline std::size_t hash_count() + { + return salt_.size(); + } + +protected: + + inline virtual void compute_indices(const bloom_type& hash, std::size_t& bit_index, std::size_t& bit) const + { + bit_index = hash % table_size_; + bit = bit_index % bits_per_char; + } + + void generate_unique_salt() + { + /* + Note: + A distinct hash function need not be implementation-wise + distinct. In the current implementation "seeding" a common + hash function with different values seems to be adequate. + */ + const unsigned int predef_salt_count = 128; + static const bloom_type predef_salt[predef_salt_count] = + { + 0xAAAAAAAA, 0x55555555, 0x33333333, 0xCCCCCCCC, + 0x66666666, 0x99999999, 0xB5B5B5B5, 0x4B4B4B4B, + 0xAA55AA55, 0x55335533, 0x33CC33CC, 0xCC66CC66, + 0x66996699, 0x99B599B5, 0xB54BB54B, 0x4BAA4BAA, + 0xAA33AA33, 0x55CC55CC, 0x33663366, 0xCC99CC99, + 0x66B566B5, 0x994B994B, 0xB5AAB5AA, 0xAAAAAA33, + 0x555555CC, 0x33333366, 0xCCCCCC99, 0x666666B5, + 0x9999994B, 0xB5B5B5AA, 0xFFFFFFFF, 0xFFFF0000, + 0xB823D5EB, 0xC1191CDF, 0xF623AEB3, 0xDB58499F, + 0xC8D42E70, 0xB173F616, 0xA91A5967, 0xDA427D63, + 0xB1E8A2EA, 0xF6C0D155, 0x4909FEA3, 0xA68CC6A7, + 0xC395E782, 0xA26057EB, 0x0CD5DA28, 0x467C5492, + 0xF15E6982, 0x61C6FAD3, 0x9615E352, 0x6E9E355A, + 0x689B563E, 0x0C9831A8, 0x6753C18B, 0xA622689B, + 0x8CA63C47, 0x42CC2884, 0x8E89919B, 0x6EDBD7D3, + 0x15B6796C, 0x1D6FDFE4, 0x63FF9092, 0xE7401432, + 0xEFFE9412, 0xAEAEDF79, 0x9F245A31, 0x83C136FC, + 0xC3DA4A8C, 0xA5112C8C, 0x5271F491, 0x9A948DAB, + 0xCEE59A8D, 0xB5F525AB, 0x59D13217, 0x24E7C331, + 0x697C2103, 0x84B0A460, 0x86156DA9, 0xAEF2AC68, + 0x23243DA5, 0x3F649643, 0x5FA495A8, 0x67710DF8, + 0x9A6C499E, 0xDCFB0227, 0x46A43433, 0x1832B07A, + 0xC46AFF3C, 0xB9C8FFF0, 0xC9500467, 0x34431BDF, + 0xB652432B, 0xE367F12B, 0x427F4C1B, 0x224C006E, + 0x2E7E5A89, 0x96F99AA5, 0x0BEB452A, 0x2FD87C39, + 0x74B2E1FB, 0x222EFD24, 0xF357F60C, 0x440FCB1E, + 0x8BBE030F, 0x6704DC29, 0x1144D12F, 0x948B1355, + 0x6D8FD7E9, 0x1C11A014, 0xADD1592F, 0xFB3C712E, + 0xFC77642F, 0xF9C4CE8C, 0x31312FB9, 0x08B0DD79, + 0x318FA6E7, 0xC040D23D, 0xC0589AA7, 0x0CA5C075, + 0xF874B172, 0x0CF914D5, 0x784D3280, 0x4E8CFEBC, + 0xC569F575, 0xCDB2A091, 0x2CC016B4, 0x5C5F4421 + }; + + if (salt_count_ <= predef_salt_count) + { + std::copy(predef_salt, + predef_salt + salt_count_, + std::back_inserter(salt_)); + for (unsigned int i = 0; i < salt_.size(); ++i) + { + /* + Note: + This is done to integrate the user defined random seed, + so as to allow for the generation of unique bloom filter + instances. + */ + salt_[i] = salt_[i] * salt_[(i + 3) % salt_.size()] + static_cast(random_seed_); + } + } + else + { + std::copy(predef_salt,predef_salt + predef_salt_count,std::back_inserter(salt_)); + srand(static_cast(random_seed_)); + while (salt_.size() < salt_count_) + { + bloom_type current_salt = static_cast(rand()) * static_cast(rand()); + if (0 == current_salt) continue; + if (salt_.end() == std::find(salt_.begin(), salt_.end(), current_salt)) + { + salt_.push_back(current_salt); + } + } + } + } + + inline bloom_type hash_ap(const unsigned char* begin, std::size_t remaining_length, bloom_type hash) const + { + const unsigned char* itr = begin; + unsigned int loop = 0; + while (remaining_length >= 8) + { + const unsigned int& i1 = *(reinterpret_cast(itr)); itr += sizeof(unsigned int); + const unsigned int& i2 = *(reinterpret_cast(itr)); itr += sizeof(unsigned int); + hash ^= (hash << 7) ^ i1 * (hash >> 3) ^ + (~((hash << 11) + (i2 ^ (hash >> 5)))); + remaining_length -= 8; + } + if (remaining_length) + { + if (remaining_length >= 4) + { + const unsigned int& i = *(reinterpret_cast(itr)); + if (loop & 0x01) + hash ^= (hash << 7) ^ i * (hash >> 3); + else + hash ^= (~((hash << 11) + (i ^ (hash >> 5)))); + ++loop; + remaining_length -= 4; + itr += sizeof(unsigned int); + } + if (remaining_length >= 2) + { + const unsigned short& i = *(reinterpret_cast(itr)); + if (loop & 0x01) + hash ^= (hash << 7) ^ i * (hash >> 3); + else + hash ^= (~((hash << 11) + (i ^ (hash >> 5)))); + ++loop; + remaining_length -= 2; + itr += sizeof(unsigned short); + } + if (remaining_length) + { + hash += ((*itr) ^ (hash * 0xA5A5A5A5)) + loop; + } + } + return hash; + } + +public: + std::vector salt_; + std::vector bit_table_; + unsigned int salt_count_; + unsigned long long int table_size_; + unsigned long long int raw_table_size_; + unsigned long long int projected_element_count_; + unsigned int inserted_element_count_; + unsigned long long int random_seed_; + double desired_false_positive_probability_; +}; + +inline bloom_filter operator & (const bloom_filter& a, const bloom_filter& b) +{ + bloom_filter result = a; + result &= b; + return result; +} + +inline bloom_filter operator | (const bloom_filter& a, const bloom_filter& b) +{ + bloom_filter result = a; + result |= b; + return result; +} + +inline bloom_filter operator ^ (const bloom_filter& a, const bloom_filter& b) +{ + bloom_filter result = a; + result ^= b; + return result; +} + + +} // namespace fc + + +FC_REFLECT( fc::bloom_filter, (salt_)(bit_table_)(salt_count_)(table_size_)(raw_table_size_)(projected_element_count_)(inserted_element_count_)(random_seed_)(desired_false_positive_probability_) ) +FC_REFLECT( fc::bloom_parameters::optimal_parameters_t, (number_of_hashes)(table_size) ) +FC_REFLECT( fc::bloom_parameters, (minimum_size)(maximum_size)(minimum_number_of_hashes)(maximum_number_of_hashes)(projected_element_count)(false_positive_probability)(random_seed)(optimal_parameters) ) + +/* + Note 1: + If it can be guaranteed that bits_per_char will be of the form 2^n then + the following optimization can be used: + + hash_table[bit_index >> n] |= bit_mask[bit_index & (bits_per_char - 1)]; + + Note 2: + For performance reasons where possible when allocating memory it should + be aligned (aligned_alloc) according to the architecture being used. +*/ diff --git a/libraries/libfc/include/fc/compress/smaz.hpp b/libraries/libfc/include/fc/compress/smaz.hpp new file mode 100644 index 0000000000..ad60292fe5 --- /dev/null +++ b/libraries/libfc/include/fc/compress/smaz.hpp @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace fc { + + std::string smaz_compress( const std::string& in ); + std::string smaz_decompress( const std::string& compressed ); + +} // namespace fc diff --git a/libraries/libfc/include/fc/compress/zlib.hpp b/libraries/libfc/include/fc/compress/zlib.hpp new file mode 100644 index 0000000000..10a3a0e6c2 --- /dev/null +++ b/libraries/libfc/include/fc/compress/zlib.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace fc +{ + + string zlib_compress(const string& in); + +} // namespace fc diff --git a/libraries/libfc/include/fc/container/container_detail.hpp b/libraries/libfc/include/fc/container/container_detail.hpp new file mode 100644 index 0000000000..4798851367 --- /dev/null +++ b/libraries/libfc/include/fc/container/container_detail.hpp @@ -0,0 +1,160 @@ +#pragma once + +#include +#include + +namespace fc { + + namespace raw { + + namespace detail { + + template class Set, typename Stream, typename T, typename... U> + inline void pack_set( Stream& s, const Set& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + pack( s, unsigned_int((uint32_t)value.size()) ); + for( const auto& item : value ) { + pack( s, item ); + } + } + + template class Set, typename Stream, typename T, typename... U> + inline void unpack_set( Stream& s, Set& value ) { + unsigned_int size; unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.clear(); + for( uint32_t i = 0; i < size.value; ++i ) { + auto tmp = ::fc::detail::construct_maybe_with_allocator( value.get_allocator() ); + unpack( s, tmp ); + value.insert( value.end(), std::move(tmp) ); + } + } + + template class Set, typename Stream, typename T, typename... U> + inline void unpack_flat_set( Stream& s, Set& value ) { + unsigned_int size; unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.clear(); + value.reserve( size.value ); + for( uint32_t i = 0; i < size.value; ++i ) { + auto tmp = ::fc::detail::construct_maybe_with_allocator( value.get_allocator() ); + unpack( s, tmp ); + value.insert( value.end(), std::move(tmp) ); + } + } + + template class Map, typename Stream, typename K, typename V, typename... U> + inline void pack_map( Stream& s, const Map& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + pack( s, unsigned_int((uint32_t)value.size()) ); + for( const auto& item : value ) { + pack( s, item ); + } + } + + template class Map, typename Stream, typename K, typename V, typename... U> + inline void unpack_map( Stream& s, Map& value ) { + unsigned_int size; unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.clear(); + for( uint32_t i = 0; i < size.value; ++i ) { + auto tmp = ::fc::detail::default_construct_pair_maybe_with_allocator( value.get_allocator() ); + unpack( s, tmp ); + value.insert( value.end(), std::move(tmp) ); + } + } + + template class Map, typename Stream, typename K, typename V, typename... U> + inline void unpack_flat_map( Stream& s, Map& value ) { + unsigned_int size; unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.clear(); + value.reserve( size.value ); + for( uint32_t i = 0; i < size.value; ++i ) { + auto tmp = ::fc::detail::default_construct_pair_maybe_with_allocator( value.get_allocator() ); + unpack( s, tmp ); + value.insert( value.end(), std::move(tmp) ); + } + } + + } + + } + + namespace detail { + + template class Set, typename T, typename... U> + void to_variant_from_set( const Set& s, fc::variant& vo ) { + FC_ASSERT( s.size() <= MAX_NUM_ARRAY_ELEMENTS ); + variants vars; + vars.reserve( s.size() ); + for( const auto& item : s ) { + vars.emplace_back( item ); + } + vo = std::move( vars ); + } + + template class Set, typename T, typename... U> + void from_variant_to_set( const fc::variant& v, Set< T, U... >& s ) { + const variants& vars = v.get_array(); + FC_ASSERT( vars.size() <= MAX_NUM_ARRAY_ELEMENTS ); + s.clear(); + for( const auto& var : vars ) { + auto item = construct_maybe_with_allocator( s.get_allocator() ); + var.as( item ); + s.insert( s.end(), std::move(item) ); + } + } + + template class Set, typename T, typename... U> + void from_variant_to_flat_set( const fc::variant& v, Set< T, U... >& s ) { + const variants& vars = v.get_array(); + FC_ASSERT( vars.size() <= MAX_NUM_ARRAY_ELEMENTS ); + s.clear(); + s.reserve( vars.size() ); + for( const auto& var : vars ) { + auto item = construct_maybe_with_allocator( s.get_allocator() ); + var.as( item ); + s.insert( s.end(), std::move(item) ); + } + } + + template class Map, typename K, typename V, typename... U > + void to_variant_from_map( const Map< K, V, U... >& m, fc::variant& vo ) { + FC_ASSERT( m.size() <= MAX_NUM_ARRAY_ELEMENTS ); + variants vars; + vars.reserve( m.size() ); + for( const auto& item : m ) { + vars.emplace_back( item ); + } + vo = std::move( vars ); + } + + template class Map, typename K, typename V, typename... U> + void from_variant_to_map( const variant& v, Map& m ) { + const variants& vars = v.get_array(); + FC_ASSERT( vars.size() <= MAX_NUM_ARRAY_ELEMENTS ); + m.clear(); + for( const auto& var : vars ) { + auto item = default_construct_pair_maybe_with_allocator( m.get_allocator() ); + var.as< std::pair >( item ); + m.insert( m.end(), std::move(item) ); + } + } + + template class Map, typename K, typename V, typename... U> + void from_variant_to_flat_map( const variant& v, Map& m ) { + const variants& vars = v.get_array(); + FC_ASSERT( vars.size() <= MAX_NUM_ARRAY_ELEMENTS ); + m.clear(); + m.reserve( vars.size() ); + for( const auto& var : vars ) { + auto item = default_construct_pair_maybe_with_allocator( m.get_allocator() ); + var.as< std::pair >( item ); + m.insert( m.end(), std::move(item) ); + } + } + + } + +} diff --git a/libraries/libfc/include/fc/container/deque.hpp b/libraries/libfc/include/fc/container/deque.hpp new file mode 100644 index 0000000000..6a05dc8ff5 --- /dev/null +++ b/libraries/libfc/include/fc/container/deque.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + +namespace fc { + namespace raw { + + + } // namespace raw + +} // namespace fc diff --git a/libraries/libfc/include/fc/container/deque_fwd.hpp b/libraries/libfc/include/fc/container/deque_fwd.hpp new file mode 100644 index 0000000000..80359ea67c --- /dev/null +++ b/libraries/libfc/include/fc/container/deque_fwd.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace fc { + + namespace raw { + template + void pack( Stream& s, const std::deque& value ); + template + void unpack( Stream& s, std::deque& value ); + } +} // namespace fc diff --git a/libraries/libfc/include/fc/container/flat.hpp b/libraries/libfc/include/fc/container/flat.hpp new file mode 100644 index 0000000000..d9569c3daa --- /dev/null +++ b/libraries/libfc/include/fc/container/flat.hpp @@ -0,0 +1,186 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace fc { + + namespace raw { + + template + void pack( Stream& s, const boost::container::vector& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + pack( s, unsigned_int((uint32_t)value.size()) ); + if( !std::is_fundamental::value ) { + for( const auto& item : value ) { + pack( s, item ); + } + } else if( value.size() ) { + s.write( (const char*)value.data(), value.size() ); + } + } + + template + void unpack( Stream& s, boost::container::vector& value ) { + unsigned_int size; + unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.clear(); + value.resize( size.value ); + if( !std::is_fundamental::value ) { + for( auto& item : value ) { + unpack( s, item ); + } + } else if( value.size() ) { + s.read( (char*)value.data(), value.size() ); + } + } + + template + void pack( Stream& s, const boost::container::vector& value ) { + FC_ASSERT( value.size() <= MAX_SIZE_OF_BYTE_ARRAYS ); + pack( s, unsigned_int((uint32_t)value.size()) ); + if( value.size() ) + s.write( (const char*)value.data(), value.size() ); + } + + template + void unpack( Stream& s, boost::container::vector& value ) { + unsigned_int size; + unpack( s, size ); + FC_ASSERT( size.value <= MAX_SIZE_OF_BYTE_ARRAYS ); + value.clear(); + value.resize( size.value ); + if( value.size() ) + s.read( (char*)value.data(), value.size() ); + } + + template + void pack( Stream& s, const flat_set& value ) { + detail::pack_set( s, value ); + } + + template + void unpack( Stream& s, flat_set& value ) { + detail::unpack_flat_set( s, value ); + } + + template + void pack( Stream& s, const flat_multiset& value ) { + detail::pack_set( s, value ); + } + + template + void unpack( Stream& s, flat_multiset& value ) { + detail::unpack_flat_set( s, value ); + } + + template + void pack( Stream& s, const flat_map& value ) { + detail::pack_map( s, value ); + } + + template + void unpack( Stream& s, flat_map& value ) { + detail::unpack_flat_map( s, value ); + } + + template + void pack( Stream& s, const flat_multimap& value ) { + detail::pack_map( s, value ); + } + + template + void unpack( Stream& s, flat_multimap& value ) { + detail::unpack_flat_map( s, value ); + } + + } // namespace raw + + template + void to_variant( const boost::container::vector& vec, fc::variant& vo ) { + FC_ASSERT( vec.size() <= MAX_NUM_ARRAY_ELEMENTS ); + variants vars; + vars.reserve( vec.size() ); + for( const auto& item : vec ) { + vars.emplace_back( item ); + } + vo = std::move(vars); + } + + template + void from_variant( const fc::variant& v, boost::container::vector& vec ) { + const variants& vars = v.get_array(); + FC_ASSERT( vars.size() <= MAX_NUM_ARRAY_ELEMENTS ); + vec.clear(); + vec.resize( vars.size() ); + for( uint32_t i = 0; i < vars.size(); ++i ) { + from_variant( vars[i], vec[i] ); + } + } + + template + void to_variant( const boost::container::vector& vec, fc::variant& vo ) { + FC_ASSERT( vec.size() <= MAX_SIZE_OF_BYTE_ARRAYS ); + if( vec.size() ) + vo = variant( fc::to_hex( vec.data(), vec.size() ) ); + else + vo = ""; + } + + template + void from_variant( const fc::variant& v, boost::container::vector& vec ) + { + const auto& str = v.get_string(); + FC_ASSERT( str.size() <= 2*MAX_SIZE_OF_BYTE_ARRAYS ); // Doubled because hex strings needs two characters per byte + vec.resize( str.size() / 2 ); + if( vec.size() ) { + size_t r = fc::from_hex( str, vec.data(), vec.size() ); + FC_ASSERT( r == vec.size() ); + } + } + + template + void to_variant( const flat_set< T, U... >& s, fc::variant& vo ) { + detail::to_variant_from_set( s, vo ); + } + + template + void from_variant( const fc::variant& v, flat_set< T, U... >& s ) { + detail::from_variant_to_flat_set( v, s ); + } + + template + void to_variant( const flat_multiset< T, U... >& s, fc::variant& vo ) { + detail::to_variant_from_set( s, vo ); + } + + template + void from_variant( const fc::variant& v, flat_multiset< T, U... >& s ) { + detail::from_variant_to_flat_set( v, s ); + } + + template + void to_variant( const flat_map< K, V, U... >& m, fc::variant& vo ) { + detail::to_variant_from_map( m, vo ); + } + + template + void from_variant( const variant& v, flat_map& m ) { + detail::from_variant_to_flat_map( v, m ); + } + + template + void to_variant( const flat_multimap< K, V, U... >& m, fc::variant& vo ) { + detail::to_variant_from_map( m, vo ); + } + + template + void from_variant( const variant& v, flat_multimap& m ) { + detail::from_variant_to_flat_map( v, m ); + } + +} diff --git a/libraries/libfc/include/fc/container/flat_fwd.hpp b/libraries/libfc/include/fc/container/flat_fwd.hpp new file mode 100644 index 0000000000..3c70561833 --- /dev/null +++ b/libraries/libfc/include/fc/container/flat_fwd.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include // only added to maintain backwards compatibility with existing consumers of fc (can eventually remove) + +namespace fc { + + using boost::container::flat_set; + using boost::container::flat_multiset; + using boost::container::flat_map; + using boost::container::flat_multimap; + + namespace raw { + + template + void pack( Stream& s, const boost::container::vector& value ); + template + void unpack( Stream& s, boost::container::vector& value ); + + template + void pack( Stream& s, const flat_set& value ); + template + void unpack( Stream& s, flat_set& value ); + + template + void pack( Stream& s, const flat_multiset& value ); + template + void unpack( Stream& s, flat_multiset& value ); + + template + void pack( Stream& s, const flat_map& value ); + template + void unpack( Stream& s, flat_map& value ) ; + + template + void pack( Stream& s, const flat_multimap& value ); + template + void unpack( Stream& s, flat_multimap& value ) ; + + } // namespace raw + +} // fc diff --git a/libraries/libfc/include/fc/container/tracked_storage.hpp b/libraries/libfc/include/fc/container/tracked_storage.hpp new file mode 100644 index 0000000000..b7e62003fc --- /dev/null +++ b/libraries/libfc/include/fc/container/tracked_storage.hpp @@ -0,0 +1,124 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace fc { + + /** + * Specialize tracked::memory_size() if obj does not provide a memory_size() method that represents memory size. + */ + namespace tracked { + template + size_t memory_size( const T& obj ) { + return obj.memory_size(); + } + } + + /** + * @class tracked_storage + * @brief tracks the size of storage allocated to its underlying multi_index + * + * This class wraps a multi_index container and tracks the memory allocated as + * the container creates, modifies, and deletes. It also provides read and write + * methods for persistence. + * + * Requires ContainerType::value_type to have a size() method that represents the + * memory used for that object or specialized tracked::size() and is required to + * be a pack/unpack-able type. + */ + + template + class tracked_storage { + private: + size_t _memory_size = 0; + ContainerType _index; + public: + typedef typename ContainerType::template nth_index<0>::type primary_index_type; + + tracked_storage() = default; + + // read in the contents of a persisted tracked_storage and prevent reading in + // more stored objects once max_memory is reached. + // returns true if entire persisted tracked_storage was read + bool read(fc::cfile_datastream& ds, size_t max_memory) { + auto container_size = _index.size(); + fc::raw::unpack(ds, container_size); + for (size_t i = 0; i < container_size; ++i) { + if (memory_size() >= max_memory) { + return false; + } + typename ContainerType::value_type v; + fc::raw::unpack(ds, v); + insert(std::move(v)); + } + + return true; + } + + void write(fc::cfile& dat_content) const { + const auto container_size = _index.size(); + dat_content.write( reinterpret_cast(&container_size), sizeof(container_size) ); + + for (const auto& item : _index) { + auto data = fc::raw::pack(item); + dat_content.write(data.data(), data.size()); + } + } + + std::pair insert(typename ContainerType::value_type obj) { + const auto size = tracked::memory_size(obj); + auto result = _index.insert(std::move(obj)); + if (result.second) { + _memory_size += size; + } + return result; + } + + template + typename primary_index_type::iterator find(const Key& key) { + primary_index_type& primary_idx = _index.template get<0>(); + return primary_idx.find(key); + } + + template + typename primary_index_type::const_iterator find(const Key& key) const { + const primary_index_type& primary_idx = _index.template get<0>(); + return primary_idx.find(key); + } + + template + void modify(typename primary_index_type::iterator itr, Lam lam) { + _memory_size -= tracked::memory_size(*itr); + if (_index.modify( itr, std::move(lam))) { + _memory_size += tracked::memory_size(*itr); + } + } + + template + void erase(const Key& key) { + auto itr = _index.find(key); + if (itr == _index.end()) + return; + + _memory_size -= tracked::memory_size(*itr); + _index.erase(itr); + } + + void erase(typename primary_index_type::iterator itr) { + _memory_size -= tracked::memory_size(*itr); + _index.erase(itr); + } + + size_t memory_size() const { + return _memory_size; + } + + const ContainerType& index() const { + return _index; + } + }; +} diff --git a/libraries/libfc/include/fc/crypto/aes.hpp b/libraries/libfc/include/fc/crypto/aes.hpp new file mode 100644 index 0000000000..92b65f6767 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/aes.hpp @@ -0,0 +1,54 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace fc { + class path; + + class aes_encoder + { + public: + void init( const fc::sha256& key, const fc::uint128& init_value ); + uint32_t encode( const char* plaintxt, uint32_t len, char* ciphertxt ); + // uint32_t final_encode( char* ciphertxt ); + + private: + struct impl; + fc::fwd my; + }; + class aes_decoder + { + public: + void init( const fc::sha256& key, const fc::uint128& init_value ); + uint32_t decode( const char* ciphertxt, uint32_t len, char* plaintext ); +// uint32_t final_decode( char* plaintext ); + + private: + struct impl; + fc::fwd my; + }; + + unsigned aes_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, + unsigned char *iv, unsigned char *ciphertext); + unsigned aes_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, + unsigned char *iv, unsigned char *plaintext); + unsigned aes_cfb_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, + unsigned char *iv, unsigned char *plaintext); + + std::vector aes_encrypt( const fc::sha512& key, const std::vector& plain_text ); + std::vector aes_decrypt( const fc::sha512& key, const std::vector& cipher_text ); + + /** encrypts plain_text and then includes a checksum that enables us to verify the integrety of + * the file / key prior to decryption. + */ + void aes_save( const fc::path& file, const fc::sha512& key, std::vector plain_text ); + + /** + * recovers the plain_text saved via aes_save() + */ + std::vector aes_load( const fc::path& file, const fc::sha512& key ); + +} // namespace fc diff --git a/libraries/libfc/include/fc/crypto/alt_bn128.hpp b/libraries/libfc/include/fc/crypto/alt_bn128.hpp new file mode 100644 index 0000000000..46368d7f41 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/alt_bn128.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace fc { + using bytes = std::vector; + + enum class alt_bn128_error : int32_t { + operand_component_invalid, + operand_not_in_curve, + pairing_list_size_error, + operand_outside_g2, + input_len_error, + invalid_scalar_size + }; + + std::variant alt_bn128_add(const bytes& op1, const bytes& op2); + std::variant alt_bn128_mul(const bytes& g1_point, const bytes& scalar); + std::variant alt_bn128_pair(const bytes& g1_g2_pairs, const yield_function_t& yield); + +} // fc diff --git a/libraries/libfc/include/fc/crypto/base32.hpp b/libraries/libfc/include/fc/crypto/base32.hpp new file mode 100644 index 0000000000..15bf169826 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/base32.hpp @@ -0,0 +1,10 @@ +#pragma once +#include +#include + +namespace fc +{ + std::vector from_base32( const fc::string& b32 ); + fc::string to_base32( const std::vector& vec ); + fc::string to_base32( const char* data, size_t len ); +} diff --git a/libraries/libfc/include/fc/crypto/base36.hpp b/libraries/libfc/include/fc/crypto/base36.hpp new file mode 100644 index 0000000000..aeb9c352c9 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/base36.hpp @@ -0,0 +1,10 @@ +#pragma once +#include +#include + +namespace fc +{ + std::vector from_base36( const fc::string& b36 ); + fc::string to_base36( const std::vector& vec ); + fc::string to_base36( const char* data, size_t len ); +} diff --git a/libraries/libfc/include/fc/crypto/base58.hpp b/libraries/libfc/include/fc/crypto/base58.hpp new file mode 100644 index 0000000000..ebf94d7830 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/base58.hpp @@ -0,0 +1,11 @@ +#pragma once +#include +#include +#include + +namespace fc { + std::string to_base58( const char* d, size_t s, const fc::yield_function_t& yield ); + std::string to_base58( const std::vector& data, const fc::yield_function_t& yield ); + std::vector from_base58( const std::string& base58_str ); + size_t from_base58( const std::string& base58_str, char* out_data, size_t out_data_len ); +} diff --git a/libraries/libfc/include/fc/crypto/base64.hpp b/libraries/libfc/include/fc/crypto/base64.hpp new file mode 100644 index 0000000000..9559214df1 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/base64.hpp @@ -0,0 +1,14 @@ +#pragma once +#include + +namespace fc { +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len); +inline std::string base64_encode(char const* bytes_to_encode, unsigned int in_len) { return base64_encode( (unsigned char const*)bytes_to_encode, in_len); } +std::string base64_encode( const std::string& enc ); +std::string base64_decode( const std::string& encoded_string); + +std::string base64url_encode(unsigned char const* bytes_to_encode, unsigned int in_len); +inline std::string base64url_encode(char const* bytes_to_encode, unsigned int in_len) { return base64url_encode( (unsigned char const*)bytes_to_encode, in_len); } +std::string base64url_encode( const std::string& enc ); +std::string base64url_decode( const std::string& encoded_string); +} // namespace fc diff --git a/libraries/libfc/include/fc/crypto/bigint.hpp b/libraries/libfc/include/fc/crypto/bigint.hpp new file mode 100644 index 0000000000..b91631a1e9 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/bigint.hpp @@ -0,0 +1,76 @@ +#pragma once +#include +#include +#include + +struct bignum_st; +typedef bignum_st BIGNUM; + +namespace fc { + class bigint { + public: + bigint( const std::vector& bige ); + bigint( const char* bige, uint32_t l ); + bigint(uint64_t value); + bigint( ); + bigint( const bigint& c ); + bigint( bigint&& c ); + explicit bigint( BIGNUM* n ); + ~bigint(); + + bigint& operator = ( const bigint& a ); + bigint& operator = ( bigint&& a ); + + explicit operator bool()const; + + bool is_negative()const; + int64_t to_int64()const; + + int64_t log2()const; + bigint exp( const bigint& c )const; + + static bigint random( uint32_t bits, int t, int ); + + bool operator < ( const bigint& c )const; + bool operator > ( const bigint& c )const; + bool operator >= ( const bigint& c )const; + bool operator == ( const bigint& c )const; + bool operator != ( const bigint& c )const; + + bigint operator + ( const bigint& a )const; + bigint operator * ( const bigint& a )const; + bigint operator / ( const bigint& a )const; + bigint operator % ( const bigint& a )const; + bigint operator /= ( const bigint& a ); + bigint operator *= ( const bigint& a ); + bigint& operator += ( const bigint& a ); + bigint& operator -= ( const bigint& a ); + bigint& operator <<= ( uint32_t i ); + bigint& operator >>= ( uint32_t i ); + bigint operator - ( const bigint& a )const; + + + bigint operator++(int); + bigint& operator++(); + bigint operator--(int); + bigint& operator--(); + + operator fc::string()const; + + // returns bignum as bigendian bytes + operator std::vector()const; + + BIGNUM* dup()const; + + BIGNUM* get()const { return n; } + private: + BIGNUM* n; + }; + + class variant; + /** encodes the big int as base64 string, or a number */ + void to_variant( const bigint& bi, variant& v ); + /** decodes the big int as base64 string, or a number */ + void from_variant( const variant& v, bigint& bi ); +} // namespace fc + diff --git a/libraries/libfc/include/fc/crypto/blake2.hpp b/libraries/libfc/include/fc/crypto/blake2.hpp new file mode 100644 index 0000000000..43e45d981e --- /dev/null +++ b/libraries/libfc/include/fc/crypto/blake2.hpp @@ -0,0 +1,42 @@ +// Snark - Wrapper for alt_bn128 add mul pair and modexp + +#pragma once + +#include +#include +#include +#include +#include + +namespace fc { + using bytes = std::vector; + + enum class blake2b_error : int32_t { + input_len_error + }; + + std::variant blake2b(uint32_t _rounds, const bytes& _h, const bytes& _m, const bytes& _t0_offset, const bytes& _t1_offset, bool _f, const yield_function_t& yield); + + typedef struct { + uint64_t h[8] = {0,0,0,0,0,0,0,0}; + uint64_t t[2] = {0,0}; + uint64_t f[1] = {0}; + } blake2b_state; + + class blake2b_wrapper { + public: + enum blake2b_constant { BLAKE2B_BLOCKBYTES = 128 }; + void blake2b_compress(blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES], size_t r, const yield_function_t& yield ); + + private: + uint64_t m[16]; + uint64_t v[16]; + size_t i; + + inline void G(uint8_t r, uint8_t i, uint64_t& a, uint64_t& b, uint64_t& c, uint64_t& d) noexcept; + inline void ROUND(uint8_t r) noexcept; + + void blake2b_compress_init(blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES], size_t r); + void blake2b_compress_end(blake2b_state *S); + }; +} diff --git a/libraries/libfc/include/fc/crypto/blowfish.hpp b/libraries/libfc/include/fc/crypto/blowfish.hpp new file mode 100644 index 0000000000..fcda4e56b6 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/blowfish.hpp @@ -0,0 +1,178 @@ +//////////////////////////////////////////////////////////////////////////// +/// +// Blowfish.h Header File +// +// BLOWFISH ENCRYPTION ALGORITHM +// +// encryption and decryption of Byte Strings using the Blowfish encryption Algorithm. +// Blowfish is a block cipher that encrypts data in 8-byte blocks. The algorithm consists +// of two parts: a key-expansion part and a data-ancryption part. Key expansion converts a +// variable key of at least 1 and at most 56 bytes into several subkey arrays totaling +// 4168 bytes. Blowfish has 16 rounds. Each round consists of a key-dependent permutation, +// and a key and data-dependent substitution. All operations are XORs and additions on 32-bit words. +// The only additional operations are four indexed array data lookups per round. +// Blowfish uses a large number of subkeys. These keys must be precomputed before any data +// encryption or decryption. The P-array consists of 18 32-bit subkeys: P0, P1,...,P17. +// There are also four 32-bit S-boxes with 256 entries each: S0,0, S0,1,...,S0,255; +// S1,0, S1,1,...,S1,255; S2,0, S2,1,...,S2,255; S3,0, S3,1,...,S3,255; +// +// The Electronic Code Book (ECB), Cipher Block Chaining (CBC) and Cipher Feedback modes +// are used: +// +// In ECB mode if the same block is encrypted twice with the same key, the resulting +// ciphertext blocks are the same. +// +// In CBC Mode a ciphertext block is obtained by first xoring the +// plaintext block with the previous ciphertext block, and encrypting the resulting value. +// +// In CFB mode a ciphertext block is obtained by encrypting the previous ciphertext block +// and xoring the resulting value with the plaintext +// +// The previous ciphertext block is usually stored in an Initialization Vector (IV). +// An Initialization Vector of zero is commonly used for the first block, though other +// arrangements are also in use. + +/* +http://www.counterpane.com/vectors.txt +Test vectors by Eric Young. These tests all assume Blowfish with 16 +rounds. + +All data is shown as a hex string with 012345 loading as +data[0]=0x01; +data[1]=0x23; +data[2]=0x45; +ecb test data (taken from the DES validation tests) + +key bytes clear bytes cipher bytes +0000000000000000 0000000000000000 4EF997456198DD78 +FFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFF 51866FD5B85ECB8A +3000000000000000 1000000000000001 7D856F9A613063F2 ??? +1111111111111111 1111111111111111 2466DD878B963C9D +0123456789ABCDEF 1111111111111111 61F9C3802281B096 +1111111111111111 0123456789ABCDEF 7D0CC630AFDA1EC7 +0000000000000000 0000000000000000 4EF997456198DD78 +FEDCBA9876543210 0123456789ABCDEF 0ACEAB0FC6A0A28D +7CA110454A1A6E57 01A1D6D039776742 59C68245EB05282B +0131D9619DC1376E 5CD54CA83DEF57DA B1B8CC0B250F09A0 +07A1133E4A0B2686 0248D43806F67172 1730E5778BEA1DA4 +3849674C2602319E 51454B582DDF440A A25E7856CF2651EB +04B915BA43FEB5B6 42FD443059577FA2 353882B109CE8F1A +0113B970FD34F2CE 059B5E0851CF143A 48F4D0884C379918 +0170F175468FB5E6 0756D8E0774761D2 432193B78951FC98 +43297FAD38E373FE 762514B829BF486A 13F04154D69D1AE5 +07A7137045DA2A16 3BDD119049372802 2EEDDA93FFD39C79 +04689104C2FD3B2F 26955F6835AF609A D887E0393C2DA6E3 +37D06BB516CB7546 164D5E404F275232 5F99D04F5B163969 +1F08260D1AC2465E 6B056E18759F5CCA 4A057A3B24D3977B +584023641ABA6176 004BD6EF09176062 452031C1E4FADA8E +025816164629B007 480D39006EE762F2 7555AE39F59B87BD +49793EBC79B3258F 437540C8698F3CFA 53C55F9CB49FC019 +4FB05E1515AB73A7 072D43A077075292 7A8E7BFA937E89A3 +49E95D6D4CA229BF 02FE55778117F12A CF9C5D7A4986ADB5 +018310DC409B26D6 1D9D5C5018F728C2 D1ABB290658BC778 +1C587F1C13924FEF 305532286D6F295A 55CB3774D13EF201 +0101010101010101 0123456789ABCDEF FA34EC4847B268B2 +1F1F1F1F0E0E0E0E 0123456789ABCDEF A790795108EA3CAE +E0FEE0FEF1FEF1FE 0123456789ABCDEF C39E072D9FAC631D +0000000000000000 FFFFFFFFFFFFFFFF 014933E0CDAFF6E4 +FFFFFFFFFFFFFFFF 0000000000000000 F21E9A77B71C49BC +0123456789ABCDEF 0000000000000000 245946885754369A +FEDCBA9876543210 FFFFFFFFFFFFFFFF 6B5C5A9C5D9E0A5A + +set_key test data +data[8]= FEDCBA9876543210 +c=F9AD597C49DB005E k[ 1]=F0 +c=E91D21C1D961A6D6 k[ 2]=F0E1 +c=E9C2B70A1BC65CF3 k[ 3]=F0E1D2 +c=BE1E639408640F05 k[ 4]=F0E1D2C3 +c=B39E44481BDB1E6E k[ 5]=F0E1D2C3B4 +c=9457AA83B1928C0D k[ 6]=F0E1D2C3B4A5 +c=8BB77032F960629D k[ 7]=F0E1D2C3B4A596 +c=E87A244E2CC85E82 k[ 8]=F0E1D2C3B4A59687 +c=15750E7A4F4EC577 k[ 9]=F0E1D2C3B4A5968778 +c=122BA70B3AB64AE0 k[10]=F0E1D2C3B4A596877869 +c=3A833C9AFFC537F6 k[11]=F0E1D2C3B4A5968778695A +c=9409DA87A90F6BF2 k[12]=F0E1D2C3B4A5968778695A4B +c=884F80625060B8B4 k[13]=F0E1D2C3B4A5968778695A4B3C +c=1F85031C19E11968 k[14]=F0E1D2C3B4A5968778695A4B3C2D +c=79D9373A714CA34F k[15]=F0E1D2C3B4A5968778695A4B3C2D1E ??? +c=93142887EE3BE15C k[16]=F0E1D2C3B4A5968778695A4B3C2D1E0F +c=03429E838CE2D14B k[17]=F0E1D2C3B4A5968778695A4B3C2D1E0F00 +c=A4299E27469FF67B k[18]=F0E1D2C3B4A5968778695A4B3C2D1E0F0011 +c=AFD5AED1C1BC96A8 k[19]=F0E1D2C3B4A5968778695A4B3C2D1E0F001122 +c=10851C0E3858DA9F k[20]=F0E1D2C3B4A5968778695A4B3C2D1E0F00112233 +c=E6F51ED79B9DB21F k[21]=F0E1D2C3B4A5968778695A4B3C2D1E0F0011223344 +c=64A6E14AFD36B46F k[22]=F0E1D2C3B4A5968778695A4B3C2D1E0F001122334455 +c=80C7D7D45A5479AD k[23]=F0E1D2C3B4A5968778695A4B3C2D1E0F00112233445566 +c=05044B62FA52D080 k[24]=F0E1D2C3B4A5968778695A4B3C2D1E0F0011223344556677 + +chaining mode test data +key[16] = 0123456789ABCDEFF0E1D2C3B4A59687 +iv[8] = FEDCBA9876543210 +data[29] = "7654321 Now is the time for " (includes trailing '\0') +data[29] = 37363534333231204E6F77206973207468652074696D6520666F722000 +cbc cipher text +cipher[32]= 6B77B4D63006DEE605B156E27403979358DEB9E7154616D959F1652BD5FF92CC +cfb64 cipher text cipher[29]= +E73214A2822139CAF26ECF6D2EB9E76E3DA3DE04D1517200519D57A6C3 +ofb64 cipher text cipher[29]= +E73214A2822139CA62B343CC5B65587310DD908D0C241B2263C2CF80DA + +*/ + +#pragma once +#include + +namespace fc { + +//Block Structure +struct sblock +{ + //Constructors + sblock(unsigned int l=0, unsigned int r=0) : m_uil(l), m_uir(r) {} + //Copy Constructor + sblock(const sblock& roBlock) : m_uil(roBlock.m_uil), m_uir(roBlock.m_uir) {} + sblock& operator^=(sblock& b) { m_uil ^= b.m_uil; m_uir ^= b.m_uir; return *this; } + unsigned int m_uil, m_uir; +}; + +class blowfish +{ +public: + enum { ECB=0, CBC=1, CFB=2 }; + + //Constructor - Initialize the P and S boxes for a given Key + blowfish(); + void start(unsigned char* ucKey, uint64_t n, const sblock& roChain = sblock(0UL,0UL)); + + //Resetting the chaining block + void reset_chain() { m_oChain = m_oChain0; } + + // encrypt/decrypt Buffer in Place + void encrypt(unsigned char* buf, uint64_t n, int iMode=CFB); + void decrypt(unsigned char* buf, uint64_t n, int iMode=CFB); + + // encrypt/decrypt from Input Buffer to Output Buffer + void encrypt(const unsigned char* in, unsigned char* out, uint64_t n, int iMode=CFB); + void decrypt(const unsigned char* in, unsigned char* out, uint64_t n, int iMode=CFB); + +//Private Functions +private: + unsigned int F(unsigned int ui); + void encrypt(sblock&); + void decrypt(sblock&); + +private: + //The Initialization Vector, by default {0, 0} + sblock m_oChain0; + sblock m_oChain; + unsigned int m_auiP[18]; + unsigned int m_auiS[4][256]; + static const unsigned int scm_auiInitP[18]; + static const unsigned int scm_auiInitS[4][256]; +}; + + +} // namespace fc + + diff --git a/libraries/libfc/include/fc/crypto/city.hpp b/libraries/libfc/include/fc/crypto/city.hpp new file mode 100644 index 0000000000..7047ae8be7 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/city.hpp @@ -0,0 +1,76 @@ +// Copyright (c) 2011 Google, Inc. +// +// 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. +// +// CityHash, by Geoff Pike and Jyrki Alakuijala +// +// This file provides a few functions for hashing strings. On x86-64 +// hardware in 2011, CityHash64() is faster than other high-quality +// hash functions, such as Murmur. This is largely due to higher +// instruction-level parallelism. CityHash64() and CityHash128() also perform +// well on hash-quality tests. +// +// CityHash128() is optimized for relatively long strings and returns +// a 128-bit hash. For strings more than about 2000 bytes it can be +// faster than CityHash64(). +// +// Functions in the CityHash family are not suitable for cryptography. +// +// WARNING: This code has not been tested on big-endian platforms! +// It is known to work well on little-endian platforms that have a small penalty +// for unaligned reads, such as current Intel and AMD moderate-to-high-end CPUs. +// +// By the way, for some hash functions, given strings a and b, the hash +// of a+b is easily derived from the hashes of a and b. This property +// doesn't hold for any hash functions in this file. +#pragma once + +#include // for size_t. +#include +#include + +namespace fc { + +template +class array; +class uint128; + +// Hash function for a byte array. +uint64_t city_hash64(const char *buf, size_t len); + +uint32_t city_hash32(const char *buf, size_t len); + +#if SIZE_MAX > UINT32_MAX +inline size_t city_hash_size_t(const char *buf, size_t len) { return city_hash64(buf, len); } +#else +inline size_t city_hash_size_t(const char *buf, size_t len) { return city_hash32(buf, len); } +#endif + +// Hash function for a byte array. +uint128 city_hash128(const char *s, size_t len); + +// Hash function for a byte array. +uint64_t city_hash_crc_64(const char *buf, size_t len); + +// Hash function for a byte array. +uint128 city_hash_crc_128(const char *s, size_t len); +array city_hash_crc_256(const char *s, size_t len); + + +} // namespace fc diff --git a/libraries/libfc/include/fc/crypto/common.hpp b/libraries/libfc/include/fc/crypto/common.hpp new file mode 100644 index 0000000000..2be0f4dc3a --- /dev/null +++ b/libraries/libfc/include/fc/crypto/common.hpp @@ -0,0 +1,198 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace fc { namespace crypto { + template + struct checksummed_data { + checksummed_data() {} + uint32_t check = 0; + DataType data; + + static auto calculate_checksum(const DataType& data, const char *prefix = nullptr) { + auto encoder = ripemd160::encoder(); + raw::pack(encoder, data); + + if (prefix != nullptr) { + encoder.write(prefix, const_strlen(prefix)); + } + return encoder.result()._hash[0]; + } + }; + + inline bool prefix_matches(const char* prefix, const std::string& str) { + auto prefix_len = const_strlen(prefix); + return str.size() > prefix_len && str.substr(0, prefix_len).compare(prefix) == 0; + } + + template + struct base58_str_parser_impl; + + template + struct base58_str_parser_impl { + static Result apply(const std::string& prefix_str, const std::string& data_str) + { + using data_type = typename KeyType::data_type; + using wrapper = checksummed_data; + constexpr auto prefix = Prefixes[Position]; + + if (prefix == prefix_str) { + auto bin = fc::from_base58(data_str); + fc::datastream unpacker(bin.data(), bin.size()); + wrapper wrapped; + fc::raw::unpack(unpacker, wrapped); + FC_ASSERT(!unpacker.remaining(), "decoded base58 length too long"); + auto checksum = wrapper::calculate_checksum(wrapped.data, prefix); + FC_ASSERT(checksum == wrapped.check); + return Result(KeyType(wrapped.data)); + } + + return base58_str_parser_impl::apply(prefix_str, data_str); + } + }; + + template + struct base58_str_parser_impl { + static Result apply(const std::string& prefix_str, const std::string& data_str ) { + FC_ASSERT(false, "No matching suite type for ${prefix}_${data}", ("prefix", prefix_str)("data",data_str)); + } + }; + + template + struct base58_str_parser; + + /** + * Destructure a variant and call the parse_base58str on it + * @tparam Ts + * @param base58str + * @return + */ + template + struct base58_str_parser, Prefixes> { + static std::variant apply(const std::string& base58str) { + const auto pivot = base58str.find('_'); + FC_ASSERT(pivot != std::string::npos, "No delimiter in data, cannot determine suite type: ${str}", ("str", base58str)); + + const auto prefix_str = base58str.substr(0, pivot); + auto data_str = base58str.substr(pivot + 1); + FC_ASSERT(!data_str.empty(), "Data only has suite type prefix: ${str}", ("str", base58str)); + + return base58_str_parser_impl, Prefixes, 0, Ts...>::apply(prefix_str, data_str); + } + }; + + template + struct base58str_visitor : public fc::visitor { + explicit base58str_visitor( const fc::yield_function_t& yield ) + : _yield(yield) {}; + template< typename KeyType > + std::string operator()( const KeyType& key ) const { + using data_type = typename KeyType::data_type; + constexpr int position = fc::get_index(); + constexpr bool is_default = position == DefaultPosition; + + checksummed_data wrapper; + wrapper.data = key.serialize(); + _yield(); + wrapper.check = checksummed_data::calculate_checksum(wrapper.data, !is_default ? Prefixes[position] : nullptr); + _yield(); + auto packed = raw::pack( wrapper ); + _yield(); + auto data_str = to_base58( packed.data(), packed.size(), _yield ); + _yield(); + if (!is_default) { + data_str = string(Prefixes[position]) + "_" + data_str; + } + _yield(); + + return data_str; + } + const fc::yield_function_t _yield; + }; + + template + struct eq_comparator { + static bool apply(const T& a, const T& b) { + return a.serialize() == b.serialize(); + } + }; + + template + struct eq_comparator> { + using variant_type = std::variant; + struct visitor : public fc::visitor { + visitor(const variant_type &b) + : _b(b) {} + + template + bool operator()(const KeyType &a) const { + const auto &b = std::template get(_b); + return eq_comparator::apply(a,b); + } + + const variant_type &_b; + }; + + static bool apply(const variant_type& a, const variant_type& b) { + return a.index() == b.index() && std::visit(visitor(b), a); + } + }; + + template + struct less_comparator { + static bool apply(const T& a, const T& b) { + return a.serialize() < b.serialize(); + } + }; + + template + struct less_comparator> { + using variant_type = std::variant; + struct visitor : public fc::visitor { + visitor(const variant_type &b) + : _b(b) {} + + template + bool operator()(const KeyType &a) const { + const auto &b = std::template get(_b); + return less_comparator::apply(a,b); + } + + const variant_type &_b; + }; + + static bool apply(const variant_type& a, const variant_type& b) { + return a.index() < b.index() || (a.index() == b.index() && std::visit(visitor(b), a)); + } + }; + + template + struct shim { + using data_type = Data; + + shim() + {} + + shim(data_type&& data) + :_data(forward(data)) + {} + + shim(const data_type& data) + :_data(data) + {} + + const data_type& serialize() const { + return _data; + } + + data_type _data; + }; + +} } + +FC_REFLECT_TEMPLATE((typename T), fc::crypto::checksummed_data, (data)(check) ) +FC_REFLECT_TEMPLATE((typename T), fc::crypto::shim, (_data) ) diff --git a/libraries/libfc/include/fc/crypto/dh.hpp b/libraries/libfc/include/fc/crypto/dh.hpp new file mode 100644 index 0000000000..8eaa46f985 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/dh.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include + +namespace fc { + + struct diffie_hellman { + diffie_hellman():valid(0),g(5){} + bool generate_params( int s, uint8_t g ); + bool generate_pub_key(); + bool compute_shared_key( const char* buf, uint32_t s ); + bool compute_shared_key( const std::vector& pubk); + bool validate(); + + std::vector p; + std::vector pub_key; + std::vector priv_key; + std::vector shared_key; + bool valid; + uint8_t g; + }; + +} // namespace fc + + diff --git a/libraries/libfc/include/fc/crypto/digest.hpp b/libraries/libfc/include/fc/crypto/digest.hpp new file mode 100644 index 0000000000..6777bc7af2 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/digest.hpp @@ -0,0 +1,15 @@ +#pragma once +#include +#include +#include + +namespace fc { + + template + fc::sha256 digest( const T& value ) + { + fc::sha256::encoder enc; + fc::raw::pack( enc, value ); + return enc.result(); + } +} diff --git a/libraries/libfc/include/fc/crypto/elliptic.hpp b/libraries/libfc/include/fc/crypto/elliptic.hpp new file mode 100644 index 0000000000..c16e645a4a --- /dev/null +++ b/libraries/libfc/include/fc/crypto/elliptic.hpp @@ -0,0 +1,239 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + + namespace ecc { + namespace detail + { + class public_key_impl; + class private_key_impl; + } + + typedef fc::sha256 blind_factor_type; + typedef fc::array commitment_type; + typedef fc::array public_key_data; + typedef fc::sha256 private_key_secret; + typedef fc::array public_key_point_data; ///< the full non-compressed version of the ECC point + typedef fc::array signature; + typedef fc::array compact_signature; + typedef fc::array extended_key_data; + typedef fc::sha256 blinded_hash; + typedef fc::sha256 blind_signature; + + /** + * @class public_key + * @brief contains only the public point of an elliptic curve key. + */ + class public_key + { + public: + public_key(); + public_key(const public_key& k); + ~public_key(); +// bool verify( const fc::sha256& digest, const signature& sig ); + public_key_data serialize()const; + public_key_point_data serialize_ecc_point()const; + + operator public_key_data()const { return serialize(); } + + + public_key( const public_key_data& v ); + public_key( const public_key_point_data& v ); + public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical = true ); + + public_key child( const fc::sha256& offset )const; + + bool valid()const; + + public_key( public_key&& pk ); + public_key& operator=( public_key&& pk ); + public_key& operator=( const public_key& pk ); + + inline friend bool operator==( const public_key& a, const public_key& b ) + { + return a.serialize() == b.serialize(); + } + inline friend bool operator!=( const public_key& a, const public_key& b ) + { + return a.serialize() != b.serialize(); + } + + /// Allows to convert current public key object into base58 number. + std::string to_base58() const; + static std::string to_base58( const public_key_data &key ); + static public_key from_base58( const std::string& b58 ); + + unsigned int fingerprint() const; + + private: + friend class private_key; + static public_key from_key_data( const public_key_data& v ); + static bool is_canonical( const compact_signature& c ); + fc::fwd my; + }; + + /** + * @class private_key + * @brief an elliptic curve private key. + */ + class private_key + { + public: + private_key(); + private_key( private_key&& pk ); + private_key( const private_key& pk ); + ~private_key(); + + private_key& operator=( private_key&& pk ); + private_key& operator=( const private_key& pk ); + + static private_key generate(); + static private_key regenerate( const fc::sha256& secret ); + + private_key child( const fc::sha256& offset )const; + + /** + * This method of generation enables creating a new private key in a deterministic manner relative to + * an initial seed. A public_key created from the seed can be multiplied by the offset to calculate + * the new public key without having to know the private key. + */ + static private_key generate_from_seed( const fc::sha256& seed, const fc::sha256& offset = fc::sha256() ); + + private_key_secret get_secret()const; // get the private key secret + + operator private_key_secret ()const { return get_secret(); } + + /** + * Given a public key, calculatse a 512 bit shared secret between that + * key and this private key. + */ + fc::sha512 get_shared_secret( const public_key& pub )const; + +// signature sign( const fc::sha256& digest )const; + compact_signature sign_compact( const fc::sha256& digest, bool require_canonical = true )const; +// bool verify( const fc::sha256& digest, const signature& sig ); + + public_key get_public_key()const; + + inline friend bool operator==( const private_key& a, const private_key& b ) + { + return a.get_secret() == b.get_secret(); + } + inline friend bool operator!=( const private_key& a, const private_key& b ) + { + return a.get_secret() != b.get_secret(); + } + inline friend bool operator<( const private_key& a, const private_key& b ) + { + return a.get_secret() < b.get_secret(); + } + + unsigned int fingerprint() const { return get_public_key().fingerprint(); } + + private: + private_key( EC_KEY* k ); + static fc::sha256 get_secret( const EC_KEY * const k ); + fc::fwd my; + }; + + /** + * Shims + */ + struct public_key_shim : public crypto::shim { + using crypto::shim::shim; + + bool valid()const { + return public_key(_data).valid(); + } + }; + + struct signature_shim : public crypto::shim { + using public_key_type = public_key_shim; + using crypto::shim::shim; + + public_key_type recover(const sha256& digest, bool check_canonical) const { + return public_key_type(public_key(_data, digest, check_canonical).serialize()); + } + }; + + struct private_key_shim : public crypto::shim { + using crypto::shim::shim; + using signature_type = signature_shim; + using public_key_type = public_key_shim; + + signature_type sign( const sha256& digest, bool require_canonical = true ) const + { + return signature_type(private_key::regenerate(_data).sign_compact(digest, require_canonical)); + } + + public_key_type get_public_key( ) const + { + return public_key_type(private_key::regenerate(_data).get_public_key().serialize()); + } + + sha512 generate_shared_secret( const public_key_type &pub_key ) const + { + return private_key::regenerate(_data).get_shared_secret(public_key(pub_key.serialize())); + } + + static private_key_shim generate() + { + return private_key_shim(private_key::generate().get_secret()); + } + + }; + + } // namespace ecc + void to_variant( const ecc::private_key& var, variant& vo ); + void from_variant( const variant& var, ecc::private_key& vo ); + void to_variant( const ecc::public_key& var, variant& vo ); + void from_variant( const variant& var, ecc::public_key& vo ); + + namespace raw + { + template + void unpack( Stream& s, fc::ecc::public_key& pk) + { + ecc::public_key_data ser; + fc::raw::unpack(s,ser); + pk = fc::ecc::public_key( ser ); + } + + template + void pack( Stream& s, const fc::ecc::public_key& pk) + { + fc::raw::pack( s, pk.serialize() ); + } + + template + void unpack( Stream& s, fc::ecc::private_key& pk) + { + fc::sha256 sec; + unpack( s, sec ); + pk = ecc::private_key::regenerate(sec); + } + + template + void pack( Stream& s, const fc::ecc::private_key& pk) + { + fc::raw::pack( s, pk.get_secret() ); + } + + } // namespace raw + +} // namespace fc +#include + +FC_REFLECT_TYPENAME( fc::ecc::private_key ) +FC_REFLECT_TYPENAME( fc::ecc::public_key ) +FC_REFLECT_DERIVED( fc::ecc::public_key_shim, (fc::crypto::shim), BOOST_PP_SEQ_NIL ) +FC_REFLECT_DERIVED( fc::ecc::signature_shim, (fc::crypto::shim), BOOST_PP_SEQ_NIL ) +FC_REFLECT_DERIVED( fc::ecc::private_key_shim, (fc::crypto::shim), BOOST_PP_SEQ_NIL ) diff --git a/libraries/libfc/include/fc/crypto/elliptic_r1.hpp b/libraries/libfc/include/fc/crypto/elliptic_r1.hpp new file mode 100644 index 0000000000..1ada2a9d64 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/elliptic_r1.hpp @@ -0,0 +1,228 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + + namespace crypto { namespace r1 { + namespace detail + { + class public_key_impl; + class private_key_impl; + } + + typedef fc::array public_key_data; + typedef fc::sha256 private_key_secret; + typedef fc::array public_key_point_data; ///< the full non-compressed version of the ECC point + typedef fc::array signature; + typedef fc::array compact_signature; + + int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check); + + /** + * @class public_key + * @brief contains only the public point of an elliptic curve key. + */ + class public_key + { + public: + public_key(); + public_key(const public_key& k); + ~public_key(); + bool verify( const fc::sha256& digest, const signature& sig ); + public_key_data serialize()const; + + operator public_key_data()const { return serialize(); } + + + public_key( const public_key_data& v ); + public_key( const public_key_point_data& v ); + public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical = true ); + + bool valid()const; + public_key mult( const fc::sha256& offset ); + public_key add( const fc::sha256& offset )const; + + public_key( public_key&& pk ); + public_key& operator=( public_key&& pk ); + public_key& operator=( const public_key& pk ); + + inline friend bool operator==( const public_key& a, const public_key& b ) + { + return a.serialize() == b.serialize(); + } + inline friend bool operator!=( const public_key& a, const public_key& b ) + { + return a.serialize() != b.serialize(); + } + + /// Allows to convert current public key object into base58 number. + std::string to_base58() const; + static public_key from_base58( const std::string& b58 ); + + private: + friend class private_key; + friend compact_signature signature_from_ecdsa(const EC_KEY* key, const public_key_data& pub_data, fc::ecdsa_sig& sig, const fc::sha256& d); + fc::fwd my; + }; + + /** + * @class private_key + * @brief an elliptic curve private key. + */ + class private_key + { + public: + private_key(); + private_key( private_key&& pk ); + private_key( const private_key& pk ); + ~private_key(); + + private_key& operator=( private_key&& pk ); + private_key& operator=( const private_key& pk ); + + static private_key generate(); + static private_key regenerate( const fc::sha256& secret ); + + /** + * This method of generation enables creating a new private key in a deterministic manner relative to + * an initial seed. A public_key created from the seed can be multiplied by the offset to calculate + * the new public key without having to know the private key. + */ + static private_key generate_from_seed( const fc::sha256& seed, const fc::sha256& offset = fc::sha256() ); + + private_key_secret get_secret()const; // get the private key secret + + operator private_key_secret ()const { return get_secret(); } + + /** + * Given a public key, calculatse a 512 bit shared secret between that + * key and this private key. + */ + fc::sha512 get_shared_secret( const public_key& pub )const; + + signature sign( const fc::sha256& digest )const; + compact_signature sign_compact( const fc::sha256& digest )const; + bool verify( const fc::sha256& digest, const signature& sig ); + + public_key get_public_key()const; + + inline friend bool operator==( const private_key& a, const private_key& b ) + { + return a.get_secret() == b.get_secret(); + } + inline friend bool operator!=( const private_key& a, const private_key& b ) + { + return a.get_secret() != b.get_secret(); + } + inline friend bool operator<( const private_key& a, const private_key& b ) + { + return a.get_secret() < b.get_secret(); + } + + private: + fc::fwd my; + }; + + /** + * Shims + */ + struct public_key_shim : public crypto::shim { + using crypto::shim::shim; + + bool valid()const { + return public_key(_data).valid(); + } + }; + + struct signature_shim : public crypto::shim { + using public_key_type = public_key_shim; + using crypto::shim::shim; + + public_key_type recover(const sha256& digest, bool check_canonical) const { + return public_key_type(public_key(_data, digest, check_canonical).serialize()); + } + }; + + struct private_key_shim : public crypto::shim { + using crypto::shim::shim; + using signature_type = signature_shim; + using public_key_type = public_key_shim; + + signature_type sign( const sha256& digest, bool require_canonical = true ) const + { + return signature_type(private_key::regenerate(_data).sign_compact(digest)); + } + + public_key_type get_public_key( ) const + { + return public_key_type(private_key::regenerate(_data).get_public_key().serialize()); + } + + sha512 generate_shared_secret( const public_key_type &pub_key ) const + { + return private_key::regenerate(_data).get_shared_secret(public_key(pub_key.serialize())); + } + + static private_key_shim generate() + { + return private_key_shim(private_key::generate().get_secret()); + } + }; + + //key here is just an optimization for getting the curve's parameters from an already constructed curve + compact_signature signature_from_ecdsa(const EC_KEY* key, const public_key_data& pub, fc::ecdsa_sig& sig, const fc::sha256& d); + + } // namespace r1 + } // namespace crypto + void to_variant( const crypto::r1::private_key& var, variant& vo ); + void from_variant( const variant& var, crypto::r1::private_key& vo ); + void to_variant( const crypto::r1::public_key& var, variant& vo ); + void from_variant( const variant& var, crypto::r1::public_key& vo ); + + namespace raw + { + template + void unpack( Stream& s, fc::crypto::r1::public_key& pk) + { + crypto::r1::public_key_data ser; + fc::raw::unpack(s,ser); + pk = fc::crypto::r1::public_key( ser ); + } + + template + void pack( Stream& s, const fc::crypto::r1::public_key& pk) + { + fc::raw::pack( s, pk.serialize() ); + } + + template + void unpack( Stream& s, fc::crypto::r1::private_key& pk) + { + fc::sha256 sec; + unpack( s, sec ); + pk = crypto::r1::private_key::regenerate(sec); + } + + template + void pack( Stream& s, const fc::crypto::r1::private_key& pk) + { + fc::raw::pack( s, pk.get_secret() ); + } + + } // namespace raw + +} // namespace fc +#include + +FC_REFLECT_TYPENAME( fc::crypto::r1::private_key ) +FC_REFLECT_TYPENAME( fc::crypto::r1::public_key ) +FC_REFLECT_DERIVED( fc::crypto::r1::public_key_shim, (fc::crypto::shim), BOOST_PP_SEQ_NIL ) +FC_REFLECT_DERIVED( fc::crypto::r1::signature_shim, (fc::crypto::shim), BOOST_PP_SEQ_NIL ) +FC_REFLECT_DERIVED( fc::crypto::r1::private_key_shim, (fc::crypto::shim), BOOST_PP_SEQ_NIL ) diff --git a/libraries/libfc/include/fc/crypto/elliptic_webauthn.hpp b/libraries/libfc/include/fc/crypto/elliptic_webauthn.hpp new file mode 100644 index 0000000000..06b3742992 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/elliptic_webauthn.hpp @@ -0,0 +1,148 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { namespace crypto { namespace webauthn { + +class signature; + +class public_key { + public: + using public_key_data_type = fc::array; + + //Used for base58 de/serialization + using data_type = public_key; + public_key serialize() const { return *this; } + + enum class user_presence_t : uint8_t { + USER_PRESENCE_NONE, + USER_PRESENCE_PRESENT, + USER_PRESENCE_VERIFIED + }; + + bool valid() const { return rpid.size(); } + + public_key() {} + public_key(const public_key_data_type& p, const user_presence_t& t, const std::string& s) : + public_key_data(p), user_verification_type(t), rpid(s) { + post_init(); + } + public_key(const signature& c, const fc::sha256& digest, bool check_canonical = true); + + bool operator==(const public_key& o) const { + return public_key_data == o.public_key_data && + user_verification_type == o.user_verification_type && + rpid == o.rpid; + } + bool operator<(const public_key& o) const { + return std::tie(public_key_data, user_verification_type, rpid) < std::tie(o.public_key_data, o.user_verification_type, o.rpid); + } + + template + friend Stream& operator<<(Stream& ds, const public_key& k) { + fc::raw::pack(ds, k.public_key_data); + fc::raw::pack(ds, static_cast(k.user_verification_type)); + fc::raw::pack(ds, k.rpid); + return ds; + } + + template + friend Stream& operator>>(Stream& ds, public_key& k) { + fc::raw::unpack(ds, k.public_key_data); + uint8_t t; + fc::raw::unpack(ds, t); + k.user_verification_type = static_cast(t); + fc::raw::unpack(ds, k.rpid); + k.post_init(); + return ds; + } + + private: + public_key_data_type public_key_data; + user_presence_t user_verification_type = user_presence_t::USER_PRESENCE_NONE; + std::string rpid; + + void post_init(); +}; + +class signature { + public: + //used for base58 de/serialization + using data_type = signature; + signature serialize()const { return *this; } + + signature() {} + signature(const fc::crypto::r1::compact_signature& s, const std::vector& a, const std::string& j) : + compact_signature(s), auth_data(a), client_json(j) {} + + public_key recover(const sha256& digest, bool check_canonical) const { + return public_key(*this, digest, check_canonical); + } + + size_t variable_size() const { + return auth_data.size() + client_json.size(); + } + + bool operator==(const signature& o) const { + return compact_signature == o.compact_signature && + auth_data == o.auth_data && + client_json == o.client_json; + } + + bool operator<(const signature& o) const { + return std::tie(compact_signature, auth_data, client_json) < std::tie(o.compact_signature, o.auth_data, o.client_json); + } + + //for container usage + size_t get_hash() const { + return *(size_t*)&compact_signature.data[32-sizeof(size_t)] + *(size_t*)&compact_signature.data[64-sizeof(size_t)]; + } + + friend struct fc::reflector; + friend class public_key; + private: + fc::crypto::r1::compact_signature compact_signature; + std::vector auth_data; + std::string client_json; +}; + +} + +template<> +struct eq_comparator { + static bool apply(const webauthn::signature& a, const webauthn::signature& b) { + return a == b; + } +}; + +template<> +struct less_comparator { + static bool apply(const webauthn::signature& a, const webauthn::signature& b) { + return a < b; + } +}; + +template<> +struct eq_comparator { + static bool apply(const webauthn::public_key& a, const webauthn::public_key& b) { + return a == b; + } +}; + +template<> +struct less_comparator { + static bool apply(const webauthn::public_key& a, const webauthn::public_key& b) { + return a < b; + } +}; + +}} +#include + +FC_REFLECT(fc::crypto::webauthn::signature, (compact_signature)(auth_data)(client_json)) \ No newline at end of file diff --git a/libraries/libfc/include/fc/crypto/equihash.hpp b/libraries/libfc/include/fc/crypto/equihash.hpp new file mode 100644 index 0000000000..cf0cb01a1c --- /dev/null +++ b/libraries/libfc/include/fc/crypto/equihash.hpp @@ -0,0 +1,21 @@ +#pragma once +#include +#include + +namespace fc { namespace equihash { + + struct proof + { + uint32_t n; + uint32_t k; + sha256 seed; + std::vector< uint32_t > inputs; + + bool is_valid() const; + + static proof hash( uint32_t n, uint32_t k, sha256 seed ); + }; + +} } // fc + +FC_REFLECT( fc::equihash::proof, (n)(k)(seed)(inputs) ) diff --git a/libraries/libfc/include/fc/crypto/hex.hpp b/libraries/libfc/include/fc/crypto/hex.hpp new file mode 100644 index 0000000000..201f167842 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/hex.hpp @@ -0,0 +1,15 @@ +#pragma once +#include +#include +#include + +namespace fc { + uint8_t from_hex( char c ); + fc::string to_hex( const char* d, uint32_t s ); + std::string to_hex( const std::vector& data ); + + /** + * @return the number of bytes decoded + */ + size_t from_hex( const fc::string& hex_str, char* out_data, size_t out_data_len ); +} diff --git a/libraries/libfc/include/fc/crypto/hmac.hpp b/libraries/libfc/include/fc/crypto/hmac.hpp new file mode 100644 index 0000000000..d1a18c62da --- /dev/null +++ b/libraries/libfc/include/fc/crypto/hmac.hpp @@ -0,0 +1,63 @@ +/* + * File: hmac.hpp + * Author: Peter Conrad + * + * Created on 1. Juli 2015, 21:48 + */ + +#ifndef HMAC_HPP +#define HMAC_HPP + +#include +#include +#include + +namespace fc { + + template + class hmac + { + public: + hmac() {} + + H digest( const char* c, uint32_t c_len, const char* d, uint32_t d_len ) + { + encoder.reset(); + add_key(c, c_len, 0x36); + encoder.write( d, d_len ); + H intermediate = encoder.result(); + + encoder.reset(); + add_key(c, c_len, 0x5c); + encoder.write( intermediate.data(), intermediate.data_size() ); + return encoder.result(); + } + + private: + void add_key( const char* c, const uint32_t c_len, char pad ) + { + if ( c_len > internal_block_size() ) + { + H hash = H::hash( c, c_len ); + add_key( hash.data(), hash.data_size(), pad ); + } + else + for (unsigned int i = 0; i < internal_block_size(); i++ ) + { + encoder.put( pad ^ ((i < c_len) ? *c++ : 0) ); + } + } + + unsigned int internal_block_size() const; + + H dummy; + typename H::encoder encoder; + }; + + typedef hmac hmac_sha224; + typedef hmac hmac_sha256; + typedef hmac hmac_sha512; +} + +#endif /* HMAC_HPP */ + diff --git a/libraries/libfc/include/fc/crypto/k1_recover.hpp b/libraries/libfc/include/fc/crypto/k1_recover.hpp new file mode 100644 index 0000000000..9f99bdac95 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/k1_recover.hpp @@ -0,0 +1,20 @@ +// Snark - Wrapper for alt_bn128 add mul pair and modexp + +#pragma once + +#include +#include +#include + +namespace fc { + using bytes = std::vector; + + enum class k1_recover_error : int32_t { + init_error, + input_error, + invalid_signature, + recover_error, + }; + + std::variant k1_recover(const bytes& signature, const bytes& digest); +} // fc diff --git a/libraries/libfc/include/fc/crypto/modular_arithmetic.hpp b/libraries/libfc/include/fc/crypto/modular_arithmetic.hpp new file mode 100644 index 0000000000..c370db8ad4 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/modular_arithmetic.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +namespace fc { + using bytes = std::vector; + + enum class modular_arithmetic_error : int32_t { + modulus_len_zero, + }; + + std::variant modexp(const bytes& _base, const bytes& _exponent, const bytes& _modulus); +} diff --git a/libraries/libfc/include/fc/crypto/openssl.hpp b/libraries/libfc/include/fc/crypto/openssl.hpp new file mode 100644 index 0000000000..0644dd8870 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/openssl.hpp @@ -0,0 +1,60 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @file openssl.hpp + * Provides common utility calls for wrapping openssl c api. + */ +namespace fc +{ + class path; + + template + struct ssl_wrapper + { + ssl_wrapper(ssl_type* obj):obj(obj) {} + + operator ssl_type*() { return obj; } + operator const ssl_type*() const { return obj; } + ssl_type* operator->() { return obj; } + const ssl_type* operator->() const { return obj; } + + ssl_type* obj; + }; + + #define SSL_TYPE(name, ssl_type, free_func) \ + struct name : public ssl_wrapper \ + { \ + name(ssl_type* obj=nullptr) \ + : ssl_wrapper(obj) {} \ + ~name() \ + { \ + if( obj != nullptr ) \ + free_func(obj); \ + } \ + }; + + SSL_TYPE(ec_group, EC_GROUP, EC_GROUP_free) + SSL_TYPE(ec_point, EC_POINT, EC_POINT_free) + SSL_TYPE(ecdsa_sig, ECDSA_SIG, ECDSA_SIG_free) + SSL_TYPE(bn_ctx, BN_CTX, BN_CTX_free) + SSL_TYPE(evp_cipher_ctx, EVP_CIPHER_CTX, EVP_CIPHER_CTX_free ) + SSL_TYPE(ec_key, EC_KEY, EC_KEY_free) + + /** allocates a bignum by default.. */ + struct ssl_bignum : public ssl_wrapper + { + ssl_bignum() : ssl_wrapper(BN_new()) {} + ~ssl_bignum() { BN_free(obj); } + }; + +} // namespace fc diff --git a/libraries/libfc/include/fc/crypto/pke.hpp b/libraries/libfc/include/fc/crypto/pke.hpp new file mode 100644 index 0000000000..3f1c607dd6 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/pke.hpp @@ -0,0 +1,113 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace fc { + namespace detail { class pke_impl; } + + class private_key; + class public_key; + void generate_key_pair( public_key&, private_key& ); + + typedef std::vector bytes; + typedef bytes signature; + + class public_key + { + public: + public_key(); + explicit public_key( const bytes& d ); + public_key( const public_key& k ); + public_key( public_key&& k ); + ~public_key(); + + operator bool()const; + + public_key& operator=(const public_key& p ); + public_key& operator=(public_key&& p ); + + bool verify( const sha1& digest, const array& sig )const; + bool verify( const sha1& digest, const signature& sig )const; + bool verify( const sha256& digest, const signature& sig )const; + bytes encrypt( const char* data, size_t len )const; + bytes encrypt( const bytes& )const; + bytes decrypt( const bytes& )const; + + bytes serialize()const; + friend void generate_key_pair( public_key&, private_key& ); + private: + std::shared_ptr my; + }; + + class private_key + { + public: + private_key(); + explicit private_key( const bytes& d ); + private_key( const private_key& k ); + private_key( private_key&& k ); + ~private_key(); + + operator bool()const; + + private_key& operator=(const private_key& p ); + private_key& operator=(private_key&& p ); + + void sign( const sha1& digest, array& sig )const; + signature sign( const sha1& digest )const; + signature sign( const sha256& digest )const; + + bytes decrypt( const char* bytes, size_t len )const; + bytes decrypt( const bytes& )const; + bytes encrypt( const bytes& )const; + + bytes serialize()const; + friend void generate_key_pair( public_key&, private_key& ); + + private: + std::shared_ptr my; + }; + bool operator==( const private_key& a, const private_key& b ); + + namespace raw + { + template + void unpack( Stream& s, fc::public_key& pk) + { + bytes ser; + fc::raw::unpack(s,ser); + pk = fc::public_key( ser ); + } + + template + void pack( Stream& s, const fc::public_key& pk) + { + fc::raw::pack( s, pk.serialize() ); + } + + template + void unpack( Stream& s, fc::private_key& pk) + { + bytes ser; + fc::raw::unpack(s,ser); + pk = fc::private_key( ser ); + } + + template + void pack( Stream& s, const fc::private_key& pk) + { + fc::raw::pack( s, pk.serialize() ); + } + } + class variant; + void to_variant( const public_key& bi, variant& v ); + void from_variant( const variant& v, public_key& bi ); + void to_variant( const private_key& bi, variant& v ); + void from_variant( const variant& v, private_key& bi ); + +} // fc + diff --git a/libraries/libfc/include/fc/crypto/private_key.hpp b/libraries/libfc/include/fc/crypto/private_key.hpp new file mode 100644 index 0000000000..505bf3d3df --- /dev/null +++ b/libraries/libfc/include/fc/crypto/private_key.hpp @@ -0,0 +1,73 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace fc { namespace crypto { + + namespace config { + constexpr const char* private_key_base_prefix = "PVT"; + constexpr const char* private_key_prefix[] = { + "K1", + "R1" + }; + }; + + class private_key + { + public: + using storage_type = std::variant; + + private_key() = default; + private_key( private_key&& ) = default; + private_key( const private_key& ) = default; + private_key& operator= (const private_key& ) = default; + + public_key get_public_key() const; + signature sign( const sha256& digest, bool require_canonical = true ) const; + sha512 generate_shared_secret( const public_key& pub ) const; + + template< typename KeyType = ecc::private_key_shim > + static private_key generate() { + return private_key(storage_type(KeyType::generate())); + } + + template< typename KeyType = r1::private_key_shim > + static private_key generate_r1() { + return private_key(storage_type(KeyType::generate())); + } + + template< typename KeyType = ecc::private_key_shim > + static private_key regenerate( const typename KeyType::data_type& data ) { + return private_key(storage_type(KeyType(data))); + } + + // serialize to/from string + explicit private_key(const string& base58str); + std::string to_string(const fc::yield_function_t& yield = fc::yield_function_t()) const; + + private: + storage_type _storage; + + private_key( storage_type&& other_storage ) + :_storage(forward(other_storage)) + {} + + friend bool operator == ( const private_key& p1, const private_key& p2); + friend bool operator != ( const private_key& p1, const private_key& p2); + friend bool operator < ( const private_key& p1, const private_key& p2); + friend struct reflector; + }; // private_key + +} } // fc::crypto + +namespace fc { + void to_variant(const crypto::private_key& var, variant& vo, const fc::yield_function_t& yield = fc::yield_function_t()); + + void from_variant(const variant& var, crypto::private_key& vo); +} // namespace fc + +FC_REFLECT(fc::crypto::private_key, (_storage) ) diff --git a/libraries/libfc/include/fc/crypto/public_key.hpp b/libraries/libfc/include/fc/crypto/public_key.hpp new file mode 100644 index 0000000000..00bfb5a0af --- /dev/null +++ b/libraries/libfc/include/fc/crypto/public_key.hpp @@ -0,0 +1,64 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace fc { namespace crypto { + namespace config { + constexpr const char* public_key_legacy_prefix = "EOS"; + constexpr const char* public_key_base_prefix = "PUB"; + constexpr const char* public_key_prefix[] = { + "K1", + "R1", + "WA" + }; + }; + + class public_key + { + public: + using storage_type = std::variant; + + public_key() = default; + public_key( public_key&& ) = default; + public_key( const public_key& ) = default; + public_key& operator= (const public_key& ) = default; + + public_key( const signature& c, const sha256& digest, bool check_canonical = true ); + + public_key( storage_type&& other_storage ) + :_storage(forward(other_storage)) + {} + + bool valid()const; + + size_t which()const; + + // serialize to/from string + explicit public_key(const string& base58str); + std::string to_string(const fc::yield_function_t& yield = fc::yield_function_t()) const; + + storage_type _storage; + + private: + friend std::ostream& operator<< (std::ostream& s, const public_key& k); + friend bool operator == ( const public_key& p1, const public_key& p2); + friend bool operator != ( const public_key& p1, const public_key& p2); + friend bool operator < ( const public_key& p1, const public_key& p2); + friend struct reflector; + friend class private_key; + }; // public_key + +} } // fc::crypto + +namespace fc { + void to_variant(const crypto::public_key& var, variant& vo, const fc::yield_function_t& yield = fc::yield_function_t()); + + void from_variant(const variant& var, crypto::public_key& vo); +} // namespace fc + +FC_REFLECT(fc::crypto::public_key, (_storage) ) diff --git a/libraries/libfc/include/fc/crypto/rand.hpp b/libraries/libfc/include/fc/crypto/rand.hpp new file mode 100644 index 0000000000..749547fc11 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/rand.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace fc { + + /* provides access to the OpenSSL random number generator */ + void rand_bytes(char* buf, int count); + void rand_pseudo_bytes(char* buf, int count); +} // namespace fc diff --git a/libraries/libfc/include/fc/crypto/ripemd160.hpp b/libraries/libfc/include/fc/crypto/ripemd160.hpp new file mode 100644 index 0000000000..912c3929e3 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/ripemd160.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include + +namespace fc{ +class sha512; +class sha256; + +class ripemd160 +{ + public: + ripemd160(); + explicit ripemd160( const string& hex_str ); + + string str()const; + explicit operator string()const; + + char* data()const; + size_t data_size()const { return 160/8; } + + static ripemd160 hash( const fc::sha512& h ); + static ripemd160 hash( const fc::sha256& h ); + static ripemd160 hash( const char* d, uint32_t dlen ); + static ripemd160 hash( const string& ); + + template + static ripemd160 hash( const T& t ) + { + ripemd160::encoder e; + fc::raw::pack(e,t); + return e.result(); + } + + class encoder + { + public: + encoder(); + ~encoder(); + + void write( const char* d, uint32_t dlen ); + void put( char c ) { write( &c, 1 ); } + void reset(); + ripemd160 result(); + + private: + struct impl; + fc::fwd my; + }; + + template + inline friend T& operator<<( T& ds, const ripemd160& ep ) { + ds.write( ep.data(), sizeof(ep) ); + return ds; + } + + template + inline friend T& operator>>( T& ds, ripemd160& ep ) { + ds.read( ep.data(), sizeof(ep) ); + return ds; + } + friend ripemd160 operator << ( const ripemd160& h1, uint32_t i ); + friend bool operator == ( const ripemd160& h1, const ripemd160& h2 ); + friend bool operator != ( const ripemd160& h1, const ripemd160& h2 ); + friend ripemd160 operator ^ ( const ripemd160& h1, const ripemd160& h2 ); + friend bool operator >= ( const ripemd160& h1, const ripemd160& h2 ); + friend bool operator > ( const ripemd160& h1, const ripemd160& h2 ); + friend bool operator < ( const ripemd160& h1, const ripemd160& h2 ); + + uint32_t _hash[5]; +}; + + class variant; + void to_variant( const ripemd160& bi, variant& v ); + void from_variant( const variant& v, ripemd160& bi ); + + typedef ripemd160 uint160_t; + typedef ripemd160 uint160; + + template<> struct get_typename { static const char* name() { return "uint160_t"; } }; + +} // namespace fc + +namespace std +{ + template<> + struct hash + { + size_t operator()( const fc::ripemd160& s )const + { + return *((size_t*)&s); + } + }; +} diff --git a/libraries/libfc/include/fc/crypto/sha1.hpp b/libraries/libfc/include/fc/crypto/sha1.hpp new file mode 100644 index 0000000000..d8938f27a9 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/sha1.hpp @@ -0,0 +1,85 @@ +#pragma once +#include +#include + +namespace fc{ + +class sha1 +{ + public: + sha1(); + explicit sha1( const string& hex_str ); + + string str()const; + operator string()const; + + char* data(); + const char* data()const; + size_t data_size()const { return 20; } + + static sha1 hash( const char* d, uint32_t dlen ); + static sha1 hash( const string& ); + + template + static sha1 hash( const T& t ) + { + sha1::encoder e; + e << t; + return e.result(); + } + + class encoder + { + public: + encoder(); + ~encoder(); + + void write( const char* d, uint32_t dlen ); + void put( char c ) { write( &c, 1 ); } + void reset(); + sha1 result(); + + private: + struct impl; + fc::fwd my; + }; + + template + inline friend T& operator<<( T& ds, const sha1& ep ) { + ds.write( ep.data(), sizeof(ep) ); + return ds; + } + + template + inline friend T& operator>>( T& ds, sha1& ep ) { + ds.read( ep.data(), sizeof(ep) ); + return ds; + } + friend sha1 operator << ( const sha1& h1, uint32_t i ); + friend bool operator == ( const sha1& h1, const sha1& h2 ); + friend bool operator != ( const sha1& h1, const sha1& h2 ); + friend sha1 operator ^ ( const sha1& h1, const sha1& h2 ); + friend bool operator >= ( const sha1& h1, const sha1& h2 ); + friend bool operator > ( const sha1& h1, const sha1& h2 ); + friend bool operator < ( const sha1& h1, const sha1& h2 ); + + uint32_t _hash[5]; +}; + + class variant; + void to_variant( const sha1& bi, variant& v ); + void from_variant( const variant& v, sha1& bi ); + +} // namespace fc + +namespace std +{ + template<> + struct hash + { + size_t operator()( const fc::sha1& s )const + { + return *((size_t*)&s); + } + }; +} diff --git a/libraries/libfc/include/fc/crypto/sha224.hpp b/libraries/libfc/include/fc/crypto/sha224.hpp new file mode 100644 index 0000000000..749fabc1d9 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/sha224.hpp @@ -0,0 +1,91 @@ +#pragma once +#include +#include +#include +#include + +namespace fc +{ + +class sha224 +{ + public: + sha224(); + explicit sha224( const string& hex_str ); + + string str()const; + operator string()const; + + char* data(); + const char* data()const; + size_t data_size()const { return 224 / 8; } + + static sha224 hash( const char* d, uint32_t dlen ); + static sha224 hash( const string& ); + + template + static sha224 hash( const T& t ) + { + sha224::encoder e; + fc::raw::pack(e,t); + return e.result(); + } + + class encoder + { + public: + encoder(); + ~encoder(); + + void write( const char* d, uint32_t dlen ); + void put( char c ) { write( &c, 1 ); } + void reset(); + sha224 result(); + + private: + struct impl; + fc::fwd my; + }; + + template + inline friend T& operator<<( T& ds, const sha224& ep ) { + static_assert( sizeof(ep) == (8*3+4), "sha224 size mismatch" ); + ds.write( ep.data(), sizeof(ep) ); + return ds; + } + + template + inline friend T& operator>>( T& ds, sha224& ep ) { + ds.read( ep.data(), sizeof(ep) ); + return ds; + } + friend sha224 operator << ( const sha224& h1, uint32_t i ); + friend bool operator == ( const sha224& h1, const sha224& h2 ); + friend bool operator != ( const sha224& h1, const sha224& h2 ); + friend sha224 operator ^ ( const sha224& h1, const sha224& h2 ); + friend bool operator >= ( const sha224& h1, const sha224& h2 ); + friend bool operator > ( const sha224& h1, const sha224& h2 ); + friend bool operator < ( const sha224& h1, const sha224& h2 ); + friend std::size_t hash_value( const sha224& v ) { return uint64_t(v._hash[1])<<32 | v._hash[2]; } + + uint32_t _hash[7]; +}; + + class variant; + void to_variant( const sha224& bi, variant& v ); + void from_variant( const variant& v, sha224& bi ); + +} // fc +namespace std +{ + template<> + struct hash + { + size_t operator()( const fc::sha224& s )const + { + return *((size_t*)&s); + } + }; +} +#include +FC_REFLECT_TYPENAME( fc::sha224 ) diff --git a/libraries/libfc/include/fc/crypto/sha256.hpp b/libraries/libfc/include/fc/crypto/sha256.hpp new file mode 100644 index 0000000000..8472d27020 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/sha256.hpp @@ -0,0 +1,138 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace fc +{ + +class sha256 +{ + public: + sha256(); + explicit sha256( const string& hex_str ); + explicit sha256( const char *data, size_t size ); + + string str()const; + operator string()const; + + const char* data()const; + char* data(); + size_t data_size() const { return 256 / 8; } + + static sha256 hash( const char* d, uint32_t dlen ); + static sha256 hash( const string& ); + static sha256 hash( const sha256& ); + + template + static sha256 hash( const T& t ) + { + sha256::encoder e; + fc::raw::pack(e,t); + return e.result(); + } + + class encoder + { + public: + encoder(); + ~encoder(); + + void write( const char* d, uint32_t dlen ); + void put( char c ) { write( &c, 1 ); } + void reset(); + sha256 result(); + + private: + struct impl; + fc::fwd my; + }; + + template + inline friend T& operator<<( T& ds, const sha256& ep ) { + ds.write( ep.data(), sizeof(ep) ); + return ds; + } + + template + inline friend T& operator>>( T& ds, sha256& ep ) { + ds.read( ep.data(), sizeof(ep) ); + return ds; + } + friend sha256 operator << ( const sha256& h1, uint32_t i ); + friend sha256 operator >> ( const sha256& h1, uint32_t i ); + friend bool operator == ( const sha256& h1, const sha256& h2 ); + friend bool operator != ( const sha256& h1, const sha256& h2 ); + friend sha256 operator ^ ( const sha256& h1, const sha256& h2 ); + friend bool operator >= ( const sha256& h1, const sha256& h2 ); + friend bool operator > ( const sha256& h1, const sha256& h2 ); + friend bool operator < ( const sha256& h1, const sha256& h2 ); + + uint32_t pop_count()const + { + return (uint32_t)(__builtin_popcountll(_hash[0]) + + __builtin_popcountll(_hash[1]) + + __builtin_popcountll(_hash[2]) + + __builtin_popcountll(_hash[3])); + } + + /** + * Count leading zero bits + */ + uint16_t clz()const; + + /** + * Approximate (log_2(x) + 1) * 2**24. + * + * Detailed specs: + * - Return 0 when x == 0. + * - High 8 bits of result simply counts nonzero bits. + * - Low 24 bits of result are the 24 bits of input immediately after the most significant 1 in the input. + * - If above would require reading beyond the end of the input, zeros are used instead. + */ + uint32_t approx_log_32()const; + + void set_to_inverse_approx_log_32( uint32_t x ); + static double inverse_approx_log_32_double( uint32_t x ); + + uint64_t _hash[4]; +}; + + typedef sha256 uint256; + + class variant; + void to_variant( const sha256& bi, variant& v ); + void from_variant( const variant& v, sha256& bi ); + + uint64_t hash64(const char* buf, size_t len); + +} // fc + +namespace std +{ + template<> + struct hash + { + size_t operator()( const fc::sha256& s )const + { + return *((size_t*)&s); + } + }; + +} + +namespace boost +{ + template<> + struct hash + { + size_t operator()( const fc::sha256& s )const + { + return s._hash[3];//*((size_t*)&s); + } + }; +} +#include +FC_REFLECT_TYPENAME( fc::sha256 ) diff --git a/libraries/libfc/include/fc/crypto/sha3.hpp b/libraries/libfc/include/fc/crypto/sha3.hpp new file mode 100644 index 0000000000..20e86b1581 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/sha3.hpp @@ -0,0 +1,115 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace fc +{ + +class sha3 +{ +public: + sha3(); + ~sha3(){} + explicit sha3(const string &hex_str); + explicit sha3(const char *data, size_t size); + + string str() const; + operator string() const; + + const char *data() const; + char* data(); + size_t data_size() const { return 256 / 8; } + + static sha3 hash(const char *d, uint32_t dlen, bool is_nist=true) { + encoder e; + e.write(d, dlen); + const auto& sha = e.result(is_nist); + return sha; + } + static sha3 hash(const string& s, bool is_nist=true) { return hash(s.c_str(), s.size(), is_nist); } + static sha3 hash(const sha3& s, bool is_nist=true) { return hash(s.data(), sizeof(s._hash), is_nist); } + + template + static sha3 hash(const T &t, bool is_nist=true) + { + sha3::encoder e; + fc::raw::pack(e, t); + return e.result(is_nist); + } + + class encoder + { + public: + encoder(); + ~encoder(); + + void write(const char *d, uint32_t dlen); + void put(char c) { write(&c, 1); } + void reset(); + sha3 result(bool is_nist=true); + + private: + struct impl; + fc::fwd my; + }; + + template + inline friend T &operator<<(T &ds, const sha3 &ep) + { + ds.write(ep.data(), sizeof(ep)); + return ds; + } + + template + inline friend T &operator>>(T &ds, sha3 &ep) + { + ds.read(ep.data(), sizeof(ep)); + return ds; + } + friend sha3 operator<<(const sha3 &h1, uint32_t i); + friend sha3 operator>>(const sha3 &h1, uint32_t i); + friend bool operator==(const sha3 &h1, const sha3 &h2); + friend bool operator!=(const sha3 &h1, const sha3 &h2); + friend sha3 operator^(const sha3 &h1, const sha3 &h2); + friend bool operator>=(const sha3 &h1, const sha3 &h2); + friend bool operator>(const sha3 &h1, const sha3 &h2); + friend bool operator<(const sha3 &h1, const sha3 &h2); + + uint64_t _hash[4]; +}; + +class variant; +void to_variant(const sha3 &bi, variant &v); +void from_variant(const variant &v, sha3 &bi); + +} // namespace fc + +namespace std +{ +template <> +struct hash +{ + size_t operator()(const fc::sha3 &s) const + { + return *((size_t *)&s); + } +}; + +} // namespace std + +namespace boost +{ +template <> +struct hash +{ + size_t operator()(const fc::sha3 &s) const + { + return s._hash[3]; //*((size_t*)&s); + } +}; +} // namespace boost +#include +FC_REFLECT_TYPENAME(fc::sha3) diff --git a/libraries/libfc/include/fc/crypto/sha512.hpp b/libraries/libfc/include/fc/crypto/sha512.hpp new file mode 100644 index 0000000000..c2d7f96d86 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/sha512.hpp @@ -0,0 +1,79 @@ +#pragma once +#include +#include + +namespace fc +{ + +class sha512 +{ + public: + sha512(); + explicit sha512( const string& hex_str ); + + string str()const; + operator string()const; + + char* data(); + const char* data()const; + size_t data_size()const { return 512 / 8; } + + static sha512 hash( const char* d, uint32_t dlen ); + static sha512 hash( const string& ); + + template + static sha512 hash( const T& t ) + { + sha512::encoder e; + e << t; + return e.result(); + } + + class encoder + { + public: + encoder(); + ~encoder(); + + void write( const char* d, uint32_t dlen ); + void put( char c ) { write( &c, 1 ); } + void reset(); + sha512 result(); + + private: + struct impl; + fc::fwd my; + }; + + template + inline friend T& operator<<( T& ds, const sha512& ep ) { + ds.write( ep.data(), sizeof(ep) ); + return ds; + } + + template + inline friend T& operator>>( T& ds, sha512& ep ) { + ds.read( ep.data(), sizeof(ep) ); + return ds; + } + friend sha512 operator << ( const sha512& h1, uint32_t i ); + friend bool operator == ( const sha512& h1, const sha512& h2 ); + friend bool operator != ( const sha512& h1, const sha512& h2 ); + friend sha512 operator ^ ( const sha512& h1, const sha512& h2 ); + friend bool operator >= ( const sha512& h1, const sha512& h2 ); + friend bool operator > ( const sha512& h1, const sha512& h2 ); + friend bool operator < ( const sha512& h1, const sha512& h2 ); + + uint64_t _hash[8]; +}; + + typedef fc::sha512 uint512; + + class variant; + void to_variant( const sha512& bi, variant& v ); + void from_variant( const variant& v, sha512& bi ); + +} // fc + +#include +FC_REFLECT_TYPENAME( fc::sha512 ) diff --git a/libraries/libfc/include/fc/crypto/signature.hpp b/libraries/libfc/include/fc/crypto/signature.hpp new file mode 100644 index 0000000000..ba3ad64410 --- /dev/null +++ b/libraries/libfc/include/fc/crypto/signature.hpp @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace fc { namespace crypto { + namespace config { + constexpr const char* signature_base_prefix = "SIG"; + constexpr const char* signature_prefix[] = { + "K1", + "R1", + "WA" + }; + }; + + class signature + { + public: + using storage_type = std::variant; + + signature() = default; + signature( signature&& ) = default; + signature( const signature& ) = default; + signature& operator= (const signature& ) = default; + + // serialize to/from string + explicit signature(const string& base58str); + std::string to_string(const fc::yield_function_t& yield = fc::yield_function_t()) const; + + size_t which() const; + + size_t variable_size() const; + + private: + storage_type _storage; + + signature( storage_type&& other_storage ) + :_storage(std::forward(other_storage)) + {} + + friend bool operator == ( const signature& p1, const signature& p2); + friend bool operator != ( const signature& p1, const signature& p2); + friend bool operator < ( const signature& p1, const signature& p2); + friend std::size_t hash_value(const signature& b); //not cryptographic; for containers + friend struct reflector; + friend class private_key; + friend class public_key; + }; // public_key + + size_t hash_value(const signature& b); + +} } // fc::crypto + +namespace fc { + void to_variant(const crypto::signature& var, variant& vo, const fc::yield_function_t& yield = fc::yield_function_t()); + + void from_variant(const variant& var, crypto::signature& vo); +} // namespace fc + +namespace std { + template <> struct hash { + std::size_t operator()(const fc::crypto::signature& k) const { + return fc::crypto::hash_value(k); + } + }; +} // std + +FC_REFLECT(fc::crypto::signature, (_storage) ) diff --git a/libraries/libfc/include/fc/crypto/webauthn_json b/libraries/libfc/include/fc/crypto/webauthn_json new file mode 160000 index 0000000000..01950eb7ac --- /dev/null +++ b/libraries/libfc/include/fc/crypto/webauthn_json @@ -0,0 +1 @@ +Subproject commit 01950eb7acec78818d68b762efc869bba2420d82 diff --git a/libraries/libfc/include/fc/exception/exception.hpp b/libraries/libfc/include/fc/exception/exception.hpp new file mode 100644 index 0000000000..3afa1340fa --- /dev/null +++ b/libraries/libfc/include/fc/exception/exception.hpp @@ -0,0 +1,549 @@ +#pragma once +/** + * @file exception.hpp + * @brief Defines exception's used by fc + */ +#include +#include +#include +#include +#include +#include + +namespace fc +{ + namespace detail { class exception_impl; } + + enum exception_code + { + /** for exceptions we threw that don't have an assigned code */ + unspecified_exception_code = 0, + unhandled_exception_code = 1, ///< for unhandled 3rd party exceptions + timeout_exception_code = 2, ///< timeout exceptions + file_not_found_exception_code = 3, + parse_error_exception_code = 4, + invalid_arg_exception_code = 5, + key_not_found_exception_code = 6, + bad_cast_exception_code = 7, + out_of_range_exception_code = 8, + canceled_exception_code = 9, + assert_exception_code = 10, + eof_exception_code = 11, + std_exception_code = 13, + invalid_operation_exception_code = 14, + unknown_host_exception_code = 15, + null_optional_code = 16, + udt_error_code = 17, + aes_error_code = 18, + overflow_code = 19, + underflow_code = 20, + divide_by_zero_code = 21 + }; + + /** + * @brief Used to generate a useful error report when an exception is thrown. + * @ingroup serializable + * + * At each level in the stack where the exception is caught and rethrown a + * new log_message is added to the exception. + * + * exception's are designed to be serialized to a variant and + * deserialized from an variant. + * + * @see FC_THROW_EXCEPTION + * @see FC_RETHROW_EXCEPTION + * @see FC_RETHROW_EXCEPTIONS + */ + class exception : public std::exception + { + public: + static constexpr fc::microseconds format_time_limit = fc::milliseconds( 10 ); // limit time spent formatting exceptions + + enum code_enum + { + code_value = unspecified_exception_code + }; + + exception( int64_t code = unspecified_exception_code, + const std::string& name_value = "exception", + const std::string& what_value = "unspecified"); + exception( log_message&&, int64_t code = unspecified_exception_code, + const std::string& name_value = "exception", + const std::string& what_value = "unspecified"); + exception( log_messages&&, int64_t code = unspecified_exception_code, + const std::string& name_value = "exception", + const std::string& what_value = "unspecified"); + exception( const log_messages&, + int64_t code = unspecified_exception_code, + const std::string& name_value = "exception", + const std::string& what_value = "unspecified"); + exception( const exception& e ); + exception( exception&& e ); + virtual ~exception(); + + const char* name()const throw(); + int64_t code()const throw(); + const char* what()const noexcept override; + + /** + * @return a reference to log messages that have + * been added to this log. + */ + const log_messages& get_log()const; + void append_log( log_message m ); + + /** + * Generates a detailed string including file, line, method, + * and other information that is generally only useful for + * developers. + */ + std::string to_detail_string( log_level ll = log_level::all )const; + + /** + * Generates a user-friendly error report. + */ + std::string to_string( log_level ll = log_level::info )const; + + /** + * Generates a user-friendly error report. + */ + std::string top_message( )const; + + /** + * Throw this exception as its most derived type. + * + * @note does not return. + */ + virtual NO_RETURN void dynamic_rethrow_exception()const; + + /** + * This is equivalent to: + * @code + * try { throwAsDynamic_exception(); } + * catch( ... ) { return std::current_exception(); } + * @endcode + */ + virtual std::shared_ptr dynamic_copy_exception()const; + + friend void to_variant( const exception& e, variant& v ); + friend void from_variant( const variant& e, exception& ll ); + + exception& operator=( const exception& copy ); + exception& operator=( exception&& copy ); + protected: + std::unique_ptr my; + }; + + void to_variant( const exception& e, variant& v ); + void from_variant( const variant& e, exception& ll ); + typedef std::shared_ptr exception_ptr; + + typedef std::optional oexception; + + + /** + * @brief re-thrown whenever an unhandled exception is caught. + * @ingroup serializable + * Any exceptions thrown by 3rd party libraries that are not + * caught get wrapped in an unhandled_exception exception. + * + * The original exception is captured as a std::exception_ptr + * which may be rethrown. The std::exception_ptr does not + * propgate across process boundaries. + */ + class unhandled_exception : public exception + { + public: + enum code_enum { + code_value = unhandled_exception_code, + }; + unhandled_exception( log_message&& m, std::exception_ptr e = std::current_exception() ); + unhandled_exception( log_messages ); + unhandled_exception( const exception& ); + + std::exception_ptr get_inner_exception()const; + + virtual NO_RETURN void dynamic_rethrow_exception()const; + virtual std::shared_ptr dynamic_copy_exception()const; + private: + std::exception_ptr _inner; + }; + + /** + * @brief wrapper for std::exception + * + * The original exception is captured as a std::exception_ptr + * which may be rethrown. The std::exception_ptr does not + * propgate across process boundaries. + */ + class std_exception_wrapper : public exception + { + public: + explicit std_exception_wrapper( log_message&& m, + std::exception_ptr e = std::current_exception(), + const std::string& name_value = "exception", + const std::string& what_value = "unspecified"); + + std::exception_ptr get_inner_exception()const; + + static std_exception_wrapper from_current_exception(const std::exception& e); + + virtual NO_RETURN void dynamic_rethrow_exception()const; + virtual std::shared_ptr dynamic_copy_exception()const; + private: + std::exception_ptr _inner; + }; + + template + fc::exception_ptr copy_exception( T&& e ) + { +#if defined(_MSC_VER) && (_MSC_VER < 1700) + return std::make_shared( log_message(), + std::copy_exception(fc::forward(e)) ); +#else + return std::make_shared( log_message(), + std::make_exception_ptr(fc::forward(e)) ); +#endif + } + + + class exception_factory + { + public: + struct base_exception_builder + { + virtual NO_RETURN void rethrow( const exception& e )const = 0; + }; + + template + struct exception_builder : public base_exception_builder + { + virtual NO_RETURN void rethrow( const exception& e )const override + { + throw T( e ); + } + }; + + template + void register_exception() + { + static exception_builder builder; + auto itr = _registered_exceptions.find( T::code_value ); + assert( itr == _registered_exceptions.end() ); + (void)itr; // in release builds this hides warnings + _registered_exceptions[T::code_value] = &builder; + } + + void NO_RETURN rethrow( const exception& e )const; + + static exception_factory& instance() + { + static exception_factory once; + return once; + } + + private: + std::unordered_map _registered_exceptions; + }; +#define FC_REGISTER_EXCEPTION(r, unused, base) \ + fc::exception_factory::instance().register_exception(); + +#define FC_REGISTER_EXCEPTIONS( SEQ )\ + \ + static bool exception_init = []()->bool{ \ + BOOST_PP_SEQ_FOR_EACH( FC_REGISTER_EXCEPTION, v, SEQ ) \ + return true; \ + }(); \ + + +#define FC_DECLARE_DERIVED_EXCEPTION( TYPE, BASE, CODE, WHAT ) \ + class TYPE : public BASE \ + { \ + public: \ + enum code_enum { \ + code_value = CODE, \ + }; \ + explicit TYPE( int64_t code, const std::string& name_value, const std::string& what_value ) \ + :BASE( code, name_value, what_value ){} \ + explicit TYPE( fc::log_message&& m, int64_t code, const std::string& name_value, const std::string& what_value ) \ + :BASE( std::move(m), code, name_value, what_value ){} \ + explicit TYPE( fc::log_messages&& m, int64_t code, const std::string& name_value, const std::string& what_value )\ + :BASE( std::move(m), code, name_value, what_value ){}\ + explicit TYPE( const fc::log_messages& m, int64_t code, const std::string& name_value, const std::string& what_value )\ + :BASE( m, code, name_value, what_value ){}\ + TYPE( const std::string& what_value, const fc::log_messages& m ) \ + :BASE( m, CODE, BOOST_PP_STRINGIZE(TYPE), what_value ){} \ + TYPE( fc::log_message&& m ) \ + :BASE( fc::move(m), CODE, BOOST_PP_STRINGIZE(TYPE), WHAT ){}\ + TYPE( fc::log_messages msgs ) \ + :BASE( fc::move( msgs ), CODE, BOOST_PP_STRINGIZE(TYPE), WHAT ) {} \ + TYPE( const TYPE& c ) \ + :BASE(c){} \ + TYPE( const BASE& c ) \ + :BASE(c){} \ + TYPE():BASE(CODE, BOOST_PP_STRINGIZE(TYPE), WHAT){}\ + \ + virtual std::shared_ptr dynamic_copy_exception()const\ + { return std::make_shared( *this ); } \ + virtual NO_RETURN void dynamic_rethrow_exception()const \ + { if( code() == CODE ) throw *this;\ + else fc::exception::dynamic_rethrow_exception(); \ + } \ + }; + + #define FC_DECLARE_EXCEPTION( TYPE, CODE, WHAT ) \ + FC_DECLARE_DERIVED_EXCEPTION( TYPE, fc::exception, CODE, WHAT ) + + FC_DECLARE_EXCEPTION( timeout_exception, timeout_exception_code, "Timeout" ); + FC_DECLARE_EXCEPTION( file_not_found_exception, file_not_found_exception_code, "File Not Found" ); + /** + * @brief report's parse errors + */ + FC_DECLARE_EXCEPTION( parse_error_exception, parse_error_exception_code, "Parse Error" ); + FC_DECLARE_EXCEPTION( invalid_arg_exception, invalid_arg_exception_code, "Invalid Argument" ); + /** + * @brief reports when a key, guid, or other item is not found. + */ + FC_DECLARE_EXCEPTION( key_not_found_exception, key_not_found_exception_code, "Key Not Found" ); + FC_DECLARE_EXCEPTION( bad_cast_exception, bad_cast_exception_code, "Bad Cast" ); + FC_DECLARE_EXCEPTION( out_of_range_exception, out_of_range_exception_code, "Out of Range" ); + + /** @brief if an operation is unsupported or not valid this may be thrown */ + FC_DECLARE_EXCEPTION( invalid_operation_exception, + invalid_operation_exception_code, + "Invalid Operation" ); + /** @brief if an host name can not be resolved this may be thrown */ + FC_DECLARE_EXCEPTION( unknown_host_exception, + unknown_host_exception_code, + "Unknown Host" ); + + /** + * @brief used to report a canceled Operation + */ + FC_DECLARE_EXCEPTION( canceled_exception, canceled_exception_code, "Canceled" ); + /** + * @brief used inplace of assert() to report violations of pre conditions. + */ + FC_DECLARE_EXCEPTION( assert_exception, assert_exception_code, "Assert Exception" ); + FC_DECLARE_EXCEPTION( eof_exception, eof_exception_code, "End Of File" ); + FC_DECLARE_EXCEPTION( null_optional, null_optional_code, "null optional" ); + FC_DECLARE_EXCEPTION( udt_exception, udt_error_code, "UDT error" ); + FC_DECLARE_EXCEPTION( aes_exception, aes_error_code, "AES error" ); + FC_DECLARE_EXCEPTION( overflow_exception, overflow_code, "Integer Overflow" ); + FC_DECLARE_EXCEPTION( underflow_exception, underflow_code, "Integer Underflow" ); + FC_DECLARE_EXCEPTION( divide_by_zero_exception, divide_by_zero_code, "Integer Divide By Zero" ); + + std::string except_str(); + + void record_assert_trip( + const char* filename, + uint32_t lineno, + const char* expr + ); + + extern bool enable_record_assert_trip; +} // namespace fc + +#if __APPLE__ + #define LIKELY(x) __builtin_expect((long)!!(x), 1L) + #define UNLIKELY(x) __builtin_expect((long)!!(x), 0L) +#else + #define LIKELY(x) (x) + #define UNLIKELY(x) (x) +#endif + +/** + *@brief: Workaround for varying preprocessing behavior between MSVC and gcc + */ +#define FC_EXPAND_MACRO( x ) x +/** + * @brief Checks a condition and throws an assert_exception if the test is FALSE + */ +#define FC_ASSERT( TEST, ... ) \ + FC_EXPAND_MACRO( \ + FC_MULTILINE_MACRO_BEGIN \ + if( UNLIKELY(!(TEST)) ) \ + { \ + if( fc::enable_record_assert_trip ) \ + fc::record_assert_trip( __FILE__, __LINE__, #TEST ); \ + FC_THROW_EXCEPTION( fc::assert_exception, #TEST ": " __VA_ARGS__ ); \ + } \ + FC_MULTILINE_MACRO_END \ + ) + +#define FC_CAPTURE_AND_THROW( EXCEPTION_TYPE, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + throw EXCEPTION_TYPE( FC_LOG_MESSAGE( error, "", FC_FORMAT_ARG_PARAMS(__VA_ARGS__) ) ); \ + FC_MULTILINE_MACRO_END + +//#define FC_THROW( FORMAT, ... ) +// FC_INDIRECT_EXPAND workas around a bug in Visual C++ variadic macro processing that prevents it +// from separating __VA_ARGS__ into separate tokens +#define FC_INDIRECT_EXPAND(MACRO, ARGS) MACRO ARGS +#define FC_THROW( ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + throw fc::exception( FC_INDIRECT_EXPAND(FC_LOG_MESSAGE, ( error, __VA_ARGS__ )) ); \ + FC_MULTILINE_MACRO_END + +#define FC_EXCEPTION( EXCEPTION_TYPE, FORMAT, ... ) \ + EXCEPTION_TYPE( FC_LOG_MESSAGE( error, FORMAT, __VA_ARGS__ ) ) +/** + * @def FC_THROW_EXCEPTION( EXCEPTION, FORMAT, ... ) + * @param EXCEPTION a class in the Phoenix::Athena::API namespace that inherits + * @param format - a const char* string with "${keys}" + */ +#define FC_THROW_EXCEPTION( EXCEPTION, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + throw EXCEPTION( FC_LOG_MESSAGE( error, FORMAT, __VA_ARGS__ ) ); \ + FC_MULTILINE_MACRO_END + + +/** + * @def FC_RETHROW_EXCEPTION(ER,LOG_LEVEL,FORMAT,...) + * @brief Appends a log_message to the exception ER and rethrows it. + */ +#define FC_RETHROW_EXCEPTION( ER, LOG_LEVEL, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + ER.append_log( FC_LOG_MESSAGE( LOG_LEVEL, FORMAT, __VA_ARGS__ ) ); \ + throw; \ + FC_MULTILINE_MACRO_END + +#define FC_LOG_AND_RETHROW( ) \ + catch( const boost::interprocess::bad_alloc& ) {\ + throw;\ + } catch( fc::exception& er ) { \ + wlog( "${details}", ("details",er.to_detail_string()) ); \ + FC_RETHROW_EXCEPTION( er, warn, "rethrow" ); \ + } catch( const std::exception& e ) { \ + fc::std_exception_wrapper sew( \ + FC_LOG_MESSAGE( warn, "rethrow ${what}: ", ("what",e.what())), \ + std::current_exception(), \ + BOOST_CORE_TYPEID(e).name(), \ + e.what() ) ; \ + wlog( "${details}", ("details",sew.to_detail_string()) ); \ + throw sew;\ + } catch( ... ) { \ + fc::unhandled_exception e( \ + FC_LOG_MESSAGE( warn, "rethrow"), \ + std::current_exception() ); \ + wlog( "${details}", ("details",e.to_detail_string()) ); \ + throw e; \ + } + +#define FC_CAPTURE_LOG_AND_RETHROW( ... ) \ + catch( const boost::interprocess::bad_alloc& ) {\ + throw;\ + } catch( fc::exception& er ) { \ + wlog( "${details}", ("details",er.to_detail_string()) ); \ + wdump( __VA_ARGS__ ); \ + FC_RETHROW_EXCEPTION( er, warn, "rethrow", FC_FORMAT_ARG_PARAMS(__VA_ARGS__) ); \ + } catch( const std::exception& e ) { \ + fc::std_exception_wrapper sew( \ + FC_LOG_MESSAGE( warn, "rethrow ${what}: ", FC_FORMAT_ARG_PARAMS( __VA_ARGS__ )("what",e.what())), \ + std::current_exception(), \ + BOOST_CORE_TYPEID(e).name(), \ + e.what() ) ; \ + wlog( "${details}", ("details",sew.to_detail_string()) ); \ + wdump( __VA_ARGS__ ); \ + throw sew;\ + } catch( ... ) { \ + fc::unhandled_exception e( \ + FC_LOG_MESSAGE( warn, "rethrow", FC_FORMAT_ARG_PARAMS( __VA_ARGS__) ), \ + std::current_exception() ); \ + wlog( "${details}", ("details",e.to_detail_string()) ); \ + wdump( __VA_ARGS__ ); \ + throw e; \ + } + +#define FC_CAPTURE_AND_LOG( ... ) \ + catch( const boost::interprocess::bad_alloc& ) {\ + throw;\ + } catch( fc::exception& er ) { \ + wlog( "${details}", ("details",er.to_detail_string()) ); \ + wdump( __VA_ARGS__ ); \ + } catch( const std::exception& e ) { \ + fc::std_exception_wrapper sew( \ + FC_LOG_MESSAGE( warn, "rethrow ${what}: ",FC_FORMAT_ARG_PARAMS( __VA_ARGS__ )("what",e.what()) ), \ + std::current_exception(), \ + BOOST_CORE_TYPEID(e).name(), \ + e.what() ) ; \ + wlog( "${details}", ("details",sew.to_detail_string()) ); \ + wdump( __VA_ARGS__ ); \ + } catch( ... ) { \ + fc::unhandled_exception e( \ + FC_LOG_MESSAGE( warn, "rethrow", FC_FORMAT_ARG_PARAMS( __VA_ARGS__) ), \ + std::current_exception() ); \ + wlog( "${details}", ("details",e.to_detail_string()) ); \ + wdump( __VA_ARGS__ ); \ + } + +#define FC_LOG_AND_DROP( ... ) \ + catch( const boost::interprocess::bad_alloc& ) {\ + throw;\ + } catch( fc::exception& er ) { \ + wlog( "${details}", ("details",er.to_detail_string()) ); \ + } catch( const std::exception& e ) { \ + fc::std_exception_wrapper sew( \ + FC_LOG_MESSAGE( warn, "rethrow ${what}: ",FC_FORMAT_ARG_PARAMS( __VA_ARGS__ )("what",e.what()) ), \ + std::current_exception(), \ + BOOST_CORE_TYPEID(e).name(), \ + e.what() ) ; \ + wlog( "${details}", ("details",sew.to_detail_string()) ); \ + } catch( ... ) { \ + fc::unhandled_exception e( \ + FC_LOG_MESSAGE( warn, "rethrow", FC_FORMAT_ARG_PARAMS( __VA_ARGS__) ), \ + std::current_exception() ); \ + wlog( "${details}", ("details",e.to_detail_string()) ); \ + } + + +/** + * @def FC_RETHROW_EXCEPTIONS(LOG_LEVEL,FORMAT,...) + * @brief Catchs all exception's, std::exceptions, and ... and rethrows them after + * appending the provided log message. + */ +#define FC_RETHROW_EXCEPTIONS( LOG_LEVEL, FORMAT, ... ) \ + catch( const boost::interprocess::bad_alloc& ) {\ + throw;\ + } catch( fc::exception& er ) { \ + FC_RETHROW_EXCEPTION( er, LOG_LEVEL, FORMAT, __VA_ARGS__ ); \ + } catch( const std::exception& e ) { \ + fc::std_exception_wrapper sew( \ + FC_LOG_MESSAGE( LOG_LEVEL, "${what}: " FORMAT,__VA_ARGS__("what",e.what())), \ + std::current_exception(), \ + BOOST_CORE_TYPEID(e).name(), \ + e.what() ); \ + throw sew;\ + } catch( ... ) { \ + throw fc::unhandled_exception( \ + FC_LOG_MESSAGE( LOG_LEVEL, FORMAT,__VA_ARGS__), \ + std::current_exception() ); \ + } + +#define FC_CAPTURE_AND_RETHROW( ... ) \ + catch( const boost::interprocess::bad_alloc& ) {\ + throw;\ + } catch( fc::exception& er ) { \ + FC_RETHROW_EXCEPTION( er, warn, "", FC_FORMAT_ARG_PARAMS(__VA_ARGS__) ); \ + } catch( const std::exception& e ) { \ + fc::std_exception_wrapper sew( \ + FC_LOG_MESSAGE( warn, "${what}: ",FC_FORMAT_ARG_PARAMS(__VA_ARGS__)("what",e.what())), \ + std::current_exception(), \ + BOOST_CORE_TYPEID(e).name(), \ + e.what() ); \ + throw sew;\ + } catch( ... ) { \ + throw fc::unhandled_exception( \ + FC_LOG_MESSAGE( warn, "",FC_FORMAT_ARG_PARAMS(__VA_ARGS__)), \ + std::current_exception() ); \ + } + +#define FC_CHECK_DEADLINE( DEADLINE, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( DEADLINE < fc::time_point::maximum() && DEADLINE < fc::time_point::now() ) { \ + auto log_mgs = FC_LOG_MESSAGE( error, "deadline ${d} exceeded by ${t}us ", \ + FC_FORMAT_ARG_PARAMS(__VA_ARGS__)("d", DEADLINE)("t", fc::time_point::now() - DEADLINE) ); \ + auto msg = log_mgs.get_limited_message(); \ + throw fc::timeout_exception( std::move( log_mgs ), fc::timeout_exception_code, "timeout_exception", std::move( msg ) ); \ + } \ + FC_MULTILINE_MACRO_END diff --git a/libraries/libfc/include/fc/filesystem.hpp b/libraries/libfc/include/fc/filesystem.hpp new file mode 100644 index 0000000000..389c610825 --- /dev/null +++ b/libraries/libfc/include/fc/filesystem.hpp @@ -0,0 +1,264 @@ +#pragma once +#include +#include + +#include +#include +#include + +namespace boost { + namespace filesystem { + class path; + class directory_iterator; + class recursive_directory_iterator; + } +} + + +namespace fc { + /** + * @brief wraps boost::filesystem::path to provide platform independent path manipulation. + * + * Most calls are simply a passthrough to boost::filesystem::path, however exceptions are + * wrapped in an fc::error_report and the additional helper method fc::path::windows_string(), + * can be used to calculate paths intended for systems different than the host. + * + * @note Serializes to a fc::value() as the result of generic_string() + */ + class path { + public: + path(); + ~path(); + path( const boost::filesystem::path& ); + path( const std::string& p ); + /// Constructor to build path using unicode native characters. + path(const std::wstring& p); + path( const char* ); + path( const path& p ); + path( path&& p ); + path& operator =( const path& ); + path& operator =( path&& ); + + path& operator /=( const fc::path& ); + friend path operator /( const fc::path& p, const fc::path& ); + friend bool operator ==( const fc::path& p, const fc::path& ); + friend bool operator !=( const fc::path& p, const fc::path& ); + friend bool operator < ( const fc::path& p, const fc::path& ); + + operator boost::filesystem::path& (); + operator const boost::filesystem::path& ()const; + + void replace_extension( const fc::path& e ); + fc::path stem()const; + fc::path extension()const; + fc::path filename()const; + fc::path parent_path()const; + std::string string()const; + std::string generic_string()const; + /** On windows, returns a path where all path separators are '\' suitable for displaying + * to users. On other platforms, it does the same as generic_string() + */ + std::string preferred_string() const; + + std::wstring wstring() const; + std::wstring generic_wstring() const; + std::wstring preferred_wstring() const; + + /** Retrieves native string path representation and next converts it into + ANSI UTF-8 representation. + It is needed since not all parts of fc library accept unicode paths + (fc::file_mapping). + */ + std::string to_native_ansi_path() const; + + /** + * @brief replaces '/' with '\' in the result of generic_string() + * + * @note not part of boost::filesystem::path + */ + std::string windows_string()const; + + bool is_relative()const; + bool is_absolute()const; + bool empty() const; + + static char separator_char; + + private: + #ifdef _WIN64 + fwd _p; + #else + fwd _p; + #endif + }; + + namespace detail + { + class path_wrapper + { + public: + path_wrapper(path p) : + _path(p) + { + } + const path* operator->() const + { + return &_path; + } + private: + path _path; + }; + } + + class directory_iterator { + public: + directory_iterator( const fc::path& p ); + directory_iterator(); + ~directory_iterator(); + + fc::path operator*()const; + detail::path_wrapper operator->() const; + directory_iterator& operator++(int); + directory_iterator& operator++(); + + friend bool operator==( const directory_iterator&, const directory_iterator& ); + friend bool operator!=( const directory_iterator&, const directory_iterator& ); + private: + fwd _p; + }; + class recursive_directory_iterator { + public: + recursive_directory_iterator( const fc::path& p ); + recursive_directory_iterator(); + ~recursive_directory_iterator(); + + fc::path operator*()const; + recursive_directory_iterator& operator++(int); + recursive_directory_iterator& operator++(); + void pop(); + int level(); + + + friend bool operator==( const recursive_directory_iterator&, const recursive_directory_iterator& ); + friend bool operator!=( const recursive_directory_iterator&, const recursive_directory_iterator& ); + private: + fwd _p; + }; + + bool exists( const path& p ); + bool is_directory( const path& p ); + bool is_regular_file( const path& p ); + void create_directories( const path& p ); + void remove_all( const path& p ); + path absolute( const path& p ); + path make_relative(const path& from, const path& to); + path canonical( const path& p ); + uint64_t file_size( const path& p ); + uint64_t directory_size( const path& p ); + bool remove( const path& p ); + void copy( const path& from, const path& to ); + void rename( const path& from, const path& to ); + void resize_file( const path& file, size_t s ); + + // setuid, setgid not implemented. + // translates octal permission like 0755 to S_ stuff defined in sys/stat.h + // no-op on Windows. + void chmod( const path& p, int perm ); + + void create_hard_link( const path& from, const path& to ); + + path unique_path(); + path temp_directory_path(); + + /** @return the home directory on Linux and OS X and the Profile directory on Windows */ + const path& home_path(); + + /** @return the home_path() on Linux, home_path()/Library/Application Support/ on OS X, + * and APPDATA on windows + */ + const path& app_path(); + + /** @return application executable path */ + const fc::path& current_path(); + + class variant; + void to_variant( const fc::path&, fc::variant& ); + void from_variant( const fc::variant& , fc::path& ); + + template<> struct get_typename { static const char* name() { return "path"; } }; + + /** + * Class which creates a temporary directory inside an existing temporary directory. + */ + class temp_file_base + { + public: + inline ~temp_file_base() { remove(); } + inline operator bool() const { return _path.has_value(); } + inline bool operator!() const { return !_path.has_value(); } + const fc::path& path() const; + void remove(); + void release(); + protected: + typedef std::optional path_t; + inline temp_file_base(const path_t& path) : _path(path) {} + inline temp_file_base(path_t&& path) : _path(std::move(path)) {} + path_t _path; + }; + + /** + * Class which creates a temporary directory inside an existing temporary directory. + */ + class temp_file : public temp_file_base + { + public: + temp_file(temp_file&& other); + temp_file& operator=(temp_file&& other); + temp_file(const fc::path& tempFolder = fc::temp_directory_path(), bool create = false); + }; + + /** + * Class which creates a temporary directory inside an existing temporary directory. + */ + class temp_directory : public temp_file_base + { + public: + temp_directory(temp_directory&& other); + temp_directory& operator=(temp_directory&& other); + temp_directory(const fc::path& tempFolder = fc::temp_directory_path()); + }; + + +#if !defined(__APPLE__) + // this code is known to work on linux and windows. It may work correctly on mac, + // or it may need slight tweaks or extra includes. It's disabled now to avoid giving + // a false sense of security. +# define FC_HAS_SIMPLE_FILE_LOCK +#endif +#ifdef FC_HAS_SIMPLE_FILE_LOCK + /** simple class which only allows one process to open any given file. + * approximate usage: + * int main() { + * fc::simple_file_lock instance_lock("~/.my_app/.lock"); + * if (!instance_lock.try_lock()) { + * elog("my_app is already running"); + * return 1; + * } + * // do stuff here, file will be unlocked when instance_lock goes out of scope + * } + */ + class simple_lock_file + { + public: + simple_lock_file(const path& lock_file_path); + ~simple_lock_file(); + bool try_lock(); + void unlock(); + private: + class impl; + std::unique_ptr my; + }; +#endif // FC_HAS_SIMPLE_FILE_LOCK + +} + diff --git a/libraries/libfc/include/fc/fixed_string.hpp b/libraries/libfc/include/fc/fixed_string.hpp new file mode 100644 index 0000000000..3c087fe284 --- /dev/null +++ b/libraries/libfc/include/fc/fixed_string.hpp @@ -0,0 +1,171 @@ +#pragma once +#include + + +namespace fc { + + + /** + * This class is designed to offer in-place memory allocation of a string up to Length equal to + * sizeof(Storage). + * + * The string will serialize the same way as std::string for variant and raw formats + * The string will sort according to the comparison operators defined for Storage, this enables effecient + * sorting. + */ + template > + class fixed_string { + public: + fixed_string(){ + memset( (char*)&data, 0, sizeof(data) ); + } + fixed_string( const fixed_string& c ):data(c.data){} + + fixed_string( const std::string& str ) { + if( str.size() < sizeof(data) ) { + memset( (char*)&data, 0, sizeof(data) ); + memcpy( (char*)&data, str.c_str(), str.size() ); + } else { + memcpy( (char*)&data, str.c_str(), sizeof(data) ); + } + } + fixed_string( const char* str ) { + memset( (char*)&data, 0, sizeof(data) ); + auto l = strlen(str); + if( l < sizeof(data) ) { + memset( (char*)&data, 0, sizeof(data) ); + memcpy( (char*)&data, str, l ); + } + else { + memcpy( (char*)&data, str, sizeof(data) ); + } + } + + operator std::string()const { + const char* self = (const char*)&data; + return std::string( self, self + size() ); + } + + uint32_t size()const { + if( *(((const char*)&data)+sizeof(data) - 1) ) + return sizeof(data); + return strnlen( (const char*)&data, sizeof(data) ); + } + uint32_t length()const { return size(); } + + fixed_string& operator=( const fixed_string& str ) { + data = str.data; + return *this; + } + fixed_string& operator=( const char* str ) { + return *this = fixed_string(str); + } + + fixed_string& operator=( const std::string& str ) { + if( str.size() < sizeof(data) ) { + memset( (char*)&data, 0, sizeof(data) ); + memcpy( (char*)&data, str.c_str(), str.size() ); + } + else { + memcpy( (char*)&data, str.c_str(), sizeof(data) ); + } + return *this; + } + + friend std::string operator + ( const fixed_string& a, const std::string& b ) { + return std::string(a) + b; + } + friend std::string operator + ( const std::string& a, const fixed_string& b ) { + return a + std::string(b); + } + + friend bool operator < ( const fixed_string& a, const fixed_string& b ) { + return a.data < b.data; + } + friend bool operator <= ( const fixed_string& a, const fixed_string& b ) { + return a.data <= b.data; + } + friend bool operator > ( const fixed_string& a, const fixed_string& b ) { + return a.data > b.data; + } + friend bool operator >= ( const fixed_string& a, const fixed_string& b ) { + return a.data >= b.data; + } + friend bool operator == ( const fixed_string& a, const fixed_string& b ) { + return a.data == b.data; + } + friend bool operator != ( const fixed_string& a, const fixed_string& b ) { + return a.data != b.data; + } + + friend std::ostream& operator << ( std::ostream& out, const fixed_string& str ) { + return out << std::string(str); + } + //private: + Storage data; + }; + + namespace raw + { + template + inline void pack( Stream& s, const fc::fixed_string& u ) { + unsigned_int size = u.size(); + pack( s, size ); + s.write( (const char*)&u.data, size ); + } + + template + inline void unpack( Stream& s, fc::fixed_string& u ) { + unsigned_int size; + fc::raw::unpack( s, size ); + if( size.value > 0 ) { + if( size.value > sizeof(Storage) ) { + s.read( (char*)&u.data, sizeof(Storage) ); + char buf[1024]; + size_t left = size.value - sizeof(Storage); + while( left >= 1024 ) + { + s.read( buf, 1024 ); + left -= 1024; + } + s.read( buf, left ); + + /* + s.seekp( s.tellp() + (size.value - sizeof(Storage)) ); + char tmp; + size.value -= sizeof(storage); + while( size.value ){ s.read( &tmp, 1 ); --size.value; } + */ + // s.skip( size.value - sizeof(Storage) ); + } else { + s.read( (char*)&u.data, size.value ); + } + } + } + + /* + template + inline void pack( Stream& s, const boost::multiprecision::number& d ) { + s.write( (const char*)&d, sizeof(d) ); + } + + template + inline void unpack( Stream& s, boost::multiprecision::number& u ) { + s.read( (const char*)&u, sizeof(u) ); + } + */ + } +} + +#include +namespace fc { + template + void to_variant( const fixed_string& s, variant& v ) { + v = std::string(s); + } + + template + void from_variant( const variant& v, fixed_string& s ) { + s = v.as_string(); + } +} diff --git a/libraries/libfc/include/fc/fwd.hpp b/libraries/libfc/include/fc/fwd.hpp new file mode 100644 index 0000000000..0aabe43a85 --- /dev/null +++ b/libraries/libfc/include/fc/fwd.hpp @@ -0,0 +1,44 @@ +#pragma once +#include + +namespace fc { + +/** + * @brief Used to forward declare value types. + * + */ +template +class fwd { + public: + template + fwd( U&&... u ); + fwd(); + + fwd( const fwd& f ); + fwd( fwd&& f ); + + operator const T&()const; + operator T&(); + + T& operator*(); + const T& operator*()const; + const T* operator->()const; + + T* operator->(); + bool operator !()const; + + template + T& operator = ( U&& u ); + + T& operator = ( fwd&& u ); + T& operator = ( const fwd& u ); + + ~fwd(); + + private: + aligned _store; +}; + + +} // namespace fc + diff --git a/libraries/libfc/include/fc/fwd_impl.hpp b/libraries/libfc/include/fc/fwd_impl.hpp new file mode 100644 index 0000000000..af2b23fdda --- /dev/null +++ b/libraries/libfc/include/fc/fwd_impl.hpp @@ -0,0 +1,121 @@ +#pragma once + +#include +#include +#include + +namespace fc { + + namespace detail { + template + struct add { + typedef decltype( *((A*)0) + *((typename fc::remove_reference::type*)0) ) type; + }; + template + struct add_eq { + typedef decltype( *((A*)0) += *((typename fc::remove_reference::type*)0) ) type; + }; + + template + struct sub { + typedef decltype( *((A*)0) - *((typename fc::remove_reference::type*)0) ) type; + }; + + template + struct sub_eq { + typedef decltype( *((A*)0) -= *((typename fc::remove_reference::type*)0) ) type; + }; + template + struct insert_op { + typedef decltype( *((A*)0) << *((typename fc::remove_reference::type*)0) ) type; + }; + template + struct extract_op { + A* a; + U* u; + typedef decltype( *a >> *u ) type; + }; + } + + + template + auto operator + ( const fwd& x, U&& u ) -> typename detail::add::type { return *x+fc::forward(u); } + + template + auto operator - ( const fwd& x, U&& u ) -> typename detail::sub::type { return *x-fc::forward(u); } + + template + auto operator << ( U& u, const fwd& f ) -> typename detail::insert_op::type { return u << *f; } + + template + auto operator >> ( U& u, fwd& f ) -> typename detail::extract_op::type { return u >> *f; } + + template + bool fwd::operator !()const { return !(**this); } + + + template + void check_size() { static_assert( (ProvidedSize >= RequiredSize), "Failed to reserve enough space in fc::fwd" ); } + + template + template + fwd::fwd( U&&... u ) { + check_size(); + new (this) T( fc::forward(u)... ); + } + + template + fwd::fwd() { + check_size(); + new (this) T; + } + template + fwd::fwd( const fwd& f ){ + check_size(); + new (this) T( *f ); + } + template + fwd::fwd( fwd&& f ){ + check_size(); + new (this) T( fc::move(*f) ); + } + + + + template + fwd::operator T&() { return *(( T*)this); } + template + fwd::operator const T&()const { return *((const T*)this); } + + template + T& fwd::operator*() { return *((T*)this); } + template + const T& fwd::operator*()const { return *((const T*)this); } + template + const T* fwd::operator->()const { return ((const T*)this); } + + template + T* fwd::operator->(){ return ((T*)this); } + + + template + fwd::~fwd() { + ((T*)this)->~T(); + } + template + template + T& fwd::operator = ( U&& u ) { + return **this = fc::forward(u); + } + + template + T& fwd::operator = ( fwd&& u ) { + return **this = fc::move(*u); + } + template + T& fwd::operator = ( const fwd& u ) { + return **this = *u; + } + +} // namespace fc + diff --git a/libraries/libfc/include/fc/git_revision.hpp b/libraries/libfc/include/fc/git_revision.hpp new file mode 100644 index 0000000000..6232f3c3c5 --- /dev/null +++ b/libraries/libfc/include/fc/git_revision.hpp @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace fc { + +extern const char* const git_revision_sha; +extern const uint32_t git_revision_unix_timestamp; + +} // end namespace fc diff --git a/libraries/libfc/include/fc/interprocess/container.hpp b/libraries/libfc/include/fc/interprocess/container.hpp new file mode 100644 index 0000000000..1e0cec8df7 --- /dev/null +++ b/libraries/libfc/include/fc/interprocess/container.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + + namespace bip = boost::interprocess; + + namespace raw { + + template + void pack( Stream& s, const bip::set& value ) { + detail::pack_set( s, value ); + } + + template + void unpack( Stream& s, bip::set& value ) { + detail::unpack_set( s, value ); + } + + template + void pack( Stream& s, const bip::multiset& value ) { + detail::pack_set( s, value ); + } + + template + void unpack( Stream& s, bip::multiset& value ) { + detail::unpack_set( s, value ); + } + + template + void pack( Stream& s, const bip::map& value ) { + detail::pack_map( s, value ); + } + + template + void unpack( Stream& s, bip::map& value ) { + detail::unpack_map( s, value ); + } + + template + void pack( Stream& s, const bip::multimap& value ) { + detail::pack_map( s, value ); + } + + template + void unpack( Stream& s, bip::multimap& value ) { + detail::unpack_map( s, value ); + } + + } + + template + void to_variant( const bip::set< T, U... >& s, fc::variant& vo ) { + detail::to_variant_from_set( s, vo ); + } + + template + void from_variant( const fc::variant& v, bip::set< T, U... >& s ) { + detail::from_variant_to_set( v, s ); + } + + template + void to_variant( const bip::multiset< T, U... >& s, fc::variant& vo ) { + detail::to_variant_from_set( s, vo ); + } + + template + void from_variant( const fc::variant& v, bip::multiset< T, U... >& s ) { + detail::from_variant_to_set( v, s ); + } + + template + void to_variant( const bip::map< K, V, U... >& m, fc::variant& vo ) { + detail::to_variant_from_map( m, vo ); + } + + template + void from_variant( const variant& v, bip::map& m ) { + detail::from_variant_to_map( v, m ); + } + + template + void to_variant( const bip::multimap< K, V, U... >& m, fc::variant& vo ) { + detail::to_variant_from_map( m, vo ); + } + + template + void from_variant( const variant& v, bip::multimap& m ) { + detail::from_variant_to_map( v, m ); + } + +} diff --git a/libraries/libfc/include/fc/interprocess/file_mapping.hpp b/libraries/libfc/include/fc/interprocess/file_mapping.hpp new file mode 100644 index 0000000000..e6b6817407 --- /dev/null +++ b/libraries/libfc/include/fc/interprocess/file_mapping.hpp @@ -0,0 +1,42 @@ +#pragma once +#include +#include + +namespace boost { + namespace interprocess { + class file_mapping; + class mapped_region; + } +} +namespace fc { + enum mode_t { + read_only, + write_only, + read_write + }; + + class file_mapping { + public: + file_mapping( const char* file, mode_t ); + ~file_mapping(); + private: + friend class mapped_region; + #ifdef _WIN64 + fc::fwd my; + #else + fc::fwd my; + #endif + }; + + class mapped_region { + public: + mapped_region( const file_mapping& fm, mode_t m, uint64_t start, size_t size ); + mapped_region( const file_mapping& fm, mode_t m ); + ~mapped_region(); + void flush(); + void* get_address()const; + size_t get_size()const; + private: + fc::fwd my; + }; +} diff --git a/libraries/libfc/include/fc/interprocess/iprocess.hpp b/libraries/libfc/include/fc/interprocess/iprocess.hpp new file mode 100644 index 0000000000..c7f8c4e2a5 --- /dev/null +++ b/libraries/libfc/include/fc/interprocess/iprocess.hpp @@ -0,0 +1,67 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace fc +{ + /** + * @brief abstract interface for interacting with external processes + * + * At the very least we have ssh::process and direct child processes, and + * there may be other processes that need to implement this protocol. + */ + class iprocess + { + public: + enum exec_opts { + open_none = 0, + open_stdin = 0x01, + open_stdout = 0x02, + open_stderr = 0x04, + open_all = open_stdin|open_stdout|open_stderr, + suppress_console = 0x08 + }; + + virtual ~iprocess(){} + + /** + * + * @return *this + */ + virtual iprocess& exec( const path& exe, std::vector args, + const path& work_dir = path(), int opts = open_all ) = 0; + + /** + * @return blocks until the process exits + */ + virtual int result(const microseconds& timeout = microseconds::maximum()) = 0; + + + /** + * Forcefully kills the process. + */ + virtual void kill() = 0; + + /** + * @brief returns a stream that writes to the process' stdin + */ + virtual buffered_ostream_ptr in_stream() = 0; + + /** + * @brief returns a stream that reads from the process' stdout + */ + virtual buffered_istream_ptr out_stream() = 0; + /** + * @brief returns a stream that reads from the process' stderr + */ + virtual buffered_istream_ptr err_stream() = 0; + + }; + + typedef std::shared_ptr iprocess_ptr; + + +} // namespace fc diff --git a/libraries/libfc/include/fc/interprocess/mmap_struct.hpp b/libraries/libfc/include/fc/interprocess/mmap_struct.hpp new file mode 100644 index 0000000000..0913deb769 --- /dev/null +++ b/libraries/libfc/include/fc/interprocess/mmap_struct.hpp @@ -0,0 +1,59 @@ +#pragma once +#include +#include + +namespace fc +{ + class path; + namespace detail + { + /** + * Base class used to hide common implementation details. + */ + class mmap_struct_base + { + public: + size_t size()const; + void flush(); + + protected: + void open( const fc::path& file, size_t s, bool create ); + std::unique_ptr _file_mapping; + std::unique_ptr _mapped_region; + }; + }; + + /** + * @class mmap_struct + * @brief A struct that has been mapped from a file. + * + * @note T must be POD + */ + template + class mmap_struct : public detail::mmap_struct_base + { + public: + mmap_struct():_mapped_struct(nullptr){} + /** + * Create the file if it does not exist or is of the wrong size if create is true, then maps + * the file to memory. + * + * @throw an exception if the file does not exist or is the wrong size and create is false + */ + void open( const fc::path& file, bool create = false ) + { + detail::mmap_struct_base::open( file, sizeof(T), create ); + _mapped_struct = (T*)_mapped_region->get_address(); + } + + T* operator->() { return _mapped_struct; } + const T* operator->()const { return _mapped_struct; } + + T& operator*() { return *_mapped_struct; } + const T& operator*()const { return *_mapped_struct; } + + private: + T* _mapped_struct; + }; + +} diff --git a/libraries/libfc/include/fc/interprocess/process.hpp b/libraries/libfc/include/fc/interprocess/process.hpp new file mode 100644 index 0000000000..777c154a79 --- /dev/null +++ b/libraries/libfc/include/fc/interprocess/process.hpp @@ -0,0 +1,37 @@ +#pragma once +#include + +namespace fc { + + fc::path find_executable_in_path( const fc::string name ); + + /** + * @brief start and manage an local process + * @note this class implements reference semantics. + */ + class process : public iprocess + { + public: + process(); + ~process(); + + virtual iprocess& exec( const fc::path& exe, + std::vector args, + const fc::path& work_dir = fc::path(), + int opts = open_all ); + + + virtual int result(const microseconds& timeout = microseconds::maximum()); + virtual void kill(); + virtual fc::buffered_ostream_ptr in_stream(); + virtual fc::buffered_istream_ptr out_stream(); + virtual fc::buffered_istream_ptr err_stream(); + + class impl; + private: + std::unique_ptr my; + }; + + typedef std::shared_ptr process_ptr; + +} // namespace fc diff --git a/libraries/libfc/include/fc/io/bio_device_adaptor.hpp b/libraries/libfc/include/fc/io/bio_device_adaptor.hpp new file mode 100644 index 0000000000..609fe36712 --- /dev/null +++ b/libraries/libfc/include/fc/io/bio_device_adaptor.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +namespace fc { + +namespace bio = boost::iostreams; + +template +struct device_adaptor { + STREAM& strm; + + typedef char char_type; + typedef Category category; + size_t write(const char* data, size_t n) { return strm.write(data, n), n; } + size_t read(char* data, size_t n) { return strm.read(data, n), n; } +}; + +template +device_adaptor to_sink(STREAM& strm) { + return device_adaptor{strm}; +} + +template +device_adaptor to_source(STREAM& strm) { + return device_adaptor{strm}; +} + +template +device_adaptor to_seekable(STREAM& strm) { + return device_adaptor{strm}; +} +} // namespace fc \ No newline at end of file diff --git a/libraries/libfc/include/fc/io/buffered_iostream.hpp b/libraries/libfc/include/fc/io/buffered_iostream.hpp new file mode 100644 index 0000000000..470b1fd118 --- /dev/null +++ b/libraries/libfc/include/fc/io/buffered_iostream.hpp @@ -0,0 +1,73 @@ +#pragma once +#include + +namespace fc +{ + namespace detail + { + class buffered_istream_impl; + class buffered_ostream_impl; + } + + /** + * @brief Reads data from an unbuffered stream + * and enables peek functionality. + */ + class buffered_istream : public virtual istream + { + public: + buffered_istream( istream_ptr is ); + buffered_istream( buffered_istream&& o ); + + buffered_istream& operator=( buffered_istream&& i ); + + virtual ~buffered_istream(); + + /** read at least 1 byte or throw, if no data is available + * this method should block cooperatively until data is + * available or fc::eof_exception is thrown. + * + * @pre len > 0 + * @pre buf != nullptr + * @throws fc::eof if at least 1 byte cannot be read + **/ + virtual std::size_t readsome( char* buf, std::size_t len ); + virtual size_t readsome( const std::shared_ptr& buf, size_t len, size_t offset ); + + /** + * This method may block until at least 1 character is + * available. + */ + virtual char peek() const; + + private: + std::unique_ptr my; + }; + typedef std::shared_ptr buffered_istream_ptr; + + + /** + * + */ + class buffered_ostream : public virtual ostream + { + public: + buffered_ostream( ostream_ptr o, size_t bufsize = 4096 ); + buffered_ostream( buffered_ostream&& m ); + ~buffered_ostream(); + + buffered_ostream& operator=( buffered_ostream&& m ); + /** + * This method will return immediately unless the buffer + * is full, in which case it will flush which may block. + */ + virtual size_t writesome( const char* buf, size_t len ); + virtual size_t writesome( const std::shared_ptr& buf, size_t len, size_t offset ); + + virtual void close(); + virtual void flush(); + private: + std::unique_ptr my; + }; + typedef std::shared_ptr buffered_ostream_ptr; +} diff --git a/libraries/libfc/include/fc/io/cfile.hpp b/libraries/libfc/include/fc/io/cfile.hpp new file mode 100644 index 0000000000..3bf6d6f7f4 --- /dev/null +++ b/libraries/libfc/include/fc/io/cfile.hpp @@ -0,0 +1,278 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include + +#ifndef _WIN32 +#define FC_FOPEN(p, m) fopen(p, m) +#else +#define FC_CAT(s1, s2) s1 ## s2 +#define FC_PREL(s) FC_CAT(L, s) +#define FC_FOPEN(p, m) _wfopen(p, FC_PREL(m)) +#endif + +namespace fc { + +namespace detail { + using unique_file = std::unique_ptr; +} + +class cfile_datastream; + +/** + * Wrapper for c-file access that provides a similar interface as fstream without all the overhead of std streams. + * std::ios_base::failure exception thrown for errors. + */ +class cfile { +public: + cfile() + : _file(nullptr, &fclose) + {} + + void set_file_path( fc::path file_path ) { + _file_path = std::move( file_path ); + } + + fc::path get_file_path() const { + return _file_path; + } + + bool is_open() const { return _open; } + + auto fileno() const { + int fd = ::fileno(_file.get()); + if( -1 == fd ) { + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to convert file pointer to file descriptor, error: " + + std::to_string( errno ) ); + } + return fd; + } + + static constexpr const char* create_or_update_rw_mode = "ab+"; + static constexpr const char* update_rw_mode = "rb+"; + static constexpr const char* truncate_rw_mode = "wb+"; + + /// @param mode is any mode supported by fopen + /// Tested with: + /// "ab+" - open for binary update - create if does not exist + /// "rb+" - open for binary update - file must exist + void open( const char* mode ) { + _file.reset( FC_FOPEN( _file_path.generic_string().c_str(), mode ) ); + if( !_file ) { + throw std::ios_base::failure( "cfile unable to open: " + _file_path.generic_string() + " in mode: " + std::string( mode ) ); + } +#ifndef _WIN32 + struct stat st; + _file_blk_size = 4096; + if( fstat(fileno(), &st) == 0 ) + _file_blk_size = st.st_blksize; +#endif + _open = true; + } + + size_t tellp() const { + long result = ftell( _file.get() ); + if (result == -1) + throw std::ios_base::failure("cfile: " + get_file_path().generic_string() + + " unable to get the current position of the file, error: " + std::to_string( errno )); + return static_cast(result); + } + + void seek( long loc ) { + if( 0 != fseek( _file.get(), loc, SEEK_SET ) ) { + int err = ferror(_file.get()); + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to SEEK_SET to: " + std::to_string(loc) + + ", ferror: " + std::to_string(err) ); + } + } + + void seek_end( long loc ) { + if( 0 != fseek( _file.get(), loc, SEEK_END ) ) { + int err = ferror(_file.get()); + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to SEEK_END to: " + std::to_string(loc) + + ", ferror: " + std::to_string(err) ); + } + } + + void skip( long loc) { + if( 0 != fseek( _file.get(), loc, SEEK_CUR ) ) { + int err = ferror(_file.get()); + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to SEEK_CUR to: " + std::to_string(loc) + + ", ferror: " + std::to_string(err) ); + } + } + + void read( char* d, size_t n ) { + size_t result = fread( d, 1, n, _file.get() ); + if( result != n ) { + int err = ferror(_file.get()); + int eof = feof(_file.get()); + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to read " + std::to_string( n ) + " bytes;" + " only read " + std::to_string( result ) + + ", eof: " + (eof == 0 ? "false" : "true") + + ", ferror: " + std::to_string(err) ); + } + } + + void write( const char* d, size_t n ) { + size_t result = fwrite( d, 1, n, _file.get() ); + if( result != n ) { + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to write " + std::to_string( n ) + " bytes; only wrote " + std::to_string( result ) ); + } + } + + void flush() { + if( 0 != fflush( _file.get() ) ) { + int err = ferror( _file.get() ); + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to flush file, ferror: " + std::to_string( err ) ); + } + } + + void sync() { + const int fd = fileno(); + if( -1 == fsync( fd ) ) { + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to sync file, error: " + std::to_string( errno ) ); + } +#ifdef __APPLE__ + if( -1 == fcntl( fd, F_FULLFSYNC ) ) { + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to F_FULLFSYNC file, error: " + std::to_string( errno ) ); + } +#endif + } + + //rounds to filesystem block boundaries; e.g. punch_hole(5000, 14000) when blocksz=4096 punches from 8192 to 12288 + //end is not inclusive; eg punch_hole(4096, 8192) will punch 4096 bytes (assuming blocksz=4096) + void punch_hole(size_t begin, size_t end) { + if(begin % _file_blk_size) { + begin &= ~(_file_blk_size-1); + begin += _file_blk_size; + } + end &= ~(_file_blk_size-1); + + if(begin >= end) + return; + + int ret = 0; +#if defined(__linux__) + ret = fallocate(fileno(), FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, begin, end-begin); +#elif defined(__APPLE__) + struct fpunchhole puncher = {0, 0, static_cast(begin), static_cast(end-begin)}; + ret = fcntl(fileno(), F_PUNCHHOLE, &puncher); +#endif + if(ret == -1) + wlog("Failed to punch hole in file ${f}: ${e}", ("f", _file_path)("e", strerror(errno))); + + flush(); + } + + static bool supports_hole_punching() { +#if defined(__linux__) || defined(__APPLE__) + return true; +#endif + return false; + } + + size_t filesystem_block_size() const { return _file_blk_size; } + + bool eof() const { return feof(_file.get()) != 0; } + + int getc() { + int ret = fgetc(_file.get()); + if (ret == EOF) { + throw std::ios_base::failure( "cfile: " + _file_path.generic_string() + + " unable to read 1 byte"); + } + return ret; + } + + void close() { + _file.reset(); + _open = false; + } + + boost::interprocess::mapping_handle_t get_mapping_handle() const { + return {fileno(), false}; + } + + cfile_datastream create_datastream(); + +private: + bool _open = false; + fc::path _file_path; + size_t _file_blk_size = 4096; + detail::unique_file _file; +}; + +/* + * @brief datastream adapter that adapts cfile for use with fc unpack + * + * This class supports unpack functionality but not pack. + */ +class cfile_datastream { +public: + explicit cfile_datastream( cfile& cf ) : cf(cf) {} + + void skip( size_t s ) { + std::vector d( s ); + read( &d[0], s ); + } + + bool read( char* d, size_t s ) { + cf.read( d, s ); + return true; + } + + bool get( unsigned char& c ) { return get( *(char*)&c ); } + + bool get( char& c ) { return read(&c, 1); } + + size_t tellp() const { return cf.tellp(); } + + private: + cfile& cf; +}; + +inline cfile_datastream cfile::create_datastream() { + return cfile_datastream(*this); +} + +template <> +class datastream : public fc::cfile { + public: + using fc::cfile::cfile; + + bool seekp(size_t pos) { return this->seek(pos), true; } + + bool get(char& c) { + c = this->getc(); + return true; + } + + fc::cfile& storage() { return *this; } + const fc::cfile& storage() const { return *this; } +}; + + +} // namespace fc + +#ifndef _WIN32 +#undef FC_FOPEN +#else +#undef FC_CAT +#undef FC_PREL +#undef FC_FOPEN +#endif diff --git a/libraries/libfc/include/fc/io/console.hpp b/libraries/libfc/include/fc/io/console.hpp new file mode 100644 index 0000000000..11a9d47b99 --- /dev/null +++ b/libraries/libfc/include/fc/io/console.hpp @@ -0,0 +1,8 @@ +#pragma once +namespace fc +{ + /** enables / disables echoing of console input, useful for + * entering passwords on the console. + */ + void set_console_echo( bool enable_echo ); +} // namespace fc diff --git a/libraries/libfc/include/fc/io/datastream.hpp b/libraries/libfc/include/fc/io/datastream.hpp new file mode 100644 index 0000000000..0e81e06ed4 --- /dev/null +++ b/libraries/libfc/include/fc/io/datastream.hpp @@ -0,0 +1,313 @@ +#pragma once +#include +#include +#include +#include +#include + +#include + +namespace fc { + +namespace detail +{ + NO_RETURN void throw_datastream_range_error( const char* file, size_t len, int64_t over ); +} + +template +class datastream; + +/** + * The purpose of this datastream is to provide a fast, efficient, means + * of calculating the amount of data "about to be written" and then + * writing it. This means having two modes of operation, "test run" where + * you call the entire pack sequence calculating the size, and then + * actually packing it after doing a single allocation. + */ +template +class datastream || std::is_same_v || std::is_same_v>> { + public: + datastream( T start, size_t s ) + :_start(start),_pos(start),_end(start+s){}; + + + inline void skip( size_t s ){ _pos += s; } + inline bool read( char* d, size_t s ) { + if( size_t(_end - _pos) >= (size_t)s ) { + memcpy( d, _pos, s ); + _pos += s; + return true; + } + detail::throw_datastream_range_error( "read", _end-_start, int64_t(-((_end-_pos) - 1))); + } + + inline bool write( const char* d, size_t s ) { + if( size_t(_end - _pos) >= (size_t)s ) { + memcpy( _pos, d, s ); + _pos += s; + return true; + } + detail::throw_datastream_range_error( "write", _end-_start, int64_t(-((_end-_pos) - 1))); + } + + inline bool put(char c) { + if( _pos < _end ) { + *_pos = c; + ++_pos; + return true; + } + detail::throw_datastream_range_error( "put", _end-_start, int64_t(-((_end-_pos) - 1))); + } + + inline bool get( unsigned char& c ) { return get( *(char*)&c ); } + inline bool get( char& c ) + { + if( _pos < _end ) { + c = *_pos; + ++_pos; + return true; + } + detail::throw_datastream_range_error( "get", _end-_start, int64_t(-((_end-_pos) - 1))); + } + + T pos()const { return _pos; } + inline bool valid()const { return _pos <= _end && _pos >= _start; } + inline bool seekp(size_t p) { _pos = _start + p; return _pos <= _end; } + inline size_t tellp()const { return _pos - _start; } + inline size_t remaining()const { return _end - _pos; } + private: + T _start; + T _pos; + T _end; +}; + +template<> +class datastream { + public: + datastream( size_t init_size = 0):_size(init_size){}; + inline bool skip( size_t s ) { _size += s; return true; } + inline bool write( const char* ,size_t s ) { _size += s; return true; } + inline bool put(char ) { ++_size; return true; } + inline bool valid()const { return true; } + inline bool seekp(size_t p) { _size = p; return true; } + inline size_t tellp()const { return _size; } + inline size_t remaining()const { return 0; } + private: + size_t _size; +}; + +template +class datastream>> { + private: + Streambuf buf; + + public: + template + datastream(Args&&... args) + : buf(std::forward(args)...) {} + + size_t read(char* data, size_t n) { return buf.sgetn(data, n); } + size_t write(const char* data, size_t n) { return buf.sputn(data, n); } + size_t tellp() { return this->pubseekoff(0, std::ios::cur); } + bool skip(size_t p) { this->pubseekoff(p, std::ios::cur); return true; } + bool get(char& c) { + c = buf.sbumpc(); + return true; + } + bool seekp(size_t off) { + buf.pubseekoff(off, std::ios::beg); + return true; + } + bool remaining() { return buf.in_avail(); } + + Streambuf& storage() { return buf; } + const Streambuf& storage() const { return buf; } +}; + +template +class datastream, Container> || + std::is_same_v, Container>)>> { + private: + Container _container; + size_t cur; + + public: + template + datastream(Args&&... args) + : _container(std::forward(args)...) + , cur(0) {} + + size_t read(char* s, size_t n) { + if (cur + n > _container.size()) { + FC_THROW_EXCEPTION(out_of_range_exception, + "read datastream> of length ${len} over by ${over}", + ("len", _container.size())("over", _container.size() - n)); + } + std::copy_n(_container.begin() + cur, n, s); + cur += n; + return n; + } + + size_t write(const char* s, size_t n) { + _container.resize(std::max(cur + n, _container.size())); + std::copy_n(s, n, _container.begin() + cur); + cur += n; + return n; + } + + bool seekp(size_t off) { + cur = off; + return true; + } + + size_t tellp() const { return cur; } + bool skip(size_t p) { cur += p; return true; } + + bool get(char& c) { + this->read(&c, 1); + return true; + } + + size_t remaining() const { return _container.size() - cur; } + + Container& storage() { return _container; } + const Container& storage() const { return _container; } +}; + + + +template +inline datastream& operator<<(datastream& ds, const __int128& d) { + ds.write( (const char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator>>(datastream& ds, __int128& d) { + ds.read((char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator<<(datastream& ds, const unsigned __int128& d) { + ds.write( (const char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator>>(datastream& ds, unsigned __int128& d) { + ds.read((char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator<<(datastream& ds, const int64_t& d) { + ds.write( (const char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator>>(datastream& ds, int64_t& d) { + ds.read((char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator<<(datastream& ds, const uint64_t& d) { + ds.write( (const char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator>>(datastream& ds, uint64_t& d) { + ds.read((char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator<<(datastream& ds, const int32_t& d) { + ds.write( (const char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator>>(datastream& ds, int32_t& d) { + ds.read((char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator<<(datastream& ds, const uint32_t& d) { + ds.write( (const char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator>>(datastream& ds, uint32_t& d) { + ds.read((char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator<<(datastream& ds, const int16_t& d) { + ds.write( (const char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator>>(datastream& ds, int16_t& d) { + ds.read((char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator<<(datastream& ds, const uint16_t& d) { + ds.write( (const char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator>>(datastream& ds, uint16_t& d) { + ds.read((char*)&d, sizeof(d) ); + return ds; +} +template +inline datastream& operator<<(datastream& ds, const int8_t& d) { + ds.write( (const char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator>>(datastream& ds, int8_t& d) { + ds.read((char*)&d, sizeof(d) ); + return ds; +} +template +inline datastream& operator<<(datastream& ds, const uint8_t& d) { + ds.write( (const char*)&d, sizeof(d) ); + return ds; +} + +template +inline datastream& operator>>(datastream& ds, uint8_t& d) { + ds.read((char*)&d, sizeof(d) ); + return ds; +} +/* +template +inline datastream& operator<<(datastream& ds, const boost::multiprecision::number& n) { + unsigned char data[(std::numeric_limits::digits+1)/8]; + ds.read( (char*)data, sizeof(data) ); + boost::multiprecision::import_bits( n, data, data + sizeof(data), 1 ); +} + +template +inline datastream& operator>>(datastream& ds, boost::multiprecision::number& n) { + unsigned char data[(std::numeric_limits::digits+1)/8]; + boost::multiprecision::export_bits( n, data, 1 ); + ds.write( (const char*)data, sizeof(data) ); +} +*/ + + +} // namespace fc diff --git a/libraries/libfc/include/fc/io/enum_type.hpp b/libraries/libfc/include/fc/io/enum_type.hpp new file mode 100644 index 0000000000..22ae1c8868 --- /dev/null +++ b/libraries/libfc/include/fc/io/enum_type.hpp @@ -0,0 +1,82 @@ +#pragma once +#include +#include +#include + +namespace fc +{ + template + class enum_type + { + public: + explicit enum_type( EnumType t ) + :value(t){} + + explicit enum_type( IntType t ) + :value( (EnumType)t ){} + + enum_type(){} + + explicit operator IntType()const { return static_cast(value); } + operator EnumType()const { return value; } + operator std::string()const { return fc::reflector::to_string(value); } + + enum_type& operator=( IntType i ) { value = (EnumType)i; return *this;} + enum_type& operator=( EnumType i ) { value = i; return *this;} + bool operator<( EnumType i ) const { return value < i; } + bool operator>( EnumType i ) const { return value > i; } + + bool operator<(const enum_type& e) const { return value < e.value;} + bool operator>(const enum_type& e) const { return value > e.value;} + + bool operator<=(const enum_type& e) const { return value <= e.value;} + bool operator>=(const enum_type& e) const { return value >= e.value;} + + friend bool operator==( const enum_type& e, IntType i ){ return e.value == (EnumType)i;} + friend bool operator==( const enum_type& e, EnumType i ){ return e.value == i; } + + friend bool operator==( const enum_type& e, const enum_type& i ){ return e.value == i.value; } + friend bool operator==( IntType i, const enum_type& e){ return e.value == (EnumType)i; } + friend bool operator==( EnumType i, const enum_type& e ){ return e.value == i; } + + friend bool operator!=( const enum_type& e, IntType i ){ return e.value != (EnumType)i;} + friend bool operator!=( const enum_type& e, EnumType i ){ return e.value != i; } + friend bool operator!=( const enum_type& e, const enum_type& i ){ return e.value != i.value; } + + EnumType value; + }; + + + template + void to_variant( const enum_type& var, variant& vo ) + { + vo = (EnumType)var.value; + } + template + void from_variant( const variant& var, enum_type& vo ) + { + vo.value = var.as(); + } + + + /** serializes like an IntType */ + namespace raw + { + template + inline void pack( Stream& s, const fc::enum_type& tp ) + { + fc::raw::pack( s, static_cast(tp) ); + } + + template + inline void unpack( Stream& s, fc::enum_type& tp ) + { + IntType t; + fc::raw::unpack( s, t ); + tp = t; + } + } + +} + + diff --git a/libraries/libfc/include/fc/io/fstream.hpp b/libraries/libfc/include/fc/io/fstream.hpp new file mode 100644 index 0000000000..008e6eea7b --- /dev/null +++ b/libraries/libfc/include/fc/io/fstream.hpp @@ -0,0 +1,11 @@ +#include +#include + +namespace fc { + /** + * Grab the full contents of a file into a string object. + * NB reading a full file into memory is a poor choice + * if the file may be very large. + */ + void read_file_contents( const fc::path& filename, std::string& result ); +} diff --git a/libraries/libfc/include/fc/io/incbin.h b/libraries/libfc/include/fc/io/incbin.h new file mode 100644 index 0000000000..1228f834cf --- /dev/null +++ b/libraries/libfc/include/fc/io/incbin.h @@ -0,0 +1,478 @@ +/** + * @file incbin.h + * @author Dale Weiler + * @brief Utility for including binary files + * + * Facilities for including binary files into the current translation unit and + * making use from them externally in other translation units. + * + * From https://github.com/graphitemaster/incbin licenced in public domain + */ +#ifndef INCBIN_HDR +#define INCBIN_HDR +#include +#if defined(__AVX512BW__) || \ + defined(__AVX512CD__) || \ + defined(__AVX512DQ__) || \ + defined(__AVX512ER__) || \ + defined(__AVX512PF__) || \ + defined(__AVX512VL__) || \ + defined(__AVX512F__) +# define INCBIN_ALIGNMENT_INDEX 6 +#elif defined(__AVX__) || \ + defined(__AVX2__) +# define INCBIN_ALIGNMENT_INDEX 5 +#elif defined(__SSE__) || \ + defined(__SSE2__) || \ + defined(__SSE3__) || \ + defined(__SSSE3__) || \ + defined(__SSE4_1__) || \ + defined(__SSE4_2__) || \ + defined(__neon__) || \ + defined(__ARM_NEON) || \ + defined(__ALTIVEC__) +# define INCBIN_ALIGNMENT_INDEX 4 +#elif ULONG_MAX != 0xffffffffu +# define INCBIN_ALIGNMENT_INDEX 3 +# else +# define INCBIN_ALIGNMENT_INDEX 2 +#endif + +/* Lookup table of (1 << n) where `n' is `INCBIN_ALIGNMENT_INDEX' */ +#define INCBIN_ALIGN_SHIFT_0 1 +#define INCBIN_ALIGN_SHIFT_1 2 +#define INCBIN_ALIGN_SHIFT_2 4 +#define INCBIN_ALIGN_SHIFT_3 8 +#define INCBIN_ALIGN_SHIFT_4 16 +#define INCBIN_ALIGN_SHIFT_5 32 +#define INCBIN_ALIGN_SHIFT_6 64 + +/* Actual alignment value */ +#define INCBIN_ALIGNMENT \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_ALIGN_SHIFT, _), \ + INCBIN_ALIGNMENT_INDEX) + +/* Stringize */ +#define INCBIN_STR(X) \ + #X +#define INCBIN_STRINGIZE(X) \ + INCBIN_STR(X) +/* Concatenate */ +#define INCBIN_CAT(X, Y) \ + X ## Y +#define INCBIN_CONCATENATE(X, Y) \ + INCBIN_CAT(X, Y) +/* Deferred macro expansion */ +#define INCBIN_EVAL(X) \ + X +#define INCBIN_INVOKE(N, ...) \ + INCBIN_EVAL(N(__VA_ARGS__)) +/* Variable argument count for overloading by arity */ +#define INCBIN_VA_ARG_COUNTER(_1, _2, _3, N, ...) N +#define INCBIN_VA_ARGC(...) INCBIN_VA_ARG_COUNTER(__VA_ARGS__, 3, 2, 1, 0) + +/* Green Hills uses a different directive for including binary data */ +#if defined(__ghs__) +# if (__ghs_asm == 2) +# define INCBIN_MACRO ".file" +/* Or consider the ".myrawdata" entry in the ld file */ +# else +# define INCBIN_MACRO "\tINCBIN" +# endif +#else +# define INCBIN_MACRO ".incbin" +#endif + +#ifndef _MSC_VER +# define INCBIN_ALIGN \ + __attribute__((aligned(INCBIN_ALIGNMENT))) +#else +# define INCBIN_ALIGN __declspec(align(INCBIN_ALIGNMENT)) +#endif + +#if defined(__arm__) || /* GNU C and RealView */ \ + defined(__arm) || /* Diab */ \ + defined(_ARM) /* ImageCraft */ +# define INCBIN_ARM +#endif + +#ifdef __GNUC__ +/* Utilize .balign where supported */ +# define INCBIN_ALIGN_HOST ".balign " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" +# define INCBIN_ALIGN_BYTE ".balign 1\n" +#elif defined(INCBIN_ARM) +/* + * On arm assemblers, the alignment value is calculated as (1 << n) where `n' is + * the shift count. This is the value passed to `.align' + */ +# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT_INDEX) "\n" +# define INCBIN_ALIGN_BYTE ".align 0\n" +#else +/* We assume other inline assembler's treat `.align' as `.balign' */ +# define INCBIN_ALIGN_HOST ".align " INCBIN_STRINGIZE(INCBIN_ALIGNMENT) "\n" +# define INCBIN_ALIGN_BYTE ".align 1\n" +#endif + +/* INCBIN_CONST is used by incbin.c generated files */ +#if defined(__cplusplus) +# define INCBIN_EXTERNAL extern "C" +# define INCBIN_CONST extern const +#else +# define INCBIN_EXTERNAL extern +# define INCBIN_CONST const +#endif + +/** + * @brief Optionally override the linker section into which size and data is + * emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + */ +#if !defined(INCBIN_OUTPUT_SECTION) +# if defined(__APPLE__) +# define INCBIN_OUTPUT_SECTION ".const_data" +# else +# define INCBIN_OUTPUT_SECTION ".rodata" +# endif +#endif + +/** + * @brief Optionally override the linker section into which data is emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + */ +#if !defined(INCBIN_OUTPUT_DATA_SECTION) +# define INCBIN_OUTPUT_DATA_SECTION INCBIN_OUTPUT_SECTION +#endif + +/** + * @brief Optionally override the linker section into which size is emitted. + * + * @warning If you use this facility, you might have to deal with + * platform-specific linker output section naming on your own. + * + * @note This is useful for Harvard architectures where program memory cannot + * be directly read from the program without special instructions. With this you + * can chose to put the size variable in RAM rather than ROM. + */ +#if !defined(INCBIN_OUTPUT_SIZE_SECTION) +# define INCBIN_OUTPUT_SIZE_SECTION INCBIN_OUTPUT_SECTION +#endif + +#if defined(__APPLE__) +# include "TargetConditionals.h" +# if defined(TARGET_OS_IPHONE) && !defined(INCBIN_SILENCE_BITCODE_WARNING) +# warning "incbin is incompatible with bitcode. Using the library will break upload to App Store if you have bitcode enabled. Add `#define INCBIN_SILENCE_BITCODE_WARNING` before including this header to silence this warning." +# endif +/* The directives are different for Apple branded compilers */ +# define INCBIN_SECTION INCBIN_OUTPUT_SECTION "\n" +# define INCBIN_GLOBAL(NAME) ".globl " INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" +# define INCBIN_INT ".long " +# define INCBIN_MANGLE "_" +# define INCBIN_BYTE ".byte " +# define INCBIN_TYPE(...) +#else +# define INCBIN_SECTION ".section " INCBIN_OUTPUT_SECTION "\n" +# define INCBIN_GLOBAL(NAME) ".global " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME "\n" +# if defined(__ghs__) +# define INCBIN_INT ".word " +# else +# define INCBIN_INT ".int " +# endif +# if defined(__USER_LABEL_PREFIX__) +# define INCBIN_MANGLE INCBIN_STRINGIZE(__USER_LABEL_PREFIX__) +# else +# define INCBIN_MANGLE "" +# endif +# if defined(INCBIN_ARM) +/* On arm assemblers, `@' is used as a line comment token */ +# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", %object\n" +# elif defined(__MINGW32__) || defined(__MINGW64__) +/* Mingw doesn't support this directive either */ +# define INCBIN_TYPE(NAME) +# else +/* It's safe to use `@' on other architectures */ +# define INCBIN_TYPE(NAME) ".type " INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME ", @object\n" +# endif +# define INCBIN_BYTE ".byte " +#endif + +/* List of style types used for symbol names */ +#define INCBIN_STYLE_CAMEL 0 +#define INCBIN_STYLE_SNAKE 1 + +/** + * @brief Specify the prefix to use for symbol names. + * + * @note By default this is "g". + * + * @code + * #define INCBIN_PREFIX incbin + * #include "incbin.h" + * INCBIN(Foo, "foo.txt"); + * + * // Now you have the following symbols instead: + * // const unsigned char incbinFoo[]; + * // const unsigned char *const incbinFoo; + * // const unsigned int incbinFoo; + * @endcode + */ +#if !defined(INCBIN_PREFIX) +# define INCBIN_PREFIX g +#endif + +/** + * @brief Specify the style used for symbol names. + * + * Possible options are + * - INCBIN_STYLE_CAMEL "CamelCase" + * - INCBIN_STYLE_SNAKE "snake_case" + * + * @note By default this is INCBIN_STYLE_CAMEL + * + * @code + * #define INCBIN_STYLE INCBIN_STYLE_SNAKE + * #include "incbin.h" + * INCBIN(foo, "foo.txt"); + * + * // Now you have the following symbols: + * // const unsigned char foo_data[]; + * // const unsigned char *const foo_end; + * // const unsigned int foo_size; + * @endcode + */ +#if !defined(INCBIN_STYLE) +# define INCBIN_STYLE INCBIN_STYLE_CAMEL +#endif + +/* Style lookup tables */ +#define INCBIN_STYLE_0_DATA Data +#define INCBIN_STYLE_0_END End +#define INCBIN_STYLE_0_SIZE Size +#define INCBIN_STYLE_1_DATA _data +#define INCBIN_STYLE_1_END _end +#define INCBIN_STYLE_1_SIZE _size + +/* Style lookup: returning identifier */ +#define INCBIN_STYLE_IDENT(TYPE) \ + INCBIN_CONCATENATE( \ + INCBIN_STYLE_, \ + INCBIN_CONCATENATE( \ + INCBIN_EVAL(INCBIN_STYLE), \ + INCBIN_CONCATENATE(_, TYPE))) + +/* Style lookup: returning string literal */ +#define INCBIN_STYLE_STRING(TYPE) \ + INCBIN_STRINGIZE( \ + INCBIN_STYLE_IDENT(TYPE)) \ + +/* Generate the global labels by indirectly invoking the macro with our style + * type and concatenating the name against them. */ +#define INCBIN_GLOBAL_LABELS(NAME, TYPE) \ + INCBIN_INVOKE( \ + INCBIN_GLOBAL, \ + INCBIN_CONCATENATE( \ + NAME, \ + INCBIN_INVOKE( \ + INCBIN_STYLE_IDENT, \ + TYPE))) \ + INCBIN_INVOKE( \ + INCBIN_TYPE, \ + INCBIN_CONCATENATE( \ + NAME, \ + INCBIN_INVOKE( \ + INCBIN_STYLE_IDENT, \ + TYPE))) + +/** + * @brief Externally reference binary data included in another translation unit. + * + * Produces three external symbols that reference the binary data included in + * another translation unit. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param TYPE Optional array type. Omitting this picks a default of `unsigned char`. + * @param NAME The name given for the binary data + * + * @code + * INCBIN_EXTERN(Foo); + * + * // Now you have the following symbols: + * // extern const unsigned char Foo[]; + * // extern const unsigned char *const Foo; + * // extern const unsigned int Foo; + * @endcode + * + * You may specify a custom optional data type as well as the first argument. + * @code + * INCBIN_EXTERN(custom_type, Foo); + * + * // Now you have the following symbols: + * // extern const custom_type Foo[]; + * // extern const custom_type *const Foo; + * // extern const unsigned int Foo; + * @endcode + */ +#define INCBIN_EXTERN(...) \ + INCBIN_CONCATENATE(INCBIN_EXTERN_, INCBIN_VA_ARGC(__VA_ARGS__))(__VA_ARGS__) +#define INCBIN_EXTERN_1(NAME, ...) \ + INCBIN_EXTERN_2(unsigned char, NAME) +#define INCBIN_EXTERN_2(TYPE, NAME) \ + INCBIN_EXTERNAL const INCBIN_ALIGN TYPE \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(DATA))[]; \ + INCBIN_EXTERNAL const INCBIN_ALIGN TYPE *const \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(END)); \ + INCBIN_EXTERNAL const unsigned int \ + INCBIN_CONCATENATE( \ + INCBIN_CONCATENATE(INCBIN_PREFIX, NAME), \ + INCBIN_STYLE_IDENT(SIZE)) + +/** + * @brief Externally reference textual data included in another translation unit. + * + * Produces three external symbols that reference the textual data included in + * another translation unit. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name given for the textual data + * + * @code + * INCBIN_EXTERN(Foo); + * + * // Now you have the following symbols: + * // extern const char Foo[]; + * // extern const char *const Foo; + * // extern const unsigned int Foo; + * @endcode + */ +#define INCTXT_EXTERN(NAME) \ + INCBIN_EXTERN_2(char, NAME) + +/** + * @brief Include a binary file into the current translation unit. + * + * Includes a binary file into the current translation unit, producing three symbols + * for objects that encode the data and size respectively. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param TYPE Optional array type. Omitting this picks a default of `unsigned char`. + * @param NAME The name to associate with this binary data (as an identifier.) + * @param FILENAME The file to include (as a string literal.) + * + * @code + * INCBIN(Icon, "icon.png"); + * + * // Now you have the following symbols: + * // const unsigned char Icon[]; + * // const unsigned char *const Icon; + * // const unsigned int Icon; + * @endcode + * + * You may specify a custom optional data type as well as the first argument. + * These macros are specialized by arity. + * @code + * INCBIN(custom_type, Icon, "icon.png"); + * + * // Now you have the following symbols: + * // const custom_type Icon[]; + * // const custom_type *const Icon; + * // const unsigned int Icon; + * @endcode + * + * @warning This must be used in global scope + * @warning The identifiers may be different if INCBIN_STYLE is not default + * + * To externally reference the data included by this in another translation unit + * please @see INCBIN_EXTERN. + */ +#ifdef _MSC_VER +# define INCBIN(NAME, FILENAME) \ + INCBIN_EXTERN(NAME) +#else +# define INCBIN(...) \ + INCBIN_CONCATENATE(INCBIN_, INCBIN_VA_ARGC(__VA_ARGS__))(__VA_ARGS__) +# if defined(__GNUC__) +# define INCBIN_1(...) _Pragma("GCC error \"Single argument INCBIN not allowed\"") +# elif defined(__clang__) +# define INCBIN_1(...) _Pragma("clang error \"Single argument INCBIN not allowed\"") +# else +# define INCBIN_1(...) /* Cannot do anything here */ +# endif +# define INCBIN_2(NAME, FILENAME) \ + INCBIN_3(unsigned char, NAME, FILENAME) +# define INCBIN_3(TYPE, NAME, FILENAME) INCBIN_COMMON(TYPE, NAME, FILENAME, /* No terminator for binary data */) +# define INCBIN_COMMON(TYPE, NAME, FILENAME, TERMINATOR) \ + __asm__(INCBIN_SECTION \ + INCBIN_GLOBAL_LABELS(NAME, DATA) \ + INCBIN_ALIGN_HOST \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) ":\n" \ + INCBIN_MACRO " \"" FILENAME "\"\n" \ + TERMINATOR \ + INCBIN_GLOBAL_LABELS(NAME, END) \ + INCBIN_ALIGN_BYTE \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) ":\n" \ + INCBIN_BYTE "1\n" \ + INCBIN_GLOBAL_LABELS(NAME, SIZE) \ + INCBIN_ALIGN_HOST \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(SIZE) ":\n" \ + INCBIN_INT INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(END) " - " \ + INCBIN_MANGLE INCBIN_STRINGIZE(INCBIN_PREFIX) #NAME INCBIN_STYLE_STRING(DATA) "\n" \ + INCBIN_ALIGN_HOST \ + ".text\n" \ + ); \ + INCBIN_EXTERN(TYPE, NAME) +#endif + +/** + * @brief Include a textual file into the current translation unit. + * + * This behaves the same as INCBIN except it produces char compatible arrays + * and implicitly adds a null-terminator byte, thus the size of data included + * by this is one byte larger than that of INCBIN. + * + * Includes a textual file into the current translation unit, producing three + * symbols for objects that encode the data and size respectively. + * + * The symbol names are a concatenation of `INCBIN_PREFIX' before *NAME*; with + * "Data", as well as "End" and "Size" after. An example is provided below. + * + * @param NAME The name to associate with this binary data (as an identifier.) + * @param FILENAME The file to include (as a string literal.) + * + * @code + * INCTXT(Readme, "readme.txt"); + * + * // Now you have the following symbols: + * // const char Readme[]; + * // const char *const Readme; + * // const unsigned int Readme; + * @endcode + * + * @warning This must be used in global scope + * @warning The identifiers may be different if INCBIN_STYLE is not default + * + * To externally reference the data included by this in another translation unit + * please @see INCBIN_EXTERN. + */ +#if defined(_MSC_VER) +# define INCTXT(NAME, FILENAME) \ + INCBIN_EXTERN(NAME) +#else +# define INCTXT(NAME, FILENAME) \ + INCBIN_COMMON(char, NAME, FILENAME, INCBIN_BYTE "0\n") +#endif + +#endif diff --git a/libraries/libfc/include/fc/io/iobuffer.hpp b/libraries/libfc/include/fc/io/iobuffer.hpp new file mode 100644 index 0000000000..02f919b065 --- /dev/null +++ b/libraries/libfc/include/fc/io/iobuffer.hpp @@ -0,0 +1,81 @@ +#pragma once +#include +#include + +namespace fc +{ + /** + * Records the size, but discards the data. + */ + class size_stream : public virtual fc::ostream + { + public: + size_stream( size_t s = 0):_size(s){} + + size_t size()const { return _size; } + size_t seek( size_t pos ) { return _size = pos; } + + virtual size_t writesome( const char* /*ignored buf*/, size_t len ) + { + _size += len; + return len; + } + + virtual void close(){} + virtual void flush(){} + + private: + size_t _size; + }; + + + class iobuffer : public virtual fc::iostream + { + public: + iobuffer( size_t s ) + :_data(s){} + + size_t size()const { return _data.size(); } + size_t pos()const { return _pos; } + size_t seek( size_t pos ) + { + return _pos = std::min(_data.size(),pos); + } + + virtual size_t readsome( char* buf, size_t len ) + { + auto avail = std::min( _data.size()-_pos, len ); + if( avail == 0 ) throw fc::eof_exception(); + memcpy( buf, _data.data()+_pos, avail ); + _pos += avail; + return avail; + } + /** + * This method may block until at least 1 character is + * available. + */ + char peek()const + { + if( _pos == _data.size() ) throw fc::eof_exception(); + return _data[_pos]; + } + + virtual size_t writesome( const char* buf, size_t len ) + { + auto avail = std::max( _data.size(), _pos + len ); + _data.resize(avail); + memcpy( _data.data()+_pos, buf, len ); + _pos += avail; + return avail; + } + char* data() { return _data.data(); } + + virtual void close(){} + virtual void flush(){} + + private: + std::vector _data; + size_t _pos; + }; + +} diff --git a/libraries/libfc/include/fc/io/json.hpp b/libraries/libfc/include/fc/io/json.hpp new file mode 100644 index 0000000000..cdae756c5a --- /dev/null +++ b/libraries/libfc/include/fc/io/json.hpp @@ -0,0 +1,90 @@ +#pragma once +#include +#include +#include +#include +#include + +#define DEFAULT_MAX_RECURSION_DEPTH 200 + +namespace fc +{ + using std::ostream; + + /** + * Provides interface for json serialization. + * + * json strings are always UTF8 + */ + class json + { + public: + enum class parse_type : unsigned char + { + legacy_parser = 0, + strict_parser = 1, + relaxed_parser = 2, + legacy_parser_with_string_doubles = 3 + }; + enum class output_formatting : unsigned char + { + stringify_large_ints_and_doubles = 0, + legacy_generator = 1 + }; + using yield_function_t = fc::optional_delegate; + static constexpr uint64_t max_length_limit = std::numeric_limits::max(); + static constexpr size_t escape_string_yield_check_count = 128; + static variant from_string( const string& utf8_str, const parse_type ptype = parse_type::legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); + static variants variants_from_string( const string& utf8_str, const parse_type ptype = parse_type::legacy_parser, uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); + static string to_string( const variant& v, const yield_function_t& yield, const output_formatting format = output_formatting::stringify_large_ints_and_doubles); + static string to_pretty_string( const variant& v, const yield_function_t& yield, const output_formatting format = output_formatting::stringify_large_ints_and_doubles ); + + static bool is_valid( const std::string& json_str, const parse_type ptype = parse_type::legacy_parser, const uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); + + template + static bool save_to_file( const T& v, const fc::path& fi, const bool pretty = true, const output_formatting format = output_formatting::stringify_large_ints_and_doubles ) + { + return save_to_file( variant(v), fi, pretty, format ); + } + + static bool save_to_file( const variant& v, const fc::path& fi, const bool pretty = true, const output_formatting format = output_formatting::stringify_large_ints_and_doubles ); + static variant from_file( const fc::path& p, const parse_type ptype = parse_type::legacy_parser, const uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ); + + template + static T from_file( const fc::path& p, const parse_type ptype = parse_type::legacy_parser, const uint32_t max_depth = DEFAULT_MAX_RECURSION_DEPTH ) + { + return json::from_file(p, ptype, max_depth).as(); + } + + template + static string to_string( const T& v, const fc::time_point& deadline, const output_formatting format = output_formatting::stringify_large_ints_and_doubles, const uint64_t max_len = max_length_limit ) + { + const auto yield = [&](size_t s) { + FC_CHECK_DEADLINE(deadline); + FC_ASSERT(s <= max_len); + }; + return to_string( variant(v), yield, format ); + } + + template + static string to_pretty_string( const T& v, const fc::time_point& deadline = fc::time_point::maximum(), const output_formatting format = output_formatting::stringify_large_ints_and_doubles, const uint64_t max_len = max_length_limit ) + { + const auto yield = [&](size_t s) { + FC_CHECK_DEADLINE(deadline); + FC_ASSERT( s <= max_len ); + }; + return to_pretty_string( variant(v), yield, format ); + } + + template + static bool save_to_file( const T& v, const std::string& p, const bool pretty = true, const output_formatting format = output_formatting::stringify_large_ints_and_doubles ) + { + return save_to_file( variant(v), fc::path(p), pretty, format ); + } + }; + + std::string escape_string( const std::string_view& str, const json::yield_function_t& yield, bool escape_control_chars = true ); + +} // fc + +#undef DEFAULT_MAX_RECURSION_DEPTH diff --git a/libraries/libfc/include/fc/io/json_relaxed.hpp b/libraries/libfc/include/fc/io/json_relaxed.hpp new file mode 100644 index 0000000000..a0d34b88b0 --- /dev/null +++ b/libraries/libfc/include/fc/io/json_relaxed.hpp @@ -0,0 +1,753 @@ +#pragma once + +// This file is an internal header, +// it is not meant to be included except internally from json.cpp in fc + +#include +#include +//#include +//#include +//#include +//#include +#include +#include +//#include +#include +#include +#include + +#include + +namespace fc { namespace json_relaxed +{ + template + variant variant_from_stream( T& in, uint32_t max_depth ); + + template + std::string tokenFromStream( T& in ) + { + std::stringstream token; + try + { + char c = in.peek(); + + while( true ) + { + switch( c = in.peek() ) + { + case '\\': + token << parseEscape( in ); + break; + case '\t': + case ' ': + case ',': + case ':': + case '\0': + case '\n': + case '\x04': + in.get(); + return token.str(); + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': + case '8': case '9': + case '_': case '-': case '.': case '+': case '/': + token << c; + in.get(); + break; + case EOF: + FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); + default: + return token.str(); + } + } + return token.str(); + } + catch( const fc::eof_exception& eof ) + { + return token.str(); + } + catch (const std::ios_base::failure&) + { + return token.str(); + } + + FC_RETHROW_EXCEPTIONS( warn, "while parsing token '${token}'", + ("token", token.str() ) ); + } + + template + std::string quoteStringFromStream( T& in ) + { + std::stringstream token; + try + { + char q = in.get(); + switch( q ) + { + case '\'': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "expected: '\"' at beginning of string, got '\''" ); + // falls through + case '"': + break; + default: + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "expected: '\"' at beginning of string" ); + else + FC_THROW_EXCEPTION( parse_error_exception, "expected: '\"' | '\\\'' at beginning of string" ); + } + if( in.peek() == q ) + { + in.get(); + if( in.peek() != q ) + return std::string(); + + // triple quote processing + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "triple quote unsupported in strict mode" ); + else + { + in.get(); + + while( true ) + { + char c = in.peek(); + if( c == q ) + { + in.get(); + char c2 = in.peek(); + if( c2 == q ) + { + in.get(); + char c3 = in.peek(); + if( c3 == q ) + { + in.get(); + return token.str(); + } + token << q << q; + continue; + } + token << q; + continue; + } + else if( c == '\x04' ) + FC_THROW_EXCEPTION( parse_error_exception, "unexpected EOF in string '${token}'", + ("token", token.str() ) ); + else if( allow_escape && (c == '\\') ) + token << parseEscape( in ); + else + { + in.get(); + token << c; + } + } + } + } + + while( true ) + { + char c = in.peek(); + + if (c == EOF) { + FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); + } + + if( c == q ) + { + in.get(); + return token.str(); + } + else if( c == '\x04' ) + FC_THROW_EXCEPTION( parse_error_exception, "unexpected EOF in string '${token}'", + ("token", token.str() ) ); + else if( allow_escape && (c == '\\') ) + token << parseEscape( in ); + else if( (c == '\r') | (c == '\n') ) + FC_THROW_EXCEPTION( parse_error_exception, "unexpected EOL in string '${token}'", + ("token", token.str() ) ); + else + { + in.get(); + token << c; + } + } + + } FC_RETHROW_EXCEPTIONS( warn, "while parsing token '${token}'", + ("token", token.str() ) ); + } + + template + std::string stringFromStream( T& in ) + { + try + { + char c = in.peek(), c2; + + switch( c ) + { + case '\'': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "expected: '\"' at beginning of string, got '\''" ); + // falls through + case '"': + return quoteStringFromStream( in ); + case 'r': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "raw strings not supported in strict mode" ); + case 'R': + in.get(); + c2 = in.peek(); + switch( c2 ) + { + case '"': + case '\'': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "raw strings not supported in strict mode" ); + return quoteStringFromStream( in ); + default: + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "unquoted strings not supported in strict mode" ); + return c+tokenFromStream( in ); + } + break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': + case 'q': case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': + case '8': case '9': + case '_': case '-': case '.': case '+': case '/': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "unquoted strings not supported in strict mode" ); + return tokenFromStream( in ); + case EOF: + FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); + default: + FC_THROW_EXCEPTION( parse_error_exception, "expected: string" ); + } + + } FC_RETHROW_EXCEPTIONS( warn, "while parsing string" ); + } + + struct CharValueTable + { + public: + CharValueTable() + { + for( size_t i=0; i<0x100; i++ ) + c2v[i] = 0xFF; + c2v[(unsigned char)'0'] = 0; + c2v[(unsigned char)'1'] = 1; + c2v[(unsigned char)'2'] = 2; + c2v[(unsigned char)'3'] = 3; + c2v[(unsigned char)'4'] = 4; + c2v[(unsigned char)'5'] = 5; + c2v[(unsigned char)'6'] = 6; + c2v[(unsigned char)'7'] = 7; + c2v[(unsigned char)'8'] = 8; + c2v[(unsigned char)'9'] = 9; + c2v[(unsigned char)'a'] = c2v[(unsigned char)'A'] = 10; + c2v[(unsigned char)'b'] = c2v[(unsigned char)'B'] = 11; + c2v[(unsigned char)'c'] = c2v[(unsigned char)'C'] = 12; + c2v[(unsigned char)'d'] = c2v[(unsigned char)'D'] = 13; + c2v[(unsigned char)'e'] = c2v[(unsigned char)'E'] = 14; + c2v[(unsigned char)'f'] = c2v[(unsigned char)'F'] = 15; + c2v[(unsigned char)'g'] = c2v[(unsigned char)'G'] = 16; + c2v[(unsigned char)'h'] = c2v[(unsigned char)'H'] = 17; + c2v[(unsigned char)'i'] = c2v[(unsigned char)'I'] = 18; + c2v[(unsigned char)'j'] = c2v[(unsigned char)'J'] = 19; + c2v[(unsigned char)'k'] = c2v[(unsigned char)'K'] = 20; + c2v[(unsigned char)'l'] = c2v[(unsigned char)'L'] = 21; + c2v[(unsigned char)'m'] = c2v[(unsigned char)'M'] = 22; + c2v[(unsigned char)'n'] = c2v[(unsigned char)'N'] = 23; + c2v[(unsigned char)'o'] = c2v[(unsigned char)'O'] = 24; + c2v[(unsigned char)'p'] = c2v[(unsigned char)'P'] = 25; + c2v[(unsigned char)'q'] = c2v[(unsigned char)'Q'] = 26; + c2v[(unsigned char)'r'] = c2v[(unsigned char)'R'] = 27; + c2v[(unsigned char)'s'] = c2v[(unsigned char)'S'] = 28; + c2v[(unsigned char)'t'] = c2v[(unsigned char)'T'] = 29; + c2v[(unsigned char)'u'] = c2v[(unsigned char)'U'] = 30; + c2v[(unsigned char)'v'] = c2v[(unsigned char)'V'] = 31; + c2v[(unsigned char)'w'] = c2v[(unsigned char)'W'] = 32; + c2v[(unsigned char)'x'] = c2v[(unsigned char)'X'] = 33; + c2v[(unsigned char)'y'] = c2v[(unsigned char)'Y'] = 34; + c2v[(unsigned char)'z'] = c2v[(unsigned char)'Z'] = 35; + return; + } + + uint8_t operator[]( char index ) const { return c2v[index & 0xFF]; } + + uint8_t c2v[0x100]; + }; + + template + fc::variant parseInt( const std::string& token, size_t start ) + { + static const CharValueTable ctbl; + static const uint64_t INT64_MAX_PLUS_ONE = static_cast(INT64_MAX) + 1; + + size_t i = start, n = token.length(); + if( i >= n ) + FC_THROW_EXCEPTION( parse_error_exception, "zero-length integer" ); + + uint64_t val = 0; + uint64_t maxb4mul = UINT64_MAX / base; + + while(true) + { + char c = token[i]; + uint8_t vc = ctbl[c]; + if( vc == 0xFF ) + FC_THROW_EXCEPTION( parse_error_exception, "illegal character ${c} in integer of base ${b}", ("c", c)("b", base) ); + if( val > maxb4mul ) + FC_THROW_EXCEPTION( parse_error_exception, "integer literal overflow" ); + val *= base; + uint64_t newval = val + vc; + if( newval < val ) + FC_THROW_EXCEPTION( parse_error_exception, "integer literal overflow" ); + val = newval; + i++; + if( i >= n ) + break; + } + if( token[0] == '-' ) + { + if( val > INT64_MAX_PLUS_ONE ) + FC_THROW_EXCEPTION( parse_error_exception, "negative integer literal overflow" ); + // special cased to avoid trying to compute -INT64_MIN which is probably undefined or something + if( val == INT64_MAX_PLUS_ONE ) + return fc::variant( INT64_MIN ); + return fc::variant( -static_cast(val) ); + } + return fc::variant( val ); + } + + template + fc::variant maybeParseInt( const std::string& token, size_t start ) + { + try + { + return parseInt( token, start ); + } + catch( const parse_error_exception &e ) + { + if( strict ) + throw( e ); + else + return fc::variant( token ); + } + } + + template + fc::variant parseNumberOrStr( const std::string& token ) + { try { + //ilog( (token) ); + size_t i = 0, n = token.length(); + if( n == 0 ) + FC_THROW_EXCEPTION( parse_error_exception, "expected: non-empty token, got: empty token" ); + switch( token[0] ) + { + case '+': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "unary + not supported in strict mode" ); + i++; + break; + case '-': + i++; + break; + default: + break; + } + char c = token[i++]; + switch( c ) + { + case '0': + if( i >= n ) + return fc::variant( uint64_t( 0 ) ); + switch( token[i] ) + { + case 'b': + case 'B': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "binary numeric literals not supported in strict mode" ); + i++; + if( i >= n ) + FC_THROW_EXCEPTION( parse_error_exception, "empty binary numeric literal" ); + return maybeParseInt( token, i+1 ); + case 'o': + case 'O': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "octal numeric literals not supported in strict mode" ); + return maybeParseInt( token, i+1 ); + case 'x': + case 'X': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "hex numeric literals not supported in strict mode" ); + return maybeParseInt( token, i+1 ); + case '.': + case 'e': + case 'E': + break; + default: + // since this is a lookahead, other cases will be treated later + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "expected '.'|'e'|'E' parsing number, got '${c}'", + ( "c", c ) ); + } + break; + case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': case '-': case '.': case '+': case '/': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "illegal character '${c}' parsing number", ( "c", c ) ); + return fc::variant( token ); + default: + FC_THROW_EXCEPTION( parse_error_exception, "illegal character '${c}' in token", ( "c", c ) ); + } + size_t start = i-1; + + bool dot_ok = true; + + // int frac? exp? + while(true) + { + if( i >= n ) + return parseInt<10>( token, start ); + char c = token[i++]; + //idump((c)(std::string()+c)); + switch( c ) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + break; + case '.': + return fc::variant(token); + if( dot_ok ) + { + dot_ok = false; + if( i == n ) + { + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "number cannot end with '.' in strict mode" ); + return fc::variant( fc::to_double(token.c_str()) ); + } + + //idump((i)); + c = token[i+1]; + //idump((c)); + switch( c ) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + break; + case 'e': + case 'E': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "expected digit after '.'" ); + break; + case 'a': case 'b': case 'c': case 'd': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': case '-': case '.': case '+': case '/': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "expected digit after '.'" ); + return fc::variant( token ); + default: + FC_THROW_EXCEPTION( parse_error_exception, "illegal character '${c}' in token", ( "c", c )("i",int(c)) ); + } + } + else + { + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "illegal multiple . in number" ); + return fc::variant( token ); + } + break; + case 'e': + case 'E': + if( i == n ) + { + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "expected exponent after 'e'|'E' parsing number" ); + return fc::variant( token ); + } + c = token[i++]; + switch( c ) + { + case '+': case '-': + if( i == n ) + { + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "expected exponent" ); + return fc::variant( token ); + } + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': case '.': case '/': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "illegal character '${c}' in number", ( "c", c ) ); + return fc::variant( token ); + default: + FC_THROW_EXCEPTION( parse_error_exception, "illegal character '${c}' in token", ( "c", c ) ); + } + while( true ) + { + if( i == n ) + break; + c = token[i++]; + switch( c ) + { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': case '-': case '.': case '+': case '/': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "illegal character '${c}' in number", ( "c", c ) ); + return fc::variant( token ); + } + } + return fc::variant( fc::to_double(token.c_str()) ); + case 'a': case 'b': case 'c': case 'd': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': case '-': case '+': case '/': + if( strict ) + FC_THROW_EXCEPTION( parse_error_exception, "illegal character '${c}' parsing number", ( "c", c ) ); + return fc::variant( token ); + default: + FC_THROW_EXCEPTION( parse_error_exception, "illegal character '${c}' in number", ( "c", c ) ); + } + } + } FC_CAPTURE_AND_RETHROW( (token) ) } + + template + variant_object objectFromStream( T& in, uint32_t max_depth ) + { + mutable_variant_object obj; + try + { + char c = in.peek(); + if( c != '{' ) + FC_THROW_EXCEPTION( parse_error_exception, + "Expected '{', but read '${char}'", + ("char",string(&c, &c + 1)) ); + in.get(); + skip_white_space(in); + while( in.peek() != '}' ) + { + if( in.peek() == ',' ) + { + in.get(); + continue; + } + if( skip_white_space(in) ) continue; + string key = json_relaxed::stringFromStream( in ); + skip_white_space(in); + if( in.peek() != ':' ) + { + FC_THROW_EXCEPTION( parse_error_exception, "Expected ':' after key \"${key}\"", + ("key", key) ); + } + in.get(); + auto val = json_relaxed::variant_from_stream( in, max_depth - 1 ); + + obj(std::move(key),std::move(val)); + skip_white_space(in); + } + if( in.peek() == '}' ) + { + in.get(); + return obj; + } + FC_THROW_EXCEPTION( parse_error_exception, "Expected '}' after ${variant}", ("variant", obj ) ); + } + catch( const fc::eof_exception& e ) + { + FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF: ${e}", ("e", e.to_detail_string() ) ); + } + catch( const std::ios_base::failure& e ) + { + FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF: ${e}", ("e", e.what() ) ); + } FC_RETHROW_EXCEPTIONS( warn, "Error parsing object" ); + } + + template + variants arrayFromStream( T& in, uint32_t max_depth ) + { + variants ar; + try + { + if( in.peek() != '[' ) + FC_THROW_EXCEPTION( parse_error_exception, "Expected '['" ); + in.get(); + skip_white_space(in); + + while( in.peek() != ']' ) + { + if( in.peek() == ',' ) + { + in.get(); + continue; + } + if( skip_white_space(in) ) continue; + ar.push_back( json_relaxed::variant_from_stream(in, max_depth - 1) ); + skip_white_space(in); + } + if( in.peek() != ']' ) + FC_THROW_EXCEPTION( parse_error_exception, "Expected ']' after parsing ${variant}", + ("variant", ar) ); + + in.get(); + } FC_RETHROW_EXCEPTIONS( warn, "Attempting to parse array ${array}", + ("array", ar ) ); + return ar; + } + + template + variant numberFromStream( T& in ) + { try { + std::string token = tokenFromStream(in); + variant result = json_relaxed::parseNumberOrStr( token ); + if( strict && !(result.is_int64() || result.is_uint64() || result.is_double()) ) + FC_THROW_EXCEPTION( parse_error_exception, "expected: number" ); + return result; + } FC_CAPTURE_AND_RETHROW() } + + template + variant wordFromStream( T& in ) + { + std::string token = tokenFromStream(in); + + FC_ASSERT( token.length() > 0 ); + + switch( token[0] ) + { + case 'n': + if( token == "null" ) + return variant(); + break; + case 't': + if( token == "true" ) + return variant( true ); + break; + case 'f': + if( token == "false" ) + return variant( false ); + break; + default: + break; + } + + if( !strict ) + return token; + + FC_THROW_EXCEPTION( parse_error_exception, "expected: null|true|false" ); + } + + template + variant variant_from_stream( T& in, uint32_t max_depth ) + { + skip_white_space(in); + variant var; + while( signed char c = in.peek() ) + { + switch( c ) + { + case ' ': + case '\t': + case '\n': + case '\r': + in.get(); + continue; + case '"': + return json_relaxed::stringFromStream( in ); + case '{': + return json_relaxed::objectFromStream( in, max_depth - 1 ); + case '[': + return json_relaxed::arrayFromStream( in, max_depth - 1 ); + case '-': + case '+': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return json_relaxed::numberFromStream( in ); + // null, true, false, or 'warning' / string + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': + case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': + case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': + case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': + case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': + case 'Y': case 'Z': + case '_': case '/': + return json_relaxed::wordFromStream( in ); + case 0x04: // ^D end of transmission + case EOF: + FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); + default: + FC_THROW_EXCEPTION( parse_error_exception, "Unexpected char '${c}' in \"${s}\"", + ("c", c)("s", stringFromToken(in)) ); + } + } + return variant(); + } + +} } // fc::json_relaxed diff --git a/libraries/libfc/include/fc/io/persistence_util.hpp b/libraries/libfc/include/fc/io/persistence_util.hpp new file mode 100644 index 0000000000..89e35759ec --- /dev/null +++ b/libraries/libfc/include/fc/io/persistence_util.hpp @@ -0,0 +1,75 @@ +#pragma once +#include +#include + +namespace fc { + + /** + * This namespace provides functions related to reading and writing a persistence file + * with a header delineating the type of file and version. + */ + +namespace persistence_util { + + cfile open_cfile_for_read(const fc::path& dir, const std::string& filename) { + if (!fc::is_directory(dir)) + fc::create_directories(dir); + + auto dat_file = dir / filename; + + cfile dat_content; + dat_content.set_file_path(dat_file); + if( fc::exists( dat_file ) ) { + dat_content.open(cfile::create_or_update_rw_mode); + } + return dat_content; + } + + uint32_t read_persistence_header(cfile& dat_content, const uint32_t magic_number, const uint32_t min_supported_version, + const uint32_t max_supported_version) { + dat_content.seek(0); // needed on mac + auto ds = dat_content.create_datastream(); + + // validate totem + uint32_t totem = 0; + fc::raw::unpack( ds, totem ); + if( totem != magic_number) { + FC_THROW_EXCEPTION(fc::parse_error_exception, + "File has unexpected magic number: ${actual_totem}. Expected ${expected_totem}", + ("actual_totem", totem) + ("expected_totem", magic_number)); + } + + // validate version + uint32_t version = 0; + fc::raw::unpack( ds, version ); + if( version < min_supported_version || version > max_supported_version) { + FC_THROW_EXCEPTION(fc::parse_error_exception, + "Unsupported version for file. " + "Version is ${version} while code supports version(s) [${min},${max}]", + ("version", version) + ("min", min_supported_version) + ("max", max_supported_version)); + } + + return version; + } + + cfile open_cfile_for_write(const fc::path& dir, const std::string& filename) { + if (!fc::is_directory(dir)) + fc::create_directories(dir); + + auto dat_file = dir / filename; + cfile dat_content; + dat_content.set_file_path(dat_file.generic_string().c_str()); + dat_content.open( cfile::truncate_rw_mode ); + return dat_content; + } + + void write_persistence_header(cfile& dat_content, const uint32_t magic_number, const uint32_t current_version) { + dat_content.write( reinterpret_cast(&magic_number), sizeof(magic_number) ); + dat_content.write( reinterpret_cast(¤t_version), sizeof(current_version) ); + } +} // namespace persistence_util + +} // namespace fc diff --git a/libraries/libfc/include/fc/io/raw.hpp b/libraries/libfc/include/fc/io/raw.hpp new file mode 100644 index 0000000000..62fb508b42 --- /dev/null +++ b/libraries/libfc/include/fc/io/raw.hpp @@ -0,0 +1,809 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace fc { + namespace raw { + + namespace bip = boost::interprocess; + using shared_string = bip::basic_string< char, std::char_traits< char >, bip::allocator >; + + template + using UInt = boost::multiprecision::number< + boost::multiprecision::cpp_int_backend >; + template + using Int = boost::multiprecision::number< + boost::multiprecision::cpp_int_backend >; + template void pack( Stream& s, const UInt<256>& n ); + template void unpack( Stream& s, UInt<256>& n ); + template void pack( Stream& s, const Int<256>& n ); + template void unpack( Stream& s, Int<256>& n ); + template void pack( Stream& s, const boost::multiprecision::number& n ); + template void unpack( Stream& s, boost::multiprecision::number& n ); + + template + inline void pack( Stream& s, const Arg0& a0, Args... args ) { + pack( s, a0 ); + pack( s, args... ); + } + + template + inline void pack( Stream& s, const fc::exception& e ) + { + fc::raw::pack( s, e.code() ); + fc::raw::pack( s, std::string(e.name()) ); + fc::raw::pack( s, std::string(e.what()) ); + fc::raw::pack( s, e.get_log() ); + } + template + inline void unpack( Stream& s, fc::exception& e ) + { + int64_t code; + std::string name, what; + log_messages msgs; + + fc::raw::unpack( s, code ); + fc::raw::unpack( s, name ); + fc::raw::unpack( s, what ); + fc::raw::unpack( s, msgs ); + + e = fc::exception( fc::move(msgs), code, name, what ); + } + + template + inline void pack( Stream& s, const fc::log_message& msg ) + { + fc::raw::pack( s, variant(msg) ); + } + template + inline void unpack( Stream& s, fc::log_message& msg ) + { + fc::variant vmsg; + fc::raw::unpack( s, vmsg ); + msg = vmsg.as(); + } + + template + inline void pack( Stream& s, const fc::path& tp ) + { + fc::raw::pack( s, tp.generic_string() ); + } + + template + inline void unpack( Stream& s, fc::path& tp ) + { + std::string p; + fc::raw::unpack( s, p ); + tp = p; + } + + template + inline void pack( Stream& s, const fc::time_point_sec& tp ) + { + uint32_t usec = tp.sec_since_epoch(); + s.write( (const char*)&usec, sizeof(usec) ); + } + + template + inline void unpack( Stream& s, fc::time_point_sec& tp ) + { try { + uint32_t sec; + s.read( (char*)&sec, sizeof(sec) ); + tp = fc::time_point() + fc::seconds(sec); + } FC_RETHROW_EXCEPTIONS( warn, "" ) } + + template + inline void pack( Stream& s, const fc::time_point& tp ) + { + uint64_t usec = tp.time_since_epoch().count(); + s.write( (const char*)&usec, sizeof(usec) ); + } + + template + inline void unpack( Stream& s, fc::time_point& tp ) + { try { + uint64_t usec; + s.read( (char*)&usec, sizeof(usec) ); + tp = fc::time_point() + fc::microseconds(usec); + } FC_RETHROW_EXCEPTIONS( warn, "" ) } + + template + inline void pack( Stream& s, const fc::microseconds& usec ) + { + uint64_t usec_as_int64 = usec.count(); + s.write( (const char*)&usec_as_int64, sizeof(usec_as_int64) ); + } + + template + inline void unpack( Stream& s, fc::microseconds& usec ) + { try { + uint64_t usec_as_int64; + s.read( (char*)&usec_as_int64, sizeof(usec_as_int64) ); + usec = fc::microseconds(usec_as_int64); + } FC_RETHROW_EXCEPTIONS( warn, "" ) } + + template + inline auto pack( Stream& s, const fc::array& v) -> std::enable_if_t> + { + static_assert( N <= MAX_NUM_ARRAY_ELEMENTS, "number of elements in array is too large" ); + for (uint64_t i = 0; i < N; ++i) + fc::raw::pack(s, v.data[i]); + } + + template + inline auto pack( Stream& s, const fc::array& v) -> std::enable_if_t> + { + static_assert( N <= MAX_NUM_ARRAY_ELEMENTS, "number of elements in array is too large" ); + s.write((const char*)&v.data[0], N*sizeof(T)); + } + + template + inline auto unpack( Stream& s, fc::array& v) -> std::enable_if_t> + { try { + static_assert( N <= MAX_NUM_ARRAY_ELEMENTS, "number of elements in array is too large" ); + for (uint64_t i = 0; i < N; ++i) + fc::raw::unpack(s, v.data[i]); + } FC_RETHROW_EXCEPTIONS( warn, "fc::array<${type},${length}>", ("type",fc::get_typename::name())("length",N) ) } + + template + inline auto unpack( Stream& s, fc::array& v) -> std::enable_if_t> + { try { + static_assert( N <= MAX_NUM_ARRAY_ELEMENTS, "number of elements in array is too large" ); + s.read((char*)&v.data[0], N*sizeof(T)); + } FC_RETHROW_EXCEPTIONS( warn, "fc::array<${type},${length}>", ("type",fc::get_typename::name())("length",N) ) } + + template + inline void pack( Stream& s, T (&v)[N]) { + fc::raw::pack( s, unsigned_int((uint32_t)N) ); + for (uint64_t i = 0; i < N; ++i) + fc::raw::pack(s, v[i]); + } + + template + inline void unpack( Stream& s, T (&v)[N]) + { try { + unsigned_int size; fc::raw::unpack( s, size ); + FC_ASSERT( size.value == N ); + for (uint64_t i = 0; i < N; ++i) + fc::raw::unpack(s, v[i]); + } FC_RETHROW_EXCEPTIONS( warn, "${type} (&v)[${length}]", ("type",fc::get_typename::name())("length",N) ) } + + template + inline void pack( Stream& s, const std::shared_ptr& v) + { + fc::raw::pack( s, bool(!!v) ); + if( !!v ) fc::raw::pack( s, *v ); + } + + template + inline void unpack( Stream& s, std::shared_ptr& v) + { try { + bool b; fc::raw::unpack( s, b ); + if( b ) { v = std::make_shared(); fc::raw::unpack( s, *v ); } + } FC_RETHROW_EXCEPTIONS( warn, "std::shared_ptr", ("type",fc::get_typename::name()) ) } + + template inline void pack( Stream& s, const signed_int& v ) { + uint32_t val = (v.value<<1) ^ (v.value>>31); //apply zigzag encoding + do { + uint8_t b = uint8_t(val) & 0x7f; + val >>= 7; + b |= ((val > 0) << 7); + s.write((char*)&b,1);//.put(b); + } while( val ); + } + + template inline void pack( Stream& s, const unsigned_int& v ) { + uint64_t val = v.value; + do { + uint8_t b = uint8_t(val) & 0x7f; + val >>= 7; + b |= ((val > 0) << 7); + s.write((char*)&b,1);//.put(b); + }while( val ); + } + + template inline void unpack( Stream& s, signed_int& vi ) { + uint32_t v = 0; char b = 0; int by = 0; + do { + s.get(b); + v |= uint32_t(uint8_t(b) & 0x7f) << by; + by += 7; + } while( uint8_t(b) & 0x80 ); + vi.value= (v>>1) ^ (~(v&1)+1ull); //reverse zigzag encoding + } + + template inline void unpack( Stream& s, unsigned_int& vi ) { + uint64_t v = 0; char b = 0; uint8_t by = 0; + do { + s.get(b); + v |= uint32_t(uint8_t(b) & 0x7f) << by; + by += 7; + } while( uint8_t(b) & 0x80 && by < 32 ); + vi.value = static_cast(v); + } + + template inline void unpack( Stream& s, const T& vi ) + { + T tmp; + fc::raw::unpack( s, tmp ); + FC_ASSERT( vi == tmp ); + } + + template inline void pack( Stream& s, const char* v ) { fc::raw::pack( s, fc::string(v) ); } + + template + void pack( Stream& s, const safe& v ) { fc::raw::pack( s, v.value ); } + + template + void unpack( Stream& s, fc::safe& v ) { fc::raw::unpack( s, v.value ); } + + template + void pack( Stream& s, const fc::fwd& v ) { + fc::raw::pack( *v ); + } + + template + void unpack( Stream& s, fc::fwd& v ) { + fc::raw::unpack( *v ); + } + + // optional + template + void pack( Stream& s, const std::optional& v ) { + fc::raw::pack( s, v.has_value() ); + if( v ) fc::raw::pack( s, *v ); + } + + template + void unpack( Stream& s, std::optional& v ) + { try { + bool b; fc::raw::unpack( s, b ); + if( b ) { v = T(); fc::raw::unpack( s, *v ); } + } FC_RETHROW_EXCEPTIONS( warn, "optional<${type}>", ("type",fc::get_typename::name() ) ) } + + // std::vector + template inline void pack( Stream& s, const std::vector& value ) { + FC_ASSERT( value.size() <= MAX_SIZE_OF_BYTE_ARRAYS ); + fc::raw::pack( s, unsigned_int((uint32_t)value.size()) ); + if( value.size() ) + s.write( &value.front(), (uint32_t)value.size() ); + } + template inline void unpack( Stream& s, std::vector& value ) { + unsigned_int size; fc::raw::unpack( s, size ); + FC_ASSERT( size.value <= MAX_SIZE_OF_BYTE_ARRAYS ); + value.resize(size.value); + if( value.size() ) + s.read( value.data(), value.size() ); + } + + // fc::string + template inline void pack( Stream& s, const fc::string& v ) { + FC_ASSERT( v.size() <= MAX_SIZE_OF_BYTE_ARRAYS ); + fc::raw::pack( s, unsigned_int((uint32_t)v.size())); + if( v.size() ) s.write( v.c_str(), v.size() ); + } + + template inline void unpack( Stream& s, fc::string& v ) { + std::vector tmp; fc::raw::unpack(s,tmp); + if( tmp.size() ) + v = fc::string(tmp.data(),tmp.data()+tmp.size()); + else v = fc::string(); + } + + // bip::basic_string + template inline void pack( Stream& s, const shared_string& v ) { + FC_ASSERT( v.size() <= MAX_SIZE_OF_BYTE_ARRAYS ); + fc::raw::pack( s, unsigned_int((uint32_t)v.size())); + if( v.size() ) s.write( v.c_str(), v.size() ); + } + + template inline void unpack( Stream& s, shared_string& v ) { + std::vector tmp; fc::raw::unpack(s,tmp); + FC_ASSERT(v.size() == 0); + if( tmp.size() ) { + v.append(tmp.begin(), tmp.end()); + } + } + + // bool + template inline void pack( Stream& s, const bool& v ) { fc::raw::pack( s, uint8_t(v) ); } + template inline void unpack( Stream& s, bool& v ) + { + uint8_t b; + fc::raw::unpack( s, b ); + FC_ASSERT( (b & ~1) == 0 ); + v=(b!=0); + } + + namespace detail { + + template + struct pack_object_visitor { + pack_object_visitor(const Class& _c, Stream& _s) + :c(_c),s(_s){} + + template + void operator()( const char* name )const { + fc::raw::pack( s, c.*p ); + } + private: + const Class& c; + Stream& s; + }; + + template + struct unpack_object_visitor : public fc::reflector_init_visitor { + unpack_object_visitor(Class& _c, Stream& _s) + : fc::reflector_init_visitor(_c), s(_s){} + + template + inline void operator()( const char* name )const + { try { + fc::raw::unpack( s, this->obj.*p ); + } FC_RETHROW_EXCEPTIONS( warn, "Error unpacking field ${field}", ("field",name) ) } + + private: + Stream& s; + }; + + template + struct if_class{ + template + static inline void pack( Stream& s, const T& v ) { s << v; } + template + static inline void unpack( Stream& s, T& v ) { s >> v; } + }; + + template<> + struct if_class { + template + static inline void pack( Stream& s, const T& v ) { + s.write( (char*)&v, sizeof(v) ); + } + template + static inline void unpack( Stream& s, T& v ) { + s.read( (char*)&v, sizeof(v) ); + } + }; + + template + struct if_enum { + template + static inline void pack( Stream& s, const T& v ) { + fc::reflector::visit( pack_object_visitor( v, s ) ); + } + template + static inline void unpack( Stream& s, T& v ) { + fc::reflector::visit( unpack_object_visitor( v, s ) ); + } + }; + template<> + struct if_enum { + template + static inline void pack( Stream& s, const T& v ) { + fc::raw::pack(s, (int64_t)v); + } + template + static inline void unpack( Stream& s, T& v ) { + int64_t temp; + fc::raw::unpack(s, temp); + v = (T)temp; + } + }; + + template + struct if_reflected { + template + static inline void pack( Stream& s, const T& v ) { + if_class::type>::pack(s,v); + } + template + static inline void unpack( Stream& s, T& v ) { + if_class::type>::unpack(s,v); + } + }; + template<> + struct if_reflected { + template + static inline void pack( Stream& s, const T& v ) { + if_enum< typename fc::reflector::is_enum >::pack(s,v); + } + template + static inline void unpack( Stream& s, T& v ) { + if_enum< typename fc::reflector::is_enum >::unpack(s,v); + } + }; + + } // namesapce detail + + // allow users to verify version of fc calls reflector_init on unpacked reflected types + constexpr bool has_feature_reflector_init_on_unpacked_reflected_types = true; + + template + inline void pack( Stream& s, const std::unordered_set& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + fc::raw::pack( s, unsigned_int((uint32_t)value.size()) ); + auto itr = value.begin(); + auto end = value.end(); + while( itr != end ) { + fc::raw::pack( s, *itr ); + ++itr; + } + } + template + inline void unpack( Stream& s, std::unordered_set& value ) { + unsigned_int size; fc::raw::unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.clear(); + value.reserve(size.value); + for( uint32_t i = 0; i < size.value; ++i ) + { + T tmp; + fc::raw::unpack( s, tmp ); + value.insert( std::move(tmp) ); + } + } + + + template + inline void pack( Stream& s, const std::pair& value ) { + fc::raw::pack( s, value.first ); + fc::raw::pack( s, value.second ); + } + template + inline void unpack( Stream& s, std::pair& value ) + { + fc::raw::unpack( s, value.first ); + fc::raw::unpack( s, value.second ); + } + + template + inline void pack( Stream& s, const std::unordered_map& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + fc::raw::pack( s, unsigned_int((uint32_t)value.size()) ); + auto itr = value.begin(); + auto end = value.end(); + while( itr != end ) { + fc::raw::pack( s, *itr ); + ++itr; + } + } + template + inline void unpack( Stream& s, std::unordered_map& value ) + { + unsigned_int size; fc::raw::unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.clear(); + value.reserve(size.value); + for( uint32_t i = 0; i < size.value; ++i ) + { + std::pair tmp; + fc::raw::unpack( s, tmp ); + value.insert( std::move(tmp) ); + } + } + template + inline void pack( Stream& s, const std::map& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + fc::raw::pack( s, unsigned_int((uint32_t)value.size()) ); + auto itr = value.begin(); + auto end = value.end(); + while( itr != end ) { + fc::raw::pack( s, *itr ); + ++itr; + } + } + template + inline void unpack( Stream& s, std::map& value ) + { + unsigned_int size; fc::raw::unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.clear(); + for( uint32_t i = 0; i < size.value; ++i ) + { + std::pair tmp; + fc::raw::unpack( s, tmp ); + value.insert( std::move(tmp) ); + } + } + + template + inline void pack( Stream& s, const std::deque& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + fc::raw::pack( s, unsigned_int((uint32_t)value.size()) ); + for( const auto& i : value ) { + fc::raw::pack( s, i ); + } + } + + template + inline void unpack( Stream& s, std::deque& value ) { + unsigned_int size; fc::raw::unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.resize(size.value); + for( auto& i : value ) { + fc::raw::unpack( s, i ); + } + } + + template + inline void pack( Stream& s, const boost::container::deque& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + fc::raw::pack( s, unsigned_int( (uint32_t) value.size() ) ); + for( const auto& i : value ) { + fc::raw::pack( s, i ); + } + } + + template + inline void unpack( Stream& s, boost::container::deque& value ) { + unsigned_int size; + fc::raw::unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.resize( size.value ); + for( auto& i : value ) { + fc::raw::unpack( s, i ); + } + } + + template + inline void pack( Stream& s, const std::vector& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + fc::raw::pack( s, unsigned_int((uint32_t)value.size()) ); + for( const auto& i : value ) { + fc::raw::pack( s, i ); + } + } + + template + inline void unpack( Stream& s, std::vector& value ) { + unsigned_int size; fc::raw::unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + value.resize(size.value); + for( auto& i : value ) { + fc::raw::unpack( s, i ); + } + } + + template + inline void pack( Stream& s, const std::list& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + fc::raw::pack( s, unsigned_int((uint32_t)value.size()) ); + for( const auto& i : value ) { + fc::raw::pack( s, i ); + } + } + + template + inline void unpack( Stream& s, std::list& value ) { + unsigned_int size; fc::raw::unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + while( size.value-- ) { + T i; + fc::raw::unpack( s, i ); + value.emplace_back( std::move( i ) ); + } + } + + template + inline void pack( Stream& s, const std::set& value ) { + FC_ASSERT( value.size() <= MAX_NUM_ARRAY_ELEMENTS ); + fc::raw::pack( s, unsigned_int((uint32_t)value.size()) ); + auto itr = value.begin(); + auto end = value.end(); + while( itr != end ) { + fc::raw::pack( s, *itr ); + ++itr; + } + } + + template + inline void unpack( Stream& s, std::set& value ) { + unsigned_int size; fc::raw::unpack( s, size ); + FC_ASSERT( size.value <= MAX_NUM_ARRAY_ELEMENTS ); + for( uint64_t i = 0; i < size.value; ++i ) + { + T tmp; + fc::raw::unpack( s, tmp ); + value.insert( std::move(tmp) ); + } + } + + template + inline auto pack( Stream& s, const std::array& value ) -> std::enable_if_t> + { + s.write((const char*)value.data(), S * sizeof(T)); + } + + template + inline auto pack( Stream& s, const std::array& value ) -> std::enable_if_t> + { + for( std::size_t i = 0; i < S; ++i ) { + fc::raw::pack( s, value[i] ); + } + } + + template + inline auto unpack( Stream& s, std::array& value ) -> std::enable_if_t> + { + s.read((char*)value.data(), S * sizeof(T)); + } + + template + inline auto unpack( Stream& s, std::array& value ) -> std::enable_if_t> + { + for( std::size_t i = 0; i < S; ++i ) { + fc::raw::unpack( s, value[i] ); + } + } + + template + inline void pack( Stream& s, const T& v ) { + fc::raw::detail::if_reflected< typename fc::reflector::is_defined >::pack(s,v); + } + template + inline void unpack( Stream& s, T& v ) + { try { + fc::raw::detail::if_reflected< typename fc::reflector::is_defined >::unpack(s,v); + } FC_RETHROW_EXCEPTIONS( warn, "error unpacking ${type}", ("type",fc::get_typename::name() ) ) } + + template + inline size_t pack_size( const T& v ) + { + datastream ps; + fc::raw::pack(ps,v ); + return ps.tellp(); + } + + template + inline std::vector pack( const T& v ) { + datastream ps; + fc::raw::pack(ps,v ); + std::vector vec(ps.tellp()); + + if( vec.size() ) { + datastream ds( vec.data(), size_t(vec.size()) ); + fc::raw::pack(ds,v); + } + return vec; + } + + template + inline std::vector pack( const T& v, Next... next ) { + datastream ps; + fc::raw::pack(ps,v,next...); + std::vector vec(ps.tellp()); + + if( vec.size() ) { + datastream ds( vec.data(), size_t(vec.size()) ); + fc::raw::pack(ds,v,next...); + } + return vec; + } + + + template + inline T unpack( const std::vector& s ) + { try { + T tmp; + datastream ds( s.data(), size_t(s.size()) ); + fc::raw::unpack(ds,tmp); + return tmp; + } FC_RETHROW_EXCEPTIONS( warn, "error unpacking ${type}", ("type",fc::get_typename::name() ) ) } + + template + inline void unpack( const std::vector& s, T& tmp ) + { try { + datastream ds( s.data(), size_t(s.size()) ); + fc::raw::unpack(ds,tmp); + } FC_RETHROW_EXCEPTIONS( warn, "error unpacking ${type}", ("type",fc::get_typename::name() ) ) } + + template + inline void pack( char* d, uint32_t s, const T& v ) { + datastream ds(d,s); + fc::raw::pack(ds,v ); + } + + template + inline T unpack( const char* d, uint32_t s ) + { try { + T v; + datastream ds( d, s ); + fc::raw::unpack(ds,v); + return v; + } FC_RETHROW_EXCEPTIONS( warn, "error unpacking ${type}", ("type",fc::get_typename::name() ) ) } + + template + inline void unpack( const char* d, uint32_t s, T& v ) + { try { + datastream ds( d, s ); + fc::raw::unpack(ds,v); + } FC_RETHROW_EXCEPTIONS( warn, "error unpacking ${type}", ("type",fc::get_typename::name() ) ) } + + template + struct pack_static_variant + { + Stream& stream; + pack_static_variant( Stream& s ):stream(s){} + + typedef void result_type; + template void operator()( const T& v )const + { + fc::raw::pack( stream, v ); + } + }; + + template + struct unpack_static_variant + { + Stream& stream; + unpack_static_variant( Stream& s ):stream(s){} + + typedef void result_type; + template void operator()( T& v )const + { + fc::raw::unpack( stream, v ); + } + }; + + + template + void pack( Stream& s, const std::variant& sv ) + { + fc::raw::pack( s, unsigned_int(sv.index()) ); + std::visit( pack_static_variant(s), sv ); + } + + template void unpack( Stream& s, std::variant& sv ) + { + unsigned_int w; + fc::raw::unpack( s, w ); + fc::from_index(sv, w.value); + std::visit( unpack_static_variant(s), sv ); + } + + + + template void pack( Stream& s, const boost::multiprecision::number& n ) { + static_assert( sizeof( n ) == (std::numeric_limits>::digits+1)/8, "unexpected padding" ); + s.write( (const char*)&n, sizeof(n) ); + } + template void unpack( Stream& s, boost::multiprecision::number& n ) { + static_assert( sizeof( n ) == (std::numeric_limits>::digits+1)/8, "unexpected padding" ); + s.read( (char*)&n, sizeof(n) ); + } + + template void pack( Stream& s, const UInt<256>& n ) { + pack( s, static_cast>(n) ); + pack( s, static_cast>(n >> 128) ); + } + template void unpack( Stream& s, UInt<256>& n ) { + UInt<128> tmp[2]; + unpack( s, tmp[0] ); + unpack( s, tmp[1] ); + n = tmp[1]; + n <<= 128; + n |= tmp[0]; + } + +} } // namespace fc::raw diff --git a/libraries/libfc/include/fc/io/raw_fwd.hpp b/libraries/libfc/include/fc/io/raw_fwd.hpp new file mode 100644 index 0000000000..60acfc724a --- /dev/null +++ b/libraries/libfc/include/fc/io/raw_fwd.hpp @@ -0,0 +1,131 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + class time_point; + class time_point_sec; + class variant; + class variant_object; + class path; + + template class enum_type; + namespace ip { class endpoint; } + + namespace ecc { class public_key; class private_key; } + template class fixed_string; + + namespace raw { + template + constexpr bool is_trivial_array = std::is_scalar::value == true && std::is_pointer::value == false; + + template + inline size_t pack_size( const T& v ); + + template inline void pack( Stream& s, const fc::fixed_string& u ); + template inline void unpack( Stream& s, fc::fixed_string& u ); + + template + inline void pack( Stream& s, const fc::enum_type& tp ); + template + inline void unpack( Stream& s, fc::enum_type& tp ); + + + + template inline void pack( Stream& s, const std::set& value ); + template inline void unpack( Stream& s, std::set& value ); + template inline void pack( Stream& s, const std::unordered_set& value ); + template inline void unpack( Stream& s, std::unordered_set& value ); + + template void pack( Stream& s, const std::variant& sv ); + template void unpack( Stream& s, std::variant& sv ); + + template inline void pack( Stream& s, const std::deque& value ); + template inline void unpack( Stream& s, std::deque& value ); + + template + inline void pack( Stream& s, const boost::container::deque& value ); + template + inline void unpack( Stream& s, boost::container::deque& value ); + + template inline void pack( Stream& s, const std::unordered_map& value ); + template inline void unpack( Stream& s, std::unordered_map& value ); + + template inline void pack( Stream& s, const std::map& value ); + template inline void unpack( Stream& s, std::map& value ); + + template inline void pack( Stream& s, const std::pair& value ); + template inline void unpack( Stream& s, std::pair& value ); + + template inline auto pack( Stream& s, const std::array& value ) -> std::enable_if_t>; + template inline auto pack( Stream& s, const std::array& value ) -> std::enable_if_t>; + template inline auto unpack( Stream& s, std::array& value ) -> std::enable_if_t>; + template inline auto unpack( Stream& s, std::array& value ) -> std::enable_if_t>; + + template inline void pack( Stream& s, const variant_object& v ); + template inline void unpack( Stream& s, variant_object& v ); + template inline void pack( Stream& s, const variant& v ); + template inline void unpack( Stream& s, variant& v ); + + template inline void pack( Stream& s, const path& v ); + template inline void unpack( Stream& s, path& v ); + template inline void pack( Stream& s, const ip::endpoint& v ); + template inline void unpack( Stream& s, ip::endpoint& v ); + + + template void unpack( Stream& s, std::optional& v ); + template void unpack( Stream& s, const T& v ); + template void pack( Stream& s, const std::optional& v ); + template void pack( Stream& s, const safe& v ); + template void unpack( Stream& s, fc::safe& v ); + + template void unpack( Stream& s, time_point& ); + template void pack( Stream& s, const time_point& ); + template void unpack( Stream& s, time_point_sec& ); + template void pack( Stream& s, const time_point_sec& ); + template void unpack( Stream& s, std::string& ); + template void pack( Stream& s, const std::string& ); + template void unpack( Stream& s, fc::ecc::public_key& ); + template void pack( Stream& s, const fc::ecc::public_key& ); + template void unpack( Stream& s, fc::ecc::private_key& ); + template void pack( Stream& s, const fc::ecc::private_key& ); + + template inline void pack( Stream& s, const T& v ); + template inline void unpack( Stream& s, T& v ); + + template inline void pack( Stream& s, const std::vector& v ); + template inline void unpack( Stream& s, std::vector& v ); + + template inline void pack( Stream& s, const signed_int& v ); + template inline void unpack( Stream& s, signed_int& vi ); + + template inline void pack( Stream& s, const unsigned_int& v ); + template inline void unpack( Stream& s, unsigned_int& vi ); + + template inline void pack( Stream& s, const char* v ); + template inline void pack( Stream& s, const std::vector& value ); + template inline void unpack( Stream& s, std::vector& value ); + + template inline auto pack( Stream& s, const fc::array& v) -> std::enable_if_t>; + template inline auto pack( Stream& s, const fc::array& v) -> std::enable_if_t>; + template inline auto unpack( Stream& s, fc::array& v) -> std::enable_if_t>; + template inline auto unpack( Stream& s, fc::array& v) -> std::enable_if_t>; + + template inline void pack( Stream& s, const bool& v ); + template inline void unpack( Stream& s, bool& v ); + + template inline std::vector pack( const T& v ); + template inline T unpack( const std::vector& s ); + template inline T unpack( const char* d, uint32_t s ); + template inline void unpack( const char* d, uint32_t s, T& v ); +} } diff --git a/libraries/libfc/include/fc/io/raw_unpack_file.hpp b/libraries/libfc/include/fc/io/raw_unpack_file.hpp new file mode 100644 index 0000000000..580a64cce6 --- /dev/null +++ b/libraries/libfc/include/fc/io/raw_unpack_file.hpp @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include +#include + +namespace fc +{ + namespace raw + { + template + void unpack_file( const fc::path& filename, T& obj ) + { + try { + fc::file_mapping fmap( filename.generic_string().c_str(), fc::read_only); + fc::mapped_region mapr( fmap, fc::read_only, 0, fc::file_size(filename) ); + auto cs = (const char*)mapr.get_address(); + + fc::datastream ds( cs, mapr.get_size() ); + fc::raw::unpack(ds,obj); + } FC_RETHROW_EXCEPTIONS( info, "unpacking file ${file}", ("file",filename) ); + } + } +} diff --git a/libraries/libfc/include/fc/io/raw_variant.hpp b/libraries/libfc/include/fc/io/raw_variant.hpp new file mode 100644 index 0000000000..925040062f --- /dev/null +++ b/libraries/libfc/include/fc/io/raw_variant.hpp @@ -0,0 +1,159 @@ +#pragma once +#include +#include +#include +#include + +namespace fc { namespace raw { + + template + class variant_packer : public variant::visitor + { + public: + variant_packer( Stream& _s ):s(_s){} + virtual void handle()const { } + virtual void handle( const int64_t& v )const + { + fc::raw::pack( s, v ); + } + virtual void handle( const uint64_t& v )const + { + fc::raw::pack( s, v ); + } + virtual void handle( const double& v )const + { + fc::raw::pack( s, v ); + } + virtual void handle( const bool& v )const + { + fc::raw::pack( s, v ); + } + virtual void handle( const string& v )const + { + fc::raw::pack( s, v ); + } + virtual void handle( const variant_object& v)const + { + fc::raw::pack( s, v ); + } + virtual void handle( const variants& v)const + { + fc::raw::pack( s, v ); + } + virtual void handle( const blob& v)const + { + fc::raw::pack( s, v ); + } + + Stream& s; + + }; + + + template + inline void pack( Stream& s, const variant& v ) + { + pack( s, uint8_t(v.get_type()) ); + v.visit( variant_packer(s) ); + } + template + inline void unpack( Stream& s, variant& v ) + { + uint8_t t; + unpack( s, t ); + switch( t ) + { + case variant::null_type: + return; + case variant::int64_type: + { + int64_t val; + raw::unpack(s,val); + v = val; + return; + } + case variant::uint64_type: + { + uint64_t val; + raw::unpack(s,val); + v = val; + return; + } + case variant::double_type: + { + double val; + raw::unpack(s,val); + v = val; + return; + } + case variant::bool_type: + { + bool val; + raw::unpack(s,val); + v = val; + return; + } + case variant::string_type: + { + fc::string val; + raw::unpack(s,val); + v = fc::move(val); + return; + } + case variant::array_type: + { + variants val; + raw::unpack(s,val); + v = fc::move(val); + return; + } + case variant::object_type: + { + variant_object val; + raw::unpack(s,val); + v = fc::move(val); + return; + } + case variant::blob_type: + { + blob val; + raw::unpack(s,val); + v = fc::move(val); + return; + } + default: + FC_THROW_EXCEPTION( parse_error_exception, "Unknown Variant Type ${t}", ("t", t) ); + } + } + + template + inline void pack( Stream& s, const variant_object& v ) + { + unsigned_int vs = (uint32_t)v.size(); + pack( s, vs ); + for( auto itr = v.begin(); itr != v.end(); ++itr ) + { + pack( s, itr->key() ); + pack( s, itr->value() ); + } + } + template + inline void unpack( Stream& s, variant_object& v ) + { + unsigned_int vs; + unpack( s, vs ); + + mutable_variant_object mvo; + mvo.reserve(vs.value); + for( uint32_t i = 0; i < vs.value; ++i ) + { + fc::string key; + fc::variant value; + fc::raw::unpack(s,key); + fc::raw::unpack(s,value); + mvo.set( fc::move(key), fc::move(value) ); + } + v = fc::move(mvo); + } + +} } // fc::raw diff --git a/libraries/libfc/include/fc/io/sstream.hpp b/libraries/libfc/include/fc/io/sstream.hpp new file mode 100644 index 0000000000..5f4bf8a152 --- /dev/null +++ b/libraries/libfc/include/fc/io/sstream.hpp @@ -0,0 +1,35 @@ +#pragma once +/* +#include +#include + +namespace fc { + + class stringstream : virtual public iostream { + public: + stringstream(); + stringstream( fc::string& s); + stringstream( const fc::string& s); + ~stringstream(); + + fc::string str(); + void str(const fc::string& s); + + void clear(); + + virtual bool eof()const; + virtual size_t writesome( const char* buf, size_t len ); + virtual size_t writesome( const std::shared_ptr& buf, size_t len, size_t offset ); + virtual size_t readsome( char* buf, size_t len ); + virtual size_t readsome( const std::shared_ptr& buf, size_t len, size_t offset ); + virtual void close(); + virtual void flush(); + char peek(); + + private: + class impl; + fwd my; + }; + +} +*/ diff --git a/libraries/libfc/include/fc/io/varint.hpp b/libraries/libfc/include/fc/io/varint.hpp new file mode 100644 index 0000000000..c451847bcd --- /dev/null +++ b/libraries/libfc/include/fc/io/varint.hpp @@ -0,0 +1,98 @@ +#pragma once +#include + +namespace fc { + +struct unsigned_int { + unsigned_int( uint32_t v = 0 ):value(v){} + + template + unsigned_int( T v ):value(v){} + + operator uint32_t()const { return value; } + //operator uint64_t()const { return value; } + + unsigned_int& operator=( int32_t v ) { value = v; return *this; } + + uint32_t value; + + friend bool operator==( const unsigned_int& i, const uint32_t& v ) { return i.value == v; } + friend bool operator==( const uint32_t& i, const unsigned_int& v ) { return i == v.value; } + friend bool operator==( const unsigned_int& i, const unsigned_int& v ) { return i.value == v.value; } + + friend bool operator!=( const unsigned_int& i, const uint32_t& v ) { return i.value != v; } + friend bool operator!=( const uint32_t& i, const unsigned_int& v ) { return i != v.value; } + friend bool operator!=( const unsigned_int& i, const unsigned_int& v ) { return i.value != v.value; } + + friend bool operator<( const unsigned_int& i, const uint32_t& v ) { return i.value < v; } + friend bool operator<( const uint32_t& i, const unsigned_int& v ) { return i < v.value; } + friend bool operator<( const unsigned_int& i, const unsigned_int& v ) { return i.value < v.value; } + + friend bool operator>=( const unsigned_int& i, const uint32_t& v ) { return i.value >= v; } + friend bool operator>=( const uint32_t& i, const unsigned_int& v ) { return i >= v.value; } + friend bool operator>=( const unsigned_int& i, const unsigned_int& v ) { return i.value >= v.value; } +}; + +/** + * @brief serializes a 32 bit signed interger in as few bytes as possible + * + * Uses the google protobuf algorithm for seralizing signed numbers + */ +struct signed_int { + signed_int( int32_t v = 0 ):value(v){} + operator int32_t()const { return value; } + template + signed_int& operator=( const T& v ) { value = v; return *this; } + signed_int operator++(int) { return value++; } + signed_int& operator++(){ ++value; return *this; } + + int32_t value; + + friend bool operator==( const signed_int& i, const int32_t& v ) { return i.value == v; } + friend bool operator==( const int32_t& i, const signed_int& v ) { return i == v.value; } + friend bool operator==( const signed_int& i, const signed_int& v ) { return i.value == v.value; } + + friend bool operator!=( const signed_int& i, const int32_t& v ) { return i.value != v; } + friend bool operator!=( const int32_t& i, const signed_int& v ) { return i != v.value; } + friend bool operator!=( const signed_int& i, const signed_int& v ) { return i.value != v.value; } + + friend bool operator<( const signed_int& i, const int32_t& v ) { return i.value < v; } + friend bool operator<( const int32_t& i, const signed_int& v ) { return i < v.value; } + friend bool operator<( const signed_int& i, const signed_int& v ) { return i.value < v.value; } + + friend bool operator>=( const signed_int& i, const int32_t& v ) { return i.value >= v; } + friend bool operator>=( const int32_t& i, const signed_int& v ) { return i >= v.value; } + friend bool operator>=( const signed_int& i, const signed_int& v ) { return i.value >= v.value; } +}; + +class variant; + +void to_variant( const signed_int& var, variant& vo ); +void from_variant( const variant& var, signed_int& vo ); +void to_variant( const unsigned_int& var, variant& vo ); +void from_variant( const variant& var, unsigned_int& vo ); + +} // namespace fc + +#include +namespace std +{ + template<> + struct hash + { + public: + size_t operator()(const fc::signed_int &a) const + { + return std::hash()(a.value); + } + }; + template<> + struct hash + { + public: + size_t operator()(const fc::signed_int &a) const + { + return std::hash()(a.value); + } + }; +} diff --git a/libraries/libfc/include/fc/log/appender.hpp b/libraries/libfc/include/fc/log/appender.hpp new file mode 100644 index 0000000000..c01aadb0e3 --- /dev/null +++ b/libraries/libfc/include/fc/log/appender.hpp @@ -0,0 +1,42 @@ +#pragma once +#include +#include +#include + +#if BOOST_VERSION >= 106600 +namespace boost { namespace asio { class io_context; typedef io_context io_service; } } +#else +namespace boost { namespace asio { class io_service; } } +#endif + +namespace fc { + class appender; + class log_message; + class variant; + + class appender_factory { + public: + typedef std::shared_ptr ptr; + + virtual ~appender_factory(){}; + virtual std::shared_ptr create( const variant& args ) = 0; + }; + + namespace detail { + template + class appender_factory_impl : public appender_factory { + public: + virtual std::shared_ptr create( const variant& args ) { + return std::shared_ptr(new T(args)); + } + }; + } + + class appender { + public: + typedef std::shared_ptr ptr; + + virtual void initialize( boost::asio::io_service& io_service ) = 0; + virtual void log( const log_message& m ) = 0; + }; +} diff --git a/libraries/libfc/include/fc/log/console_appender.hpp b/libraries/libfc/include/fc/log/console_appender.hpp new file mode 100644 index 0000000000..a01411fc25 --- /dev/null +++ b/libraries/libfc/include/fc/log/console_appender.hpp @@ -0,0 +1,73 @@ +#pragma once +#include +#include +#include + +namespace fc +{ + class console_appender final : public appender + { + public: + struct color + { + enum type { + red, + green, + brown, + blue, + magenta, + cyan, + white, + console_default, + }; + }; + + struct stream { enum type { std_out, std_error }; }; + + struct level_color + { + level_color( log_level l=log_level::all, + color::type c=color::console_default ) + :level(l),color(c){} + + log_level level; + console_appender::color::type color; + }; + + struct config + { + config() + :format( "${timestamp} ${thread_name} ${context} ${file}:${line} ${method} ${level}] ${message}" ), + stream(console_appender::stream::std_error),flush(true){} + + fc::string format; + console_appender::stream::type stream; + std::vector level_colors; + bool flush; + }; + + + console_appender( const variant& args ); + console_appender( const config& cfg ); + console_appender(); + + ~console_appender(); + void initialize( boost::asio::io_service& io_service ) {} + virtual void log( const log_message& m ); + + void print( const std::string& text_to_print, + color::type text_color = color::console_default ); + + void configure( const config& cfg ); + + private: + class impl; + std::unique_ptr my; + }; +} // namespace fc + +#include +FC_REFLECT_ENUM( fc::console_appender::stream::type, (std_out)(std_error) ) +FC_REFLECT_ENUM( fc::console_appender::color::type, (red)(green)(brown)(blue)(magenta)(cyan)(white)(console_default) ) +FC_REFLECT( fc::console_appender::level_color, (level)(color) ) +FC_REFLECT( fc::console_appender::config, (format)(stream)(level_colors)(flush) ) diff --git a/libraries/libfc/include/fc/log/dmlog_appender.hpp b/libraries/libfc/include/fc/log/dmlog_appender.hpp new file mode 100644 index 0000000000..cf07a83a1e --- /dev/null +++ b/libraries/libfc/include/fc/log/dmlog_appender.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +namespace fc { + + /** + * Specialized appender for deep mind tracer that sends log messages + * through `stdout` correctly formatted for latter consumption by + * deep mind postprocessing tools from dfuse. + */ + class dmlog_appender : public appender + { + public: + struct config + { + std::string file = "-"; + }; + explicit dmlog_appender( const variant& args ); + explicit dmlog_appender( const std::optional& args) ; + + virtual ~dmlog_appender(); + virtual void initialize( boost::asio::io_service& io_service ) override; + + virtual void log( const log_message& m ) override; + + private: + dmlog_appender(); + class impl; + std::unique_ptr my; + }; +} + +FC_REFLECT(fc::dmlog_appender::config, (file)) diff --git a/libraries/libfc/include/fc/log/gelf_appender.hpp b/libraries/libfc/include/fc/log/gelf_appender.hpp new file mode 100644 index 0000000000..3b8b33e8ce --- /dev/null +++ b/libraries/libfc/include/fc/log/gelf_appender.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#include + +namespace fc +{ + // Log appender that sends log messages in JSON format over UDP + // https://www.graylog2.org/resources/gelf/specification + class gelf_appender final : public appender + { + public: + struct config + { + static const std::vector reserved_field_names; + static const std::regex user_field_name_pattern; + string endpoint = "127.0.0.1:12201"; + string host = "fc"; // the name of the host, source or application that sent this message (just passed through to GELF server) + variant_object user_fields = {}; + }; + + gelf_appender(const variant& args); + ~gelf_appender(); + /** \brief Required for name resolution and socket initialization. + * + * \warning If this method is not called, this appender will log nothing. + * + * In a single-threaded world with a boost::io_service that's not owned + * by this library, ugly things are required. Tough. + */ + void initialize(boost::asio::io_service& io_service) override; + virtual void log(const log_message& m) override; + + private: + class impl; + std::shared_ptr my; + }; +} // namespace fc + +#include +FC_REFLECT(fc::gelf_appender::config, + (endpoint)(host)(user_fields)) diff --git a/libraries/libfc/include/fc/log/log_message.hpp b/libraries/libfc/include/fc/log/log_message.hpp new file mode 100644 index 0000000000..c7f41aa2fd --- /dev/null +++ b/libraries/libfc/include/fc/log/log_message.hpp @@ -0,0 +1,167 @@ +#pragma once +/** + * @file log_message.hpp + * @brief Defines types and helper macros necessary for generating log messages. + */ +#include +#include +#include + +namespace fc +{ + namespace detail + { + class log_context_impl; + class log_message_impl; + } + + /** + * Named scope for log_level enumeration. + */ + class log_level + { + public: + /** + * @brief Define's the various log levels for reporting. + * + * Each log level includes all higher levels such that + * Debug includes Error, but Error does not include Debug. + */ + enum values + { + all, + debug, + info, + warn, + error, + off + }; + log_level( values v = off ):value(v){} + explicit log_level( int v ):value( static_cast(v)){} + operator int()const { return value; } + string to_string()const; + values value; + }; + + void to_variant( log_level e, variant& v ); + void from_variant( const variant& e, log_level& ll ); + + /** + * @brief provides information about where and when a log message was generated. + * @ingroup AthenaSerializable + * + * @see FC_LOG_CONTEXT + */ + class log_context + { + public: + log_context(); + log_context( log_level ll, + const char* file, + uint64_t line, + const char* method ); + ~log_context(); + explicit log_context( const variant& v ); + variant to_variant()const; + + string get_file()const; + uint64_t get_line_number()const; + string get_method()const; + string get_thread_name()const; + string get_task_name()const; + string get_host_name()const; + time_point get_timestamp()const; + log_level get_log_level()const; + string get_context()const; + + void append_context( const fc::string& c ); + + string to_string()const; + private: + std::shared_ptr my; + }; + + void to_variant( const log_context& l, variant& v ); + void from_variant( const variant& l, log_context& c ); + + /** + * @brief aggregates a message along with the context and associated meta-information. + * @ingroup AthenaSerializable + * + * @note log_message has reference semantics, all copies refer to the same log message + * and the message is read-only after construction. + * + * When converted to JSON, log_message has the following form: + * @code + * { + * "context" : { ... }, + * "format" : "string with ${keys}", + * "data" : { "keys" : "values" } + * } + * @endcode + * + * @see FC_LOG_MESSAGE + */ + class log_message + { + public: + log_message(); + /** + * @param ctx - generally provided using the FC_LOG_CONTEXT(LEVEL) macro + */ + log_message( log_context ctx, std::string format, variant_object args = variant_object() ); + ~log_message(); + + log_message( const variant& v ); + variant to_variant()const; + + string get_message()const; + /** + * A faster version of get_message which does limited formatting and excludes large variants + * @return formatted message according to format and variant args + */ + string get_limited_message()const; + + log_context get_context()const; + string get_format()const; + variant_object get_data()const; + + private: + std::shared_ptr my; + }; + + void to_variant( const log_message& l, variant& v ); + void from_variant( const variant& l, log_message& c ); + + typedef std::vector log_messages; + + +} // namespace fc + +FC_REFLECT_TYPENAME( fc::log_message ); + +#ifndef __func__ +#define __func__ __FUNCTION__ +#endif + +/** + * @def FC_LOG_CONTEXT(LOG_LEVEL) + * @brief Automatically captures the File, Line, and Method names and passes them to + * the constructor of fc::log_context along with LOG_LEVEL + * @param LOG_LEVEL - a valid log_level::Enum name. + */ +#define FC_LOG_CONTEXT(LOG_LEVEL) \ + fc::log_context( fc::log_level::LOG_LEVEL, __FILE__, __LINE__, __func__ ) + +/** + * @def FC_LOG_MESSAGE(LOG_LEVEL,FORMAT,...) + * + * @brief A helper method for generating log messages. + * + * @param LOG_LEVEL a valid log_level::Enum name to be passed to the log_context + * @param FORMAT A const char* string containing zero or more references to keys as "${key}" + * @param ... A set of key/value pairs denoted as ("key",val)("key2",val2)... + */ +#define FC_LOG_MESSAGE( LOG_LEVEL, FORMAT, ... ) \ + fc::log_message( FC_LOG_CONTEXT(LOG_LEVEL), FORMAT, fc::mutable_variant_object()__VA_ARGS__ ) + diff --git a/libraries/libfc/include/fc/log/logger.hpp b/libraries/libfc/include/fc/log/logger.hpp new file mode 100644 index 0000000000..803a2de0a8 --- /dev/null +++ b/libraries/libfc/include/fc/log/logger.hpp @@ -0,0 +1,179 @@ +#pragma once +#include +#include +#include + +#ifndef DEFAULT_LOGGER +#define DEFAULT_LOGGER "default" +#endif + +namespace fc +{ + + class appender; + + /** + * + * + @code + void my_class::func() + { + fc_dlog( my_class_logger, "Format four: ${arg} five: ${five}", ("arg",4)("five",5) ); + } + @endcode + */ + class logger + { + public: + static logger get( const fc::string& name = DEFAULT_LOGGER ); + static void update( const fc::string& name, logger& log ); + + logger(); + logger( const string& name, const logger& parent = nullptr ); + logger( std::nullptr_t ); + logger( const logger& c ); + logger( logger&& c ); + ~logger(); + logger& operator=(const logger&); + logger& operator=(logger&&); + friend bool operator==( const logger&, nullptr_t ); + friend bool operator!=( const logger&, nullptr_t ); + + logger& set_log_level( log_level e ); + log_level get_log_level()const; + logger& set_parent( const logger& l ); + logger get_parent()const; + + void set_name( const fc::string& n ); + const fc::string& name()const; + + bool is_enabled( log_level e )const; + void log( log_message m ); + + private: + friend struct log_config; + void add_appender( const std::shared_ptr& a ); + + private: + class impl; + std::shared_ptr my; + }; + +} // namespace fc + +// suppress warning "conditional expression is constant" in the while(0) for visual c++ +// http://cnicholson.net/2009/03/stupid-c-tricks-dowhile0-and-c4127/ +#define FC_MULTILINE_MACRO_BEGIN do { +#ifdef _MSC_VER +# define FC_MULTILINE_MACRO_END \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) \ + } while (0) \ + __pragma(warning(pop)) +#else +# define FC_MULTILINE_MACRO_END } while (0) +#endif + +#define fc_dlog( LOGGER, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( (LOGGER).is_enabled( fc::log_level::debug ) ) \ + (LOGGER).log( FC_LOG_MESSAGE( debug, FORMAT, __VA_ARGS__ ) ); \ + FC_MULTILINE_MACRO_END + +#define fc_ilog( LOGGER, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( (LOGGER).is_enabled( fc::log_level::info ) ) \ + (LOGGER).log( FC_LOG_MESSAGE( info, FORMAT, __VA_ARGS__ ) ); \ + FC_MULTILINE_MACRO_END + +#define fc_wlog( LOGGER, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( (LOGGER).is_enabled( fc::log_level::warn ) ) \ + (LOGGER).log( FC_LOG_MESSAGE( warn, FORMAT, __VA_ARGS__ ) ); \ + FC_MULTILINE_MACRO_END + +#define fc_elog( LOGGER, FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( (LOGGER).is_enabled( fc::log_level::error ) ) \ + (LOGGER).log( FC_LOG_MESSAGE( error, FORMAT, __VA_ARGS__ ) ); \ + FC_MULTILINE_MACRO_END + +#define dlog( FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( (fc::logger::get(DEFAULT_LOGGER)).is_enabled( fc::log_level::debug ) ) \ + (fc::logger::get(DEFAULT_LOGGER)).log( FC_LOG_MESSAGE( debug, FORMAT, __VA_ARGS__ ) ); \ + FC_MULTILINE_MACRO_END + +/** + * Sends the log message to a special 'user' log stream designed for messages that + * the end user may like to see. + */ +#define ulog( FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( (fc::logger::get("user")).is_enabled( fc::log_level::debug ) ) \ + (fc::logger::get("user")).log( FC_LOG_MESSAGE( debug, FORMAT, __VA_ARGS__ ) ); \ + FC_MULTILINE_MACRO_END + + +#define ilog( FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( (fc::logger::get(DEFAULT_LOGGER)).is_enabled( fc::log_level::info ) ) \ + (fc::logger::get(DEFAULT_LOGGER)).log( FC_LOG_MESSAGE( info, FORMAT, __VA_ARGS__ ) ); \ + FC_MULTILINE_MACRO_END + +#define wlog( FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( (fc::logger::get(DEFAULT_LOGGER)).is_enabled( fc::log_level::warn ) ) \ + (fc::logger::get(DEFAULT_LOGGER)).log( FC_LOG_MESSAGE( warn, FORMAT, __VA_ARGS__ ) ); \ + FC_MULTILINE_MACRO_END + +#define elog( FORMAT, ... ) \ + FC_MULTILINE_MACRO_BEGIN \ + if( (fc::logger::get(DEFAULT_LOGGER)).is_enabled( fc::log_level::error ) ) \ + (fc::logger::get(DEFAULT_LOGGER)).log( FC_LOG_MESSAGE( error, FORMAT, __VA_ARGS__ ) ); \ + FC_MULTILINE_MACRO_END + +#include +#include +#include +#include +#include +#include + + +#define FC_FORMAT_ARG(r, unused, base) \ + BOOST_PP_STRINGIZE(base) ": ${" BOOST_PP_STRINGIZE( base ) "} " + +#define FC_FORMAT_ARGS(r, unused, base) \ + BOOST_PP_LPAREN() BOOST_PP_STRINGIZE(base),fc::variant(base) BOOST_PP_RPAREN() + +#define FC_FORMAT( SEQ )\ + BOOST_PP_SEQ_FOR_EACH( FC_FORMAT_ARG, v, SEQ ) + +// takes a ... instead of a SEQ arg because it can be called with an empty SEQ +// from FC_CAPTURE_AND_THROW() +#define FC_FORMAT_ARG_PARAMS( ... )\ + BOOST_PP_SEQ_FOR_EACH( FC_FORMAT_ARGS, v, __VA_ARGS__ ) + +#define idump( SEQ ) \ + ilog( FC_FORMAT(SEQ), FC_FORMAT_ARG_PARAMS(SEQ) ) +#define wdump( SEQ ) \ + wlog( FC_FORMAT(SEQ), FC_FORMAT_ARG_PARAMS(SEQ) ) +#define edump( SEQ ) \ + elog( FC_FORMAT(SEQ), FC_FORMAT_ARG_PARAMS(SEQ) ) + +// this disables all normal logging statements -- not something you'd normally want to do, +// but it's useful if you're benchmarking something and suspect logging is causing +// a slowdown. +#ifdef FC_DISABLE_LOGGING +# undef ulog +# define ulog(...) FC_MULTILINE_MACRO_BEGIN FC_MULTILINE_MACRO_END +# undef elog +# define elog(...) FC_MULTILINE_MACRO_BEGIN FC_MULTILINE_MACRO_END +# undef wlog +# define wlog(...) FC_MULTILINE_MACRO_BEGIN FC_MULTILINE_MACRO_END +# undef ilog +# define ilog(...) FC_MULTILINE_MACRO_BEGIN FC_MULTILINE_MACRO_END +# undef dlog +# define dlog(...) FC_MULTILINE_MACRO_BEGIN FC_MULTILINE_MACRO_END +#endif \ No newline at end of file diff --git a/libraries/libfc/include/fc/log/logger_config.hpp b/libraries/libfc/include/fc/log/logger_config.hpp new file mode 100644 index 0000000000..04de647d50 --- /dev/null +++ b/libraries/libfc/include/fc/log/logger_config.hpp @@ -0,0 +1,83 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace fc { + class path; + struct appender_config { + appender_config(const string& name = "", + const string& type = "", + variant args = variant()) : + name(name), + type(type), + args(fc::move(args)), + enabled(true) + {} + string name; + string type; + variant args; + bool enabled; + }; + + struct logger_config { + logger_config(const fc::string& name = ""):name(name),enabled(true),additivity(false){} + string name; + ostring parent; + /// if not set, then parents level is used. + std::optional level; + bool enabled; + /// if any appenders are sepecified, then parent's appenders are not set. + bool additivity; + std::vector appenders; + }; + + struct logging_config { + static logging_config default_config(); + std::vector includes; + std::vector appenders; + std::vector loggers; + }; + + struct log_config { + + template + static bool register_appender(const fc::string& type) { + return register_appender( type, std::make_shared>() ); + } + + static bool register_appender( const fc::string& type, const appender_factory::ptr& f ); + + static logger get_logger( const fc::string& name ); + static void update_logger( const fc::string& name, logger& log ); + + static void initialize_appenders( boost::asio::io_service& ios ); + + static bool configure_logging( const logging_config& l ); + + private: + static log_config& get(); + + friend class logger; + + std::mutex log_mutex; + std::unordered_map appender_factory_map; + std::unordered_map appender_map; + std::unordered_map logger_map; + }; + + void configure_logging( const fc::path& log_config ); + bool configure_logging( const logging_config& l ); + + void set_os_thread_name( const string& name ); + void set_thread_name( const string& name ); + const string& get_thread_name(); +} + +#include +FC_REFLECT( fc::appender_config, (name)(type)(args)(enabled) ) +FC_REFLECT( fc::logger_config, (name)(parent)(level)(enabled)(additivity)(appenders) ) +FC_REFLECT( fc::logging_config, (includes)(appenders)(loggers) ) diff --git a/libraries/libfc/include/fc/log/trace.hpp b/libraries/libfc/include/fc/log/trace.hpp new file mode 100644 index 0000000000..6a0d3de14c --- /dev/null +++ b/libraries/libfc/include/fc/log/trace.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +/// @param TRACE_STR const char* identifier for trace +/// @return implementation defined type RAII object that submits trace on exit of scope +#define fc_create_trace( TRACE_STR ) \ + ::fc::zipkin_config::is_enabled() ? \ + ::fc::optional_trace{ ::std::optional<::fc::zipkin_trace>( ::std::in_place, (TRACE_STR) ) } \ + : ::fc::optional_trace{}; + +/// @param TRACE_STR const char* identifier for trace +/// @param TRACE_ID fc::sha256 id to use +/// @return implementation defined type RAII object that submits trace on exit of scope +#define fc_create_trace_with_id( TRACE_STR, TRACE_ID ) \ + ::fc::zipkin_config::is_enabled() ? \ + ::fc::optional_trace{ ::std::optional<::fc::zipkin_trace>( ::std::in_place, ::fc::zipkin_span::to_id(TRACE_ID), (TRACE_STR) ) } \ + : ::fc::optional_trace{}; + +/// @param TRACE_VARNAME variable returned from fc_create_trace +/// @param SPAN_STR const char* indentifier +/// @return implementation defined type RAII object that submits span on exit of scope +#define fc_create_span( TRACE_VARNAME, SPAN_STR ) \ + ( (TRACE_VARNAME) && ::fc::zipkin_config::is_enabled() ) ? \ + (TRACE_VARNAME).opt->create_span( (SPAN_STR) ) \ + : ::std::optional<::fc::zipkin_span>{}; + +/// @param TRACE_TOKEN variable returned from trace.get_token() +/// @param SPAN_STR const char* indentifier +/// @return implementation defined type RAII object that submits span on exit of scope +#define fc_create_span_from_token( TRACE_TOKEN, SPAN_STR ) \ + ( (TRACE_TOKEN) && ::fc::zipkin_config::is_enabled() ) ? \ + ::fc::zipkin_trace::create_span_from_token( (TRACE_TOKEN), (SPAN_STR) ) \ + : ::std::optional<::fc::zipkin_span>{}; + +/// @param SPAN_VARNAME variable returned from fc_create_span +/// @param TAG_KEY_STR string key +/// @param TAG_VALUE string value +#define fc_add_tag( SPAN_VARNAME, TAG_KEY_STR, TAG_VALUE_STR) \ + FC_MULTILINE_MACRO_BEGIN \ + if( (SPAN_VARNAME) && ::fc::zipkin_config::is_enabled() ) \ + (SPAN_VARNAME)->add_tag( (TAG_KEY_STR), (TAG_VALUE_STR) ); \ + FC_MULTILINE_MACRO_END diff --git a/libraries/libfc/include/fc/log/zipkin.hpp b/libraries/libfc/include/fc/log/zipkin.hpp new file mode 100644 index 0000000000..32010a1140 --- /dev/null +++ b/libraries/libfc/include/fc/log/zipkin.hpp @@ -0,0 +1,197 @@ +#pragma once + +#include +#include +#include + +namespace fc { +/// Active Object that sends zipkin messages in JSON format +/// https://zipkin.io/zipkin-api/ +/// +/// Span contains following data +/// uint64_t traceId - unique id for trace, all children spans shared same id +/// std::string name - logical operation, should have low cardinality +/// uint64_t parentId - The parent span id, or absent if root span +/// uint64_t id - unique id for this span +/// int64_t timestamp - epoch microseconds of start of span +/// int64_t duration - microseconds of span +/// +/// Enable zipkin by calling zipkin_config::init() from main thread on startup. +/// Use macros defined in trace.hpp. + +class zipkin; + +class sha256; + +class zipkin_config { +public: + /// Thread safe only if init() called from main thread before spawning of any threads + static bool is_enabled() { return get_zipkin_() != nullptr; } + + /// Not thread safe, call from main thread before spawning any threads that might use zipkin. + /// @param url the url endpoint of zipkin server. e.g. http://127.0.0.1:9411/api/v2/spans + /// @param service_name the service name to include in each zipkin span + /// @param timeout_us the timeout in microseconds for each http call (9 consecutive failures and zipkin is disabled) + static void init( const std::string& url, const std::string& service_name, uint32_t timeout_us ); + + /// Thread safe only if init() called from main thread before spawning of any threads + /// @throw assert_exception if called before init() + static zipkin& get_zipkin(); + + /// Thread safe only if init() called from main thread before spawning of any threads + /// @throw assert_exception if called before init() + static void shutdown(); + + /// Starts with a random id and increments on each call, will not return 0 + static uint64_t get_next_unique_id(); + +private: + /// Provide access to initialized zipkin endpoint + /// Thread safe as long as init() called correctly + /// @return nullptr if init() not called + static zipkin* get_zipkin_() { return get().zip.get(); }; + + static zipkin_config& get(); + +private: + std::unique_ptr zip; +}; + +struct zipkin_span { + explicit zipkin_span( std::string name, uint64_t parent_id = 0 ) + : data( std::move( name ), parent_id ) {} + + explicit zipkin_span( uint64_t id, std::string name, uint64_t parent_id = 0 ) + : data( id, std::move( name ), parent_id ) {} + + zipkin_span( const zipkin_span& ) = delete; + zipkin_span& operator=( const zipkin_span& ) = delete; + zipkin_span& operator=( zipkin_span&& ) = delete; + + zipkin_span( zipkin_span&& rhs ) noexcept + : data( std::move( rhs.data ) ) { + rhs.data.id = 0; + } + + ~zipkin_span(); + + void add_tag( const std::string& key, const std::string& var ) { + // zipkin tags are required to be strings + data.tags( key, var ); + } + + void add_tag( const std::string& key, const char* var ) { + // zipkin tags are required to be strings + data.tags( key, var ); + } + + void add_tag( const std::string& key, bool v ) { + // zipkin tags are required to be strings + data.tags( key, v ? "true" : "false" ); + } + + template + std::enable_if_t>, void> + add_tag( const std::string& key, T&& var ) { + data.tags( key, std::to_string( std::forward( var ) ) ); + } + + template + std::enable_if_t>, void> + add_tag( const std::string& key, T&& var ) { + data.tags( key, (std::string) var ); + } + + struct token { + friend zipkin_span; + friend struct zipkin_trace; + friend struct optional_trace; + constexpr explicit operator bool() const noexcept { return id != 0; } + private: + explicit token( uint64_t id ) + : id( id ) {} + uint64_t id; + }; + + token get_token() const { return token{data.id}; }; + + static uint64_t to_id( const fc::sha256& id ); + + template + static uint64_t to_id( const T& id ) { + static_assert( std::is_same_v, "expected uint64_t" ); + return id.data()[3]; + } + + struct span_data { + explicit span_data( std::string name, uint64_t parent_id = 0 ) + : id( zipkin_config::get_next_unique_id() ), parent_id( parent_id ), + start( time_point::now() ), name( std::move( name ) ) {} + + explicit span_data( uint64_t id, std::string name, uint64_t parent_id = 0 ) + : id( id ), parent_id( parent_id ), start( time_point::now() ), name( std::move( name ) ) {} + + span_data( const span_data& ) = delete; + span_data& operator=( const span_data& ) = delete; + span_data& operator=( span_data&& ) = delete; + span_data( span_data&& rhs ) = default; + + uint64_t id; + // zipkin traceId and parentId are same (when parent_id set) since only allowing trace with span children. + // Not currently supporting spans with children, only trace with children spans. + const uint64_t parent_id; + const fc::time_point start; + fc::time_point stop; + std::string name; + fc::mutable_variant_object tags; + }; + + span_data data; +}; + +struct zipkin_trace : public zipkin_span { + using zipkin_span::zipkin_span; + + [[nodiscard]] std::optional create_span( std::string name ) const { + return zipkin_span{std::move( name ), get_token().id}; + } + + [[nodiscard]] static std::optional + create_span_from_token( zipkin_span::token token, std::string name ) { + return zipkin_span{std::move( name ), token.id}; + } +}; + +struct optional_trace { + std::optional opt; + + constexpr explicit operator bool() const noexcept { return opt.has_value(); } + + [[nodiscard]] zipkin_span::token get_token() const { + return opt ? opt->get_token() : zipkin_span::token( 0 ); + } +}; + +class zipkin { +public: + zipkin( const std::string& url, const std::string& service_name, uint32_t timeout_us ); + + /// finishes logging all queued up spans + ~zipkin() = default; + + /// Starts with a random id and increments on each call, will not return 0 + uint64_t get_next_unique_id(); + + // finish logging all queued up spans, not restartable + void shutdown(); + + // Logs zipkin json via http on separate thread + void log( zipkin_span::span_data&& span ); + +private: + class impl; + std::unique_ptr my; +}; + +} // namespace fc + diff --git a/libraries/libfc/include/fc/make_fused.hpp b/libraries/libfc/include/fc/make_fused.hpp new file mode 100644 index 0000000000..acbf901076 --- /dev/null +++ b/libraries/libfc/include/fc/make_fused.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include + +namespace fc { + template + std::function > make_fused( const std::function& f ) { + return [=]( fc::tuple<> ){ return f(); }; + } + template + std::function) > make_fused( const std::function& f ) { + return [f]( fc::tuple t){ return f(t.a); }; + } + template + std::function) > make_fused( const std::function& f ) { + return [f]( fc::tuple t){ return f(t.a,t.b); }; + } + template + std::function) > make_fused( const std::function& f ) { + return [f]( fc::tuple t){ return f(t.a,t.b,t.c); }; + } + template + std::function) > make_fused( const std::function& f ) { + return [f]( fc::tuple t){ return f(t.a,t.b,t.c,t.d); }; + } +} diff --git a/libraries/libfc/include/fc/mock_time.hpp b/libraries/libfc/include/fc/mock_time.hpp new file mode 100644 index 0000000000..164d116a93 --- /dev/null +++ b/libraries/libfc/include/fc/mock_time.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +namespace fc { + +/// mock out fc::time_point::now() and provide a mock deadline timer +class mock_time_traits { + typedef boost::asio::deadline_timer::traits_type source_traits; +public: + typedef source_traits::time_type time_type; + typedef source_traits::duration_type duration_type; + + // Requires set_now() to be called first on main thread before any calls to fc::time_point::now() + static time_type now() noexcept; + + // First call should be on one thread before any calls to fc::time_point::now() + static void set_now( time_type t ); + static void set_now( const fc::time_point& now ); + + // Thread safe only if first call to set_now is before any threads are spawned or memory barrier introduced + static bool is_set() { return mock_enabled_; } + + static time_type add( time_type t, duration_type d ) { return source_traits::add( t, d ); } + static duration_type subtract( time_type t1, time_type t2 ) { return source_traits::subtract( t1, t2 ); } + static bool less_than( time_type t1, time_type t2 ) { return source_traits::less_than( t1, t2 ); } + + // This function is called by asio to determine how often to check + // if the timer is ready to fire. By manipulating this function, we + // can make sure asio detects changes to now_ in a timely fashion. + static boost::posix_time::time_duration to_posix_duration( duration_type d ) { + return d < boost::posix_time::milliseconds( 1 ) ? d : boost::posix_time::milliseconds( 1 ); + } + + // return now as fc::time_point, used by fc::time_point::now() if mock_time_traits is_set() + static fc::time_point fc_now(); + +private: + static bool mock_enabled_; + static const boost::posix_time::ptime epoch_; + static std::atomic now_; +}; + +typedef boost::asio::basic_deadline_timer mock_deadline_timer; + +} // namespace fc diff --git a/libraries/libfc/include/fc/network/http/http_client.hpp b/libraries/libfc/include/fc/network/http/http_client.hpp new file mode 100644 index 0000000000..47a38f434e --- /dev/null +++ b/libraries/libfc/include/fc/network/http/http_client.hpp @@ -0,0 +1,36 @@ +/** + * @file + * @copyright defined in LICENSE.txt + */ +#pragma once + +#include +#include +#include +#include +#include + +namespace fc { + +class http_client { + public: + http_client(); + ~http_client(); + + variant post_sync(const url& dest, const variant& payload, const time_point& deadline = time_point::maximum()); + + template + variant post_sync(const url& dest, const T& payload, const time_point& deadline = time_point::maximum()) { + variant payload_v; + to_variant(payload, payload_v); + return post_sync(dest, payload_v, deadline); + } + + void add_cert(const std::string& cert_pem_string); + void set_verify_peers(bool enabled); + +private: + std::unique_ptr _my; +}; + +} \ No newline at end of file diff --git a/libraries/libfc/include/fc/network/ip.hpp b/libraries/libfc/include/fc/network/ip.hpp new file mode 100644 index 0000000000..5a1bd0c981 --- /dev/null +++ b/libraries/libfc/include/fc/network/ip.hpp @@ -0,0 +1,128 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace fc { + + namespace ip { + class address { + public: + address( uint32_t _ip = 0 ); + address( const fc::string& s ); + + address& operator=( const fc::string& s ); + operator fc::string()const; + operator uint32_t()const; + + friend bool operator==( const address& a, const address& b ); + friend bool operator!=( const address& a, const address& b ); + + /** + * @return true if the ip is in the following ranges: + * + * 10.0.0.0 to 10.255.255.255 + * 172.16.0.0 to 172.31.255.255 + * 192.168.0.0 to 192.168.255.255 + * 169.254.0.0 to 169.254.255.255 + * + */ + bool is_private_address()const; + /** + * 224.0.0.0 to 239.255.255.255 + */ + bool is_multicast_address()const; + + /** !private & !multicast */ + bool is_public_address()const; + private: + uint32_t _ip; + }; + + class endpoint { + public: + endpoint(); + endpoint( const address& i, uint16_t p = 0); + + /** Converts "IP:PORT" to an endpoint */ + static endpoint from_string( const string& s ); + /** returns "IP:PORT" */ + operator string()const; + + void set_port(uint16_t p ) { _port = p; } + uint16_t port()const; + const address& get_address()const; + + friend bool operator==( const endpoint& a, const endpoint& b ); + friend bool operator!=( const endpoint& a, const endpoint& b ); + friend bool operator< ( const endpoint& a, const endpoint& b ); + + private: + /** + * The compiler pads endpoint to a full 8 bytes, so while + * a port number is limited in range to 16 bits, we specify + * a full 32 bits so that memcmp can be used with sizeof(), + * otherwise 2 bytes will be 'random' and you do not know + * where they are stored. + */ + uint32_t _port; + address _ip; + }; + + } + class variant; + void to_variant( const ip::endpoint& var, variant& vo ); + void from_variant( const variant& var, ip::endpoint& vo ); + + void to_variant( const ip::address& var, variant& vo ); + void from_variant( const variant& var, ip::address& vo ); + + + namespace raw + { + template + inline void pack( Stream& s, const ip::address& v ) + { + fc::raw::pack( s, uint32_t(v) ); + } + template + inline void unpack( Stream& s, ip::address& v ) + { + uint32_t _ip; + fc::raw::unpack( s, _ip ); + v = ip::address(_ip); + } + + template + inline void pack( Stream& s, const ip::endpoint& v ) + { + fc::raw::pack( s, v.get_address() ); + fc::raw::pack( s, v.port() ); + } + template + inline void unpack( Stream& s, ip::endpoint& v ) + { + ip::address a; + uint16_t p; + fc::raw::unpack( s, a ); + fc::raw::unpack( s, p ); + v = ip::endpoint(a,p); + } + + } +} // namespace fc +FC_REFLECT_TYPENAME( fc::ip::address ) +FC_REFLECT_TYPENAME( fc::ip::endpoint ) +namespace std +{ + template<> + struct hash + { + size_t operator()( const fc::ip::endpoint& e )const + { + return fc::city_hash_size_t( (char*)&e, sizeof(e) ); + } + }; +} diff --git a/libraries/libfc/include/fc/network/message_buffer.hpp b/libraries/libfc/include/fc/network/message_buffer.hpp new file mode 100644 index 0000000000..f8e7f83307 --- /dev/null +++ b/libraries/libfc/include/fc/network/message_buffer.hpp @@ -0,0 +1,352 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace fc { + template + class mb_datastream; + template + class mb_peek_datastream; + + /** + * @brief abstraction for a message buffer that spans a chain of physical buffers + * + * This message buffer abstraction will allocate individual character arrays + * of size buffer_len from a boost::singleton_pool. It supports creation of a + * vector of boost::mutable_buffer for use with async_read() and async_read_some(). + * It also supports use with the fc unpack() functionality via a datastream + * helper class. + */ + template + class message_buffer { + public: + /* + * index abstraction that references a point in the chain of buffers. + * first refers to the buffer's index in the deque. + * second refers to the byte in the character buffer. + */ + typedef std::pair index_t; + + message_buffer() : buffers{malloc()}, read_ind{0,0}, write_ind{0,0}, sanity_check (1) { } + + ~message_buffer() { + while (buffers.size() > 0) { + free(buffers.back()); + buffers.pop_back(); + } + } + + /* + * Returns the current read index referencing the byte in the buffer + * that is next to be read. + */ + index_t read_index() const { return read_ind; } + + /* + * Returns the current write index referencing the byte in the buffer + * that is next to be written to. + */ + index_t write_index() const { return write_ind; } + + /* + * Returns the current read pointer pointing to the memory location + * of the next byte to be read. + */ + char* read_ptr() { + return &buffers[read_ind.first]->at(read_ind.second); + } + + /* + * Returns the current write pointer pointing to the memory location + * of the next byte to be written to. + */ + char* write_ptr() { + return &buffers[write_ind.first]->at(write_ind.second); + } + + /* + * Adds an additional buffer of buffer_len to the chain of buffers. + * Does not affect the read or write pointer. + */ + void add_buffer_to_chain() { + sanity_check++; + buffers.push_back(malloc()); + } + + /* + * Adds additional buffers of length buffer_len to the chain of buffers + * in order to add at least bytes to the space available. + * Does not affect the read or write pointer. + */ + void add_space(uint32_t bytes) { + int buffers_to_add = bytes / buffer_len + 1; + for (int i = 0; i < buffers_to_add; i++) { + sanity_check++; + buffers.push_back(malloc()); + } + } + + /* + * Resets the message buffer to the initial state. Any unread data is + * discarded. + */ + void reset() { + // some condition exists that can send *both* buffers.size() and sanity_check to well over 10^6. + // this seems to be related to some sort of memory overrun possibly. By forcing an exit here, an + // external watchdog can be used to restart the process and avoid hanging. + if( buffers.size() != sanity_check || buffers.size() > 1000000) { + elog( "read_ind = ${r1}, ${r2} write_ind = ${w1}, ${w2}, buff.size = ${bs}, sanity = ${s}", + ( "r1", read_ind.first )( "r2", read_ind.second )( "w1", write_ind.first )( "w2", write_ind.second ) + ( "bs", buffers.size() )( "s", sanity_check ) ); + elog("Buffer manager overwrite detected. Terminating to allow external restart"); + exit(1); + } + while (buffers.size() > 1) { + sanity_check--; + free(buffers.back()); + buffers.pop_back(); + } + + read_ind = { 0, 0 }; + write_ind = { 0, 0 }; + } + + /* + * Returns the current number of bytes remaining to be read. + * Logically, this is the different between where the read and write pointers are. + */ + uint32_t bytes_to_read() const { + return bytes_to_read_from_index(read_ind); + } + + /* + * Returns the current number of bytes remaining to be read from a given index + * Logically, this is the different between where the given index is and the write pointers. + */ + uint32_t bytes_to_read_from_index(const index_t& ind) const { + return (write_ind.first - ind.first) * buffer_len + write_ind.second - ind.second; + } + + /* + * Returns the current number of bytes available to be written. + * Logically, this is the different between the write pointer and the + * end of the buffer. If this is not enough room, call either + * add_buffer_to_chain() or add_room() to create more space. + */ + uint32_t bytes_to_write() const { + return total_bytes() - write_ind.first * buffer_len - write_ind.second; + } + + /* + * Returns the total number of bytes in the buffer chain. + */ + uint32_t total_bytes() const { + return buffer_len * buffers.size(); + } + + /* + * Advances the read pointer/index the supplied number of bytes along + * the buffer chain. Any buffers that the read pointer moves beyond + * will be removed from the buffer chain. If the read pointer becomes + * equal to the write pointer, the message buffer will be reset to + * its initial state (one buffer with read and write pointers at the + * start). + */ + void advance_read_ptr(uint32_t bytes) { + advance_index(read_ind, bytes); + if (read_ind == write_ind) { + reset(); + } else if (read_ind.first > 0) { + while (read_ind.first > 0) { + free(buffers.front()); + buffers.pop_front(); + sanity_check--; + read_ind.first--; + write_ind.first--; + } + } + } + + /* + * Advances the write pointer/index the supplied number of bytes along + * the buffer chain. + */ + void advance_write_ptr(uint32_t bytes) { + advance_index(write_ind, bytes); + while (write_ind.first >= buffers.size()) { + sanity_check++; + buffers.push_back(malloc()); + } + } + + /* + * Creates and returns a vector of boost mutable_buffers that can + * be passed to boost async_read() and async_read_some() functions. + * The beginning of the vector will be the write pointer, which + * should be advanced the number of bytes read after the read returns. + */ + std::vector get_buffer_sequence_for_boost_async_read() { + std::vector seq; + FC_ASSERT(write_ind.first < buffers.size()); + seq.push_back(boost::asio::buffer(&buffers[write_ind.first]->at(write_ind.second), + buffer_len - write_ind.second)); + for (std::size_t i = write_ind.first + 1; i < buffers.size(); i++) { + seq.push_back(boost::asio::buffer(&buffers[i]->at(0), buffer_len)); + } + return seq; + } + + /* + * Reads size bytes from the buffer chain starting at the read pointer. + * The read pointer is advanced size bytes. + */ + bool read(void* s, uint32_t size) { + if (bytes_to_read() < size) { + FC_THROW_EXCEPTION( out_of_range_exception, "tried to read ${r} but only ${s} left", + ("r", size)( "s", bytes_to_read() ) ); + } + if (read_ind.second + size <= buffer_len) { + memcpy(s, read_ptr(), size); + advance_read_ptr(size); + } else { + uint32_t num_in_buffer = buffer_len - read_ind.second; + memcpy(s, read_ptr(), num_in_buffer); + advance_read_ptr(num_in_buffer); + read((char*)s + num_in_buffer, size - num_in_buffer); + } + return true; + } + + /* + * Reads size bytes from the buffer chain starting at the supplied index. + * The supplied index is advanced, but the read pointer is unaffected. + */ + bool peek(void* s, uint32_t size, index_t& index) const { + if (bytes_to_read_from_index(index) < size) { + FC_THROW_EXCEPTION( out_of_range_exception, "tried to peek ${r} but only ${s} left", + ("r", size)( "s", bytes_to_read_from_index( index ) ) ); + } + if (index.second + size <= buffer_len) { + memcpy(s, get_ptr(index), size); + advance_index(index, size); + } else { + uint32_t num_in_buffer = buffer_len - index.second; + memcpy(s, get_ptr(index), num_in_buffer); + advance_index(index, num_in_buffer); + peek((char*)s + num_in_buffer, size - num_in_buffer, index); + } + return true; + } + + /* + * Advances the supplied index along the buffer chain the specified + * number of bytes. + */ + static void advance_index(index_t& index, uint32_t bytes) { + index.first += (bytes + index.second) / buffer_len; + index.second = (bytes + index.second) % buffer_len; + } + + /* + * Creates an mb_datastream object that can be used with the + * fc library's unpack functionality. + */ + mb_datastream create_datastream(); + + /* + * Creates an mb_peek_datastream object that can be used with the + * fc library's unpack functionality. + */ + mb_peek_datastream create_peek_datastream(); + + private: + using buffer_type = std::array; + using pool_type = boost::singleton_pool; + static buffer_type* malloc() { return static_cast(pool_type::malloc()); } + static void free(buffer_type* ptr) { pool_type::free(ptr); } + + /* + * Returns the character pointer associated with the supplied index. + */ + char* get_ptr(const index_t& index) const { + return &buffers[index.first]->at(index.second); + } + + std::deque* > buffers; + index_t read_ind; + index_t write_ind; + size_t sanity_check; + + }; + + /* + * @brief datastream adapter that adapts message_buffer for use with fc unpack + * + * This class supports unpack functionality but not pack. + */ + template + class mb_datastream { + public: + explicit mb_datastream( message_buffer& m ) : mb(m) {} + + inline void skip( size_t s ) { mb.advance_read_ptr(s); } + inline bool read( char* d, size_t s ) { + if (mb.bytes_to_read() >= s) { + mb.read(d, s); + return true; + } + fc::detail::throw_datastream_range_error( "read", mb.bytes_to_read(), s - mb.bytes_to_read()); + } + + inline bool get( unsigned char& c ) { return mb.read(&c, 1); } + inline bool get( char& c ) { return mb.read(&c, 1); } + + private: + message_buffer& mb; + }; + + template + inline mb_datastream message_buffer::create_datastream() { + return mb_datastream(*this); + } + + /* + * @brief peek datastream adapter that adapts message_buffer for use with fc unpack + * + * This class supports unpack functionality but not pack. + */ + template + class mb_peek_datastream { + public: + explicit mb_peek_datastream( const message_buffer& m ) + : mb( m ), index( m.read_index() ) {} + + inline void skip( size_t s ) { message_buffer::advance_index( index, s ); } + + inline bool read( char* d, size_t s ) { + if( mb.bytes_to_read_from_index(index) >= s ) { + mb.peek( d, s, index ); + return true; + } + fc::detail::throw_datastream_range_error( "peek", + mb.bytes_to_read_from_index(index), s - mb.bytes_to_read_from_index(index) ); + } + + inline bool get( unsigned char& c ) { return mb.peek( &c, 1, index ); } + inline bool get( char& c ) { return mb.peek( &c, 1, index ); } + + private: + const message_buffer& mb; + typename message_buffer::index_t index{0,0}; + }; + + template + inline mb_peek_datastream message_buffer::create_peek_datastream() { + return mb_peek_datastream( *this ); + } + +} // namespace fc diff --git a/libraries/libfc/include/fc/network/platform_root_ca.hpp b/libraries/libfc/include/fc/network/platform_root_ca.hpp new file mode 100644 index 0000000000..3679102d7d --- /dev/null +++ b/libraries/libfc/include/fc/network/platform_root_ca.hpp @@ -0,0 +1,13 @@ +/** + * @file + * @copyright defined in LICENSE.txt + */ +#pragma once + +#include + +namespace fc { + +//Add the platform's trusted root CAs to the ssl context +void add_platform_root_cas_to_context(boost::asio::ssl::context& ctx); +} \ No newline at end of file diff --git a/libraries/libfc/include/fc/network/resolve.hpp b/libraries/libfc/include/fc/network/resolve.hpp new file mode 100644 index 0000000000..8f99cefc89 --- /dev/null +++ b/libraries/libfc/include/fc/network/resolve.hpp @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +namespace fc +{ + std::vector resolve(boost::asio::io_service& io_service, + const std::string& host, uint16_t port); +} diff --git a/libraries/libfc/include/fc/network/udp_socket.hpp b/libraries/libfc/include/fc/network/udp_socket.hpp new file mode 100644 index 0000000000..43786a5f8f --- /dev/null +++ b/libraries/libfc/include/fc/network/udp_socket.hpp @@ -0,0 +1,39 @@ +#pragma once +#include +#include + +#include + +namespace fc { + namespace ip { + class endpoint; + class address; + } + + /** + * The udp_socket class has reference semantics, all copies will + * refer to the same underlying socket. + */ + class udp_socket { + public: + udp_socket(); + udp_socket( const udp_socket& s ); + ~udp_socket(); + + void initialize(boost::asio::io_service &); + void open(); + void send_to(const char* b, size_t l, boost::asio::ip::udp::endpoint &to); + void send_to(const std::shared_ptr& b, size_t l, boost::asio::ip::udp::endpoint &to); + void close(); + + void set_reuse_address(bool); + + void connect(const boost::asio::ip::udp::endpoint& e); + const boost::asio::ip::udp::endpoint local_endpoint() const; + + private: + class impl; + std::shared_ptr my; + }; + +} diff --git a/libraries/libfc/include/fc/network/url.hpp b/libraries/libfc/include/fc/network/url.hpp new file mode 100644 index 0000000000..c9cd47ce8c --- /dev/null +++ b/libraries/libfc/include/fc/network/url.hpp @@ -0,0 +1,62 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace fc { + + typedef std::optional ostring; + typedef std::optional opath; + typedef std::optional ovariant_object; + + namespace detail { class url_impl; } + + class mutable_url; + + /** + * Used to pass an immutable URL and + * query its parts. + */ + class url + { + public: + url(); + explicit url( const string& u ); + url( const url& c ); + url( url&& c ); + url( const string& proto, const ostring& host, const ostring& user, const ostring& pass, + const opath& path, const ostring& query, const ovariant_object& args, const std::optional& port); + ~url(); + + url& operator=( const url& c ); + url& operator=( url&& c ); + + url& operator=( const mutable_url& c ); + url& operator=( mutable_url&& c ); + + bool operator==( const url& cmp )const; + + operator string()const; + + //// file, ssh, tcp, http, ssl, etc... + string proto()const; + ostring host()const; + ostring user()const; + ostring pass()const; + opath path()const; + ostring query()const; + ovariant_object args()const; + std::optional port()const; + + private: + friend class mutable_url; + std::shared_ptr my; + }; + + void to_variant( const url& u, fc::variant& v ); + void from_variant( const fc::variant& v, url& u ); + +} // namespace fc + diff --git a/libraries/libfc/include/fc/noncopyable.hpp b/libraries/libfc/include/fc/noncopyable.hpp new file mode 100644 index 0000000000..87fad6b034 --- /dev/null +++ b/libraries/libfc/include/fc/noncopyable.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace fc +{ + class noncopyable + { + public: + noncopyable(){} + private: + noncopyable( const noncopyable& ) = delete; + noncopyable& operator=( const noncopyable& ) = delete; + }; +} + diff --git a/libraries/libfc/include/fc/platform_independence.hpp b/libraries/libfc/include/fc/platform_independence.hpp new file mode 100644 index 0000000000..a3963e40c6 --- /dev/null +++ b/libraries/libfc/include/fc/platform_independence.hpp @@ -0,0 +1,15 @@ +#ifdef _MSC_VER +#include + #ifdef _M_X64 + #define __builtin_popcountll __popcnt64 + #else + inline int __builtin_popcountll(unsigned __int64 value) + { + unsigned int lowBits = (unsigned int)value; + int count = __popcnt(lowBits); + unsigned int highBits = (unsigned int)(value >> 32); + count += __popcnt(highBits); + return count; + } + #endif +#endif \ No newline at end of file diff --git a/libraries/libfc/include/fc/real128.hpp b/libraries/libfc/include/fc/real128.hpp new file mode 100644 index 0000000000..3a7d26dbef --- /dev/null +++ b/libraries/libfc/include/fc/real128.hpp @@ -0,0 +1,52 @@ +#pragma once +#include + +#define FC_REAL128_PRECISION (uint64_t(1000000) * uint64_t(1000000) * uint64_t(1000000)) + +namespace fc { + class variant; + + /** + * Provides fixed point math operations based on decimal fractions + * with 18 places. + * Delegates to fc::bigint for multiplication and division. + */ + class real128 + { + public: + real128( uint64_t integer = 0); + explicit real128( const std::string& str ); + operator std::string()const; + + friend real128 operator * ( real128 a, const real128& b ) { a *= b; return a; } + friend real128 operator / ( real128 a, const real128& b ) { a /= b; return a; } + friend real128 operator + ( real128 a, const real128& b ) { a += b; return a; } + friend real128 operator - ( real128 a, const real128& b ) { a -= b; return a; } + + real128& operator += ( const real128& o ); + real128& operator -= ( const real128& o ); + real128& operator /= ( const real128& o ); + real128& operator *= ( const real128& o ); + + static real128 from_fixed( const uint128& fixed ); + + uint64_t to_uint64()const; + + private: + uint128 fixed; + }; + + void to_variant( const real128& var, variant& vo ); + void from_variant( const variant& var, real128& vo ); + + namespace raw + { + template + inline void pack( Stream& s, const real128& value_to_pack ) { s.write( (char*)&value_to_pack, sizeof(value_to_pack) ); } + template + inline void unpack( Stream& s, real128& value_to_unpack ) { s.read( (char*)&value_to_unpack, sizeof(value_to_unpack) ); } + } + + + +} // namespace fc diff --git a/libraries/libfc/include/fc/reflect/reflect.hpp b/libraries/libfc/include/fc/reflect/reflect.hpp new file mode 100644 index 0000000000..2f5ea7b6a4 --- /dev/null +++ b/libraries/libfc/include/fc/reflect/reflect.hpp @@ -0,0 +1,323 @@ +#pragma once +/** + * @file fc/reflect.hpp + * + * @brief Defines types and macros used to provide reflection. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace fc { + +/** + * @brief defines visit functions for T + * Unless this is specialized, visit() will not be defined for T. + * + * @tparam T - the type that will be visited. + * + * The @ref FC_REFLECT(TYPE,MEMBERS) or FC_STATIC_REFLECT_DERIVED(TYPE,BASES,MEMBERS) macro is used to specialize this + * class for your type. + */ +template +struct reflector{ + typedef T type; + typedef fc::false_type is_defined; + typedef fc::false_type is_enum; + + /** + * @tparam Visitor a function object of the form: + * + * @code + * struct functor { + * template + * void operator()( const char* name )const; + * }; + * @endcode + * + * If reflection requires an init (what a constructor might normally do) then + * derive your Visitor publicly from fc::reflector_init_visitor, derive your reflected + * type from fc::reflect_init and implement a reflector_init() method + * on your reflected type. reflector_init() needs to be public or you can friend: + * friend struct fc::reflector_init_visitor; + * friend struct fc::has_reflector_init; + * Note that if your attributes are also protected/private then you also need: + * friend struct fc::reflector; + * + * @code + * template + * struct functor : public fc::reflector_init_visitor { + * functor(Class& _c) + * : fc::reflector_init_visitor(_c) {} + * + * template + * void operator()( const char* name )const; + * }; + * @endcode + * + * If T is an enum then the functor has the following form: + * @code + * struct functor { + * template + * void operator()( const char* name )const; + * }; + * @endcode + * + * @param v a functor that will be called for each member on T + * + * @note - this method is not defined for non-reflected types. + */ + #ifdef DOXYGEN + template + static inline void visit( const Visitor& v ); + #endif // DOXYGEN +}; + +void throw_bad_enum_cast( int64_t i, const char* e ); +void throw_bad_enum_cast( const char* k, const char* e ); + +template +struct has_reflector_init { +private: + template + static auto test( int ) -> decltype( std::declval().reflector_init(), std::true_type() ) { return {}; } + template + static std::false_type test( long ) { return {}; } +public: + static constexpr bool value = std::is_same( 0 ) ), std::true_type>::value; +}; + +struct reflect_init {}; + +template +struct reflector_init_visitor { + explicit reflector_init_visitor( Class& c ) + : obj(c) {} + + void reflector_init() const { + reflect_init( obj ); + } + void reflector_init() { + reflect_init( obj ); + } + + private: + + // 0 matches int if Class derived from reflect_init (SFINAE) + template + typename std::enable_if::value>::type + init_imp(T& t) { + t.reflector_init(); + } + + // SFINAE if Class not derived from reflect_init + template + typename std::enable_if::value>::type + init_imp(T& t) {} + + template + auto reflect_init(T& t) -> decltype(init_imp(t), void()) { + init_imp(t); + } + + protected: + Class& obj; +}; + +} // namespace fc + + +#ifndef DOXYGEN + +#define FC_REFLECT_VISIT_BASE(r, visitor, base) \ + fc::reflector::visit_base( visitor ); + + +#ifndef _MSC_VER + #define FC_TEMPLATE template +#else + // Disable warning C4482: nonstandard extention used: enum 'enum_type::enum_value' used in qualified name + #pragma warning( disable: 4482 ) + #define FC_TEMPLATE +#endif + +#define FC_REFLECT_VISIT_MEMBER( r, visitor, elem ) \ +{ typedef decltype((static_cast(nullptr))->elem) member_type; \ + visitor.FC_TEMPLATE operator()( BOOST_PP_STRINGIZE(elem) ); \ +} + + +#define FC_REFLECT_BASE_MEMBER_COUNT( r, OP, elem ) \ + OP fc::reflector::total_member_count + +#define FC_REFLECT_MEMBER_COUNT( r, OP, elem ) \ + OP 1 + +#define FC_REFLECT_DERIVED_IMPL_INLINE( TYPE, INHERITS, MEMBERS ) \ +template\ +static inline void visit_base( Visitor&& v ) { \ + BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_VISIT_BASE, v, INHERITS ) \ + BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_VISIT_MEMBER, v, MEMBERS ) \ +} \ +template\ +static inline void visit( Visitor&& v ) { \ + BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_VISIT_BASE, v, INHERITS ) \ + BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_VISIT_MEMBER, v, MEMBERS ) \ + init( std::forward(v) ); \ +} + +#endif // DOXYGEN + + +#define FC_REFLECT_VISIT_ENUM( r, enum_type, elem ) \ + v.operator()(BOOST_PP_STRINGIZE(elem), int64_t(enum_type::elem) ); +#define FC_REFLECT_ENUM_TO_STRING( r, enum_type, elem ) \ + case enum_type::elem: return BOOST_PP_STRINGIZE(elem); +#define FC_REFLECT_ENUM_TO_FC_STRING( r, enum_type, elem ) \ + case enum_type::elem: return fc::string(BOOST_PP_STRINGIZE(elem)); + +#define FC_REFLECT_ENUM_FROM_STRING( r, enum_type, elem ) \ + if( strcmp( s, BOOST_PP_STRINGIZE(elem) ) == 0 ) return enum_type::elem; +#define FC_REFLECT_ENUM_FROM_STRING_CASE( r, enum_type, elem ) \ + case enum_type::elem: + +#define FC_REFLECT_ENUM( ENUM, FIELDS ) \ +namespace fc { \ +template<> struct reflector { \ + typedef fc::true_type is_defined; \ + typedef fc::true_type is_enum; \ + static const char* to_string(ENUM elem) { \ + switch( elem ) { \ + BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_ENUM_TO_STRING, ENUM, FIELDS ) \ + default: \ + fc::throw_bad_enum_cast( fc::to_string(int64_t(elem)).c_str(), BOOST_PP_STRINGIZE(ENUM) ); \ + }\ + return nullptr; \ + } \ + static const char* to_string(int64_t i) { \ + return to_string(ENUM(i)); \ + } \ + static fc::string to_fc_string(ENUM elem) { \ + switch( elem ) { \ + BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_ENUM_TO_FC_STRING, ENUM, FIELDS ) \ + } \ + return fc::to_string(int64_t(elem)); \ + } \ + static fc::string to_fc_string(int64_t i) { \ + return to_fc_string(ENUM(i)); \ + } \ + static ENUM from_int(int64_t i) { \ + ENUM e = ENUM(i); \ + switch( e ) \ + { \ + BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_ENUM_FROM_STRING_CASE, ENUM, FIELDS ) \ + break; \ + default: \ + fc::throw_bad_enum_cast( i, BOOST_PP_STRINGIZE(ENUM) ); \ + } \ + return e;\ + } \ + static ENUM from_string( const char* s ) { \ + BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_ENUM_FROM_STRING, ENUM, FIELDS ) \ + int64_t i = 0; \ + try \ + { \ + i = boost::lexical_cast(s); \ + } \ + catch( const boost::bad_lexical_cast& e ) \ + { \ + fc::throw_bad_enum_cast( s, BOOST_PP_STRINGIZE(ENUM) ); \ + } \ + return from_int(i); \ + } \ + template< typename Visitor > \ + static void visit( Visitor& v ) \ + { \ + BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_VISIT_ENUM, ENUM, FIELDS ) \ + } \ +}; \ +template<> struct get_typename { static const char* name() { return BOOST_PP_STRINGIZE(ENUM); } }; \ +} + +/* Note: FC_REFLECT_ENUM previously defined this function, but I don't think it ever + * did what we expected it to do. I've disabled it for now. + * + * template \ + * static inline void visit( const Visitor& v ) { \ + * BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_VISIT_ENUM, ENUM, FIELDS ) \ + * }\ + */ + +/** + * @def FC_REFLECT_DERIVED(TYPE,INHERITS,MEMBERS) + * + * @brief Specializes fc::reflector for TYPE where + * type inherits other reflected classes + * + * @param INHERITS - a sequence of base class names (basea)(baseb)(basec) + * @param MEMBERS - a sequence of member names. (field1)(field2)(field3) + */ +#define FC_REFLECT_DERIVED_TEMPLATE( TEMPLATE_ARGS, TYPE, INHERITS, MEMBERS ) \ +namespace fc { \ + template struct get_typename { static const char* name() { return BOOST_PP_STRINGIZE(TYPE); } }; \ +template struct reflector {\ + typedef TYPE type; \ + typedef fc::true_type is_defined; \ + typedef fc::false_type is_enum; \ + template \ + static auto init_imp(Visitor&& v, int) -> decltype(std::forward(v).reflector_init(), void()) { \ + std::forward(v).reflector_init(); \ + } \ + template \ + static auto init_imp(Visitor&& v, long) -> decltype(v, void()) {} \ + template \ + static auto init(Visitor&& v) -> decltype(init_imp(std::forward(v), 0), void()) { \ + init_imp(std::forward(v), 0); \ + } \ + enum member_count_enum { \ + local_member_count = 0 BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_MEMBER_COUNT, +, MEMBERS ),\ + total_member_count = local_member_count BOOST_PP_SEQ_FOR_EACH( FC_REFLECT_BASE_MEMBER_COUNT, +, INHERITS )\ + }; \ + FC_REFLECT_DERIVED_IMPL_INLINE( TYPE, INHERITS, MEMBERS ) \ + static_assert( not fc::has_reflector_init::value || \ + std::is_base_of::value, "must derive from fc::reflect_init" ); \ + static_assert( not std::is_base_of::value || \ + fc::has_reflector_init::value, "must provide reflector_init() method" ); \ +}; } + +#define FC_REFLECT_DERIVED( TYPE, INHERITS, MEMBERS ) \ + FC_REFLECT_DERIVED_TEMPLATE( (), TYPE, INHERITS, MEMBERS ) + +//BOOST_PP_SEQ_SIZE(MEMBERS), + +/** + * @def FC_REFLECT(TYPE,MEMBERS) + * @brief Specializes fc::reflector for TYPE + * + * @param MEMBERS - a sequence of member names. (field1)(field2)(field3) + * + * @see FC_REFLECT_DERIVED + */ +#define FC_REFLECT( TYPE, MEMBERS ) \ + FC_REFLECT_DERIVED( TYPE, BOOST_PP_SEQ_NIL, MEMBERS ) + +#define FC_REFLECT_TEMPLATE( TEMPLATE_ARGS, TYPE, MEMBERS ) \ + FC_REFLECT_DERIVED_TEMPLATE( TEMPLATE_ARGS, TYPE, BOOST_PP_SEQ_NIL, MEMBERS ) + +#define FC_REFLECT_EMPTY( TYPE ) \ + FC_REFLECT_DERIVED( TYPE, BOOST_PP_SEQ_NIL, BOOST_PP_SEQ_NIL ) + +#define FC_REFLECT_TYPENAME( TYPE ) \ +namespace fc { \ + template<> struct get_typename { static const char* name() { return BOOST_PP_STRINGIZE(TYPE); } }; \ +} diff --git a/libraries/libfc/include/fc/reflect/typename.hpp b/libraries/libfc/include/fc/reflect/typename.hpp new file mode 100644 index 0000000000..889c8fefa8 --- /dev/null +++ b/libraries/libfc/include/fc/reflect/typename.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include + +namespace fc { + class value; + class exception; + namespace ip { class address; } + + template struct get_typename; + template<> struct get_typename { static const char* name() { return "int8_t"; } }; + template<> struct get_typename { static const char* name() { return "uint8_t"; } }; + template<> struct get_typename { static const char* name() { return "int16_t"; } }; + template<> struct get_typename { static const char* name() { return "uint16_t"; } }; + template<> struct get_typename { static const char* name() { return "int32_t"; } }; + template<> struct get_typename { static const char* name() { return "uint32_t"; } }; + template<> struct get_typename { static const char* name() { return "int64_t"; } }; + template<> struct get_typename { static const char* name() { return "uint64_t"; } }; + template<> struct get_typename<__int128> { static const char* name() { return "int128_t"; } }; + template<> struct get_typename { static const char* name() { return "uint128_t"; } }; + + template<> struct get_typename { static const char* name() { return "double"; } }; + template<> struct get_typename { static const char* name() { return "float"; } }; + template<> struct get_typename { static const char* name() { return "bool"; } }; + template<> struct get_typename { static const char* name() { return "char"; } }; + template<> struct get_typename { static const char* name() { return "char"; } }; + template<> struct get_typename { static const char* name() { return "string"; } }; + template<> struct get_typename { static const char* name() { return "value"; } }; + template<> struct get_typename { static const char* name() { return "fc::exception"; } }; + template<> struct get_typename> { static const char* name() { return "std::vector"; } }; + template struct get_typename> + { + static const char* name() { + static std::string n = std::string("std::vector<") + get_typename::name() + ">"; + return n.c_str(); + } + }; + template struct get_typename> + { + static const char* name() { + static std::string n = std::string("flat_set<") + get_typename::name() + ">"; + return n.c_str(); + } + }; + template struct get_typename< std::deque > + { + static const char* name() + { + static std::string n = std::string("std::deque<") + get_typename::name() + ">"; + return n.c_str(); + } + }; + template struct get_typename> + { + static const char* name() { + static std::string n = std::string("optional<") + get_typename::name() + ">"; + return n.c_str(); + } + }; + template struct get_typename> + { + static const char* name() { + static std::string n = std::string("std::map<") + get_typename::name() + ","+get_typename::name()+">"; + return n.c_str(); + } + }; + + struct signed_int; + struct unsigned_int; + template<> struct get_typename { static const char* name() { return "signed_int"; } }; + template<> struct get_typename { static const char* name() { return "unsigned_int"; } }; + +} diff --git a/libraries/libfc/include/fc/reflect/variant.hpp b/libraries/libfc/include/fc/reflect/variant.hpp new file mode 100644 index 0000000000..15b4e309b1 --- /dev/null +++ b/libraries/libfc/include/fc/reflect/variant.hpp @@ -0,0 +1,109 @@ +#pragma once +#include +#include + +namespace fc +{ + template + void to_variant( const T& o, variant& v ); + template + void from_variant( const variant& v, T& o ); + + + template + class to_variant_visitor + { + public: + to_variant_visitor( mutable_variant_object& mvo, const T& v ) + :vo(mvo),val(v){} + + template + void operator()( const char* name )const + { + this->add(vo,name,(val.*member)); + } + + private: + template + void add( mutable_variant_object& vo, const char* name, const std::optional& v )const + { + if( v ) + vo(name,*v); + } + template + void add( mutable_variant_object& vo, const char* name, const M& v )const + { vo(name,v); } + + mutable_variant_object& vo; + const T& val; + }; + + template + class from_variant_visitor : public reflector_init_visitor + { + public: + from_variant_visitor( const variant_object& _vo, T& v ) + :reflector_init_visitor(v) + ,vo(_vo){} + + template + void operator()( const char* name )const + { + auto itr = vo.find(name); + if( itr != vo.end() ) + from_variant( itr->value(), this->obj.*member ); + } + + const variant_object& vo; + }; + + template + struct if_enum + { + template + static inline void to_variant( const T& v, fc::variant& vo ) + { + mutable_variant_object mvo; + fc::reflector::visit( to_variant_visitor( mvo, v ) ); + vo = fc::move(mvo); + } + template + static inline void from_variant( const fc::variant& v, T& o ) + { + const variant_object& vo = v.get_object(); + fc::reflector::visit( from_variant_visitor( vo, o ) ); + } + }; + + template<> + struct if_enum + { + template + static inline void to_variant( const T& o, fc::variant& v ) + { + v = fc::reflector::to_fc_string(o); + } + template + static inline void from_variant( const fc::variant& v, T& o ) + { + if( v.is_string() ) + o = fc::reflector::from_string( v.get_string().c_str() ); + else + o = fc::reflector::from_int( v.as_int64() ); + } + }; + + + template + void to_variant( const T& o, variant& v ) + { + if_enum::is_enum>::to_variant( o, v ); + } + + template + void from_variant( const variant& v, T& o ) + { + if_enum::is_enum>::from_variant( v, o ); + } + +} diff --git a/libraries/libfc/include/fc/rpc/api_connection.hpp b/libraries/libfc/include/fc/rpc/api_connection.hpp new file mode 100644 index 0000000000..d0ca7c12f1 --- /dev/null +++ b/libraries/libfc/include/fc/rpc/api_connection.hpp @@ -0,0 +1,516 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + class api_connection; + + namespace detail { + template + class callback_functor + { + public: + typedef typename std::function::result_type result_type; + + callback_functor( std::weak_ptr< fc::api_connection > con, uint64_t id ) + :_callback_id(id),_api_connection(con){} + + template + result_type operator()( Args... args )const; + + private: + uint64_t _callback_id; + std::weak_ptr< fc::api_connection > _api_connection; + }; + + template + std::function bind_first_arg( const std::function& f, Arg0 a0 ) + { + return [=]( Args... args ) { return f( a0, args... ); }; + } + template + R call_generic( const std::function& f, variants::const_iterator a0, variants::const_iterator e ) + { + return f(); + } + + template + R call_generic( const std::function& f, variants::const_iterator a0, variants::const_iterator e ) + { + FC_ASSERT( a0 != e ); + return call_generic( bind_first_arg( f, a0->as< typename std::decay::type >() ), a0+1, e ); + } + + template + std::function to_generic( const std::function& f ) + { + return [=]( const variants& args ) { + return variant( call_generic( f, args.begin(), args.end() ) ); + }; + } + + template + std::function to_generic( const std::function& f ) + { + return [=]( const variants& args ) { + call_generic( f, args.begin(), args.end() ); + return variant(); + }; + } + + /** + * If api is returned from a remote method, the API is eagerly bound to api of + * the correct type in api_visitor::from_variant(). This binding [1] needs a reference + * to the api_connection, which is made available to from_variant() as a parameter. + * + * However, in the case of a remote method which returns api_base which can subsequently + * be cast by the caller with as, we need to keep track of the connection because + * the binding is done later (when the client code actually calls as). + * + * [1] The binding actually happens in get_remote_api(). + */ + class any_api : public api_base + { + public: + any_api( api_id_type api_id, const std::shared_ptr& con ) + : _api_id(api_id), _api_connection(con) {} + + virtual uint64_t get_handle()const override + { return _api_id; } + + virtual api_id_type register_api( api_connection& conn )const override + { FC_ASSERT( false ); return api_id_type(); } + + api_id_type _api_id; + std::weak_ptr _api_connection; + }; + + } // namespace detail + + class generic_api + { + public: + template + generic_api( const Api& a, const std::shared_ptr& c ); + + generic_api( const generic_api& cpy ) = delete; + + variant call( const string& name, const variants& args ) + { + auto itr = _by_name.find(name); + FC_ASSERT( itr != _by_name.end(), "no method with name '${name}'", ("name",name)("api",_by_name) ); + return call( itr->second, args ); + } + + variant call( uint32_t method_id, const variants& args ) + { + FC_ASSERT( method_id < _methods.size() ); + return _methods[method_id](args); + } + + std::weak_ptr< fc::api_connection > get_connection() + { + return _api_connection; + } + + std::vector get_method_names()const + { + std::vector result; + result.reserve( _by_name.size() ); + for( auto& m : _by_name ) result.push_back(m.first); + return result; + } + + private: + friend struct api_visitor; + + template + std::function bind_first_arg( const std::function& f, Arg0 a0 )const + { + return [=]( Args... args ) { return f( a0, args... ); }; + } + + template + R call_generic( const std::function& f, variants::const_iterator a0, variants::const_iterator e )const + { + return f(); + } + + template + R call_generic( const std::function,Args...)>& f, variants::const_iterator a0, variants::const_iterator e ) + { + FC_ASSERT( a0 != e, "too few arguments passed to method" ); + detail::callback_functor arg0( get_connection(), a0->as() ); + return call_generic( this->bind_first_arg,Args...>( f, std::function(arg0) ), a0+1, e ); + } + template + R call_generic( const std::function&,Args...)>& f, variants::const_iterator a0, variants::const_iterator e ) + { + FC_ASSERT( a0 != e, "too few arguments passed to method" ); + detail::callback_functor arg0( get_connection(), a0->as() ); + return call_generic( this->bind_first_arg&,Args...>( f, arg0 ), a0+1, e ); + } + + template + R call_generic( const std::function& f, variants::const_iterator a0, variants::const_iterator e ) + { + FC_ASSERT( a0 != e, "too few arguments passed to method" ); + return call_generic( this->bind_first_arg( f, a0->as< typename std::decay::type >() ), a0+1, e ); + } + + struct api_visitor + { + api_visitor( generic_api& a, const std::weak_ptr& s ):_api(a),_api_con(s){ } + + template + std::function to_generic( const std::function(Args...)>& f )const; + + template + std::function to_generic( const std::function>(Args...)>& f )const; + + template + std::function to_generic( const std::function& f )const; + + template + std::function to_generic( const std::function& f )const; + + template + std::function to_generic( const std::function& f )const; + + template + void operator()( const char* name, std::function& memb )const { + _api._methods.emplace_back( to_generic( memb ) ); + _api._by_name[name] = _api._methods.size() - 1; + } + + generic_api& _api; + const std::weak_ptr& _api_con; + }; + + + std::weak_ptr _api_connection; + fc::any _api; + std::map< std::string, uint32_t > _by_name; + std::vector< std::function > _methods; + }; // class generic_api + + + + class api_connection : public std::enable_shared_from_this + { + public: + api_connection(){} + virtual ~api_connection(){}; + + + template + api get_remote_api( api_id_type api_id = 0 ) + { + api result; + result->visit( api_visitor( api_id, this->shared_from_this() ) ); + return result; + } + + /** makes calls to the remote server */ + virtual variant send_call( api_id_type api_id, string method_name, variants args = variants() ) = 0; + virtual variant send_callback( uint64_t callback_id, variants args = variants() ) = 0; + virtual void send_notice( uint64_t callback_id, variants args = variants() ) = 0; + + variant receive_call( api_id_type api_id, const string& method_name, const variants& args = variants() )const + { + FC_ASSERT( _local_apis.size() > api_id ); + return _local_apis[api_id]->call( method_name, args ); + } + variant receive_callback( uint64_t callback_id, const variants& args = variants() )const + { + FC_ASSERT( _local_callbacks.size() > callback_id ); + return _local_callbacks[callback_id]( args ); + } + void receive_notice( uint64_t callback_id, const variants& args = variants() )const + { + FC_ASSERT( _local_callbacks.size() > callback_id ); + _local_callbacks[callback_id]( args ); + } + + template + api_id_type register_api( const Interface& a ) + { + auto handle = a.get_handle(); + auto itr = _handle_to_id.find(handle); + if( itr != _handle_to_id.end() ) return itr->second; + + _local_apis.push_back( std::unique_ptr( new generic_api(a, shared_from_this() ) ) ); + _handle_to_id[handle] = _local_apis.size() - 1; + return _local_apis.size() - 1; + } + + template + uint64_t register_callback( const std::function& cb ) + { + _local_callbacks.push_back( detail::to_generic( cb ) ); + return _local_callbacks.size() - 1; + } + + std::vector get_method_names( api_id_type local_api_id = 0 )const { return _local_apis[local_api_id]->get_method_names(); } + + fc::signal closed; + private: + std::vector< std::unique_ptr > _local_apis; + std::map< uint64_t, api_id_type > _handle_to_id; + std::vector< std::function > _local_callbacks; + + + struct api_visitor + { + uint32_t _api_id; + std::shared_ptr _connection; + + api_visitor( uint32_t api_id, std::shared_ptr con ) + :_api_id(api_id),_connection(std::move(con)) + { + } + + api_visitor() = delete; + + template + static Result from_variant( const variant& v, Result*, const std::shared_ptr& ) + { + return v.as(); + } + + template + static fc::api from_variant( const variant& v, + fc::api* /*used for template deduction*/, + const std::shared_ptr& con + ) + { + return con->get_remote_api( v.as_uint64() ); + } + + static fc::api_ptr from_variant( + const variant& v, + fc::api_ptr* /* used for template deduction */, + const std::shared_ptr& con + ) + { + return fc::api_ptr( new detail::any_api( v.as_uint64(), con ) ); + } + + template + static fc::variant convert_callbacks( const std::shared_ptr&, const T& v ) + { + return fc::variant(v); + } + + template + static fc::variant convert_callbacks( const std::shared_ptr& con, const std::function& v ) + { + return con->register_callback( v ); + } + + template + void operator()( const char* name, std::function& memb )const + { + auto con = _connection; + auto api_id = _api_id; + memb = [con,api_id,name]( Args... args ) { + auto var_result = con->send_call( api_id, name, { convert_callbacks(con,args)...} ); + return from_variant( var_result, (Result*)nullptr, con ); + }; + } + template + void operator()( const char* name, std::function& memb )const + { + auto con = _connection; + auto api_id = _api_id; + memb = [con,api_id,name]( Args... args ) { + con->send_call( api_id, name, { convert_callbacks(con,args)...} ); + }; + } + }; + }; + + class local_api_connection : public api_connection + { + public: + /** makes calls to the remote server */ + virtual variant send_call( api_id_type api_id, string method_name, variants args = variants() ) override + { + FC_ASSERT( _remote_connection ); + return _remote_connection->receive_call( api_id, method_name, std::move(args) ); + } + virtual variant send_callback( uint64_t callback_id, variants args = variants() ) override + { + FC_ASSERT( _remote_connection ); + return _remote_connection->receive_callback( callback_id, args ); + } + virtual void send_notice( uint64_t callback_id, variants args = variants() ) override + { + FC_ASSERT( _remote_connection ); + _remote_connection->receive_notice( callback_id, args ); + } + + + void set_remote_connection( const std::shared_ptr& rc ) + { + FC_ASSERT( !_remote_connection ); + FC_ASSERT( rc != this->shared_from_this() ); + _remote_connection = rc; + } + const std::shared_ptr& remote_connection()const { return _remote_connection; } + + std::shared_ptr _remote_connection; + }; + + template + generic_api::generic_api( const Api& a, const std::shared_ptr& c ) + :_api_connection(c),_api(a) + { + boost::any_cast(a)->visit( api_visitor( *this, c ) ); + } + + template + std::function generic_api::api_visitor::to_generic( + const std::function(Args...)>& f )const + { + auto api_con = _api_con; + auto gapi = &_api; + return [=]( const variants& args ) { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + + auto api_result = gapi->call_generic( f, args.begin(), args.end() ); + return con->register_api( api_result ); + }; + } + template + std::function generic_api::api_visitor::to_generic( + const std::function>(Args...)>& f )const + { + auto api_con = _api_con; + auto gapi = &_api; + return [=]( const variants& args )-> fc::variant { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + + auto api_result = gapi->call_generic( f, args.begin(), args.end() ); + if( api_result ) + return con->register_api( *api_result ); + return variant(); + }; + } + + template + std::function generic_api::api_visitor::to_generic( + const std::function& f )const + { + auto api_con = _api_con; + auto gapi = &_api; + return [=]( const variants& args ) -> fc::variant { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + + auto api_result = gapi->call_generic( f, args.begin(), args.end() ); + if( !api_result ) + return variant(); + return api_result->register_api( *con ); + }; + } + + template + std::function generic_api::api_visitor::to_generic( const std::function& f )const + { + generic_api* gapi = &_api; + return [f,gapi]( const variants& args ) { + return variant( gapi->call_generic( f, args.begin(), args.end() ) ); + }; + } + + template + std::function generic_api::api_visitor::to_generic( const std::function& f )const + { + generic_api* gapi = &_api; + return [f,gapi]( const variants& args ) { + gapi->call_generic( f, args.begin(), args.end() ); + return variant(); + }; + } + + /** + * It is slightly unclean tight coupling to have this method in the api class. + * It breaks encapsulation by requiring an api class method to have a pointer + * to an api_connection. The reason this is necessary is we have a goal of being + * able to call register_api() on an api through its base class api_base. But + * register_api() must know the template parameters! + * + * The only reasonable way to achieve the goal is to implement register_api() + * as a method in api (which obviously knows the template parameter T), + * then make the implementation accessible through the base class (by making + * it a pure virtual method in the base class which is overridden by the subclass's + * implementation). + */ + template< typename Interface, typename Transform > + api_id_type api< Interface, Transform >::register_api( api_connection& conn )const + { + return conn.register_api( *this ); + } + + template< typename T > + api api_base::as() + { + // TODO: this method should probably be const (if it is not too hard) + api* maybe_requested_type = dynamic_cast< api* >(this); + if( maybe_requested_type != nullptr ) + return *maybe_requested_type; + + detail::any_api* maybe_any = dynamic_cast< detail::any_api* >(this); + FC_ASSERT( maybe_any != nullptr ); + std::shared_ptr< api_connection > api_conn = maybe_any->_api_connection.lock(); + FC_ASSERT( api_conn ); + return api_conn->get_remote_api( maybe_any->_api_id ); + } + + namespace detail { + template + template + typename callback_functor::result_type callback_functor::operator()( Args... args )const + { + std::shared_ptr< fc::api_connection > locked = _api_connection.lock(); + // TODO: make new exception type for this instead of recycling eof_exception + if( !locked ) + throw fc::eof_exception(); + locked->send_callback( _callback_id, fc::variants{ args... } ).template as< result_type >(); + } + + + template + class callback_functor + { + public: + typedef void result_type; + + callback_functor( std::weak_ptr< fc::api_connection > con, uint64_t id ) + :_callback_id(id),_api_connection(con){} + + void operator()( Args... args )const + { + std::shared_ptr< fc::api_connection > locked = _api_connection.lock(); + // TODO: make new exception type for this instead of recycling eof_exception + if( !locked ) + throw fc::eof_exception(); + locked->send_notice( _callback_id, fc::variants{ args... } ); + } + + private: + uint64_t _callback_id; + std::weak_ptr< fc::api_connection > _api_connection; + }; + } // namespace detail + +} // fc diff --git a/libraries/libfc/include/fc/rpc/binary_api_connection.hpp b/libraries/libfc/include/fc/rpc/binary_api_connection.hpp new file mode 100644 index 0000000000..4e389f515c --- /dev/null +++ b/libraries/libfc/include/fc/rpc/binary_api_connection.hpp @@ -0,0 +1,530 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + class binary_api_connection; + + namespace detail { + template + class callback_functor + { + public: + typedef typename std::function::result_type result_type; + + callback_functor( std::weak_ptr< fc::binary_api_connection > con, uint64_t id ) + :_callback_id(id),_binary_api_connection(con){} + + template + result_type operator()( Args... args )const; + + private: + uint64_t _callback_id; + std::weak_ptr< fc::binary_api_connection > _binary_api_connection; + }; + + template + std::function bind_first_arg( const std::function& f, Arg0 a0 ) + { + return [=]( Args... args ) { return f( a0, args... ); }; + } + template + R call_generic( const std::function& f, variants::const_iterator a0, variants::const_iterator e ) + { + return f(); + } + + template + R call_generic( const std::function& f, variants::const_iterator a0, variants::const_iterator e ) + { + FC_ASSERT( a0 != e ); + return call_generic( bind_first_arg( f, a0->as< typename std::decay::type >() ), a0+1, e ); + } + + template + std::function to_generic( const std::function& f ) + { + return [=]( const variants& args ) { + return variant( call_generic( f, args.begin(), args.end() ) ); + }; + } + + template + std::function to_generic( const std::function& f ) + { + return [=]( const variants& args ) { + call_generic( f, args.begin(), args.end() ); + return variant(); + }; + } + + /** + * If api is returned from a remote method, the API is eagerly bound to api of + * the correct type in api_visitor::from_variant(). This binding [1] needs a reference + * to the binary_api_connection, which is made available to from_variant() as a parameter. + * + * However, in the case of a remote method which returns api_base which can subsequently + * be cast by the caller with as, we need to keep track of the connection because + * the binding is done later (when the client code actually calls as). + * + * [1] The binding actually happens in get_remote_api(). + */ + class any_api : public api_base + { + public: + any_api( api_id_type api_id, const std::shared_ptr& con ) + : _api_id(api_id), _binary_api_connection(con) {} + + virtual uint64_t get_handle()const override + { return _api_id; } + + virtual api_id_type register_api( binary_api_connection& conn )const override + { FC_ASSERT( false ); return api_id_type(); } + + api_id_type _api_id; + std::weak_ptr _binary_api_connection; + }; + + } // namespace detail + + class generic_api + { + public: + template + generic_api( const Api& a, const std::shared_ptr& c ); + + generic_api( const generic_api& cpy ) = delete; + + vector call( const string& name, const vector& args ) + { + auto itr = _by_name.find(name); + FC_ASSERT( itr != _by_name.end(), "no method with name '${name}'", ("name",name)("api",_by_name) ); + return call( itr->second, args ); + } + + vector call( uint32_t method_id, const vector& args ) + { + FC_ASSERT( method_id < _methods.size() ); + return _methods[method_id](args); + } + + std::weak_ptr< fc::binary_api_connection > get_connection() + { + return _binary_api_connection; + } + + std::vector get_method_names()const + { + std::vector result; + result.reserve( _by_name.size() ); + for( auto& m : _by_name ) result.push_back(m.first); + return result; + } + + private: + friend struct api_visitor; + + template + std::function bind_first_arg( const std::function& f, Arg0 a0 )const + { + return [=]( Args... args ) { return f( a0, args... ); }; + } + + template + R call_generic( const std::function& f, datastream& ds )const + { + return f(); + } + + template + R call_generic( const std::function,Args...)>& f, datastream& ds ) + { + uint64_t callback_id = 0; + fc::raw::unpack( ds, callback_id ); + detail::callback_functor arg0( get_connection(), callback_id ); + return call_generic( this->bind_first_arg,Args...>( f, std::function(arg0) ), ds ); + } + template + R call_generic( const std::function&,Args...)>& f, fc::datastream& ds ) + { + uint64_t callback_id = 0; + fc::raw::unpack( ds, callback_id ); + detail::callback_functor arg0( get_connection(), callback_id ); + return call_generic( this->bind_first_arg&,Args...>( f, arg0 ), ds ); + } + + template + R call_generic( const std::function& f, fc::datastream& ds ) + { + std::decay::type a0; + fc::raw::unpack( ds, a0 ); + return call_generic( this->bind_first_arg( f, a0 ), ds ); + } + + struct api_visitor + { + api_visitor( generic_api& a, const std::weak_ptr& s ):api(a),_api_con(s){ } + + template + std::function to_generic( const std::function(Args...)>& f )const; + + template + std::function to_generic( const std::function>(Args...)>& f )const; + + template + std::function to_generic( const std::function& f )const; + + template + std::function to_generic( const std::function& f )const; + + template + std::function to_generic( const std::function& f )const; + + template + void operator()( const char* name, std::function& memb )const { + api._methods.emplace_back( to_generic( memb ) ); + api._by_name[name] = api._methods.size() - 1; + } + + generic_api& api; + const std::weak_ptr& _api_con; + }; + + + std::weak_ptr _binary_api_connection; + fc::any _api; + std::map< std::string, uint32_t > _by_name; + std::vector< std::function(const vector&)> > _methods; + }; // class generic_api + + + + class binary_api_connection : public std::enable_shared_from_this + { + public: + typedef std::vector params_type; + typedef std::vector result_type; + + binary_api_connection(){} + virtual ~binary_api_connection(){}; + + + template + api get_remote_api( api_id_type api_id = 0 ) + { + api result; + result->visit( api_visitor( api_id, this->shared_from_this() ) ); + return result; + } + + /** makes calls to the remote server */ + virtual result_type send_call( api_id_type api_id, string method_name, params_type args = params_type() ) = 0; + virtual result_type send_callback( uint64_t callback_id, params_type args = params_type() ) = 0; + virtual void send_notice( uint64_t callback_id, params_type args = params_type() ) = 0; + + result_type receive_call( api_id_type api_id, const string& method_name, const params_type& args = params_type() )const + { + FC_ASSERT( _local_apis.size() > api_id ); + return _local_apis[api_id]->call( method_name, args ); + } + result_type receive_callback( uint64_t callback_id, const params_type& args = params_type() )const + { + FC_ASSERT( _local_callbacks.size() > callback_id ); + return _local_callbacks[callback_id]( args ); + } + void receive_notice( uint64_t callback_id, const params_type& args = params_type() )const + { + FC_ASSERT( _local_callbacks.size() > callback_id ); + _local_callbacks[callback_id]( args ); + } + + template + api_id_type register_api( const Interface& a ) + { + auto handle = a.get_handle(); + auto itr = _handle_to_id.find(handle); + if( itr != _handle_to_id.end() ) return itr->second; + + _local_apis.push_back( std::unique_ptr( new generic_api(a, shared_from_this() ) ) ); + _handle_to_id[handle] = _local_apis.size() - 1; + return _local_apis.size() - 1; + } + + template + uint64_t register_callback( const std::function& cb ) + { + _local_callbacks.push_back( detail::to_generic( cb ) ); + return _local_callbacks.size() - 1; + } + + std::vector get_method_names( api_id_type local_api_id = 0 )const { return _local_apis[local_api_id]->get_method_names(); } + + fc::signal closed; + private: + std::vector< std::unique_ptr > _local_apis; + std::map< uint64_t, api_id_type > _handle_to_id; + std::vector< std::function > _local_callbacks; + + + struct api_visitor + { + uint32_t _api_id; + std::shared_ptr _connection; + + api_visitor( uint32_t api_id, std::shared_ptr con ) + :_api_id(api_id),_connection(std::move(con)) + { + } + + api_visitor() = delete; + + template + static Result from_vector( const vector& v, Result*, const std::shared_ptr& ) + { + return fc::raw::unpack( v ); + } + + template + static fc::api from_vector( const vector& v, + fc::api* /*used for template deduction*/, + const std::shared_ptr& con + ) + { + return con->get_remote_api( fc::raw::unpack( v ) ); + } + + static fc::api_ptr from_vector( + const vector& v, + fc::api_ptr* /* used for template deduction */, + const std::shared_ptr& con + ) + { + return fc::api_ptr( new detail::any_api( fc::raw::unpack(v), con ) ); + } + + template + static result_type convert_callbacks( const std::shared_ptr&, const T& v ) + { + return fc::raw::pack(v); + } + + template + static result_type convert_callbacks( const std::shared_ptr& con, const std::function& v ) + { + return con->register_callback( v ); + } + + template + void operator()( const char* name, std::function& memb )const + { + auto con = _connection; + auto api_id = _api_id; + memb = [con,api_id,name]( Args... args ) { + auto var_result = con->send_call( api_id, name, { convert_callbacks(con,args)...} ); + return from_vector( var_result, (Result*)nullptr, con ); + }; + } + template + void operator()( const char* name, std::function& memb )const + { + auto con = _connection; + auto api_id = _api_id; + memb = [con,api_id,name]( Args... args ) { + con->send_call( api_id, name, { convert_callbacks(con,args)...} ); + }; + } + }; + }; + + class local_binary_api_connection : public binary_api_connection + { + public: + /** makes calls to the remote server */ + virtual result_type send_call( api_id_type api_id, string method_name, params_type args = params_type() ) override + { + FC_ASSERT( _remote_connection ); + return _remote_connection->receive_call( api_id, method_name, std::move(args) ); + } + virtual result_type send_callback( uint64_t callback_id, params_type args = params_type() ) override + { + FC_ASSERT( _remote_connection ); + return _remote_connection->receive_callback( callback_id, args ); + } + virtual void send_notice( uint64_t callback_id, params_type args = params_type() ) override + { + FC_ASSERT( _remote_connection ); + _remote_connection->receive_notice( callback_id, args ); + } + + + void set_remote_connection( const std::shared_ptr& rc ) + { + FC_ASSERT( !_remote_connection ); + FC_ASSERT( rc != this->shared_from_this() ); + _remote_connection = rc; + } + const std::shared_ptr& remote_connection()const { return _remote_connection; } + + std::shared_ptr _remote_connection; + }; + + template + generic_api::generic_api( const Api& a, const std::shared_ptr& c ) + :_binary_api_connection(c),_api(a) + { + boost::any_cast(a)->visit( api_visitor( *this, c ) ); + } + + template + std::function generic_api::api_visitor::to_generic( + const std::function(Args...)>& f )const + { + auto api_con = _api_con; + auto gapi = &api; + return [=]( const params_type& args ) { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + + fc::raw::datastream ds( args.data(), args.size() ); + auto api_result = gapi->call_generic( f, args ); + return con->register_api( api_result ); + }; + } + template + std::function generic_api::api_visitor::to_generic( + const std::function>(Args...)>& f )const + { + auto api_con = _api_con; + auto gapi = &api; + return [=]( const params_type& args )-> fc::variant { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + + fc::raw::datastream ds( args.data(), args.size() ); + auto api_result = gapi->call_generic( f, ds ); + if( api_result ) + return con->register_api( *api_result ); + return result_type(); + }; + } + + template + std::function generic_api::api_visitor::to_generic( + const std::function& f )const + { + auto api_con = _api_con; + auto gapi = &api; + return [=]( const variants& args ) -> fc::variant { + auto con = api_con.lock(); + FC_ASSERT( con, "not connected" ); + + fc::raw::datastream ds( args.data(), args.size() ); + auto api_result = gapi->call_generic( f, ds ); + if( !api_result ) + return result_type(); + return api_result->register_api( *con ); + }; + } + + template + std::function generic_api::api_visitor::to_generic( const std::function& f )const + { + generic_api* gapi = &api; + return [f,gapi]( const params_type& args ) { + fc::raw::datastream ds( args.data(), args.size() ); + return fc::raw::pack(gapi->call_generic( f, ds )); + }; + } + + template + std::function generic_api::api_visitor::to_generic( const std::function& f )const + { + generic_api* gapi = &api; + return [f,gapi]( const params_type& args ) { + fc::raw::datastream ds( args.data(), args.size() ); + gapi->call_generic( f, ds ); + return result_type(); + }; + } + + /** + * It is slightly unclean tight coupling to have this method in the api class. + * It breaks encapsulation by requiring an api class method to have a pointer + * to an binary_api_connection. The reason this is necessary is we have a goal of being + * able to call register_api() on an api through its base class api_base. But + * register_api() must know the template parameters! + * + * The only reasonable way to achieve the goal is to implement register_api() + * as a method in api (which obviously knows the template parameter T), + * then make the implementation accessible through the base class (by making + * it a pure virtual method in the base class which is overridden by the subclass's + * implementation). + */ + template< typename Interface, typename Transform > + api_id_type api< Interface, Transform >::register_api( binary_api_connection& conn )const + { + return conn.register_api( *this ); + } + + template< typename T > + api api_base::as() + { + // TODO: this method should probably be const (if it is not too hard) + api* maybe_requested_type = dynamic_cast< api* >(this); + if( maybe_requested_type != nullptr ) + return *maybe_requested_type; + + detail::any_api* maybe_any = dynamic_cast< detail::any_api* >(this); + FC_ASSERT( maybe_any != nullptr ); + std::shared_ptr< binary_api_connection > api_conn = maybe_any->_binary_api_connection.lock(); + FC_ASSERT( api_conn ); + return api_conn->get_remote_api( maybe_any->_api_id ); + } + + namespace detail { + template + template + typename callback_functor::result_type callback_functor::operator()( Args... args )const + { + std::shared_ptr< fc::binary_api_connection > locked = _binary_api_connection.lock(); + // TODO: make new exception type for this instead of recycling eof_exception + if( !locked ) + throw fc::eof_exception(); + + /// TODO------------->>> pack args... + locked->send_callback( _callback_id, fc::raw::pack( args... ) ).template as< result_type >(); + } + + + template + class callback_functor + { + public: + typedef void result_type; + + callback_functor( std::weak_ptr< fc::binary_api_connection > con, uint64_t id ) + :_callback_id(id),_binary_api_connection(con){} + + void operator()( Args... args )const + { + std::shared_ptr< fc::binary_api_connection > locked = _binary_api_connection.lock(); + // TODO: make new exception type for this instead of recycling eof_exception + if( !locked ) + throw fc::eof_exception(); + locked->send_notice( _callback_id, fc::variants{ args... } ); + } + + private: + uint64_t _callback_id; + std::weak_ptr< fc::binary_api_connection > _binary_api_connection; + }; + } // namespace detail + +} // fc diff --git a/libraries/libfc/include/fc/rpc/cli.hpp b/libraries/libfc/include/fc/rpc/cli.hpp new file mode 100644 index 0000000000..bb4975fe60 --- /dev/null +++ b/libraries/libfc/include/fc/rpc/cli.hpp @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include + +namespace fc { namespace rpc { + + /** + * Provides a simple wrapper for RPC calls to a given interface. + */ + class cli : public api_connection + { + public: + ~cli(); + + virtual variant send_call( api_id_type api_id, string method_name, variants args = variants() ); + virtual variant send_callback( uint64_t callback_id, variants args = variants() ); + virtual void send_notice( uint64_t callback_id, variants args = variants() ); + + void start(); + void stop(); + void wait(); + void format_result( const string& method, std::function formatter); + + virtual void getline( const fc::string& prompt, fc::string& line ); + + void set_prompt( const string& prompt ); + + private: + void run(); + + std::string _prompt = ">>>"; + std::map > _result_formatters; + fc::future _run_complete; + }; +} } diff --git a/libraries/libfc/include/fc/rpc/http_api.hpp b/libraries/libfc/include/fc/rpc/http_api.hpp new file mode 100644 index 0000000000..47eb289fef --- /dev/null +++ b/libraries/libfc/include/fc/rpc/http_api.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace fc { namespace rpc { + + class http_api_connection : public api_connection + { + public: + http_api_connection(); + ~http_api_connection(); + + virtual variant send_call( + api_id_type api_id, + string method_name, + variants args = variants() ) override; + virtual variant send_callback( + uint64_t callback_id, + variants args = variants() ) override; + virtual void send_notice( + uint64_t callback_id, + variants args = variants() ) override; + + void on_request( + const fc::http::request& req, + const fc::http::server::response& resp ); + + fc::rpc::state _rpc_state; + }; + +} } // namespace fc::rpc diff --git a/libraries/libfc/include/fc/rpc/variant_connection.hpp b/libraries/libfc/include/fc/rpc/variant_connection.hpp new file mode 100644 index 0000000000..04d2cdf8d6 --- /dev/null +++ b/libraries/libfc/include/fc/rpc/variant_connection.hpp @@ -0,0 +1,140 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace fc { namespace rpc { + + namespace detail { class variant_connection_impl; } + + /** + * @brief Implements JSON-RPC 2.0 over a set of io streams + * + * Each JSON RPC message is expected to be on its own line, violators + * will be prosecuted to the fullest extent of the law. + */ + class variant_connection + { + public: + typedef std::function method; + typedef std::function named_param_method; + + variant_connection( fc::variant_stream::ptr in, fc::variant_stream::ptr out ); + ~variant_connection(); + + /** + * Starts processing messages from input + */ + future exec(); + + logger get_logger()const; + void set_logger( const logger& l ); + + /** + * @name server interface + * + * Adding methods to the interface allows the remote side + * to call them. + */ + ///@{ + void add_method( const fc::string& name, method ); + void add_named_param_method( const fc::string& name, named_param_method ); + void remove_method( const fc::string& name ); + //@} + + /** + * @name client interface + */ + ///@{ + void notice( const fc::string& method ); + void notice( const fc::string& method, const variants& args ); + void notice( const fc::string& method, const variant_object& named_args ); + + /// args will be handled as named params + future async_call( const fc::string& method, + const variant_object& args ); + + future async_call( const fc::string& method, mutable_variant_object args ); + + /// Sending in an array of variants will be handled as positional arguments + future async_call( const fc::string& method, + const variants& args ); + + future async_call( const fc::string& method ); + + future async_call( const fc::string& method, + const fc::variant& a1 ); + + future async_call( const fc::string& method, + const fc::variant& a1, + const fc::variant& a2 ); + + future async_call( const fc::string& method, + const fc::variant& a1, + const fc::variant& a2, + const fc::variant& a3 ); + + template + Result call( const fc::string& method, + const fc::variant& a1, + const fc::variant& a2, + const fc::variant& a3, + microseconds timeout = microseconds::maximum()) + { + return async_call( method, a1, a2, a3 ).wait(timeout).as(); + } + + template + Result call( const fc::string& method, + const fc::variant& a1, + const fc::variant& a2, + microseconds timeout = microseconds::maximum()) + { + return async_call( method, a1, a2 ).wait(timeout).as(); + } + + template + Result call( const fc::string& method, + const fc::variant& a1, + microseconds timeout = microseconds::maximum()) + { + return async_call( method, a1 ).wait(timeout).as(); + } + + template + Result call( const fc::string& method, + variant_object a1, + microseconds timeout = microseconds::maximum()) + { + return async_call( method, fc::move(a1) ).wait(timeout).as(); + } + template + Result call( const fc::string& method, + mutable_variant_object a1, + microseconds timeout = microseconds::maximum()) + { + return async_call( method, variant_object( fc::move(a1) ) ).wait(timeout).as(); + } + + + template + Result call( const fc::string& method, microseconds timeout = microseconds::maximum() ) + { + return async_call( method ).wait(timeout).as(); + } + + /// Sending in a variant_object will be issued as named parameters + variant call( const fc::string& method, const variant_object& named_args ); + ///@} + + private: + std::unique_ptr my; + }; + typedef std::shared_ptr variant_connection_ptr; + +}} // fc::rpc + + + diff --git a/libraries/libfc/include/fc/rpc/variant_stream.hpp b/libraries/libfc/include/fc/rpc/variant_stream.hpp new file mode 100644 index 0000000000..9c9bc2e42e --- /dev/null +++ b/libraries/libfc/include/fc/rpc/variant_stream.hpp @@ -0,0 +1,36 @@ +#pragma once + +namespace fc +{ + + /** + * Thread-safe, circular buffer for passing variants + * between threads. + */ + class variant_stream + { + public: + variant_stream( size_t s ); + ~variant_stream(); + + /** producer api */ + int64_t free(); // number of spaces available + int64_t claim( int64_t num ); + int64_t publish( int64_t pos ); + int64_t wait_free(); // wait for free space + + // producer/consumer api + variant& get( int64_t pos ); + + /** consumer api */ + int64_t begin(); // returns the first index ready to be read + int64_t end(); // returns the first index that cannot be read + int64_t wait(); // wait for variants to be posted + + private: + std::vector _variants; + uint64_t _read_pos; + uint64_t _write_pos; + }; + +} diff --git a/libraries/libfc/include/fc/rpc/websocket_api.hpp b/libraries/libfc/include/fc/rpc/websocket_api.hpp new file mode 100644 index 0000000000..92d7f2eb5e --- /dev/null +++ b/libraries/libfc/include/fc/rpc/websocket_api.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace fc { namespace rpc { + + class websocket_api_connection : public api_connection + { + public: + websocket_api_connection( fc::http::websocket_connection& c ); + ~websocket_api_connection(); + + virtual variant send_call( + api_id_type api_id, + string method_name, + variants args = variants() ) override; + virtual variant send_callback( + uint64_t callback_id, + variants args = variants() ) override; + virtual void send_notice( + uint64_t callback_id, + variants args = variants() ) override; + + protected: + std::string on_message( + const std::string& message, + bool send_message = true ); + + fc::http::websocket_connection& _connection; + fc::rpc::state _rpc_state; + }; + +} } // namespace fc::rpc diff --git a/libraries/libfc/include/fc/safe.hpp b/libraries/libfc/include/fc/safe.hpp new file mode 100644 index 0000000000..d9290b9f40 --- /dev/null +++ b/libraries/libfc/include/fc/safe.hpp @@ -0,0 +1,230 @@ +#pragma once +#include +#include + +#include + +namespace fc { + + /** + * This type is designed to provide automatic checks for + * integer overflow and default initialization. It will + * throw an exception on overflow conditions. + * + * It can only be used on built-in types. In particular, + * safe is buggy and should not be used. + * + * Implemented using spec from: + * https://www.securecoding.cert.org/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow + */ + template + struct safe + { + T value = 0; + + template + safe( O o ):value(o){} + safe(){} + safe( const safe& o ):value(o.value){} + + static safe min() + { + return std::numeric_limits::min(); + } + static safe max() + { + return std::numeric_limits::max(); + } + + friend safe operator + ( const safe& a, const safe& b ) + { + if( b.value > 0 && a.value > (std::numeric_limits::max() - b.value) ) FC_CAPTURE_AND_THROW( overflow_exception, (a)(b) ); + if( b.value < 0 && a.value < (std::numeric_limits::min() - b.value) ) FC_CAPTURE_AND_THROW( underflow_exception, (a)(b) ); + return safe( a.value + b.value ); + } + friend safe operator - ( const safe& a, const safe& b ) + { + if( b.value > 0 && a.value < (std::numeric_limits::min() + b.value) ) FC_CAPTURE_AND_THROW( underflow_exception, (a)(b) ); + if( b.value < 0 && a.value > (std::numeric_limits::max() + b.value) ) FC_CAPTURE_AND_THROW( overflow_exception, (a)(b) ); + return safe( a.value - b.value ); + } + + friend safe operator * ( const safe& a, const safe& b ) + { + if( a.value > 0 ) + { + if( b.value > 0 ) + { + if( a.value > (std::numeric_limits::max() / b.value) ) FC_CAPTURE_AND_THROW( overflow_exception, (a)(b) ); + } + else + { + if( b.value < (std::numeric_limits::min() / a.value) ) FC_CAPTURE_AND_THROW( underflow_exception, (a)(b) ); + } + } + else + { + if( b.value > 0 ) + { + if( a.value < (std::numeric_limits::min() / b.value) ) FC_CAPTURE_AND_THROW( underflow_exception, (a)(b) ); + } + else + { + if( a.value != 0 && b.value < (std::numeric_limits::max() / a.value) ) FC_CAPTURE_AND_THROW( overflow_exception, (a)(b) ); + } + } + + return safe( a.value * b.value ); + } + + friend safe operator / ( const safe& a, const safe& b ) + { + if( b.value == 0 ) FC_CAPTURE_AND_THROW( divide_by_zero_exception, (a)(b) ); + if( a.value == std::numeric_limits::min() && b.value == -1 ) FC_CAPTURE_AND_THROW( overflow_exception, (a)(b) ); + return safe( a.value / b.value ); + } + friend safe operator % ( const safe& a, const safe& b ) + { + if( b.value == 0 ) FC_CAPTURE_AND_THROW( divide_by_zero_exception, (a)(b) ); + if( a.value == std::numeric_limits::min() && b.value == -1 ) FC_CAPTURE_AND_THROW( overflow_exception, (a)(b) ); + return safe( a.value % b.value ); + } + + safe operator - ()const + { + if( value == std::numeric_limits::min() ) FC_CAPTURE_AND_THROW( overflow_exception, (*this) ); + return safe( -value ); + } + + safe& operator += ( const safe& b ) + { + value = (*this + b).value; + return *this; + } + safe& operator -= ( const safe& b ) + { + value = (*this - b).value; + return *this; + } + safe& operator *= ( const safe& b ) + { + value = (*this * b).value; + return *this; + } + safe& operator /= ( const safe& b ) + { + value = (*this / b).value; + return *this; + } + safe& operator %= ( const safe& b ) + { + value = (*this % b).value; + return *this; + } + + safe& operator++() + { + *this += 1; + return *this; + } + safe operator++( int ) + { + safe bak = *this; + *this += 1; + return bak; + } + + safe& operator--() + { + *this -= 1; + return *this; + } + safe operator--( int ) + { + safe bak = *this; + *this -= 1; + return bak; + } + + friend bool operator == ( const safe& a, const safe& b ) + { + return a.value == b.value; + } + friend bool operator == ( const safe& a, const T& b ) + { + return a.value == b; + } + friend bool operator == ( const T& a, const safe& b ) + { + return a == b.value; + } + + friend bool operator < ( const safe& a, const safe& b ) + { + return a.value < b.value; + } + friend bool operator < ( const safe& a, const T& b ) + { + return a.value < b; + } + friend bool operator < ( const T& a, const safe& b ) + { + return a < b.value; + } + + friend bool operator > ( const safe& a, const safe& b ) + { + return a.value > b.value; + } + friend bool operator > ( const safe& a, const T& b ) + { + return a.value > b; + } + friend bool operator > ( const T& a, const safe& b ) + { + return a > b.value; + } + + friend bool operator != ( const safe& a, const safe& b ) + { + return !(a == b); + } + friend bool operator != ( const safe& a, const T& b ) + { + return !(a == b); + } + friend bool operator != ( const T& a, const safe& b ) + { + return !(a == b); + } + + friend bool operator <= ( const safe& a, const safe& b ) + { + return !(a > b); + } + friend bool operator <= ( const safe& a, const T& b ) + { + return !(a > b); + } + friend bool operator <= ( const T& a, const safe& b ) + { + return !(a > b); + } + + friend bool operator >= ( const safe& a, const safe& b ) + { + return !(a < b); + } + friend bool operator >= ( const safe& a, const T& b ) + { + return !(a < b); + } + friend bool operator >= ( const T& a, const safe& b ) + { + return !(a < b); + } + }; + +} + +FC_REFLECT_TEMPLATE( (typename T), safe, (value) ) diff --git a/libraries/libfc/include/fc/scoped_exit.hpp b/libraries/libfc/include/fc/scoped_exit.hpp new file mode 100644 index 0000000000..8cae626b87 --- /dev/null +++ b/libraries/libfc/include/fc/scoped_exit.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +namespace fc { + + template + class scoped_exit { + public: + template + scoped_exit( C&& c ):callback( std::forward(c) ){} + + scoped_exit( scoped_exit&& mv ) + :callback( std::move( mv.callback ) ),canceled(mv.canceled) + { + mv.canceled = true; + } + + scoped_exit( const scoped_exit& ) = delete; + scoped_exit& operator=( const scoped_exit& ) = delete; + + ~scoped_exit() { + if (!canceled) + try { callback(); } catch( ... ) {} + } + + void cancel() { canceled = true; } + + private: + Callback callback; + bool canceled = false; + }; + + template + scoped_exit make_scoped_exit( Callback&& c ) { + return scoped_exit( std::forward(c) ); + } + +} diff --git a/libraries/libfc/include/fc/static_variant.hpp b/libraries/libfc/include/fc/static_variant.hpp new file mode 100644 index 0000000000..d9731490d2 --- /dev/null +++ b/libraries/libfc/include/fc/static_variant.hpp @@ -0,0 +1,93 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace fc { + +template +struct visitor {}; + +template +void from_index(variant& v, int index) +{ + if constexpr(i >= std::variant_size_v) + { + FC_THROW_EXCEPTION(fc::assert_exception, "Provided index out of range for variant."); + } + else if (index == 0) + { + auto value = variant(std::in_place_index); + v = std::move(value); + } + else + { + from_index(v, index - 1); + } +} + +template +constexpr std::size_t get_index() +{ + if constexpr (index == std::variant_size_v) + { + return index; + } + else if constexpr (std::is_same_v, T>) + { + return index; + } + else + { + return get_index(); + } +} + +struct from_static_variant +{ + variant& var; + from_static_variant( variant& dv ):var(dv){} + + template void operator()( const T& v )const + { + to_variant( v, var ); + } +}; + +struct to_static_variant +{ + const variant& var; + to_static_variant( const variant& dv ):var(dv){} + + template void operator()( T& v )const + { + from_variant( var, v ); + } +}; + +template void to_variant( const std::variant& s, fc::variant& v ) +{ + variants vars(2); + vars[0] = s.index(); + std::visit( from_static_variant(vars[1]), s ); + v = std::move(vars); +} + +template void from_variant( const fc::variant& v, std::variant& s ) +{ + auto ar = v.get_array(); + if( ar.size() < 2 ) + { + s = std::variant(); + return; + } + from_index(s, ar[0].as_uint64()); + std::visit( to_static_variant(ar[1]), s ); +} + +template struct get_typename { static const char* name() { return BOOST_CORE_TYPEID(std::variant).name(); } }; + +} \ No newline at end of file diff --git a/libraries/libfc/include/fc/string.hpp b/libraries/libfc/include/fc/string.hpp new file mode 100644 index 0000000000..74dcf690b1 --- /dev/null +++ b/libraries/libfc/include/fc/string.hpp @@ -0,0 +1,152 @@ +#pragma once +#include +#include + +#ifndef USE_FC_STRING +#include +#include +namespace fc +{ + typedef std::string string; + + int64_t to_int64( const fc::string& ); + uint64_t to_uint64( const fc::string& ); + double to_double( const fc::string& ); + fc::string to_string( double ); + fc::string to_string( uint64_t ); + fc::string to_string( int64_t ); + fc::string to_string( uint16_t ); + std::string to_pretty_string( int64_t ); + inline fc::string to_string( int32_t v ) { return to_string( int64_t(v) ); } + inline fc::string to_string( uint32_t v ){ return to_string( uint64_t(v) ); } +#ifdef __APPLE__ + inline fc::string to_string( size_t s) { return to_string(uint64_t(s)); } +#endif + + typedef std::optional ostring; + class variant_object; + fc::string format_string( const fc::string&, const variant_object&, bool minimize = false ); + fc::string trim( const fc::string& ); + fc::string to_lower( const fc::string& ); + string trim_and_normalize_spaces( const string& s ); +} + +#else + +/** + * There is debate about whether doing this is 'standard conforming', but + * it works everywhere and enables the purpose of this library which is to + * accelerate compiles while maintaining compatability. + */ +namespace std { + template + struct char_traits; + + template + class allocator; + + template + class basic_string; + + typedef basic_string, allocator > string; +} + + +namespace fc { + /** + * @brief wrapper on std::string + * + * Including results in 4000 lines of code + * that must be included to build your header. This + * class hides all of those details while maintaining + * compatability with std::string. Using fc::string + * instead of std::string can accelerate compile times + * 10x. + * + * The implementation of this class is std::string, this header simply + * accelerates compile times. fc::string is automatically convertable to / from + * std::string. + */ + class string { + public: + typedef char* iterator; + typedef const char* const_iterator; + enum { npos = size_t(-1) }; + // static const size_t npos;// = -1; + + string(); + string( const std::string& s ); + string( std::string&& s ); + string( const string& c ); + string( string&& c ); + string( const char* c ); + string( const char* c, int s ); + string( const_iterator b, const_iterator e ); + ~string(); + + operator std::string&(); + operator const std::string&()const; + + iterator begin(); + iterator end(); + + const_iterator begin()const; + const_iterator end()const; + + char& operator[](size_t idx); + const char& operator[](size_t idx)const; + + string& operator =( const string& c ); + string& operator =( string&& c ); + + void reserve( size_t ); + size_t size()const; + size_t find( char c, size_t pos = 0 )const; + size_t find(const fc::string& str, size_t pos = 0) const; + size_t find(const char* s, size_t pos = 0) const; + size_t rfind( char c, size_t pos = npos )const; + size_t rfind( const char* c, size_t pos = npos )const; + size_t rfind( const fc::string& c, size_t pos = npos )const; + size_t find_first_of (const fc::string& str, size_t pos = 0) const; + size_t find_first_of (const char* s, size_t pos = 0) const; + string& replace(size_t pos, size_t len, const fc::string& str); + string& replace(size_t pos, size_t len, const char* s); + + void resize( size_t s ); + void clear(); + + const char* c_str()const; + char* data(); + + bool operator == ( const char* s )const; + bool operator == ( const string& s )const; + bool operator != ( const string& s )const; + + friend bool operator < ( const string& a, const string& b ); + + string& operator+=( const string& s ); + string& operator+=( char c ); + + friend string operator + ( const string&, const string& ); + friend string operator + ( const string&, char c ); + + fc::string substr( size_t start, size_t len = fc::string::npos )const; + + private: + fc::fwd my; + }; + + int64_t to_int64( const fc::string& ); + uint64_t to_uint64( const fc::string& ); + double to_double( const fc::string& ); + fc::string to_string( double ); + fc::string to_string( uint64_t ); + fc::string to_string( int64_t ); + + typedef std::optional ostring; + class variant_object; + fc::string format_string( const fc::string&, const variant_object& ); + +} // namespace fc + +#endif diff --git a/libraries/libfc/include/fc/time.hpp b/libraries/libfc/include/fc/time.hpp new file mode 100644 index 0000000000..1c1b433bd5 --- /dev/null +++ b/libraries/libfc/include/fc/time.hpp @@ -0,0 +1,143 @@ +#pragma once +#include +#include + +#ifdef _MSC_VER + #pragma warning (push) + #pragma warning (disable : 4244) +#endif //// _MSC_VER + +namespace fc { + class microseconds { + public: + constexpr explicit microseconds( int64_t c = 0) :_count(c){} + static constexpr microseconds maximum() { return microseconds(0x7fffffffffffffffll); } + friend constexpr microseconds operator + (const microseconds& l, const microseconds& r ) { return microseconds(l._count+r._count); } + friend constexpr microseconds operator - (const microseconds& l, const microseconds& r ) { return microseconds(l._count-r._count); } + + constexpr bool operator==(const microseconds& c)const { return _count == c._count; } + constexpr bool operator!=(const microseconds& c)const { return _count != c._count; } + friend constexpr bool operator>(const microseconds& a, const microseconds& b){ return a._count > b._count; } + friend constexpr bool operator>=(const microseconds& a, const microseconds& b){ return a._count >= b._count; } + constexpr friend bool operator<(const microseconds& a, const microseconds& b){ return a._count < b._count; } + constexpr friend bool operator<=(const microseconds& a, const microseconds& b){ return a._count <= b._count; } + constexpr microseconds& operator+=(const microseconds& c) { _count += c._count; return *this; } + constexpr microseconds& operator-=(const microseconds& c) { _count -= c._count; return *this; } + constexpr int64_t count()const { return _count; } + constexpr int64_t to_seconds()const { return _count/1000000; } + private: + friend class time_point; + int64_t _count; + }; + inline constexpr microseconds seconds( int64_t s ) { return microseconds( s * 1000000 ); } + inline constexpr microseconds milliseconds( int64_t s ) { return microseconds( s * 1000 ); } + inline constexpr microseconds minutes(int64_t m) { return seconds(60*m); } + inline constexpr microseconds hours(int64_t h) { return minutes(60*h); } + inline constexpr microseconds days(int64_t d) { return hours(24*d); } + + class variant; + void to_variant( const fc::microseconds&, fc::variant& ); + void from_variant( const fc::variant& , fc::microseconds& ); + + class time_point { + public: + constexpr explicit time_point( microseconds e = microseconds() ) :elapsed(e){} + static time_point now(); + static constexpr time_point maximum() { return time_point( microseconds::maximum() ); } + static constexpr time_point min() { return time_point(); } + + operator fc::string()const; + static time_point from_iso_string( const fc::string& s ); + + constexpr const microseconds& time_since_epoch()const { return elapsed; } + constexpr uint32_t sec_since_epoch()const { return elapsed.count() / 1000000; } + constexpr bool operator > ( const time_point& t )const { return elapsed._count > t.elapsed._count; } + constexpr bool operator >=( const time_point& t )const { return elapsed._count >=t.elapsed._count; } + constexpr bool operator < ( const time_point& t )const { return elapsed._count < t.elapsed._count; } + constexpr bool operator <=( const time_point& t )const { return elapsed._count <=t.elapsed._count; } + constexpr bool operator ==( const time_point& t )const { return elapsed._count ==t.elapsed._count; } + constexpr bool operator !=( const time_point& t )const { return elapsed._count !=t.elapsed._count; } + constexpr time_point& operator += ( const microseconds& m) { elapsed+=m; return *this; } + constexpr time_point& operator -= ( const microseconds& m) { elapsed-=m; return *this; } + constexpr time_point operator + (const microseconds& m) const { return time_point(elapsed+m); } + constexpr time_point operator - (const microseconds& m) const { return time_point(elapsed-m); } + constexpr microseconds operator - (const time_point& m) const { return microseconds(elapsed.count() - m.elapsed.count()); } + private: + microseconds elapsed; + }; + + /** + * A lower resolution time_point accurate only to seconds from 1970 + */ + class time_point_sec + { + public: + constexpr time_point_sec() + :utc_seconds(0){} + + constexpr explicit time_point_sec(uint32_t seconds ) + :utc_seconds(seconds){} + + constexpr time_point_sec( const time_point& t ) + :utc_seconds( t.time_since_epoch().count() / 1000000ll ){} + + static constexpr time_point_sec maximum() { return time_point_sec(0xffffffff); } + static constexpr time_point_sec min() { return time_point_sec(0); } + + constexpr operator time_point()const { return time_point( fc::seconds( utc_seconds) ); } + constexpr uint32_t sec_since_epoch()const { return utc_seconds; } + + constexpr time_point_sec operator = ( const fc::time_point& t ) + { + utc_seconds = t.time_since_epoch().count() / 1000000ll; + return *this; + } + constexpr friend bool operator < ( const time_point_sec& a, const time_point_sec& b ) { return a.utc_seconds < b.utc_seconds; } + constexpr friend bool operator > ( const time_point_sec& a, const time_point_sec& b ) { return a.utc_seconds > b.utc_seconds; } + constexpr friend bool operator <= ( const time_point_sec& a, const time_point_sec& b ) { return a.utc_seconds <= b.utc_seconds; } + constexpr friend bool operator >= ( const time_point_sec& a, const time_point_sec& b ) { return a.utc_seconds >= b.utc_seconds; } + constexpr friend bool operator == ( const time_point_sec& a, const time_point_sec& b ) { return a.utc_seconds == b.utc_seconds; } + constexpr friend bool operator != ( const time_point_sec& a, const time_point_sec& b ) { return a.utc_seconds != b.utc_seconds; } + constexpr time_point_sec& operator += ( uint32_t m ) { utc_seconds+=m; return *this; } + constexpr time_point_sec& operator += ( microseconds m ) { utc_seconds+=m.to_seconds(); return *this; } + constexpr time_point_sec& operator -= ( uint32_t m ) { utc_seconds-=m; return *this; } + constexpr time_point_sec& operator -= ( microseconds m ) { utc_seconds-=m.to_seconds(); return *this; } + constexpr time_point_sec operator +( uint32_t offset )const { return time_point_sec(utc_seconds + offset); } + constexpr time_point_sec operator -( uint32_t offset )const { return time_point_sec(utc_seconds - offset); } + + friend constexpr time_point operator + ( const time_point_sec& t, const microseconds& m ) { return time_point(t) + m; } + friend constexpr time_point operator - ( const time_point_sec& t, const microseconds& m ) { return time_point(t) - m; } + friend constexpr microseconds operator - ( const time_point_sec& t, const time_point_sec& m ) { return time_point(t) - time_point(m); } + friend constexpr microseconds operator - ( const time_point& t, const time_point_sec& m ) { return time_point(t) - time_point(m); } + + fc::string to_non_delimited_iso_string()const; + fc::string to_iso_string()const; + + operator fc::string()const; + static time_point_sec from_iso_string( const fc::string& s ); + + private: + uint32_t utc_seconds; + }; + + typedef std::optional otime_point; + + /** return a human-readable approximate time, relative to now() + * e.g., "4 hours ago", "2 months ago", etc. + */ + string get_approximate_relative_time_string(const time_point_sec& event_time, + const time_point_sec& relative_to_time = fc::time_point::now(), + const std::string& ago = " ago"); + string get_approximate_relative_time_string(const time_point& event_time, + const time_point& relative_to_time = fc::time_point::now(), + const std::string& ago = " ago"); +} + +#include +FC_REFLECT_TYPENAME( fc::time_point ) +FC_REFLECT_TYPENAME( fc::microseconds ) +FC_REFLECT_TYPENAME( fc::time_point_sec ) + +#ifdef _MSC_VER + #pragma warning (pop) +#endif /// #ifdef _MSC_VER diff --git a/libraries/libfc/include/fc/tuple.hpp b/libraries/libfc/include/fc/tuple.hpp new file mode 100644 index 0000000000..9fe9638fa4 --- /dev/null +++ b/libraries/libfc/include/fc/tuple.hpp @@ -0,0 +1,136 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + + /** + * Provides a fast-compiling tuple that doesn't use fancy meta-programming + * techniques. It is limited to 4 parameters which is sufficient for most + * methods argument lists which is the primary use case for this tuple. Methods + * that require more than 4 parameters are probably better served by defining + * a struct. + * + * The members of the tuple are easily visited with a simple visitor functor + * of the form: + * @code + * struct visitor { + * template + * void operator()( MemberType& m ); + * + * template + * void operator()( const MemberType& m ); + * }; + * @endcode + template + struct tuple { + tuple(){} + enum size_enum { size = 4 }; + + template + tuple( AA&& aa, BB&& bb, CC&& cc, DD&& dd ) + :a( fc::forward(aa) ), + b( fc::forward(bb) ), + c( fc::forward(cc) ), + d( fc::forward
(dd) ) + {} + + template + void visit( V&& v ) { v(a); v(b); v(c); v(d); } + template + void visit( V&& v )const { v(a); v(b); v(c); v(d); } + + A a; + B b; + C c; + D d; + }; + */ + template struct tuple{}; + + template<> + struct tuple<> { + enum size_enum { size = 0 }; + template + void visit( V&& v)const{}; + }; + template + auto call_fused( Functor f, const tuple<>& t ) -> decltype( f( ) ) { + return f(); + } + + inline tuple<> make_tuple(){ return tuple<>(); } + + template + struct is_tuple { + typedef fc::false_type type; + }; + + #define RREF_PARAMS(z,n,data) BOOST_PP_CAT(AA,n)&& BOOST_PP_CAT(p,n) + #define ILIST_PARAMS(z,n,data) BOOST_PP_CAT(a,n)( fc::forward( BOOST_PP_CAT(p,n) ) ) + #define ILIST_PARAMS_COPY(z,n,data) BOOST_PP_CAT(a,n)( t.BOOST_PP_CAT(a,n) ) + #define VISIT_PARAMS(z,n,data) v(BOOST_PP_CAT(a,n)); + #define LIST_MEMBERS_ON(z,n,data) data.BOOST_PP_CAT(a,n) + #define DEDUCE_MEMBERS(z,n,data) typename fc::deduce::type + #define FORWARD_PARAMS(z,n,data) fc::forward(BOOST_PP_CAT(p,n)) + #define MEM_PARAMS(z,n,data) BOOST_PP_CAT(A,n) BOOST_PP_CAT(a,n); + #define TUPLE(z,n,unused) \ + template \ + struct tuple { \ + enum size_enum { size = n }; \ + template \ + explicit tuple( BOOST_PP_ENUM(n, RREF_PARAMS, unused ) )BOOST_PP_IF(n,:,BOOST_PP_EMPTY())BOOST_PP_ENUM( n, ILIST_PARAMS,unused){} \ + tuple( const tuple& t )BOOST_PP_IF(n,:,BOOST_PP_EMPTY())BOOST_PP_ENUM( n, ILIST_PARAMS_COPY,unused){} \ + tuple( tuple&& t )BOOST_PP_IF(n,:,BOOST_PP_EMPTY())BOOST_PP_ENUM( n, ILIST_PARAMS_COPY,unused){} \ + tuple(){}\ + template\ + void visit( V&& v ) { BOOST_PP_REPEAT(n,VISIT_PARAMS,a) }\ + template\ + void visit( V&& v )const { BOOST_PP_REPEAT(n,VISIT_PARAMS,a) }\ + BOOST_PP_REPEAT(n,MEM_PARAMS,a) \ + }; \ + template \ + tuple make_tuple( BOOST_PP_ENUM( n, RREF_PARAMS, unused) ) { \ + return tuple( BOOST_PP_ENUM( n, FORWARD_PARAMS,unused ) ); \ + } \ + template \ + auto call_fused( Functor f, tuple& t ) \ + -> decltype( f( BOOST_PP_ENUM( n, LIST_MEMBERS_ON, t) ) ) { \ + return f( BOOST_PP_ENUM( n, LIST_MEMBERS_ON, t) ); \ + } \ + template \ + auto call_fused( Functor f, const tuple& t ) \ + -> decltype( f( BOOST_PP_ENUM( n, LIST_MEMBERS_ON, t) ) ) { \ + return f( BOOST_PP_ENUM( n, LIST_MEMBERS_ON, t) ); \ + } \ + template \ + struct is_tuple > { \ + typedef fc::true_type type; \ + }; \ + template \ + struct deduce > { \ + typedef fc::tuple type; \ + }; + + BOOST_PP_REPEAT_FROM_TO( 1, 5, TUPLE, unused ) + + + #undef FORWARD_PARAMS + #undef DEDUCE_MEMBERS + #undef RREF_PARAMS + #undef LIST_MEMBERS_ON + #undef ILIST_PARAMS + #undef ILIST_PARAMS_COPY + #undef VISIT_PARAMS + #undef MEM_PARAMS + #undef TUPLE + + +} + diff --git a/libraries/libfc/include/fc/uint128.hpp b/libraries/libfc/include/fc/uint128.hpp new file mode 100644 index 0000000000..4663ccc4f7 --- /dev/null +++ b/libraries/libfc/include/fc/uint128.hpp @@ -0,0 +1,160 @@ +#pragma once +#include +#include +#include + +#include +#include + +#ifdef _MSC_VER + #pragma warning (push) + #pragma warning (disable : 4244) +#endif //// _MSC_VER + +namespace fc +{ + class bigint; + /** + * @brief an implementation of 128 bit unsigned integer + * + */ + class uint128 + { + + + public: + uint128():hi(0),lo(0){} + uint128( uint32_t l ):hi(0),lo(l){} + uint128( int32_t l ):hi( -(l<0) ),lo(l){} + uint128( int64_t l ):hi( -(l<0) ),lo(l){} + uint128( uint64_t l ):hi(0),lo(l){} + uint128( const std::string& s ); + uint128( uint64_t _h, uint64_t _l ) + :hi(_h),lo(_l){} + uint128( const fc::bigint& bi ); + explicit uint128( unsigned __int128 i ):hi( i >> 64 ), lo(i){ } + + operator std::string()const; + operator fc::bigint()const; + + explicit operator unsigned __int128()const { + unsigned __int128 result(hi); + result <<= 64; + return result | lo; + } + + bool operator == ( const uint128& o )const{ return hi == o.hi && lo == o.lo; } + bool operator != ( const uint128& o )const{ return hi != o.hi || lo != o.lo; } + bool operator < ( const uint128& o )const { return (hi == o.hi) ? lo < o.lo : hi < o.hi; } + bool operator < ( const int64_t& o )const { return *this < uint128(o); } + bool operator !()const { return !(hi !=0 || lo != 0); } + uint128 operator -()const { return ++uint128( ~hi, ~lo ); } + uint128 operator ~()const { return uint128( ~hi, ~lo ); } + + uint128& operator++() { hi += (++lo == 0); return *this; } + uint128& operator--() { hi -= (lo-- == 0); return *this; } + uint128 operator++(int) { auto tmp = *this; ++(*this); return tmp; } + uint128 operator--(int) { auto tmp = *this; --(*this); return tmp; } + + uint128& operator |= ( const uint128& u ) { hi |= u.hi; lo |= u.lo; return *this; } + uint128& operator &= ( const uint128& u ) { hi &= u.hi; lo &= u.lo; return *this; } + uint128& operator ^= ( const uint128& u ) { hi ^= u.hi; lo ^= u.lo; return *this; } + uint128& operator <<= ( const uint128& u ); + uint128& operator >>= ( const uint128& u ); + + uint128& operator += ( const uint128& u ) { const uint64_t old = lo; lo += u.lo; hi += u.hi + (lo < old); return *this; } + uint128& operator -= ( const uint128& u ) { return *this += -u; } + uint128& operator *= ( const uint128& u ); + uint128& operator /= ( const uint128& u ); + uint128& operator %= ( const uint128& u ); + + + friend uint128 operator + ( const uint128& l, const uint128& r ) { return uint128(l)+=r; } + friend uint128 operator - ( const uint128& l, const uint128& r ) { return uint128(l)-=r; } + friend uint128 operator * ( const uint128& l, const uint128& r ) { return uint128(l)*=r; } + friend uint128 operator / ( const uint128& l, const uint128& r ) { return uint128(l)/=r; } + friend uint128 operator % ( const uint128& l, const uint128& r ) { return uint128(l)%=r; } + friend uint128 operator | ( const uint128& l, const uint128& r ) { return uint128(l)|=(r); } + friend uint128 operator & ( const uint128& l, const uint128& r ) { return uint128(l)&=r; } + friend uint128 operator ^ ( const uint128& l, const uint128& r ) { return uint128(l)^=r; } + friend uint128 operator << ( const uint128& l, const uint128& r ) { return uint128(l)<<=r; } + friend uint128 operator >> ( const uint128& l, const uint128& r ) { return uint128(l)>>=r; } + friend bool operator > ( const uint128& l, const uint128& r ) { return r < l; } + friend bool operator > ( const uint128& l, const int64_t& r ) { return uint128(r) < l; } + friend bool operator > ( const int64_t& l, const uint128& r ) { return r < uint128(l); } + + friend bool operator >= ( const uint128& l, const uint128& r ) { return l == r || l > r; } + friend bool operator >= ( const uint128& l, const int64_t& r ) { return l >= uint128(r); } + friend bool operator >= ( const int64_t& l, const uint128& r ) { return uint128(l) >= r; } + friend bool operator <= ( const uint128& l, const uint128& r ) { return l == r || l < r; } + friend bool operator <= ( const uint128& l, const int64_t& r ) { return l <= uint128(r); } + friend bool operator <= ( const int64_t& l, const uint128& r ) { return uint128(l) <= r; } + + friend std::size_t hash_value( const uint128& v ) { return city_hash_size_t((const char*)&v, sizeof(v)); } + + uint32_t to_integer()const + { + FC_ASSERT( hi == 0 ); + uint32_t lo32 = (uint32_t) lo; + FC_ASSERT( lo == lo32 ); + return lo32; + } + uint64_t to_uint64()const + { + FC_ASSERT( hi == 0 ); + return lo; + } + uint32_t low_32_bits()const { return (uint32_t) lo; } + uint64_t low_bits()const { return lo; } + uint64_t high_bits()const { return hi; } + + static uint128 max_value() { + const uint64_t max64 = std::numeric_limits::max(); + return uint128( max64, max64 ); + } + + static void full_product( const uint128& a, const uint128& b, uint128& result_hi, uint128& result_lo ); + + uint8_t popcount() const; + + // fields must be public for serialization + uint64_t hi; + uint64_t lo; + }; + static_assert( sizeof(uint128) == 2*sizeof(uint64_t), "validate packing assumptions" ); + + class variant; + + void to_variant( const uint128& var, variant& vo ); + void from_variant( const variant& var, uint128& vo ); +// void to_variant( const unsigned __int128& var, variant& vo ); +// void from_variant( const variant& var, unsigned __int128& vo ); + + namespace raw + { + template + inline void pack( Stream& s, const uint128& u ) { s.write( (char*)&u, sizeof(u) ); } + template + inline void unpack( Stream& s, uint128& u ) { s.read( (char*)&u, sizeof(u) ); } + } + + size_t city_hash_size_t(const char *buf, size_t len); +} // namespace fc + +namespace std +{ + template<> + struct hash + { + size_t operator()( const fc::uint128& s )const + { + return fc::city_hash_size_t((char*)&s, sizeof(s)); + } + }; +} + +FC_REFLECT( fc::uint128, (hi)(lo) ) + +#ifdef _MSC_VER + #pragma warning (pop) +#endif ///_MSC_VER diff --git a/libraries/libfc/include/fc/unique_ptr.hpp b/libraries/libfc/include/fc/unique_ptr.hpp new file mode 100644 index 0000000000..609eff8b3c --- /dev/null +++ b/libraries/libfc/include/fc/unique_ptr.hpp @@ -0,0 +1,60 @@ +#pragma once +#include +#include + +namespace fc +{ + template + class unique_ptr + { + public: + typedef T* pointer; + + explicit unique_ptr( pointer t = nullptr ):_p(t){} + + unique_ptr( unique_ptr&& m ) + :_p(m._p){ m._p = nullptr; } + + ~unique_ptr() { delete _p; } + + operator bool()const { return _p != nullptr; } + friend bool operator==(const unique_ptr& p, nullptr_t) + { + return p._p == nullptr; + } + + friend bool operator!=(const unique_ptr& p, nullptr_t) + { + return p._p != nullptr; + } + + unique_ptr& operator=( nullptr_t ) + { + delete _p; _p = nullptr; + } + + unique_ptr& operator=( unique_ptr&& o ) + { + fc_swap( _p, o._p ); + return *this; + } + + pointer operator->()const { return _p; } + T& operator*()const { return *_p; } + + void reset( pointer v ) + { + delete _p; _p = v; + } + pointer release() + { + auto tmp = _p; + _p = nullptr; + return tmp; + } + + private: + pointer _p; + }; + +} diff --git a/libraries/libfc/include/fc/utf8.hpp b/libraries/libfc/include/fc/utf8.hpp new file mode 100644 index 0000000000..84468690c7 --- /dev/null +++ b/libraries/libfc/include/fc/utf8.hpp @@ -0,0 +1,39 @@ +#ifndef __UTF8_HPP +#define __UTF8_HPP + +#include + +/// This file contains general purpose utilities related to UTF-8 <-> Unicode conversions + +namespace fc +{ + + /** + * 0x80-0x9F C1 control characters are considered invalid as well as invalid utf8 code points. + * @return true if prune_invalid_utf8 should be called. + */ + bool is_valid_utf8( const std::string_view& str ); + + /** + * Invalid utf8 code points are purned, 0x80-0x9F C1 control characters are escaped + */ + std::string prune_invalid_utf8( const std::string_view& str ); + + bool is_utf8( const std::string& str ); + + /** Decodes utf 8 std::string into unicode string. + @param input - input string to be decoded and stored in 'storage' + @param storage - buffer for converted text. Cannot be nullptr. + */ + void decodeUtf8(const std::string& input, std::wstring* storage); + + /** Encodes given wide (unicode) string into UTF-8 representation. + @param input - input string to be encoded and stored in 'storage' + @param storage - buffer for converted text. Cannot be nullptr. + */ + void encodeUtf8(const std::wstring& input, std::string* storage); + +} /// namespace fc + +#endif ///__UTF8_HPP + diff --git a/libraries/libfc/include/fc/utility.hpp b/libraries/libfc/include/fc/utility.hpp new file mode 100644 index 0000000000..f30623a7cf --- /dev/null +++ b/libraries/libfc/include/fc/utility.hpp @@ -0,0 +1,218 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#pragma warning(disable: 4482) // nonstandard extension used enum Name::Val, standard in C++11 +#define NO_RETURN __declspec(noreturn) +#else +#define NO_RETURN __attribute__((noreturn)) +#endif + +#define MAX_NUM_ARRAY_ELEMENTS (1024*1024) +#define MAX_SIZE_OF_BYTE_ARRAYS (20*1024*1024) + +//namespace std { +// typedef decltype(sizeof(int)) size_t; +// typedef decltype(nullptr) nullptr_t; +//} + +namespace fc { + using std::size_t; + typedef decltype(nullptr) nullptr_t; + + template struct remove_reference { typedef T type; }; + template struct remove_reference { typedef T type; }; + template struct remove_reference { typedef T type; }; + + template struct deduce { typedef T type; }; + template struct deduce { typedef T type; }; + template struct deduce { typedef T type; }; + template struct deduce { typedef T type; }; + template struct deduce{ typedef T type; }; + + + using std::move; + using std::forward; + + struct true_type { enum _value { value = 1 }; }; + struct false_type { enum _value { value = 0 }; }; + + namespace detail { + template fc::true_type is_class_helper(void(T::*)()); + template fc::false_type is_class_helper(...); + + template + struct supports_allocator { + public: + static constexpr bool value = std::uses_allocator::value; + static constexpr bool leading_allocator = std::is_constructible< T, std::allocator_arg_t, A, Args... >::value; + static constexpr bool trailing_allocator = std::is_constructible< T, Args..., A>::value; + }; + + template + auto construct_maybe_with_allocator( A&& allocator, Args&&... args ) + -> std::enable_if_t::value && supports_allocator::leading_allocator, T> + { + return T( std::allocator_arg, std::forward(allocator), std::forward(args)... ); + } + + template + auto construct_maybe_with_allocator( A&& allocator, Args&&... args ) + -> std::enable_if_t::value && !supports_allocator::leading_allocator, T> + { + static_assert( supports_allocator::trailing_allocator, "type supposedly supports allocators but cannot be constructed by either the leading- or trailing-allocator convention" ); + return T( std::forward(args)..., std::forward(allocator) ); + } + + template + auto construct_maybe_with_allocator( A&&, Args&&... args ) + -> std::enable_if_t::value , T> + { + return T( std::forward(args)... ); + } + + template + auto maybe_augment_constructor_arguments_with_allocator( + A&& allocator, std::tuple args, + std::enable_if_t::value && supports_allocator::leading_allocator, int> = 0 + ) { + return std::tuple_cat( std::forward_as_tuple( std::allocator_arg, allocator ), args ); + } + + template + auto maybe_augment_constructor_arguments_with_allocator( + A&& allocator, std::tuple<>, + std::enable_if_t::value && supports_allocator::leading_allocator, int> = 0 + ) { + return std::forward_as_tuple( std::allocator_arg, allocator ); + } + + template + auto maybe_augment_constructor_arguments_with_allocator( + A&& allocator, std::tuple args, + std::enable_if_t::value && !supports_allocator::leading_allocator, int> = 0 + ) { + static_assert( supports_allocator::trailing_allocator, "type supposedly supports allocators but cannot be constructed by either the leading- or trailing-allocator convention" ); + return std::tuple_cat( args, std::forward_as_tuple( allocator ) ); + } + + template + auto maybe_augment_constructor_arguments_with_allocator( + A&& allocator, std::tuple<>, + std::enable_if_t::value && !supports_allocator::leading_allocator, int> = 0 + ) { + static_assert( supports_allocator::trailing_allocator, "type supposedly supports allocators but cannot be constructed by either the leading- or trailing-allocator convention" ); + return std::forward_as_tuple( allocator ); + } + + template + auto maybe_augment_constructor_arguments_with_allocator( + A&&, std::tuple args, + std::enable_if_t::value, int> = 0 + ) { + return args; + } + + template + std::pair default_construct_pair_maybe_with_allocator( A&& allocator ) + { + return std::pair( + std::piecewise_construct, + maybe_augment_constructor_arguments_with_allocator( allocator, std::make_tuple() ), + maybe_augment_constructor_arguments_with_allocator( allocator, std::make_tuple() ) + ); + } + } + + template + struct is_class { typedef decltype(detail::is_class_helper(0)) type; enum value_enum { value = type::value }; }; +#ifdef min +#undef min +#endif + template + const T& min( const T& a, const T& b ) { return a < b ? a: b; } + + constexpr size_t const_strlen(const char* str) { + int i = 0; + while(*(str+i) != '\0') + i++; + return i; + } + + + template + void move_append(Container& dest, Container&& src ) { + if (src.empty()) { + return; + } else if (dest.empty()) { + dest = std::move(src); + } else { + dest.insert(std::end(dest), std::make_move_iterator(std::begin(src)), std::make_move_iterator(std::end(src))); + } + } + + template + void copy_append(Container& dest, const Container& src ) { + if (src.empty()) { + return; + } else { + dest.insert(std::end(dest), std::begin(src), std::end(src)); + } + } + + template + void deduplicate( Container& entries ) { + if (entries.size() > 1) { + std::sort( entries.begin(), entries.end() ); + auto itr = std::unique( entries.begin(), entries.end() ); + entries.erase( itr, entries.end() ); + } + } + + /** + * std::function type that verifies std::function is set before invocation + */ + template + class optional_delegate; + + template + class optional_delegate : private std::function { + public: + using std::function::function; + + auto operator()( Args... args ) const -> R { + if (static_cast(*this)) { + if constexpr( std::is_move_constructible_v ) { + return std::function::operator()(std::move(args)...); + } else { + return std::function::operator()(args...); + } + } else { + if constexpr( !std::is_void_v ) { + return {}; + } + } + } + }; + + using yield_function_t = optional_delegate; + +} + + // outside of namespace fc becuase of VC++ conflict with std::swap + template + void fc_swap( T& a, T& b ) { + T tmp = fc::move(a); + a = fc::move(b); + b = fc::move(tmp); + } + +#define LLCONST(constant) static_cast(constant##ll) +#define ULLCONST(constant) static_cast(constant##ull) diff --git a/libraries/libfc/include/fc/variant.hpp b/libraries/libfc/include/fc/variant.hpp new file mode 100644 index 0000000000..c7edc71377 --- /dev/null +++ b/libraries/libfc/include/fc/variant.hpp @@ -0,0 +1,712 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include // memset + +#include +#include +#include +#include +#include +#include +#include + +namespace fc +{ + /** + * @defgroup serializable Serializable _types + * @brief Clas_ses that may be converted to/from an variant + * + * To make a class 'serializable' the following methods must be available + * for your Serializable_type + * + * @code + * void to_variant( const Serializable_type& e, variant& v ); + * void from_variant( const variant& e, Serializable_type& ll ); + * @endcode + */ + + class variant; + class variant_object; + class mutable_variant_object; + class time_point; + class time_point_sec; + class microseconds; + template struct safe; + + struct blob { std::vector data; }; + + void to_variant( const blob& var, fc::variant& vo ); + void from_variant( const fc::variant& var, blob& vo ); + + + template void to_variant( const boost::multi_index_container& s, fc::variant& v ); + template void from_variant( const fc::variant& v, boost::multi_index_container& s ); + + template + using UInt = boost::multiprecision::number< + boost::multiprecision::cpp_int_backend >; + template + using Int = boost::multiprecision::number< + boost::multiprecision::cpp_int_backend >; + + void to_variant( const UInt<8>& n, fc::variant& v ); + void from_variant( const fc::variant& v, UInt<8>& n ); + + void to_variant( const UInt<16>& n, fc::variant& v ); + void from_variant( const fc::variant& v, UInt<16>& n ); + + void to_variant( const UInt<32>& n, fc::variant& v ); + void from_variant( const fc::variant& v, UInt<32>& n ); + + void to_variant( const UInt<64>& n, fc::variant& v ); + void from_variant( const fc::variant& v, UInt<64>& n ); + + template void to_variant( const boost::multiprecision::number& n, fc::variant& v ); + template void from_variant( const fc::variant& v, boost::multiprecision::number& n ); + + template void to_variant( const safe& s, fc::variant& v ); + template void from_variant( const fc::variant& v, safe& s ); + template void to_variant( const std::unique_ptr& s, fc::variant& v ); + template void from_variant( const fc::variant& v, std::unique_ptr& s ); + + template void to_variant( const std::variant& s, fc::variant& v ); + template void from_variant( const fc::variant& v, std::variant& s ); + + void to_variant( const uint8_t& var, fc::variant& vo ); + void from_variant( const fc::variant& var, uint8_t& vo ); + void to_variant( const int8_t& var, fc::variant& vo ); + void from_variant( const fc::variant& var, int8_t& vo ); + + void to_variant( const uint16_t& var, fc::variant& vo ); + void from_variant( const fc::variant& var, uint16_t& vo ); + void to_variant( const int16_t& var, fc::variant& vo ); + void from_variant( const fc::variant& var, int16_t& vo ); + + void to_variant( const uint32_t& var, fc::variant& vo ); + void from_variant( const fc::variant& var, uint32_t& vo ); + void to_variant( const int32_t& var, fc::variant& vo ); + void from_variant( const fc::variant& var, int32_t& vo ); + + void to_variant( const unsigned __int128& var, fc::variant& vo ); + void from_variant( const fc::variant& var, unsigned __int128& vo ); + void to_variant( const __int128& var, fc::variant& vo ); + void from_variant( const fc::variant& var, __int128& vo ); + + void to_variant( const variant_object& var, fc::variant& vo ); + void from_variant( const fc::variant& var, variant_object& vo ); + void to_variant( const mutable_variant_object& var, fc::variant& vo ); + void from_variant( const fc::variant& var, mutable_variant_object& vo ); + void to_variant( const std::vector& var, fc::variant& vo ); + void from_variant( const fc::variant& var, std::vector& vo ); + + template + void to_variant( const std::unordered_map& var, fc::variant& vo ); + template + void from_variant( const fc::variant& var, std::unordered_map& vo ); + + template + void to_variant( const std::map& var, fc::variant& vo ); + template + void from_variant( const fc::variant& var, std::map& vo ); + template + void to_variant( const std::multimap& var, fc::variant& vo ); + template + void from_variant( const fc::variant& var, std::multimap& vo ); + + + template + void to_variant( const std::unordered_set& var, fc::variant& vo ); + template + void from_variant( const fc::variant& var, std::unordered_set& vo ); + + template + void to_variant( const std::deque& var, fc::variant& vo ); + template + void from_variant( const fc::variant& var, std::deque& vo ); + + template + void to_variant( const boost::container::deque& d, fc::variant& vo ); + template + void from_variant( const fc::variant& v, boost::container::deque& d ); + + template + void to_variant( const std::set& var, fc::variant& vo ); + template + void from_variant( const fc::variant& var, std::set& vo ); + + template + void to_variant( const std::array& var, fc::variant& vo ); + template + void from_variant( const fc::variant& var, std::array& vo ); + + void to_variant( const time_point& var, fc::variant& vo ); + void from_variant( const fc::variant& var, time_point& vo ); + + void to_variant( const time_point_sec& var, fc::variant& vo ); + void from_variant( const fc::variant& var, time_point_sec& vo ); + + void to_variant( const microseconds& input_microseconds, fc::variant& output_variant ); + void from_variant( const fc::variant& input_variant, microseconds& output_microseconds ); + + #ifdef __APPLE__ + void to_variant( size_t s, fc::variant& v ); + #elif !defined(_MSC_VER) + void to_variant( long long int s, fc::variant& v ); + void to_variant( unsigned long long int s, fc::variant& v ); + #endif + void to_variant( const std::string& s, fc::variant& v ); + + template + void to_variant( const std::shared_ptr& var, fc::variant& vo ); + + template + void from_variant( const fc::variant& var, std::shared_ptr& vo ); + + typedef std::vector variants; + template + void to_variant( const std::pair& t, fc::variant& v ); + template + void from_variant( const fc::variant& v, std::pair& p ); + + + + /** + * @brief stores null, int64, uint64, double, bool, string, std::vector, + * and variant_object's. + * + * variant's allocate everything but strings, arrays, and objects on the + * stack and are 'move aware' for values allcoated on the heap. + * + * Memory usage on 64 bit systems is 16 bytes and 12 bytes on 32 bit systems. + */ + class variant + { + public: + enum type_id + { + null_type = 0, + int64_type = 1, + uint64_type = 2, + double_type = 3, + bool_type = 4, + string_type = 5, + array_type = 6, + object_type = 7, + blob_type = 8 + }; + + /// Constructs a null_type variant + variant(); + /// Constructs a null_type variant + variant( nullptr_t ); + + /// @param str - UTF8 string + variant( const char* str ); + variant( char* str ); + variant( wchar_t* str ); + variant( const wchar_t* str ); + variant( float val ); + variant( uint8_t val ); + variant( int8_t val ); + variant( uint16_t val ); + variant( int16_t val ); + variant( uint32_t val ); + variant( int32_t val ); + variant( uint64_t val ); + variant( int64_t val ); + variant( double val ); + variant( bool val ); + variant( blob val ); + variant( fc::string val ); + variant( variant_object ); + variant( mutable_variant_object ); + variant( variants ); + variant( const variant& ); + variant( variant&& ); + ~variant(); + + /** + * Read-only access to the content of the variant. + */ + class visitor + { + public: + virtual ~visitor(){} + /// handles null_type variants + virtual void handle()const = 0; + virtual void handle( const int64_t& v )const = 0; + virtual void handle( const uint64_t& v )const = 0; + virtual void handle( const double& v )const = 0; + virtual void handle( const bool& v )const = 0; + virtual void handle( const string& v )const = 0; + virtual void handle( const variant_object& v)const = 0; + virtual void handle( const variants& v)const = 0; + virtual void handle( const blob& v)const = 0; + }; + + void visit( const visitor& v )const; + + type_id get_type()const; + + bool is_null()const; + bool is_string()const; + bool is_bool()const; + bool is_int64()const; + bool is_uint64()const; + bool is_double()const; + bool is_object()const; + bool is_array()const; + bool is_blob()const; + /** + * int64, uint64, double,bool + */ + bool is_numeric()const; + /** + * int64, uint64, bool + */ + bool is_integer()const; + + int64_t as_int64()const; + uint64_t as_uint64()const; + bool as_bool()const; + double as_double()const; + + blob& get_blob(); + const blob& get_blob()const; + blob as_blob()const; + + /** Convert's double, ints, bools, etc to a string + * @throw if get_type() == array_type | get_type() == object_type + */ + string as_string()const; + + /// @pre get_type() == string_type + const string& get_string()const; + + /// @throw if get_type() != array_type | null_type + variants& get_array(); + + /// @throw if get_type() != array_type + const variants& get_array()const; + + /// @throw if get_type() != object_type | null_type + variant_object& get_object(); + + /// @throw if get_type() != object_type + const variant_object& get_object()const; + + /// @pre is_object() + const variant& operator[]( const char* )const; + /// @pre is_array() + const variant& operator[]( size_t pos )const; + /// @pre is_array() + size_t size()const; + + size_t estimated_size()const; + /** + * _types that use non-intrusive variant conversion can implement the + * following method to implement conversion from variant to T. + * + * + * void from_variant( const Variant& var, T& val ) + * + * + * The above form is not always convienant, so the this templated + * method is used to enable conversion from Variants to other + * types. + */ + template + T as()const + { + T tmp; + from_variant( *this, tmp ); + return tmp; + } + + template + void as( T& v )const + { + from_variant( *this, v ); + } + + variant& operator=( variant&& v ); + variant& operator=( const variant& v ); + + template + variant& operator=( T&& v ) + { + return *this = variant( fc::forward(v) ); + } + + template + explicit variant( const std::optional& v ) + { + memset( this, 0, sizeof(*this) ); + if( v.has_value() ) *this = variant(*v); + } + + template + explicit variant( const T& val ); + + template + explicit variant( const T& val, const fc::yield_function_t& yield ); + + void clear(); + private: + void init(); + double _data; ///< Alligned according to double requirements + char _type[sizeof(void*)]; ///< pad to void* size + }; + + typedef std::optional ovariant; + + /** @ingroup Serializable */ + void from_variant( const fc::variant& var, string& vo ); + /** @ingroup Serializable */ + void from_variant( const fc::variant& var, fc::variants& vo ); + void from_variant( const fc::variant& var, fc::variant& vo ); + /** @ingroup Serializable */ + void from_variant( const fc::variant& var, int64_t& vo ); + /** @ingroup Serializable */ + void from_variant( const fc::variant& var, uint64_t& vo ); + /** @ingroup Serializable */ + void from_variant( const fc::variant& var, bool& vo ); + /** @ingroup Serializable */ + void from_variant( const fc::variant& var, double& vo ); + /** @ingroup Serializable */ + void from_variant( const fc::variant& var, float& vo ); + /** @ingroup Serializable */ + void from_variant( const fc::variant& var, int32_t& vo ); + /** @ingroup Serializable */ + void from_variant( const fc::variant& var, uint32_t& vo ); + /** @ingroup Serializable */ + template + void from_variant( const variant& var, std::optional& vo ) + { + if( var.is_null() ) vo = std::optional(); + else + { + vo = T(); + from_variant( var, *vo ); + } + } + template + void to_variant( const std::unordered_set& var, fc::variant& vo ) + { + if( var.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + std::vector vars(var.size()); + size_t i = 0; + for( auto itr = var.begin(); itr != var.end(); ++itr, ++i ) + vars[i] = fc::variant(*itr); + vo = vars; + } + template + void from_variant( const fc::variant& var, std::unordered_set& vo ) + { + const variants& vars = var.get_array(); + if( vars.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + vo.clear(); + vo.reserve( vars.size() ); + for( auto itr = vars.begin(); itr != vars.end(); ++itr ) + vo.insert( itr->as() ); + } + + + template + void to_variant( const std::unordered_map& var, fc::variant& vo ) + { + if( var.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + std::vector< fc::variant > vars(var.size()); + size_t i = 0; + for( auto itr = var.begin(); itr != var.end(); ++itr, ++i ) + vars[i] = fc::variant(*itr); + vo = vars; + } + template + void from_variant( const fc::variant& var, std::unordered_map& vo ) + { + const variants& vars = var.get_array(); + if( vars.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + vo.clear(); + for( auto itr = vars.begin(); itr != vars.end(); ++itr ) + vo.insert( itr->as< std::pair >() ); + + } + template + void to_variant( const std::map& var, fc::variant& vo ) + { + if( var.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + std::vector< fc::variant > vars(var.size()); + size_t i = 0; + for( auto itr = var.begin(); itr != var.end(); ++itr, ++i ) + vars[i] = fc::variant(*itr); + vo = vars; + } + template + void from_variant( const fc::variant& var, std::map& vo ) + { + const variants& vars = var.get_array(); + if( vars.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + vo.clear(); + for( auto itr = vars.begin(); itr != vars.end(); ++itr ) + vo.insert( itr->as< std::pair >() ); + } + + template + void to_variant( const std::multimap& var, fc::variant& vo ) + { + if( var.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + std::vector< fc::variant > vars(var.size()); + size_t i = 0; + for( auto itr = var.begin(); itr != var.end(); ++itr, ++i ) + vars[i] = fc::variant(*itr); + vo = vars; + } + template + void from_variant( const fc::variant& var, std::multimap& vo ) + { + const variants& vars = var.get_array(); + if( vars.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + vo.clear(); + for( auto itr = vars.begin(); itr != vars.end(); ++itr ) + vo.insert( itr->as< std::pair >() ); + } + + + template + void to_variant( const std::set& var, fc::variant& vo ) + { + if( var.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + std::vector vars(var.size()); + size_t i = 0; + for( auto itr = var.begin(); itr != var.end(); ++itr, ++i ) + vars[i] = fc::variant(*itr); + vo = vars; + } + template + void from_variant( const fc::variant& var, std::set& vo ) + { + const variants& vars = var.get_array(); + if( vars.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + vo.clear(); + //vo.reserve( vars.size() ); + for( auto itr = vars.begin(); itr != vars.end(); ++itr ) + vo.insert( itr->as() ); + } + + /** @ingroup Serializable */ + template + void from_variant( const fc::variant& var, std::deque& tmp ) + { + const variants& vars = var.get_array(); + if( vars.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + tmp.clear(); + for( auto itr = vars.begin(); itr != vars.end(); ++itr ) + tmp.push_back( itr->as() ); + } + + /** @ingroup Serializable */ + template + void to_variant( const std::deque& t, fc::variant& v ) + { + if( t.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + std::vector vars(t.size()); + for( size_t i = 0; i < t.size(); ++i ) + vars[i] = fc::variant(t[i]); + v = std::move(vars); + } + + /** @ingroup Serializable */ + template + void from_variant( const fc::variant& v, boost::container::deque& d ) + { + const variants& vars = v.get_array(); + if( vars.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + d.clear(); + d.resize( vars.size() ); + for( uint32_t i = 0; i < vars.size(); ++i ) { + from_variant( vars[i], d[i] ); + } + } + + /** @ingroup Serializable */ + template + void to_variant( const boost::container::deque& d, fc::variant& vo ) + { + if( d.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + variants vars(d.size()); + for( size_t i = 0; i < d.size(); ++i ) { + vars[i] = fc::variant( d[i] ); + } + vo = std::move( vars ); + } + + /** @ingroup Serializable */ + template + void from_variant( const fc::variant& var, std::vector& tmp ) + { + const variants& vars = var.get_array(); + if( vars.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + tmp.clear(); + tmp.reserve( vars.size() ); + for( auto itr = vars.begin(); itr != vars.end(); ++itr ) + tmp.push_back( itr->as() ); + } + + /** @ingroup Serializable */ + template + void to_variant( const std::vector& t, fc::variant& v ) + { + if( t.size() > MAX_NUM_ARRAY_ELEMENTS ) throw std::range_error( "too large" ); + std::vector vars(t.size()); + for( size_t i = 0; i < t.size(); ++i ) + vars[i] = fc::variant(t[i]); + v = std::move(vars); + } + + /** @ingroup Serializable */ + template + void from_variant( const fc::variant& var, std::array& tmp ) + { + const variants& vars = var.get_array(); + for( std::size_t i = 0; i < S; ++i ) + tmp[i] = vars.at(i).as(); + } + + /** @ingroup Serializable */ + template + void to_variant( const std::array& t, fc::variant& v ) + { + std::vector vars(S); + for( std::size_t i = 0; i < S; ++i ) + vars[i] = fc::variant(t[i]); + v = std::move(vars); + } + + /** @ingroup Serializable */ + template + void to_variant( const std::pair& t, fc::variant& v ) + { + std::vector vars(2); + vars[0] = fc::variant(t.first); + vars[1] = fc::variant(t.second); + v = vars; + } + + /** @ingroup Serializable */ + template + void from_variant( const fc::variant& v, std::pair& p ) + { + const variants& vars = v.get_array(); + if( vars.size() > 0 ) + vars[0].as( p.first ); + if( vars.size() > 1 ) + vars[1].as( p.second ); + } + + template + variant::variant( const T& val ) + { + memset( this, 0, sizeof(*this) ); + to_variant( val, *this ); + } + + template + variant::variant( const T& val, const fc::yield_function_t& yield ) + { + memset( this, 0, sizeof(*this) ); + to_variant( val, *this, yield ); + } + + #ifdef __APPLE__ + inline void to_variant( size_t s, fc::variant& v ) { v = fc::variant(uint64_t(s)); } + #endif + template + void to_variant( const std::shared_ptr& var, fc::variant& vo ) + { + if( var ) to_variant( *var, vo ); + else vo = nullptr; + } + + template + void from_variant( const fc::variant& var, std::shared_ptr& vo ) + { + if( var.is_null() ) vo = nullptr; + else if( vo ) from_variant( var, *vo ); + else { + vo = std::make_shared(); + from_variant( var, *vo ); + } + } + template + void to_variant( const std::unique_ptr& var, fc::variant& vo ) + { + if( var ) to_variant( *var, vo ); + else vo = nullptr; + } + + template + void from_variant( const fc::variant& var, std::unique_ptr& vo ) + { + if( var.is_null() ) vo.reset(); + else if( vo ) from_variant( var, *vo ); + else { + vo.reset( new T() ); + from_variant( var, *vo ); + } + } + + + template + void to_variant( const safe& s, fc::variant& v ) { v = s.value; } + + template + void from_variant( const fc::variant& v, safe& s ) { s.value = v.as_uint64(); } + + template void to_variant( const boost::multi_index_container& c, fc::variant& v ) + { + std::vector vars; + vars.reserve( c.size() ); + for( const auto& item : c ) + vars.emplace_back( fc::variant(item) ); + v = std::move(vars); + } + + template void from_variant( const fc::variant& v, boost::multi_index_container& c ) + { + const variants& vars = v.get_array(); + c.clear(); + for( const auto& item : vars ) + c.insert( item.as() ); + } + template void to_variant( const boost::multiprecision::number& n, fc::variant& v ) { + v = n.str(); + } + template void from_variant( const fc::variant& v, boost::multiprecision::number& n ) { + n = boost::multiprecision::number(v.get_string()); + } + + fc::variant operator + ( const fc::variant& a, const fc::variant& b ); + fc::variant operator - ( const fc::variant& a, const fc::variant& b ); + fc::variant operator * ( const fc::variant& a, const fc::variant& b ); + fc::variant operator / ( const fc::variant& a, const fc::variant& b ); + + bool operator == ( const fc::variant& a, const fc::variant& b ); + bool operator != ( const fc::variant& a, const fc::variant& b ); + bool operator < ( const fc::variant& a, const fc::variant& b ); + bool operator > ( const fc::variant& a, const fc::variant& b ); + bool operator ! ( const fc::variant& a ); +} // namespace fc + +#include +FC_REFLECT_TYPENAME( fc::variant ) +FC_REFLECT_ENUM( fc::variant::type_id, (null_type)(int64_type)(uint64_type)(double_type)(bool_type)(string_type)(array_type)(object_type)(blob_type) ) +FC_REFLECT( fc::blob, (data) ); diff --git a/libraries/libfc/include/fc/variant_object.hpp b/libraries/libfc/include/fc/variant_object.hpp new file mode 100644 index 0000000000..8540609003 --- /dev/null +++ b/libraries/libfc/include/fc/variant_object.hpp @@ -0,0 +1,245 @@ +#pragma once +#include +#include + +namespace fc +{ + class mutable_variant_object; + + /** + * @ingroup Serializable + * + * @brief An order-preserving dictionary of variants. + * + * Keys are kept in the order they are inserted. + * This dictionary implements copy-on-write + * + * @note This class is not optimized for random-access on large + * sets of key-value pairs. + */ + class variant_object + { + public: + /** @brief a key/value pair */ + class entry + { + public: + entry(); + entry( string k, variant v ); + entry( entry&& e ); + entry( const entry& e); + entry& operator=(const entry&); + entry& operator=(entry&&); + + const string& key()const; + const variant& value()const; + void set( variant v ); + + variant& value(); + + friend bool operator == (const entry& a, const entry& b) { + return a._key == b._key && a._value == b._value; + } + friend bool operator != (const entry& a, const entry& b) { + return !(a == b); + } + + private: + string _key; + variant _value; + }; + + typedef std::vector< entry >::const_iterator iterator; + + /** + * @name Immutable Interface + * + * Calling these methods will not result in copies of the + * underlying type. + */ + ///@{ + iterator begin()const; + iterator end()const; + iterator find( const string& key )const; + iterator find( const char* key )const; + const variant& operator[]( const string& key )const; + const variant& operator[]( const char* key )const; + size_t size()const; + bool contains( const char* key ) const { return find(key) != end(); } + ///@} + + variant_object(); + + /** initializes the first key/value pair in the object */ + variant_object( string key, variant val ); + + template + variant_object( string key, T&& val ) + :_key_value( std::make_shared >() ) + { + *this = variant_object( std::move(key), variant(forward(val)) ); + } + variant_object( const variant_object& ); + variant_object( variant_object&& ); + + variant_object( const mutable_variant_object& ); + variant_object( mutable_variant_object&& ); + + variant_object& operator=( variant_object&& ); + variant_object& operator=( const variant_object& ); + + variant_object& operator=( mutable_variant_object&& ); + variant_object& operator=( const mutable_variant_object& ); + + size_t estimated_size()const; + + private: + std::shared_ptr< std::vector< entry > > _key_value; + friend class mutable_variant_object; + }; + /** @ingroup Serializable */ + void to_variant( const variant_object& var, variant& vo ); + /** @ingroup Serializable */ + void from_variant( const variant& var, variant_object& vo ); + + + /** + * @ingroup Serializable + * + * @brief An order-preserving dictionary of variants. + * + * Keys are kept in the order they are inserted. + * This dictionary implements copy-on-write + * + * @note This class is not optimized for random-access on large + * sets of key-value pairs. + */ + class mutable_variant_object + { + public: + /** @brief a key/value pair */ + typedef variant_object::entry entry; + + typedef std::vector< entry >::iterator iterator; + typedef std::vector< entry >::const_iterator const_iterator; + + /** + * @name Immutable Interface + * + * Calling these methods will not result in copies of the + * underlying type. + */ + ///@{ + iterator begin()const; + iterator end()const; + iterator find( const string& key )const; + iterator find( const char* key )const; + const variant& operator[]( const string& key )const; + const variant& operator[]( const char* key )const; + size_t size()const; + ///@} + variant& operator[]( const string& key ); + variant& operator[]( const char* key ); + + /** + * @name mutable Interface + * + * Calling these methods will result in a copy of the underlying type + * being created if there is more than one reference to this object. + */ + ///@{ + void reserve( size_t s); + iterator begin(); + iterator end(); + void erase( const string& key ); + /** + * + * @return end() if key is not found + */ + iterator find( const string& key ); + iterator find( const char* key ); + + + /** replaces the value at \a key with \a var or inserts \a key if not found */ + mutable_variant_object& set( string key, variant var ) &; + mutable_variant_object set( string key, variant var ) &&; + + /** Appends \a key and \a var without checking for duplicates, designed to + * simplify construction of dictionaries using (key,val)(key2,val2) syntax + */ + /** + * Convenience method to simplify the manual construction of + * variant_objects + * + * Instead of: + * mutable_variant_object("c",c).set("a",a).set("b",b); + * + * You can use: + * mutable_variant_object( "c", c )( "b", b)( "c",c ) + * + * @return *this; + */ + mutable_variant_object& operator()( string key, variant var ) &; + mutable_variant_object operator()( string key, variant var ) &&; + template + mutable_variant_object& operator()( string key, T&& var ) & + { + set(std::move(key), variant( fc::forward(var) ) ); + return *this; + } + template + mutable_variant_object operator()( string key, T&& var ) && + { + set(std::move(key), variant( fc::forward(var) ) ); + return std::move(*this); + } + /** + * Copy a variant_object into this mutable_variant_object. + */ + mutable_variant_object& operator()( const variant_object& vo ) &; + mutable_variant_object operator()( const variant_object& vo ) &&; + /** + * Copy another mutable_variant_object into this mutable_variant_object. + */ + mutable_variant_object& operator()( const mutable_variant_object& mvo ) &; + mutable_variant_object operator()( const mutable_variant_object& mvo ) &&; + ///@} + + + template>::value>> + explicit mutable_variant_object( T&& v ) + :_key_value( new std::vector() ) + { + *this = variant(fc::forward(v)).get_object(); + } + + mutable_variant_object(); + + /** initializes the first key/value pair in the object */ + mutable_variant_object( string key, variant val ); + template + mutable_variant_object( string key, T&& val ) + :_key_value( new std::vector() ) + { + set( std::move(key), variant(forward(val)) ); + } + + mutable_variant_object( mutable_variant_object&& ); + mutable_variant_object( const mutable_variant_object& ); + mutable_variant_object( const variant_object& ); + + mutable_variant_object& operator=( mutable_variant_object&& ); + mutable_variant_object& operator=( const mutable_variant_object& ); + mutable_variant_object& operator=( const variant_object& ); + private: + std::unique_ptr< std::vector< entry > > _key_value; + friend class variant_object; + }; + /** @ingroup Serializable */ + void to_variant( const mutable_variant_object& var, variant& vo ); + /** @ingroup Serializable */ + void from_variant( const variant& var, mutable_variant_object& vo ); + +} // namespace fc diff --git a/libraries/libfc/include/fc/vector.hpp b/libraries/libfc/include/fc/vector.hpp new file mode 100644 index 0000000000..29b3388abd --- /dev/null +++ b/libraries/libfc/include/fc/vector.hpp @@ -0,0 +1,2 @@ +#pragma once +#include diff --git a/libraries/libfc/include/fc/vector_fwd.hpp b/libraries/libfc/include/fc/vector_fwd.hpp new file mode 100644 index 0000000000..a57fec70f1 --- /dev/null +++ b/libraries/libfc/include/fc/vector_fwd.hpp @@ -0,0 +1,11 @@ +#ifndef _FC_VECTOR_FWD_HPP_ +#define _FC_VECTOR_FWD_HPP_ +#if 0 +#include +#else +namespace fc { + template class vector; +}; +#endif + +#endif // _FC_VECTOR_FWD_HPP_ diff --git a/libraries/libfc/libraries/ff b/libraries/libfc/libraries/ff new file mode 160000 index 0000000000..c680b2456b --- /dev/null +++ b/libraries/libfc/libraries/ff @@ -0,0 +1 @@ +Subproject commit c680b2456b2b94fb5cde17d6d758ae2db44340b5 diff --git a/libraries/libfc/secp256k1/CMakeLists.txt b/libraries/libfc/secp256k1/CMakeLists.txt new file mode 100644 index 0000000000..9801860431 --- /dev/null +++ b/libraries/libfc/secp256k1/CMakeLists.txt @@ -0,0 +1,40 @@ +#the secp256k1-internal INTERFACE library is used to define some include paths & compile defs that are needed not just +# for compiling the library (where PRIVATE would have been fine), but also for the unit tests. +add_library(secp256k1-internal INTERFACE) + +target_include_directories(secp256k1-internal + INTERFACE + secp256k1/src + config +) + +target_compile_definitions(secp256k1-internal INTERFACE HAVE_CONFIG_H) + +add_library(secp256k1 STATIC + secp256k1/src/secp256k1.c secp256k1/src/precomputed_ecmult.c secp256k1/src/precomputed_ecmult_gen.c +) + +target_include_directories(secp256k1 + PUBLIC + secp256k1 + secp256k1/include +) + +target_link_libraries(secp256k1 PRIVATE secp256k1-internal) + +install( TARGETS secp256k1 + RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR} ${INSTALL_COMPONENT_ARGS} + LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} ${INSTALL_COMPONENT_ARGS} + ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} ${INSTALL_COMPONENT_ARGS} +) + +add_executable(secp256k1-bench secp256k1/src/bench.c) +target_link_libraries(secp256k1-bench secp256k1 secp256k1-internal) + +add_executable(secp256k1-tests secp256k1/src/tests.c) +target_link_libraries(secp256k1-tests secp256k1 secp256k1-internal) +add_test(secp256k1-tests secp256k1-tests) + +add_executable(secp256k1-exhaustive-tests secp256k1/src/tests_exhaustive.c) +target_link_libraries(secp256k1-exhaustive-tests secp256k1 secp256k1-internal) +add_test(secp256k1-exhaustive-tests secp256k1-exhaustive-tests) diff --git a/libraries/libfc/secp256k1/config/libsecp256k1-config.h b/libraries/libfc/secp256k1/config/libsecp256k1-config.h new file mode 100644 index 0000000000..b4e4550177 --- /dev/null +++ b/libraries/libfc/secp256k1/config/libsecp256k1-config.h @@ -0,0 +1,11 @@ +#pragma once + +#define ENABLE_MODULE_RECOVERY 1 + +#define ECMULT_GEN_PREC_BITS 4 +#define ECMULT_WINDOW_SIZE 15 + +//enable asm +#ifdef __x86_64__ + #define USE_ASM_X86_64 1 +#endif diff --git a/libraries/libfc/secp256k1/secp256k1 b/libraries/libfc/secp256k1/secp256k1 new file mode 160000 index 0000000000..485f608fa9 --- /dev/null +++ b/libraries/libfc/secp256k1/secp256k1 @@ -0,0 +1 @@ +Subproject commit 485f608fa9e28f132f127df97136617645effe81 diff --git a/libraries/libfc/src/byteswap.hpp b/libraries/libfc/src/byteswap.hpp new file mode 100644 index 0000000000..dde31147d8 --- /dev/null +++ b/libraries/libfc/src/byteswap.hpp @@ -0,0 +1,14 @@ +#pragma once + +#ifdef _WIN32 +# include +# define bswap_64(x) _byteswap_uint64(x) +#elif defined(__APPLE__) +# include +# define bswap_64(x) OSSwapInt64(x) +#elif defined(__FreeBSD__) +# include +# define bswap_64(x) bswap64(x) +#else +# include +#endif diff --git a/libraries/libfc/src/compress/smaz.cpp b/libraries/libfc/src/compress/smaz.cpp new file mode 100644 index 0000000000..da1521acc9 --- /dev/null +++ b/libraries/libfc/src/compress/smaz.cpp @@ -0,0 +1,223 @@ +#include +#include +#include +#include +namespace fc { + +typedef const char* const_char_ptr; +/* Our compression codebook, used for compression */ +static const_char_ptr Smaz_cb[241] = { +"\002s,\266", "\003had\232\002leW", "\003on \216", "", "\001yS", +"\002ma\255\002li\227", "\003or \260", "", "\002ll\230\003s t\277", +"\004fromg\002mel", "", "\003its\332", "\001z\333", "\003ingF", "\001>\336", +"\001 \000\003 (\002nc\344", "\002nd=\003 on\312", +"\002ne\213\003hat\276\003re q", "", "\002ngT\003herz\004have\306\003s o\225", +"", "\003ionk\003s a\254\002ly\352", "\003hisL\003 inN\003 be\252", "", +"\003 fo\325\003 of \003 ha\311", "", "\002of\005", +"\003 co\241\002no\267\003 ma\370", "", "", "\003 cl\356\003enta\003 an7", +"\002ns\300\001\"e", "\003n t\217\002ntP\003s, \205", +"\002pe\320\003 we\351\002om\223", "\002on\037", "", "\002y G", "\003 wa\271", +"\003 re\321\002or*", "", "\002=\"\251\002ot\337", "\003forD\002ou[", +"\003 toR", "\003 th\r", "\003 it\366", +"\003but\261\002ra\202\003 wi\363\002<\346", "\002to\024", "\003arew", "\001d\030", +"\002tr\303", "", "\001\n1\003 a \222", "\003f tv\002veo", "\002un\340", "", +"\003e o\242", "\002a \243\002wa\326\001e\002", "\002ur\226\003e a\274", +"\002us\244\003\n\r\n\247", "\002ut\304\003e c\373", "\002we\221", "", "", +"\002wh\302", "\001f,", "", "", "", "\003d t\206", "", "", "\003th \343", +"\001g;", "", "", "\001\r9\003e s\265", "\003e t\234", "", "\003to Y", +"\003e\r\n\236", "\002d \036\001h\022", "", "\001,Q", "\002 a\031", "\002 b^", +"\002\r\n\025\002 cI", "\002 d\245", "\002 e\253", "\002 fh\001i\b\002e \v", +"", "\002 hU\001-\314", "\002 i8", "", "", "\002 l\315", "\002 m{", +"\002f :\002 n\354", "\002 o\035", "\002 p}\001.n\003\r\n\r\250", "", +"\002 r\275", "\002 s>", "\002 t\016", "", "\002g \235\005which+\003whi\367", +"\002 w5", "\001/\305", "\003as \214", "\003at \207", "", "\003who\331", "", +"\001l\026\002h \212", "", "\002, $", "", "\004withV", "", "", "", "\001m-", "", +"", "\002ac\357", "\002ad\350", "\003TheH", "", "", "\004this\233\001n\t", +"", "\002. y", "", "\002alX\003e, \365", "\003tio\215\002be\\", +"\002an\032\003ver\347", "", "\004that0\003tha\313\001o\006", "\003was2", +"\002arO", "\002as.", "\002at'\003the\001\004they\200\005there\322\005theird", +"\002ce\210", "\004were]", "", "\002ch\231\002l \264\001p<", "", "", +"\003one\256", "", "\003he \023\002dej", "\003ter\270", "\002cou", "", +"\002by\177\002di\201\002eax", "", "\002ec\327", "\002edB", "\002ee\353", "", +"", "\001r\f\002n )", "", "", "", "\002el\262", "", "\003in i\002en3", "", +"\002o `\001s\n", "", "\002er\033", "\003is t\002es6", "", "\002ge\371", +"\004.com\375", "\002fo\334\003our\330", "\003ch \301\001t\003", "\002hab", "", +"\003men\374", "", "\002he\020", "", "", "\001u&", "\002hif", "", +"\003not\204\002ic\203", "\003ed @\002id\355", "", "", "\002ho\273", +"\002r K\001vm", "", "", "", "\003t t\257\002il\360", "\002im\342", +"\003en \317\002in\017", "\002io\220", "\002s \027\001wA", "", "\003er |", +"\003es ~\002is%", "\002it/", "", "\002iv\272", "", +"\002t #\ahttp://C\001x\372", "\002la\211", "\001<\341", "\003, a\224" +}; + + +/* Reverse compression codebook, used for decompression */ +static const_char_ptr Smaz_rcb[254] = { +" ", "the", "e", "t", "a", "of", "o", "and", "i", "n", "s", "e ", "r", " th", +" t", "in", "he", "th", "h", "he ", "to", "\r\n", "l", "s ", "d", " a", "an", +"er", "c", " o", "d ", "on", " of", "re", "of ", "t ", ", ", "is", "u", "at", +" ", "n ", "or", "which", "f", "m", "as", "it", "that", "\n", "was", "en", +" ", " w", "es", " an", " i", "\r", "f ", "g", "p", "nd", " s", "nd ", "ed ", +"w", "ed", "http://", "for", "te", "ing", "y ", "The", " c", "ti", "r ", "his", +"st", " in", "ar", "nt", ",", " to", "y", "ng", " h", "with", "le", "al", "to ", +"b", "ou", "be", "were", " b", "se", "o ", "ent", "ha", "ng ", "their", "\"", +"hi", "from", " f", "in ", "de", "ion", "me", "v", ".", "ve", "all", "re ", +"ri", "ro", "is ", "co", "f t", "are", "ea", ". ", "her", " m", "er ", " p", +"es ", "by", "they", "di", "ra", "ic", "not", "s, ", "d t", "at ", "ce", "la", +"h ", "ne", "as ", "tio", "on ", "n t", "io", "we", " a ", "om", ", a", "s o", +"ur", "li", "ll", "ch", "had", "this", "e t", "g ", "e\r\n", " wh", "ere", +" co", "e o", "a ", "us", " d", "ss", "\n\r\n", "\r\n\r", "=\"", " be", " e", +"s a", "ma", "one", "t t", "or ", "but", "el", "so", "l ", "e s", "s,", "no", +"ter", " wa", "iv", "ho", "e a", " r", "hat", "s t", "ns", "ch ", "wh", "tr", +"ut", "/", "have", "ly ", "ta", " ha", " on", "tha", "-", " l", "ati", "en ", +"pe", " re", "there", "ass", "si", " fo", "wa", "ec", "our", "who", "its", "z", +"fo", "rs", ">", "ot", "un", "<", "im", "th ", "nc", "ate", "><", "ver", "ad", +" we", "ly", "ee", " n", "id", " cl", "ac", "il", " 1) h2 += in[1]; + if (inlen > 2) h3 = h2^in[2]; + if (j > inlen) j = inlen; + + /* Try to lookup substrings into the hash table, starting from the + * longer to the shorter substrings */ + for (; j > 0; j--) { + switch(j) { + case 1: slot = Smaz_cb[h1%241]; break; + case 2: slot = Smaz_cb[h2%241]; break; + default: slot = Smaz_cb[h3%241]; break; + } + while(slot[0]) { + if (slot[0] == j && memcmp(slot+1,in,j) == 0) { + /* Match found in the hash table, + * prepare a verbatim bytes flush if needed */ + if (verblen) { + needed = (verblen == 1) ? 2 : 2+verblen; + flush = out; + out += needed; + outlen -= needed; + } + /* Emit the byte */ + if (outlen <= 0) return _outlen+1; + out[0] = slot[slot[0]+1]; + out++; + outlen--; + inlen -= j; + in += j; + goto out; + } else { + slot += slot[0]+2; + } + } + } + /* Match not found - add the byte to the verbatim buffer */ + verb[verblen] = in[0]; + verblen++; + inlen--; + in++; +out: + /* Prepare a flush if we reached the flush length limit, and there + * is not already a pending flush operation. */ + if (!flush && (verblen == 256 || (verblen > 0 && inlen == 0))) { + needed = (verblen == 1) ? 2 : 2+verblen; + flush = out; + out += needed; + outlen -= needed; + if (outlen < 0) return _outlen+1; + } + /* Perform a verbatim flush if needed */ + if (flush) { + if (verblen == 1) { + flush[0] = (signed char)254; + flush[1] = verb[0]; + } else { + flush[0] = (signed char)255; + flush[1] = (signed char)(verblen-1); + memcpy(flush+2,verb,verblen); + } + flush = NULL; + verblen = 0; + } + } + return out-_out; +} + +void smaz_decompress(const char *in, uint32_t inlen, std::stringstream& ss ) { + const unsigned char *c = (const unsigned char*) in; +// char *_out = out; +// int _outlen = outlen; + + while(inlen) { + if (*c == 254) { + /* Verbatim byte */ + //if (outlen < 1) return _outlen+1; + //*out = *(c+1); + ss.put( *(c+1) ); + //out++; + //outlen--; + c += 2; + inlen -= 2; + } else if (*c == 255) { + /* Verbatim string */ + int len = (*(c+1))+1; + //if (outlen < len) return _outlen+1; + ss.write( (const char*)(c+2),len ); + + //memcpy(out,c+2,len); + //out += len; + //outlen -= len; + c += 2+len; + inlen -= 2+len; + } else { + /* Codebook entry */ + const char *s = Smaz_rcb[*c]; + int len = strlen(s); + + //if (outlen < len) return _outlen+1; + //memcpy(out,s,len); + ss.write( s, len ); + //out += len; + //outlen -= len; + c++; + inlen--; + } + } +} + + + + std::string smaz_compress( const std::string& in ) + { + std::string out; + out.resize(in.size()); + auto out_len = smaz_compress( in.c_str(), in.size(), &out[0], out.size() ); + FC_ASSERT( out_len <= out.size() ); + out.resize(out_len); + return out; + } + std::string smaz_decompress( const std::string& compressed ) + { + std::stringstream ss; + smaz_decompress( compressed.c_str(), compressed.length(), ss ); + return ss.str(); + } + +} // namespace fc diff --git a/libraries/libfc/src/compress/zlib.cpp b/libraries/libfc/src/compress/zlib.cpp new file mode 100644 index 0000000000..356e052f10 --- /dev/null +++ b/libraries/libfc/src/compress/zlib.cpp @@ -0,0 +1,21 @@ +#include + +#include +#include +#include + +namespace bio = boost::iostreams; + +namespace fc +{ + string zlib_compress(const string& in) + { + string out; + bio::filtering_ostream comp; + comp.push(bio::zlib_compressor(bio::zlib::default_compression)); + comp.push(bio::back_inserter(out)); + bio::write(comp, in.data(), in.size()); + bio::close(comp); + return out; + } +} diff --git a/libraries/libfc/src/crypto/_digest_common.cpp b/libraries/libfc/src/crypto/_digest_common.cpp new file mode 100644 index 0000000000..67a015bfb2 --- /dev/null +++ b/libraries/libfc/src/crypto/_digest_common.cpp @@ -0,0 +1,53 @@ +#include +#include "_digest_common.hpp" + +namespace fc { namespace detail { + static void shift_l( const uint8_t* in, uint8_t* out, std::size_t n, unsigned int i) { + if (i < n) { + memcpy( out, in + i, n-i ); + } else { + i = n; + } + memset( out + (n-i), 0, i ); + } + + void shift_l( const char* in, char* out, std::size_t n, unsigned int i) { + const uint8_t* in8 = (uint8_t*) in; + uint8_t* out8 = (uint8_t*) out; + + if (i >= 8) { + shift_l( in8, out8, n, i / 8 ); + i &= 7; + in8 = out8; + } + + std::size_t p; + for( p = 0; p < n-1; ++p ) + out8[p] = (in8[p] << i) | (in8[p+1]>>(8-i)); + out8[p] = in8[p] << i; + } + static void shift_r( const uint8_t* in, uint8_t* out, std::size_t n, unsigned int i) { + if (i < n) { + memcpy( out+i, in, n-i ); + } else { + i = n; + } + memset( out, 0, i ); + } + + void shift_r( const char* in, char* out, std::size_t n, unsigned int i) { + const uint8_t* in8 = (uint8_t*) in; + uint8_t* out8 = (uint8_t*) out; + + if (i >= 8) { + shift_r( in8, out8, n, i / 8 ); + i &= 7; + in8 = out8; + } + + std::size_t p; + for( p = n-1; p > 0; --p ) + out8[p] = (in8[p] >> i) | (in8[p-1]<<(8-i)); + out8[p] = in8[p] >> i; + } +}} diff --git a/libraries/libfc/src/crypto/_digest_common.hpp b/libraries/libfc/src/crypto/_digest_common.hpp new file mode 100644 index 0000000000..8582309bfe --- /dev/null +++ b/libraries/libfc/src/crypto/_digest_common.hpp @@ -0,0 +1,8 @@ +#pragma once + +/* Common stuff for cryptographic hashes + */ +namespace fc { namespace detail { + void shift_l( const char* in, char* out, std::size_t n, unsigned int i); + void shift_r( const char* in, char* out, std::size_t n, unsigned int i); +}} diff --git a/libraries/libfc/src/crypto/_elliptic_impl_priv.hpp b/libraries/libfc/src/crypto/_elliptic_impl_priv.hpp new file mode 100644 index 0000000000..45d0ef8791 --- /dev/null +++ b/libraries/libfc/src/crypto/_elliptic_impl_priv.hpp @@ -0,0 +1,25 @@ +#pragma once +#include + +/* private_key_impl based on libsecp256k1 + * used by mixed + secp256k1 + */ + +namespace fc { namespace ecc { namespace detail { + + +const secp256k1_context* _get_context(); +void _init_lib(); + +class private_key_impl +{ + public: + private_key_impl() BOOST_NOEXCEPT; + private_key_impl( const private_key_impl& cpy ) BOOST_NOEXCEPT; + + private_key_impl& operator=( const private_key_impl& pk ) BOOST_NOEXCEPT; + + private_key_secret _key; +}; + +}}} diff --git a/libraries/libfc/src/crypto/_elliptic_impl_pub.hpp b/libraries/libfc/src/crypto/_elliptic_impl_pub.hpp new file mode 100644 index 0000000000..b982ba125d --- /dev/null +++ b/libraries/libfc/src/crypto/_elliptic_impl_pub.hpp @@ -0,0 +1,33 @@ +#pragma once +#include +#include + +/* public_key_impl implementation based on openssl + * used by mixed + openssl + */ + +namespace fc { namespace ecc { namespace detail { + +void _init_lib(); + +class public_key_impl +{ + public: + public_key_impl() BOOST_NOEXCEPT; + public_key_impl( const public_key_impl& cpy ) BOOST_NOEXCEPT; + public_key_impl( public_key_impl&& cpy ) BOOST_NOEXCEPT; + ~public_key_impl() BOOST_NOEXCEPT; + + public_key_impl& operator=( const public_key_impl& pk ) BOOST_NOEXCEPT; + + public_key_impl& operator=( public_key_impl&& pk ) BOOST_NOEXCEPT; + + static int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check); + + EC_KEY* _key = nullptr; + + private: + void free_key() BOOST_NOEXCEPT; +}; + +}}} diff --git a/libraries/libfc/src/crypto/aes.cpp b/libraries/libfc/src/crypto/aes.cpp new file mode 100644 index 0000000000..68c3b2e0e9 --- /dev/null +++ b/libraries/libfc/src/crypto/aes.cpp @@ -0,0 +1,367 @@ +#include +#include +#include +#include + +//#include +#include + +#include + +#include +#ifndef OPENSSL_THREADS +# error "OpenSSL must be configured to support threads" +#endif +#include + +#if defined(_WIN32) +# include +#endif + +#include +#include +#include +#include + +namespace fc { + +struct aes_encoder::impl +{ + evp_cipher_ctx ctx; +}; + + +void aes_encoder::init( const fc::sha256& key, const fc::uint128& init_value ) +{ + my->ctx.obj = EVP_CIPHER_CTX_new(); + /* Create and initialise the context */ + if(!my->ctx) + { + FC_THROW_EXCEPTION( aes_exception, "error allocating evp cipher context", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + + /* Initialise the encryption operation. IMPORTANT - ensure you use a key + * and IV size appropriate for your cipher + * In this example we are using 256 bit AES (i.e. a 256 bit key). The + * IV size for *most* modes is the same as the block size. For AES this + * is 128 bits */ + if(1 != EVP_EncryptInit_ex(my->ctx, EVP_aes_256_cbc(), NULL, (unsigned char*)&key, (unsigned char*)&init_value)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc encryption init", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + EVP_CIPHER_CTX_set_padding( my->ctx, 0 ); +} + +uint32_t aes_encoder::encode( const char* plaintxt, uint32_t plaintext_len, char* ciphertxt ) +{ + int ciphertext_len = 0; + /* Provide the message to be encrypted, and obtain the encrypted output. + * * EVP_EncryptUpdate can be called multiple times if necessary + * */ + if(1 != EVP_EncryptUpdate(my->ctx, (unsigned char*)ciphertxt, &ciphertext_len, (const unsigned char*)plaintxt, plaintext_len)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc encryption update", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + FC_ASSERT( ciphertext_len == static_cast(plaintext_len), "", ("ciphertext_len",ciphertext_len)("plaintext_len",plaintext_len) ); + return ciphertext_len; +} +#if 0 +uint32_t aes_encoder::final_encode( char* ciphertxt ) +{ + int ciphertext_len = 0; + /* Finalise the encryption. Further ciphertext bytes may be written at + * * this stage. + * */ + if(1 != EVP_EncryptFinal_ex(my->ctx, (unsigned char*)ciphertxt, &ciphertext_len)) + { + FC_THROW_EXCEPTION( exception, "error during aes 256 cbc encryption final", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + return ciphertext_len; +} +#endif + + +struct aes_decoder::impl +{ + evp_cipher_ctx ctx; +}; + +void aes_decoder::init( const fc::sha256& key, const fc::uint128& init_value ) +{ + my->ctx.obj = EVP_CIPHER_CTX_new(); + /* Create and initialise the context */ + if(!my->ctx) + { + FC_THROW_EXCEPTION( aes_exception, "error allocating evp cipher context", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + + /* Initialise the encryption operation. IMPORTANT - ensure you use a key + * and IV size appropriate for your cipher + * In this example we are using 256 bit AES (i.e. a 256 bit key). The + * IV size for *most* modes is the same as the block size. For AES this + * is 128 bits */ + if(1 != EVP_DecryptInit_ex(my->ctx, EVP_aes_256_cbc(), NULL, (unsigned char*)&key, (unsigned char*)&init_value)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc encryption init", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + EVP_CIPHER_CTX_set_padding( my->ctx, 0 ); +} + +uint32_t aes_decoder::decode( const char* ciphertxt, uint32_t ciphertxt_len, char* plaintext ) +{ + int plaintext_len = 0; + /* Provide the message to be decrypted, and obtain the decrypted output. + * * EVP_DecryptUpdate can be called multiple times if necessary + * */ + if (1 != EVP_DecryptUpdate(my->ctx, (unsigned char*)plaintext, &plaintext_len, (const unsigned char*)ciphertxt, ciphertxt_len)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc decryption update", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + FC_ASSERT( ciphertxt_len == static_cast(plaintext_len), "", ("ciphertxt_len",ciphertxt_len)("plaintext_len",plaintext_len) ); + return plaintext_len; +} +#if 0 +uint32_t aes_decoder::final_decode( char* plaintext ) +{ + return 0; + int ciphertext_len = 0; + /* Finalise the encryption. Further ciphertext bytes may be written at + * * this stage. + * */ + if(1 != EVP_DecryptFinal_ex(my->ctx, (unsigned char*)plaintext, &ciphertext_len)) + { + FC_THROW_EXCEPTION( exception, "error during aes 256 cbc encryption final", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + return ciphertext_len; +} +#endif + + + + + + + + + + + + +/** example method from wiki.opensslfoundation.com */ +unsigned aes_encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *key, + unsigned char *iv, unsigned char *ciphertext) +{ + evp_cipher_ctx ctx( EVP_CIPHER_CTX_new() ); + + int len = 0; + unsigned ciphertext_len = 0; + + /* Create and initialise the context */ + if(!ctx) + { + FC_THROW_EXCEPTION( aes_exception, "error allocating evp cipher context", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + + /* Initialise the encryption operation. IMPORTANT - ensure you use a key + * and IV size appropriate for your cipher + * In this example we are using 256 bit AES (i.e. a 256 bit key). The + * IV size for *most* modes is the same as the block size. For AES this + * is 128 bits */ + if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc encryption init", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + + /* Provide the message to be encrypted, and obtain the encrypted output. + * * EVP_EncryptUpdate can be called multiple times if necessary + * */ + if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc encryption update", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + ciphertext_len = len; + + /* Finalise the encryption. Further ciphertext bytes may be written at + * * this stage. + * */ + if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc encryption final", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + ciphertext_len += len; + + return ciphertext_len; +} + +unsigned aes_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, + unsigned char *iv, unsigned char *plaintext) +{ + evp_cipher_ctx ctx( EVP_CIPHER_CTX_new() ); + int len = 0; + unsigned plaintext_len = 0; + + /* Create and initialise the context */ + if(!ctx) + { + FC_THROW_EXCEPTION( aes_exception, "error allocating evp cipher context", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + + /* Initialise the decryption operation. IMPORTANT - ensure you use a key + * * and IV size appropriate for your cipher + * * In this example we are using 256 bit AES (i.e. a 256 bit key). The + * * IV size for *most* modes is the same as the block size. For AES this + * * is 128 bits */ + if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc decrypt init", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + + /* Provide the message to be decrypted, and obtain the plaintext output. + * * EVP_DecryptUpdate can be called multiple times if necessary + * */ + if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc decrypt update", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + + plaintext_len = len; + + /* Finalise the decryption. Further plaintext bytes may be written at + * * this stage. + * */ + if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc decrypt final", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + plaintext_len += len; + + return plaintext_len; +} + +unsigned aes_cfb_decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, + unsigned char *iv, unsigned char *plaintext) +{ + evp_cipher_ctx ctx( EVP_CIPHER_CTX_new() ); + int len = 0; + unsigned plaintext_len = 0; + + /* Create and initialise the context */ + if(!ctx) + { + FC_THROW_EXCEPTION( aes_exception, "error allocating evp cipher context", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + + /* Initialise the decryption operation. IMPORTANT - ensure you use a key + * * and IV size appropriate for your cipher + * * In this example we are using 256 bit AES (i.e. a 256 bit key). The + * * IV size for *most* modes is the same as the block size. For AES this + * * is 128 bits */ + if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_256_cfb128(), NULL, key, iv)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc decrypt init", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + + /* Provide the message to be decrypted, and obtain the plaintext output. + * * EVP_DecryptUpdate can be called multiple times if necessary + * */ + if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc decrypt update", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + + plaintext_len = len; + + /* Finalise the decryption. Further plaintext bytes may be written at + * * this stage. + * */ + if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) + { + FC_THROW_EXCEPTION( aes_exception, "error during aes 256 cbc decrypt final", + ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + plaintext_len += len; + + return plaintext_len; +} + +std::vector aes_encrypt( const fc::sha512& key, const std::vector& plain_text ) +{ + std::vector cipher_text(plain_text.size()+16); + auto cipher_len = aes_encrypt( (unsigned char*)plain_text.data(), (int)plain_text.size(), + (unsigned char*)&key, ((unsigned char*)&key)+32, + (unsigned char*)cipher_text.data() ); + FC_ASSERT( cipher_len <= cipher_text.size() ); + cipher_text.resize(cipher_len); + return cipher_text; + +} +std::vector aes_decrypt( const fc::sha512& key, const std::vector& cipher_text ) +{ + std::vector plain_text( cipher_text.size() ); + auto plain_len = aes_decrypt( (unsigned char*)cipher_text.data(), (int)cipher_text.size(), + (unsigned char*)&key, ((unsigned char*)&key)+32, + (unsigned char*)plain_text.data() ); + plain_text.resize(plain_len); + return plain_text; +} + + +/** encrypts plain_text and then includes a checksum that enables us to verify the integrety of + * the file / key prior to decryption. + */ +void aes_save( const fc::path& file, const fc::sha512& key, std::vector plain_text ) +{ try { + auto cipher = aes_encrypt( key, plain_text ); + fc::sha512::encoder check_enc; + fc::raw::pack( check_enc, key ); + fc::raw::pack( check_enc, cipher ); + auto check = check_enc.result(); + + std::ofstream out(file.generic_string().c_str()); + fc::raw::pack( out, check ); + fc::raw::pack( out, cipher ); +} FC_RETHROW_EXCEPTIONS( warn, "", ("file",file) ) } + +/** + * recovers the plain_text saved via aes_save() + */ +std::vector aes_load( const fc::path& file, const fc::sha512& key ) +{ try { + FC_ASSERT( fc::exists( file ) ); + + std::ifstream in( file.generic_string().c_str(), std::ifstream::binary ); + fc::sha512 check; + std::vector cipher; + + fc::raw::unpack( in, check ); + fc::raw::unpack( in, cipher ); + + fc::sha512::encoder check_enc; + fc::raw::pack( check_enc, key ); + fc::raw::pack( check_enc, cipher ); + + FC_ASSERT( check_enc.result() == check ); + + return aes_decrypt( key, cipher ); +} FC_RETHROW_EXCEPTIONS( warn, "", ("file",file) ) } + +} // namespace fc diff --git a/libraries/libfc/src/crypto/alt_bn128.cpp b/libraries/libfc/src/crypto/alt_bn128.cpp new file mode 100644 index 0000000000..8c6b91a714 --- /dev/null +++ b/libraries/libfc/src/crypto/alt_bn128.cpp @@ -0,0 +1,230 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace fc { + + using Scalar = libff::bigint; + + void initLibSnark() noexcept { + static bool s_initialized = []() noexcept { + libff::inhibit_profiling_info = true; + libff::inhibit_profiling_counters = true; + libff::alt_bn128_pp::init_public_params(); + return true; + }(); + (void)s_initialized; + } + + Scalar to_scalar(const bytes& be) noexcept { + mpz_t m; + mpz_init(m); + mpz_import(m, be.size(), /*order=*/1, /*size=*/1, /*endian=*/0, /*nails=*/0, &be[0]); + Scalar out{m}; + mpz_clear(m); + return out; + } + + // Notation warning: Yellow Paper's p is the same libff's q. + // Returns x < p (YP notation). + static bool valid_element_of_fp(const Scalar& x) noexcept { + return mpn_cmp(x.data, libff::alt_bn128_modulus_q.data, libff::alt_bn128_q_limbs) < 0; + } + + std::variant decode_g1_element(const bytes& bytes64_be) noexcept { + if(bytes64_be.size() != 64) { + return alt_bn128_error::input_len_error; + } + + bytes sub1(bytes64_be.begin(), bytes64_be.begin()+32); + bytes sub2(bytes64_be.begin()+32, bytes64_be.begin()+64); + + Scalar x{to_scalar(sub1)}; + Scalar y{to_scalar(sub2)}; + + if (!valid_element_of_fp(x) || !valid_element_of_fp(y)) { + return alt_bn128_error::operand_component_invalid; + } + + if (x.is_zero() && y.is_zero()) { + return libff::alt_bn128_G1::zero(); + } + + libff::alt_bn128_G1 point{x, y, libff::alt_bn128_Fq::one()}; + if (!point.is_well_formed()) { + return alt_bn128_error::operand_not_in_curve; + } + return point; + } + + std::variant decode_fp2_element(const bytes& bytes64_be) noexcept { + if(bytes64_be.size() != 64) { + return alt_bn128_error::input_len_error; + } + + // big-endian encoding + bytes sub1(bytes64_be.begin()+32, bytes64_be.begin()+64); + bytes sub2(bytes64_be.begin(), bytes64_be.begin()+32); + + Scalar c0{to_scalar(sub1)}; + Scalar c1{to_scalar(sub2)}; + + if (!valid_element_of_fp(c0) || !valid_element_of_fp(c1)) { + return alt_bn128_error::operand_component_invalid; + } + + return libff::alt_bn128_Fq2{c0, c1}; + } + + std::variant decode_g2_element(const bytes& bytes128_be) noexcept { + if(bytes128_be.size() != 128) { + return alt_bn128_error::input_len_error; + } + + bytes sub1(bytes128_be.begin(), bytes128_be.begin()+64); + auto maybe_x = decode_fp2_element(sub1); + if (std::holds_alternative(maybe_x)) { + return std::get(maybe_x); + } + + bytes sub2(bytes128_be.begin()+64, bytes128_be.begin()+128); + auto maybe_y = decode_fp2_element(sub2); + if (std::holds_alternative(maybe_y)) { + return std::get(maybe_y); + } + + const auto& x = std::get(maybe_x); + const auto& y = std::get(maybe_y); + + if (x.is_zero() && y.is_zero()) { + return libff::alt_bn128_G2::zero(); + } + + libff::alt_bn128_G2 point{x, y, libff::alt_bn128_Fq2::one()}; + if (!point.is_well_formed()) { + return alt_bn128_error::operand_not_in_curve; + } + + if (!(libff::alt_bn128_G2::order() * point).is_zero()) { + // wrong order, doesn't belong to the subgroup G2 + return alt_bn128_error::operand_outside_g2; + } + + return point; + } + + bytes encode_g1_element(libff::alt_bn128_G1 p) noexcept { + bytes out(64, '\0'); + if (p.is_zero()) { + return out; + } + + p.to_affine_coordinates(); + + auto x{p.X.as_bigint()}; + auto y{p.Y.as_bigint()}; + + std::memcpy(&out[0], y.data, 32); + std::memcpy(&out[32], x.data, 32); + + std::reverse(out.begin(), out.end()); + return out; + } + + std::variant alt_bn128_add(const bytes& op1, const bytes& op2) { + fc::initLibSnark(); + + auto maybe_x = decode_g1_element(op1); + if (std::holds_alternative(maybe_x)) { + return std::get(maybe_x); + } + + auto maybe_y = decode_g1_element(op2); + if (std::holds_alternative(maybe_y)) { + return std::get(maybe_y); + } + + const auto& x = std::get(maybe_x); + const auto& y = std::get(maybe_y); + + libff::alt_bn128_G1 g1Sum = x + y; + return encode_g1_element(g1Sum); + } + + std::variant alt_bn128_mul(const bytes& g1_point, const bytes& scalar) { + initLibSnark(); + + auto maybe_x = decode_g1_element(g1_point); + if (std::holds_alternative(maybe_x)) { + return std::get(maybe_x); + } + + auto& x = std::get(maybe_x); + + if(scalar.size() != 32) { + return alt_bn128_error::invalid_scalar_size; + } + + Scalar n{to_scalar(scalar)}; + + libff::alt_bn128_G1 g1Product = n * x; + return encode_g1_element(g1Product); + } + + static constexpr size_t kSnarkvStride{192}; + + std::variant alt_bn128_pair(const bytes& g1_g2_pairs, const yield_function_t& yield) { + if (g1_g2_pairs.size() % kSnarkvStride != 0) { + return alt_bn128_error::pairing_list_size_error; + } + + size_t k{g1_g2_pairs.size() / kSnarkvStride}; + + initLibSnark(); + using namespace libff; + + static const auto one{alt_bn128_Fq12::one()}; + auto accumulator{one}; + + for (size_t i{0}; i < k; ++i) { + auto offset = i * kSnarkvStride; + + bytes sub1(g1_g2_pairs.begin()+offset, g1_g2_pairs.begin()+offset+64); + auto maybe_a = decode_g1_element(sub1); + if (std::holds_alternative(maybe_a)) { + return std::get(maybe_a); + } + + bytes sub2(g1_g2_pairs.begin()+offset+64, g1_g2_pairs.begin()+offset+64+128); + auto maybe_b = decode_g2_element(sub2); + if (std::holds_alternative(maybe_b)) { + return std::get(maybe_b); + } + + const auto& a = std::get(maybe_a); + const auto& b = std::get(maybe_b); + + if (a.is_zero() || b.is_zero()) { + continue; + } + + accumulator = accumulator * alt_bn128_miller_loop(alt_bn128_precompute_G1(a), alt_bn128_precompute_G2(b)); + yield(); + } + + bool pair_result = false; + if (alt_bn128_final_exponentiation(accumulator) == one) { + pair_result = true; + } + + return pair_result; + } +} diff --git a/libraries/libfc/src/crypto/base32.cpp b/libraries/libfc/src/crypto/base32.cpp new file mode 100644 index 0000000000..bb5354d820 --- /dev/null +++ b/libraries/libfc/src/crypto/base32.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +namespace fc +{ + std::vector from_base32( const std::string& b32 ) + { + auto len = cyoBase32DecodeGetLength( b32.size() ); + std::vector v(len); + len = cyoBase32Decode( v.data(), b32.c_str(), b32.size() ); + v.resize( len ); + return v; + } + + std::string to_base32( const char* data, size_t len ) + { + auto s = cyoBase32EncodeGetLength(len); + std::vector b32; + b32.resize(s); + cyoBase32Encode( b32.data(), data, len ); + b32.resize( b32.size()-1); // strip the nullterm + return std::string(b32.begin(),b32.end()); + } + + std::string to_base32( const std::vector& vec ) + { + return to_base32( vec.data(), vec.size() ); + } +} diff --git a/libraries/libfc/src/crypto/base36.cpp b/libraries/libfc/src/crypto/base36.cpp new file mode 100644 index 0000000000..d0685aea2a --- /dev/null +++ b/libraries/libfc/src/crypto/base36.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include + +namespace fc +{ + fc::string to_base36( const char* data, size_t len ) + { + if( len == 0 ) return fc::string(); + + const char* src = data; + int src_len = len; + std::unique_ptr buffer(new char[len+1]); + if (*data & 0x80) { + buffer[0] = 0; + memcpy( buffer.get() + 1, data, len ); + src = buffer.get(); + src_len++; + } + fc::bigint value( src, src_len ); + + auto base36 = "0123456789abcdefghijklmnopqrstuvwxyz"; + std::vector out( static_cast(len * 1.6) + 2 ); + int pos = out.size() - 1; + out[pos] = '\0'; + fc::bigint _36(36); + do { + if( value ) { + --pos; + out[pos] = base36[(value % _36).to_int64()]; + } + } while (value /= _36); + while (len-- > 0 && *data++ == 0) { + out[--pos] = '0'; + } + return &out[pos]; //fc::string( &out[pos], out.size() - pos); + } + + fc::string to_base36( const std::vector& vec ) + { + return to_base36( (const char*)vec.data(), vec.size() ); + } + + std::vector from_base36( const fc::string& b36 ) + { + if ( b36.empty() ) { + std::vector empty; + return empty; + } + + fc::bigint value; + + fc::bigint pos = 0; + fc::bigint _36(36); + for( auto itr = b36.rbegin(); itr != b36.rend(); ++itr ) + { + if( *itr >= '0' && *itr <= '9' ) + value = value + _36.exp(pos) * fc::bigint(*itr - '0'); + else if( *itr >= 'a' && *itr <= 'z' ) + value = value + (_36.exp(pos) * fc::bigint(10+*itr - 'a')); + else if( *itr >= 'A' && *itr <= 'Z' ) + value = value + (_36.exp(pos) * fc::bigint(10+*itr - 'A')); + else + { + wlog("unknown '${char}'", ("char",fc::string(&*itr,1)) ); + } + ++pos; + } + + std::vector bytes = value; + int leading_zeros = 0, len = bytes.size(); + const char *in = b36.c_str(); + while (*in++ == '0') { leading_zeros++; } + char* first = bytes.data(); + while (len > 0 && *first == 0) { first++; len--; } + std::vector result; + result.resize(leading_zeros + len, 0); + memcpy( result.data() + leading_zeros, first, len ); + return result; + } +} diff --git a/libraries/libfc/src/crypto/base58.cpp b/libraries/libfc/src/crypto/base58.cpp new file mode 100644 index 0000000000..ad26a9ea17 --- /dev/null +++ b/libraries/libfc/src/crypto/base58.cpp @@ -0,0 +1,650 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2012 The Bitcoin Developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +// +// Why base-58 instead of standard base-64 encoding? +// - Don't want 0OIl characters that look the same in some fonts and +// could be used to create visually identical looking account numbers. +// - A string with non-alphanumeric characters is not as easily accepted as an account number. +// - E-mail usually won't line-break if there's no punctuation to break at. +// - Doubleclicking selects the whole number as one word if it's all alphanumeric. +// +#ifndef BITCOIN_BASE58_H +#define BITCOIN_BASE58_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +/** Errors thrown by the bignum class */ +class bignum_error : public std::runtime_error +{ +public: + explicit bignum_error(const std::string& str) : std::runtime_error(str) {} +}; + + +/** RAII encapsulated BN_CTX (OpenSSL bignum context) */ +class CAutoBN_CTX +{ +protected: + BN_CTX* pctx; + BN_CTX* operator=(BN_CTX* pnew) { return pctx = pnew; } + +public: + CAutoBN_CTX() + { + pctx = BN_CTX_new(); + if (pctx == NULL) + throw bignum_error("CAutoBN_CTX : BN_CTX_new() returned NULL"); + } + + ~CAutoBN_CTX() + { + if (pctx != NULL) + BN_CTX_free(pctx); + } + + operator BN_CTX*() { return pctx; } + BN_CTX& operator*() { return *pctx; } + BN_CTX** operator&() { return &pctx; } + bool operator!() { return (pctx == NULL); } +}; + + +/** C++ wrapper for BIGNUM (OpenSSL bignum) */ +class CBigNum +{ + BIGNUM* bn; +public: + CBigNum() + : bn(BN_new()) {} + + CBigNum(const CBigNum& b) + : CBigNum() + { + if (!BN_copy(bn, b.bn)) + { + BN_clear_free(bn); + throw bignum_error("CBigNum::CBigNum(const CBigNum&) : BN_copy failed"); + } + } + + CBigNum& operator=(const CBigNum& b) + { + if (!BN_copy(bn, b.bn)) + throw bignum_error("CBigNum::operator= : BN_copy failed"); + return (*this); + } + + ~CBigNum() + { + BN_clear_free(bn); + } + + //CBigNum(char n) is not portable. Use 'signed char' or 'unsigned char'. + CBigNum(signed char n) :CBigNum() { if (n >= 0) setulong(n); else setint64(n); } + CBigNum(short n) :CBigNum() { if (n >= 0) setulong(n); else setint64(n); } + CBigNum(int n) :CBigNum() { if (n >= 0) setulong(n); else setint64(n); } + CBigNum(int64_t n) :CBigNum() { setint64(n); } + CBigNum(unsigned char n) :CBigNum() { setulong(n); } + CBigNum(unsigned short n) :CBigNum() { setulong(n); } + CBigNum(unsigned int n) :CBigNum() { setulong(n); } + CBigNum(uint64_t n) :CBigNum() { setuint64(n); } + + explicit CBigNum(const std::vector& vch) + : CBigNum() + { + setvch(vch); + } + + void setulong(unsigned long n) + { + if (!BN_set_word(bn, n)) + throw bignum_error("CBigNum conversion from unsigned long : BN_set_word failed"); + } + + unsigned long getulong() const + { + return BN_get_word(bn); + } + + unsigned int getuint() const + { + return BN_get_word(bn); + } + + int getint() const + { + unsigned long n = BN_get_word(bn); + if (!BN_is_negative(bn)) + return (n > (unsigned long)std::numeric_limits::max() ? std::numeric_limits::max() : n); + else + return (n > (unsigned long)std::numeric_limits::max() ? std::numeric_limits::min() : -(int)n); + } + + void setint64(int64_t n) + { + unsigned char pch[sizeof(n) + 6]; + unsigned char* p = pch + 4; + bool fNegative = false; + if (n < (int64_t)0) + { + n = -n; + fNegative = true; + } + bool fLeadingZeroes = true; + for (int i = 0; i < 8; i++) + { + unsigned char c = (n >> 56) & 0xff; + n <<= 8; + if (fLeadingZeroes) + { + if (c == 0) + continue; + if (c & 0x80) + *p++ = (fNegative ? 0x80 : 0); + else if (fNegative) + c |= 0x80; + fLeadingZeroes = false; + } + *p++ = c; + } + unsigned int nSize = p - (pch + 4); + pch[0] = (nSize >> 24) & 0xff; + pch[1] = (nSize >> 16) & 0xff; + pch[2] = (nSize >> 8) & 0xff; + pch[3] = (nSize) & 0xff; + BN_mpi2bn(pch, p - pch, bn); + } + + void setuint64(uint64_t n) + { + unsigned char pch[sizeof(n) + 6]; + unsigned char* p = pch + 4; + bool fLeadingZeroes = true; + for (int i = 0; i < 8; i++) + { + unsigned char c = (n >> 56) & 0xff; + n <<= 8; + if (fLeadingZeroes) + { + if (c == 0) + continue; + if (c & 0x80) + *p++ = 0; + fLeadingZeroes = false; + } + *p++ = c; + } + unsigned int nSize = p - (pch + 4); + pch[0] = (nSize >> 24) & 0xff; + pch[1] = (nSize >> 16) & 0xff; + pch[2] = (nSize >> 8) & 0xff; + pch[3] = (nSize) & 0xff; + BN_mpi2bn(pch, p - pch, bn); + } + + + void setvch(const std::vector& vch) + { + std::vector vch2(vch.size() + 4); + unsigned int nSize = vch.size(); + // BIGNUM's byte stream format expects 4 bytes of + // big endian size data info at the front + vch2[0] = (nSize >> 24) & 0xff; + vch2[1] = (nSize >> 16) & 0xff; + vch2[2] = (nSize >> 8) & 0xff; + vch2[3] = (nSize >> 0) & 0xff; + // swap data to big endian + reverse_copy(vch.begin(), vch.end(), vch2.begin() + 4); + BN_mpi2bn(&vch2[0], vch2.size(), bn); + } + + std::vector getvch() const + { + unsigned int nSize = BN_bn2mpi(bn, NULL); + if (nSize <= 4) + return std::vector(); + std::vector vch(nSize); + BN_bn2mpi(bn, &vch[0]); + vch.erase(vch.begin(), vch.begin() + 4); + reverse(vch.begin(), vch.end()); + return vch; + } + + CBigNum& SetCompact(unsigned int nCompact) + { + unsigned int nSize = nCompact >> 24; + std::vector vch(4 + nSize); + vch[3] = nSize; + if (nSize >= 1) vch[4] = (nCompact >> 16) & 0xff; + if (nSize >= 2) vch[5] = (nCompact >> 8) & 0xff; + if (nSize >= 3) vch[6] = (nCompact >> 0) & 0xff; + BN_mpi2bn(&vch[0], vch.size(), bn); + return *this; + } + + unsigned int GetCompact() const + { + unsigned int nSize = BN_bn2mpi(bn, NULL); + std::vector vch(nSize); + nSize -= 4; + BN_bn2mpi(bn, &vch[0]); + unsigned int nCompact = nSize << 24; + if (nSize >= 1) nCompact |= (vch[4] << 16); + if (nSize >= 2) nCompact |= (vch[5] << 8); + if (nSize >= 3) nCompact |= (vch[6] << 0); + return nCompact; + } + + void SetHex(const std::string& str) + { + // skip 0x + const char* psz = str.c_str(); + while (isspace(*psz)) + psz++; + bool fNegative = false; + if (*psz == '-') + { + fNegative = true; + psz++; + } + if (psz[0] == '0' && tolower(psz[1]) == 'x') + psz += 2; + while (isspace(*psz)) + psz++; + + // hex string to bignum + static signed char phexdigit[256] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xa,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0,0,0,0,0 }; + *this = 0; + while (isxdigit(*psz)) + { + *this <<= 4; + int n = phexdigit[(unsigned char)*psz++]; + *this += n; + } + if (fNegative) + BN_set_negative(bn, 1); + } + + std::string ToString(int nBase=10) const + { + CAutoBN_CTX pctx; + CBigNum bnBase = nBase; + CBigNum bn0 = 0; + std::string str; + CBigNum bn = *this; + BN_set_negative(bn.bn, false); + CBigNum dv; + CBigNum rem; + if (BN_cmp(bn.bn, bn0.bn) == 0) + return "0"; + while (BN_cmp(bn.bn, bn0.bn) > 0) + { + if (!BN_div(dv.bn, rem.bn, bn.bn, bnBase.bn, pctx)) + throw bignum_error("CBigNum::ToString() : BN_div failed"); + bn = dv; + unsigned int c = rem.getulong(); + str += "0123456789abcdef"[c]; + } + if (BN_is_negative(this->bn)) + str += "-"; + reverse(str.begin(), str.end()); + return str; + } + + std::string GetHex() const + { + return ToString(16); + } + + + + bool operator!() const + { + return BN_is_zero(bn); + } + + CBigNum& operator+=(const CBigNum& b) + { + if (!BN_add(bn, bn, b.bn)) + throw bignum_error("CBigNum::operator+= : BN_add failed"); + return *this; + } + + CBigNum& operator-=(const CBigNum& b) + { + if (!BN_sub(bn, bn, b.bn)) + throw bignum_error("CBigNum::operator-= : BN_sub failed"); + return *this; + } + + CBigNum& operator*=(const CBigNum& b) + { + CAutoBN_CTX pctx; + if (!BN_mul(bn, bn, b.bn, pctx)) + throw bignum_error("CBigNum::operator*= : BN_mul failed"); + return *this; + } + + CBigNum& operator/=(const CBigNum& b) + { + CAutoBN_CTX pctx; + if (!BN_div(bn, NULL, bn, b.bn, pctx)) + throw bignum_error("CBigNum::operator/= : BN_div failed"); + return *this; + } + + CBigNum& operator%=(const CBigNum& b) + { + CAutoBN_CTX pctx; + if (!BN_div(NULL, bn, bn, b.bn, pctx)) + throw bignum_error("CBigNum::operator%= : BN_div failed"); + return *this; + } + + CBigNum& operator<<=(unsigned int shift) + { + if (!BN_lshift(bn, bn, shift)) + throw bignum_error("CBigNum:operator<<= : BN_lshift failed"); + return *this; + } + + CBigNum& operator>>=(unsigned int shift) + { + // Note: BN_rshift segfaults on 64-bit if 2^shift is greater than the number + // if built on ubuntu 9.04 or 9.10, probably depends on version of openssl + CBigNum a = 1; + a <<= shift; + if (BN_cmp(a.bn, bn) > 0) + { + *this = 0; + return *this; + } + + if (!BN_rshift(bn, bn, shift)) + throw bignum_error("CBigNum:operator>>= : BN_rshift failed"); + return *this; + } + + + CBigNum& operator++() + { + // prefix operator + if (!BN_add(bn, bn, BN_value_one())) + throw bignum_error("CBigNum::operator++ : BN_add failed"); + return *this; + } + + const CBigNum operator++(int) + { + // postfix operator + const CBigNum ret = *this; + ++(*this); + return ret; + } + + CBigNum& operator--() + { + // prefix operator + CBigNum r; + if (!BN_sub(r.bn, bn, BN_value_one())) + throw bignum_error("CBigNum::operator-- : BN_sub failed"); + *this = r; + return *this; + } + + const CBigNum operator--(int) + { + // postfix operator + const CBigNum ret = *this; + --(*this); + return ret; + } + + const BIGNUM* to_bignum() const { + return bn; + } + BIGNUM* to_bignum() { + return bn; + } +}; + + + +inline const CBigNum operator+(const CBigNum& a, const CBigNum& b) +{ + CBigNum r; + if (!BN_add(r.to_bignum(), a.to_bignum(), b.to_bignum())) + throw bignum_error("CBigNum::operator+ : BN_add failed"); + return r; +} + +inline const CBigNum operator-(const CBigNum& a, const CBigNum& b) +{ + CBigNum r; + if (!BN_sub(r.to_bignum(), a.to_bignum(), b.to_bignum())) + throw bignum_error("CBigNum::operator- : BN_sub failed"); + return r; +} + +inline const CBigNum operator-(const CBigNum& a) +{ + CBigNum r(a); + BN_set_negative(r.to_bignum(), !BN_is_negative(r.to_bignum())); + return r; +} + +inline const CBigNum operator*(const CBigNum& a, const CBigNum& b) +{ + CAutoBN_CTX pctx; + CBigNum r; + if (!BN_mul(r.to_bignum(), a.to_bignum(), b.to_bignum(), pctx)) + throw bignum_error("CBigNum::operator* : BN_mul failed"); + return r; +} + +inline const CBigNum operator/(const CBigNum& a, const CBigNum& b) +{ + CAutoBN_CTX pctx; + CBigNum r; + if (!BN_div(r.to_bignum(), NULL, a.to_bignum(), b.to_bignum(), pctx)) + throw bignum_error("CBigNum::operator/ : BN_div failed"); + return r; +} + +inline const CBigNum operator%(const CBigNum& a, const CBigNum& b) +{ + CAutoBN_CTX pctx; + CBigNum r; + if (!BN_mod(r.to_bignum(), a.to_bignum(), b.to_bignum(), pctx)) + throw bignum_error("CBigNum::operator% : BN_div failed"); + return r; +} + +inline const CBigNum operator<<(const CBigNum& a, unsigned int shift) +{ + CBigNum r; + if (!BN_lshift(r.to_bignum(), a.to_bignum(), shift)) + throw bignum_error("CBigNum:operator<< : BN_lshift failed"); + return r; +} + +inline const CBigNum operator>>(const CBigNum& a, unsigned int shift) +{ + CBigNum r = a; + r >>= shift; + return r; +} + +inline bool operator==(const CBigNum& a, const CBigNum& b) { return (BN_cmp(a.to_bignum(), b.to_bignum()) == 0); } +inline bool operator!=(const CBigNum& a, const CBigNum& b) { return (BN_cmp(a.to_bignum(), b.to_bignum()) != 0); } +inline bool operator<=(const CBigNum& a, const CBigNum& b) { return (BN_cmp(a.to_bignum(), b.to_bignum()) <= 0); } +inline bool operator>=(const CBigNum& a, const CBigNum& b) { return (BN_cmp(a.to_bignum(), b.to_bignum()) >= 0); } +inline bool operator<(const CBigNum& a, const CBigNum& b) { return (BN_cmp(a.to_bignum(), b.to_bignum()) < 0); } +inline bool operator>(const CBigNum& a, const CBigNum& b) { return (BN_cmp(a.to_bignum(), b.to_bignum()) > 0); } + + +static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + +// Encode a byte sequence as a base58-encoded string +inline std::string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend, const fc::yield_function_t& yield) +{ + CAutoBN_CTX pctx; + CBigNum bn58 = 58; + CBigNum bn0 = 0; + + // Convert big endian data to little endian + // Extra zero at the end make sure bignum will interpret as a positive number + std::vector vchTmp(pend-pbegin+1, 0); + yield(); + reverse_copy(pbegin, pend, vchTmp.begin()); + yield(); + + // Convert little endian data to bignum + CBigNum bn; + bn.setvch(vchTmp); + + // Convert bignum to std::string + std::string str; + // Expected size increase from base58 conversion is approximately 137% + // use 138% to be safe + str.reserve((pend - pbegin) * 138 / 100 + 1); + CBigNum dv; + CBigNum rem; + while (bn > bn0) + { + yield(); + if (!BN_div(dv.to_bignum(), rem.to_bignum(), bn.to_bignum(), bn58.to_bignum(), pctx)) + throw bignum_error("EncodeBase58 : BN_div failed"); + bn = dv; + unsigned int c = rem.getulong(); + str += pszBase58[c]; + } + + // Leading zeroes encoded as base58 zeros + for (const unsigned char* p = pbegin; p < pend && *p == 0; p++) + str += pszBase58[0]; + + yield(); + // Convert little endian std::string to big endian + reverse(str.begin(), str.end()); +// slog( "Encode '%s'", str.c_str() ); + yield(); + + return str; +} + +// Encode a byte vector as a base58-encoded string +inline std::string EncodeBase58(const std::vector& vch, const fc::yield_function_t& yield) +{ + return EncodeBase58(&vch[0], &vch[0] + vch.size(), yield); +} + +// Decode a base58-encoded string psz into byte vector vchRet +// returns true if decoding is succesful +inline bool DecodeBase58(const char* psz, std::vector& vchRet) +{ + CAutoBN_CTX pctx; + vchRet.clear(); + CBigNum bn58 = 58; + CBigNum bn = 0; + CBigNum bnChar; + while (isspace(*psz)) + psz++; + + // Convert big endian string to bignum + for (const char* p = psz; *p; p++) + { + const char* p1 = strchr(pszBase58, *p); + if (p1 == NULL) + { + while (isspace(*p)) + p++; + if (*p != '\0') { + //slog( "%s '%c'", pszBase58,*p ); + return false; + } + break; + } + bnChar.setulong(p1 - pszBase58); + if (!BN_mul(bn.to_bignum(), bn.to_bignum(), bn58.to_bignum(), pctx)) + throw bignum_error("DecodeBase58 : BN_mul failed"); + bn += bnChar; + } + + // Get bignum as little endian data + std::vector vchTmp = bn.getvch(); + + // Trim off sign byte if present + if (vchTmp.size() >= 2 && vchTmp.end()[-1] == 0 && vchTmp.end()[-2] >= 0x80) + vchTmp.erase(vchTmp.end()-1); + + // Restore leading zeros + int nLeadingZeros = 0; + for (const char* p = psz; *p == pszBase58[0]; p++) + nLeadingZeros++; + vchRet.assign(nLeadingZeros + vchTmp.size(), 0); + + // Convert little endian data to big endian + reverse_copy(vchTmp.begin(), vchTmp.end(), vchRet.end() - vchTmp.size()); + return true; +} + +// Decode a base58-encoded string str into byte vector vchRet +// returns true if decoding is succesful +inline bool DecodeBase58(const std::string& str, std::vector& vchRet) +{ + return DecodeBase58(str.c_str(), vchRet); +} + + +namespace fc { + +std::string to_base58( const char* d, size_t s, const fc::yield_function_t& yield ) { + return EncodeBase58( (const unsigned char*)d, (const unsigned char*)d+s, yield ); +} + +std::string to_base58( const std::vector& d, const fc::yield_function_t& yield ) +{ + if( d.size() ) + return to_base58( d.data(), d.size(), yield ); + return std::string(); +} +std::vector from_base58( const std::string& base58_str ) { + std::vector out; + if( !DecodeBase58( base58_str.c_str(), out ) ) { + FC_THROW_EXCEPTION( parse_error_exception, "Unable to decode base58 string ${base58_str}", ("base58_str",base58_str) ); + } + return std::vector((const char*)out.data(), ((const char*)out.data())+out.size() ); +} +/** + * @return the number of bytes decoded + */ +size_t from_base58( const std::string& base58_str, char* out_data, size_t out_data_len ) { + //slog( "%s", base58_str.c_str() ); + std::vector out; + if( !DecodeBase58( base58_str.c_str(), out ) ) { + FC_THROW_EXCEPTION( parse_error_exception, "Unable to decode base58 string ${base58_str}", ("base58_str",base58_str) ); + } + FC_ASSERT( out.size() <= out_data_len ); + memcpy( out_data, out.data(), out.size() ); + return out.size(); +} +} + +#endif diff --git a/libraries/libfc/src/crypto/base64.cpp b/libraries/libfc/src/crypto/base64.cpp new file mode 100644 index 0000000000..5ff4e68290 --- /dev/null +++ b/libraries/libfc/src/crypto/base64.cpp @@ -0,0 +1,161 @@ +#include +#include +#include +/* + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +namespace fc { + +static constexpr char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; +static constexpr char base64url_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_"; + +static_assert(sizeof(base64_chars) == sizeof(base64url_chars), "base64 and base64url must have the same amount of chars"); + +static inline void throw_on_nonbase64(unsigned char c, const char* const b64_chars) { + FC_ASSERT(isalnum(c) || (c == b64_chars[sizeof(base64_chars)-3]) || (c == b64_chars[sizeof(base64_chars)-2]), "encountered non-base64 character"); +} + +std::string base64_encode_impl(unsigned char const* bytes_to_encode, unsigned int in_len, const char* const b64_chars) { + + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(i = 0; (i <4) ; i++) + ret += b64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + ret += b64_chars[char_array_4[j]]; + + while((i++ < 3)) + ret += '='; + + } + + return ret; + +} + +std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + return base64_encode_impl(bytes_to_encode, in_len, base64_chars); +} + +std::string base64_encode( const std::string& enc ) { + char const* s = enc.c_str(); + return base64_encode( (unsigned char const*)s, enc.size() ); +} + +std::string base64url_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { + return base64_encode_impl(bytes_to_encode, in_len, base64url_chars); +} + +std::string base64url_encode( const std::string& enc ) { + char const* s = enc.c_str(); + return base64url_encode( (unsigned char const*)s, enc.size() ); +} + +std::string base64_decode_impl(std::string const& encoded_string, const char* const b64_chars) { + int in_len = encoded_string.size(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4], char_array_3[3]; + std::string ret; + + while (in_len-- && encoded_string[in_] != '=') { + throw_on_nonbase64(encoded_string[in_], b64_chars); + char_array_4[i++] = encoded_string[in_]; in_++; + if (i ==4) { + for (i = 0; i <4; i++) + char_array_4[i] = strchr(b64_chars, char_array_4[i]) - b64_chars; + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + ret += char_array_3[i]; + i = 0; + } + } + + if (i) { + for (j = i; j <4; j++) + char_array_4[j] = 0; + + for (j = 0; j <4; j++) + char_array_4[j] = strchr(b64_chars, char_array_4[j]) - b64_chars; + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; + } + + return ret; +} + +std::string base64_decode(std::string const& encoded_string) { + return base64_decode_impl(encoded_string, base64_chars); +} + +std::string base64url_decode(std::string const& encoded_string) { + return base64_decode_impl(encoded_string, base64url_chars); +} + +} // namespace fc + diff --git a/libraries/libfc/src/crypto/bigint.cpp b/libraries/libfc/src/crypto/bigint.cpp new file mode 100644 index 0000000000..5bc96d4111 --- /dev/null +++ b/libraries/libfc/src/crypto/bigint.cpp @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include + +#include +#include "../byteswap.hpp" + +namespace fc { + bigint::bigint( const char* bige, uint32_t l ) { + n = BN_bin2bn( (const unsigned char*)bige, l, NULL ); + FC_ASSERT( n != nullptr ); + } + bigint::bigint( const std::vector& bige ) { + n = BN_bin2bn( (const unsigned char*)bige.data(), bige.size(), NULL ); + FC_ASSERT( n != nullptr ); + } + bigint::bigint( BIGNUM* in ) + { + n = BN_dup(in); + } + bigint::bigint( ) + :n(BN_new()) + { } + + BIGNUM* bigint::dup()const + { + return BN_dup( n ); + } + + bigint::bigint(uint64_t value) + { + uint64_t big_endian_value = bswap_64(value); + n = BN_bin2bn((const unsigned char*)&big_endian_value, sizeof(big_endian_value), NULL); + } + + bigint::bigint( const bigint& c ) { + n = BN_dup( c.n ); + } + + bigint::bigint( bigint&& b ) { + n = b.n; + b.n = 0; + } + + bigint::~bigint() { + if(n!=0) BN_free(n); + } + + bool bigint::is_negative()const { return BN_is_negative(n); } + + int64_t bigint::to_int64() const + { + FC_ASSERT(BN_num_bits(n) <= 63); + size_t size = BN_num_bytes(n); + uint64_t abs_value = 0; + BN_bn2bin(n, (unsigned char*)&abs_value + (sizeof(uint64_t) - size)); + return BN_is_negative(n) ? -(int64_t)bswap_64(abs_value) : bswap_64(abs_value); + } + + int64_t bigint::log2()const { return BN_num_bits(n); } + bool bigint::operator < ( const bigint& c )const { + return BN_cmp( n, c.n ) < 0; + } + bool bigint::operator > ( const bigint& c )const { + return BN_cmp( n, c.n ) > 0; + } + bool bigint::operator >= ( const bigint& c )const { + return BN_cmp( n, c.n ) >= 0; + } + bool bigint::operator == ( const bigint& c )const { + return BN_cmp( n, c.n ) == 0; + } + bool bigint::operator != ( const bigint& c )const { + return BN_cmp( n, c.n ) != 0; + } + bigint::operator bool()const + { + return !BN_is_zero( n ); + } + bigint bigint::operator++(int) + { + bigint tmp = *this; + *this = *this + bigint(1); + return tmp; + } + bigint& bigint::operator++() + { + return *this = *this + bigint(1); + } + bigint bigint::operator--(int) + { + bigint tmp = *this; + *this = *this - bigint(1); + return tmp; + } + bigint& bigint::operator--() + { + return *this = *this - bigint(1); + } + + bigint bigint::operator + ( const bigint& a )const { + bigint tmp(*this); + BN_add( tmp.n, n, a.n ); + return tmp; + } + bigint& bigint::operator += ( const bigint& a ){ + bigint tmp(*this); + BN_add( tmp.n, n, a.n ); + std::swap(*this,tmp); + return *this; + } + bigint& bigint::operator -= ( const bigint& a ){ + bigint tmp(*this); + BN_sub( tmp.n, n, a.n ); + std::swap(*this,tmp); + return *this; + } + + + bigint bigint::operator * ( const bigint& a )const { + BN_CTX* ctx = BN_CTX_new(); + bigint tmp(*this); + BN_mul( tmp.n, n, a.n, ctx ); + BN_CTX_free(ctx); + return tmp; + } + bigint bigint::operator / ( const bigint& a ) const { + BN_CTX* ctx = BN_CTX_new(); + bigint tmp;//(*this); + BN_div( tmp.n, NULL, n, a.n, ctx ); + BN_CTX_free(ctx); + return tmp; + } + bigint bigint::operator % ( const bigint& a ) const { + BN_CTX* ctx = BN_CTX_new(); + bigint tmp;//(*this); + BN_mod( tmp.n, n, a.n, ctx ); + BN_CTX_free(ctx); + return tmp; + } + + bigint bigint::operator /= ( const bigint& a ) { + BN_CTX* ctx = BN_CTX_new(); + bigint tmp;//*this); + BN_div( tmp.n, NULL, n, a.n, ctx ); + fc_swap( tmp.n, n ); + BN_CTX_free(ctx); + return tmp; + } + bigint bigint::operator *= ( const bigint& a ) { + auto tmp = *this * a; + *this = std::move(tmp); + return *this; + } + bigint& bigint::operator >>= ( uint32_t i ) + { + bigint tmp; + BN_rshift( tmp.n, n, i ); + std::swap(*this,tmp); + return *this; + } + + bigint& bigint::operator <<= ( uint32_t i ) + { + bigint tmp; + FC_ASSERT( tmp.n != nullptr ); + FC_ASSERT( n != nullptr ); + BN_lshift( tmp.n, n, i ); + std::swap(*this,tmp); + return *this; + } + + bigint bigint::operator - ( const bigint& a )const { + bigint tmp; + BN_sub( tmp.n, n, a.n ); + return tmp; + } + bigint bigint::exp( const bigint& a )const + { + BN_CTX* ctx = BN_CTX_new(); + bigint tmp; + BN_exp( tmp.n, n, a.n, ctx ); + BN_CTX_free(ctx); + return tmp; + } + + + bigint& bigint::operator = ( bigint&& a ) { + fc_swap( a.n, n ); + return *this; + } + bigint& bigint::operator = ( const bigint& a ) { + if( &a == this ) + return *this; + BN_copy( n, a.n ); + return *this; + } + bigint::operator fc::string()const { + return BN_bn2dec(n); + } + + bigint::operator std::vector()const { + std::vector to(BN_num_bytes(n)); + BN_bn2bin(n,(unsigned char*)to.data()); + return to; + } + + /** encodes the big int as base64 string, or a number */ + void to_variant( const bigint& bi, variant& v ) + { + std::vector ve = bi; + v = fc::variant(base64_encode((unsigned char*)ve.data(),ve.size())); + } + + /** decodes the big int as base64 string, or a number */ + void from_variant( const variant& v, bigint& bi ) + { + if( v.is_numeric() ) bi = bigint( static_cast(v.as_uint64()) ); + else + { + std::string b64 = v.as_string(); + std::string bin = base64_decode(b64); + bi = bigint(bin.c_str(), bin.size() ); + } + } + +} // namespace fc diff --git a/libraries/libfc/src/crypto/blake2.cpp b/libraries/libfc/src/crypto/blake2.cpp new file mode 100644 index 0000000000..6560bb3a0a --- /dev/null +++ b/libraries/libfc/src/crypto/blake2.cpp @@ -0,0 +1,137 @@ +/* + BLAKE2 reference source code package - reference C implementations + + Copyright 2012, Samuel Neves . You may use this under the + terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at + your option. The terms of these licenses can be found at: + + - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0 + - OpenSSL license : https://www.openssl.org/source/license.html + - Apache 2.0 : http://www.apache.org/licenses/LICENSE-2.0 + + More information about the BLAKE2 hash function can be found at + https://blake2.net. +*/ + +#include +#include +#include +#include + +namespace fc { + +static const uint64_t blake2b_IV[8] = {0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL, 0x3c6ef372fe94f82bULL, + 0xa54ff53a5f1d36f1ULL, 0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL}; + +static const uint8_t blake2b_sigma[12][16] = { + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}, + {11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4}, {7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8}, + {9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13}, {2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9}, + {12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11}, {13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10}, + {6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5}, {10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0}, + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, {14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3}}; + +static inline uint64_t load64(const void *src) { + uint64_t w; + memcpy(&w, src, sizeof w); + return w; +} + +static inline uint64_t rotr64(const uint64_t w, const unsigned c) { return (w >> c) | (w << (64 - c)); } + +inline void blake2b_wrapper::G(uint8_t r, uint8_t i, uint64_t& a, uint64_t& b, uint64_t& c, uint64_t& d) noexcept +{ + a = a + b + m[blake2b_sigma[r][2 * i + 0]]; + d = rotr64(d ^ a, 32); + c = c + d; + b = rotr64(b ^ c, 24); + a = a + b + m[blake2b_sigma[r][2 * i + 1]]; + d = rotr64(d ^ a, 16); + c = c + d; + b = rotr64(b ^ c, 63); +} + +inline void blake2b_wrapper::ROUND(uint8_t r) noexcept +{ + G(r, 0, v[0], v[4], v[8], v[12]); + G(r, 1, v[1], v[5], v[9], v[13]); + G(r, 2, v[2], v[6], v[10], v[14]); + G(r, 3, v[3], v[7], v[11], v[15]); + G(r, 4, v[0], v[5], v[10], v[15]); + G(r, 5, v[1], v[6], v[11], v[12]); + G(r, 6, v[2], v[7], v[8], v[13]); + G(r, 7, v[3], v[4], v[9], v[14]); +} + +void blake2b_wrapper::blake2b_compress(blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES], size_t r, const yield_function_t& yield) { + blake2b_compress_init(S, block, r); + + for (i = 0; i < r; ++i) { + ROUND(i % 10); + if (i % 100) { + yield(); + } + } + + blake2b_compress_end(S); +} + +void blake2b_wrapper::blake2b_compress_init(blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES], size_t r) { + for (i = 0; i < 16; ++i) { + m[i] = load64(block + i * sizeof(m[i])); + } + + for (i = 0; i < 8; ++i) { + v[i] = S->h[i]; + } + + v[8] = blake2b_IV[0]; + v[9] = blake2b_IV[1]; + v[10] = blake2b_IV[2]; + v[11] = blake2b_IV[3]; + v[12] = blake2b_IV[4] ^ S->t[0]; + v[13] = blake2b_IV[5] ^ S->t[1]; + v[14] = blake2b_IV[6] ^ S->f[0]; + v[15] = blake2b_IV[7]; +} + +void blake2b_wrapper::blake2b_compress_end(blake2b_state *S) { + for (i = 0; i < 8; ++i) { + S->h[i] = S->h[i] ^ v[i] ^ v[i + 8]; + } +} + +std::variant blake2b(uint32_t _rounds, const bytes& _h, const bytes& _m, const bytes& _t0_offset, const bytes& _t1_offset, bool _f, const yield_function_t& yield) { + + // EIP-152 [4 bytes for rounds][64 bytes for h][128 bytes for m][8 bytes for t_0][8 bytes for t_1][1 byte for f] : 213 + // [------------------][64 bytes for h][128 bytes for m][8 bytes for t_0][8 bytes for t_1][------------] : 208 + // * rounds and final indicator flag are not serialized + if (_h.size() != 64 || _m.size() != blake2b_wrapper::BLAKE2B_BLOCKBYTES || _t0_offset.size() != 8 || _t1_offset.size() != 8) { + return blake2b_error::input_len_error; + } + + blake2b_wrapper b2wrapper; + blake2b_state state{}; + + memcpy(state.h, _h.data(), 64); + + // final indicator flag set words to 1's if true + state.f[0] = _f ? std::numeric_limits::max() : 0; + + memcpy(&state.t[0], _t0_offset.data(), 8); + memcpy(&state.t[1], _t1_offset.data(), 8); + + uint8_t block[blake2b_wrapper::BLAKE2B_BLOCKBYTES]; + memcpy(block, _m.data(), blake2b_wrapper::BLAKE2B_BLOCKBYTES); + + b2wrapper.blake2b_compress(&state, block, _rounds, yield); + + bytes out(sizeof(state.h), 0); + memcpy(&out[0], &state.h[0], out.size()); + return out; +} + +} +#undef G +#undef ROUND diff --git a/libraries/libfc/src/crypto/blowfish.cpp b/libraries/libfc/src/crypto/blowfish.cpp new file mode 100644 index 0000000000..5d3cbb4f3d --- /dev/null +++ b/libraries/libfc/src/crypto/blowfish.cpp @@ -0,0 +1,624 @@ +//////////////////////////////////////////////////////////////////////////// +/// +// BlowFish.cpp +// +// Implementation of Bruce Schneier's BLOWFISH algorithm from "Applied +// Cryptography", Second Edition. + +#include +#include +#include + +namespace fc { +//Extract low order byte +inline unsigned char Byte(unsigned int ui) +{ + return (unsigned char)(ui & 0xff); +} + +//Function F +inline unsigned int blowfish::F(unsigned int ui) +{ + return ((m_auiS[0][Byte(ui>>24)] + m_auiS[1][Byte(ui>>16)]) ^ m_auiS[2][Byte(ui>>8)]) + m_auiS[3][Byte(ui)]; +} + +//Initialization with a fixed string which consists of the hexadecimal digits of PI (less the initial 3) +//P-array, 18 32-bit subkeys +const unsigned int blowfish::scm_auiInitP[18] = { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b +}; + +//Four 32-bit S-boxes with 256 entries each +const unsigned int blowfish::scm_auiInitS[4][256] = { + //0 + {0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a}, + + //1 + {0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7}, + + //2 + {0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0}, + + //3 + {0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6} +}; + +//Constructor - Initialize the P and S boxes for a given Key +blowfish::blowfish() +{} + +void blowfish::start(unsigned char* ucKey, uint64_t keysize, const sblock& roChain) +{ + m_oChain0 = roChain; + m_oChain = roChain; + + if(keysize<1) + FC_THROW_EXCEPTION( exception, "invalid key length" ); + //Check the Key - the key length should be between 1 and 56 bytes + if(keysize>56) + keysize = 56; + unsigned char aucLocalKey[56]; + unsigned int i, j; + memcpy(aucLocalKey, ucKey, static_cast(keysize)); + //Reflexive Initialization of the Blowfish. + //Generating the Subkeys from the Key flood P and S boxes with PI + memcpy(m_auiP, scm_auiInitP, sizeof m_auiP); + memcpy(m_auiS, scm_auiInitS, sizeof m_auiS); + //Load P boxes with key bytes + const unsigned char* p = aucLocalKey; + unsigned int x=0; + //Repeatedly cycle through the key bits until the entire P array has been XORed with key bits + uint32_t iCount = 0; + for(i=0; i<18; i++) + { + x=0; + for(int n=4; n--; ) + { + //int iVal = (int)(*p); + x <<= 8; + x |= *(p++); + iCount++; + if(iCount == keysize) + { + //All bytes used, so recycle bytes + iCount = 0; + p = aucLocalKey; + } + } + m_auiP[i] ^= x; + } + //Reflect P and S boxes through the evolving Blowfish + sblock block(0UL,0UL); //all-zero block + for(i=0; i<18; ) + encrypt(block), m_auiP[i++] = block.m_uil, m_auiP[i++] = block.m_uir; + for(j=0; j<4; j++) + for(int k=0; k<256; ) + encrypt(block), m_auiS[j][k++] = block.m_uil, m_auiS[j][k++] = block.m_uir; +} + +//Sixteen Round Encipher of Block +void blowfish::encrypt(sblock& block) +{ + unsigned int uiLeft = block.m_uil; + unsigned int uiRight = block.m_uir; + uiLeft ^= m_auiP[0]; + uiRight ^= F(uiLeft)^m_auiP[1]; uiLeft ^= F(uiRight)^m_auiP[2]; + uiRight ^= F(uiLeft)^m_auiP[3]; uiLeft ^= F(uiRight)^m_auiP[4]; + uiRight ^= F(uiLeft)^m_auiP[5]; uiLeft ^= F(uiRight)^m_auiP[6]; + uiRight ^= F(uiLeft)^m_auiP[7]; uiLeft ^= F(uiRight)^m_auiP[8]; + uiRight ^= F(uiLeft)^m_auiP[9]; uiLeft ^= F(uiRight)^m_auiP[10]; + uiRight ^= F(uiLeft)^m_auiP[11]; uiLeft ^= F(uiRight)^m_auiP[12]; + uiRight ^= F(uiLeft)^m_auiP[13]; uiLeft ^= F(uiRight)^m_auiP[14]; + uiRight ^= F(uiLeft)^m_auiP[15]; uiLeft ^= F(uiRight)^m_auiP[16]; + uiRight ^= m_auiP[17]; + block.m_uil = uiRight; + block.m_uir = uiLeft; +} + +//Sixteen Round Decipher of sblock +void blowfish::decrypt(sblock& block) +{ + unsigned int uiLeft = block.m_uil; + unsigned int uiRight = block.m_uir; + uiLeft ^= m_auiP[17]; + uiRight ^= F(uiLeft)^m_auiP[16]; uiLeft ^= F(uiRight)^m_auiP[15]; + uiRight ^= F(uiLeft)^m_auiP[14]; uiLeft ^= F(uiRight)^m_auiP[13]; + uiRight ^= F(uiLeft)^m_auiP[12]; uiLeft ^= F(uiRight)^m_auiP[11]; + uiRight ^= F(uiLeft)^m_auiP[10]; uiLeft ^= F(uiRight)^m_auiP[9]; + uiRight ^= F(uiLeft)^m_auiP[8]; uiLeft ^= F(uiRight)^m_auiP[7]; + uiRight ^= F(uiLeft)^m_auiP[6]; uiLeft ^= F(uiRight)^m_auiP[5]; + uiRight ^= F(uiLeft)^m_auiP[4]; uiLeft ^= F(uiRight)^m_auiP[3]; + uiRight ^= F(uiLeft)^m_auiP[2]; uiLeft ^= F(uiRight)^m_auiP[1]; + uiRight ^= m_auiP[0]; + block.m_uil = uiRight; + block.m_uir = uiLeft; +} + +//Semi-Portable Byte Shuffling +inline void BytesToBlock(unsigned char const* p, sblock& b) +{ + unsigned int y; + //Left + b.m_uil = 0; + y = *p++; + y <<= 24; + b.m_uil |= y; + y = *p++; + y <<= 16; + b.m_uil |= y; + y = *p++; + y <<= 8; + b.m_uil |= y; + y = *p++; + b.m_uil |= y; + //Right + b.m_uir = 0; + y = *p++; + y <<= 24; + b.m_uir |= y; + y = *p++; + y <<= 16; + b.m_uir |= y; + y = *p++; + y <<= 8; + b.m_uir |= y; + y = *p++; + b.m_uir |= y; +} + +inline void BlockToBytes(sblock const& b, unsigned char* p) +{ + unsigned int y; + //Right + y = b.m_uir; + *--p = Byte(y); + y = b.m_uir >> 8; + *--p = Byte(y); + y = b.m_uir >> 16; + *--p = Byte(y); + y = b.m_uir >> 24; + *--p = Byte(y); + //Left + y = b.m_uil; + *--p = Byte(y); + y = b.m_uil >> 8; + *--p = Byte(y); + y = b.m_uil >> 16; + *--p = Byte(y); + y = b.m_uil >> 24; + *--p = Byte(y); +} + +//encrypt Buffer in Place +//Returns false if n is multiple of 8 +void blowfish::encrypt(unsigned char* buf, uint64_t n, int iMode) +{ + //Check the buffer's length - should be > 0 and multiple of 8 + if((n==0)||(n%8!=0)) + FC_THROW_EXCEPTION( exception, "invalid buffer length ${n}, not multiple of 8", ("n", n) ); + sblock work; + if(iMode == CBC) //CBC mode, using the Chain + { + sblock chain(m_oChain); + for(; n >= 8; n -= 8) + { + BytesToBlock(buf, work); + work ^= chain; + encrypt(work); + chain = work; + BlockToBytes(work, buf+=8); + } + } + else if(iMode == CFB) //CFB mode, using the Chain + { + sblock chain(m_oChain); + for(; n >= 8; n -= 8) + { + encrypt(chain); + BytesToBlock(buf, work); + work ^= chain; + chain = work; + BlockToBytes(work, buf+=8); + } + } + else //ECB mode, not using the Chain + { + for(; n >= 8; n -= 8) + { + BytesToBlock(buf, work); + encrypt(work); + BlockToBytes(work, buf+=8); + } + } +} + +//decrypt Buffer in Place +//Returns false if n is multiple of 8 +void blowfish::decrypt(unsigned char* buf, uint64_t n, int iMode) +{ + //Check the buffer's length - should be > 0 and multiple of 8 + if((n==0)||(n%8!=0)) + FC_THROW_EXCEPTION( exception, "invalid buffer length ${n}, not multiple of 8", ("n", n) ); + sblock work; + if(iMode == CBC) //CBC mode, using the Chain + { + sblock crypt, chain(m_oChain); + for(; n >= 8; n -= 8) + { + BytesToBlock(buf, work); + crypt = work; + decrypt(work); + work ^= chain; + chain = crypt; + BlockToBytes(work, buf+=8); + } + } + else if(iMode == CFB) //CFB mode, using the Chain, not using decrypt() + { + sblock crypt, chain(m_oChain); + for(; n >= 8; n -= 8) + { + BytesToBlock(buf, work); + encrypt(chain); + crypt = work; + work ^= chain; + chain = crypt; + BlockToBytes(work, buf+=8); + } + } + else //ECB mode, not using the Chain + { + for(; n >= 8; n -= 8) + { + BytesToBlock(buf, work); + decrypt(work); + BlockToBytes(work, buf+=8); + } + } +} + +//encrypt from Input Buffer to Output Buffer +//Returns false if n is multiple of 8 +void blowfish::encrypt(const unsigned char* in, unsigned char* out, uint64_t n, int iMode) +{ + //Check the buffer's length - should be > 0 and multiple of 8 + if((n==0)||(n%8!=0)) + FC_THROW_EXCEPTION( exception, "invalid buffer length ${n}, not multiple of 8", ("n", n) ); + sblock work; + if(iMode == CBC) //CBC mode, using the Chain + { + sblock chain(m_oChain); + for(; n >= 8; n -= 8, in += 8) + { + BytesToBlock(in, work); + work ^= chain; + encrypt(work); + chain = work; + BlockToBytes(work, out+=8); + } + } + else if(iMode == CFB) //CFB mode, using the Chain + { + sblock chain(m_oChain); + for(; n >= 8; n -= 8, in += 8) + { + encrypt(chain); + BytesToBlock(in, work); + work ^= chain; + chain = work; + BlockToBytes(work, out+=8); + } + } + else //ECB mode, not using the Chain + { + for(; n >= 8; n -= 8, in += 8) + { + BytesToBlock(in, work); + encrypt(work); + BlockToBytes(work, out+=8); + } + } +} + +//decrypt from Input Buffer to Output Buffer +//Returns false if n is multiple of 8 +void blowfish::decrypt(const unsigned char* in, unsigned char* out, uint64_t n, int iMode) +{ + //Check the buffer's length - should be > 0 and multiple of 8 + if((n==0)||(n%8!=0)) + FC_THROW_EXCEPTION( exception, "invalid buffer length ${n}, not multiple of 8", ("n", n) ); + sblock work; + if(iMode == CBC) //CBC mode, using the Chain + { + sblock crypt, chain(m_oChain); + for(; n >= 8; n -= 8, in += 8) + { + BytesToBlock(in, work); + crypt = work; + decrypt(work); + work ^= chain; + chain = crypt; + BlockToBytes(work, out+=8); + } + } + else if(iMode == CFB) //CFB mode, using the Chain, not using decrypt() + { + sblock crypt, chain(m_oChain); + for(; n >= 8; n -= 8, in += 8) + { + BytesToBlock(in, work); + encrypt(chain); + crypt = work; + work ^= chain; + chain = crypt; + BlockToBytes(work, out+=8); + } + } + else //ECB mode, not using the Chain + { + for(; n >= 8; n -= 8, in += 8) + { + BytesToBlock(in, work); + decrypt(work); + BlockToBytes(work, out+=8); + } + } +} + +} // namespace fc diff --git a/libraries/libfc/src/crypto/city.cpp b/libraries/libfc/src/crypto/city.cpp new file mode 100644 index 0000000000..971b7bdc30 --- /dev/null +++ b/libraries/libfc/src/crypto/city.cpp @@ -0,0 +1,683 @@ +// Copyright (c) 2011 Google, Inc. +// +// 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. +// +// CityHash, by Geoff Pike and Jyrki Alakuijala +// +// This file provides CityHash64() and related functions. +// +// It's probably possible to create even faster hash functions by +// writing a program that systematically explores some of the space of +// possible hash functions, by using SIMD instructions, or by +// compromising on hash quality. + +//#include "config.h" +//#include + +#include +#include // for memcpy and memset +#include +#include +#include + +#if defined(__SSE4_2__) && defined(__x86_64__) +#include +#else +uint64_t _mm_crc32_u64(uint64_t a, uint64_t b ); +#endif + +namespace fc { + +using namespace std; + + +inline uint64_t Uint128Low64(const uint128& x) { return x.low_bits(); } +inline uint64_t Uint128High64(const uint128& x) { return x.high_bits(); } + +// Hash 128 input bits down to 64 bits of output. +// This is intended to be a reasonably good hash function. +inline uint64_t Hash128to64(const uint128& x) { + // Murmur-inspired hashing. + const uint64_t kMul = 0x9ddfea08eb382d69ULL; + uint64_t a = (Uint128Low64(x) ^ Uint128High64(x)) * kMul; + a ^= (a >> 47); + uint64_t b = (Uint128High64(x) ^ a) * kMul; + b ^= (b >> 47); + b *= kMul; + return b; +} + +static uint64_t UNALIGNED_LOAD64(const char *p) { + uint64_t result; + memcpy(&result, p, sizeof(result)); + return result; +} + +static uint32_t UNALIGNED_LOAD32(const char *p) { + uint32_t result; + memcpy(&result, p, sizeof(result)); + return result; +} + +#ifdef _WIN32 + +#include +#define bswap_32(x) _byteswap_ulong(x) +#define bswap_64(x) _byteswap_uint64(x) + +#elif defined(__APPLE__) + +// Mac OS X / Darwin features +#include +#define bswap_32(x) OSSwapInt32(x) +#define bswap_64(x) OSSwapInt64(x) + +#elif defined(__sun) || defined(sun) + +#include +#define bswap_32(x) BSWAP_32(x) +#define bswap_64(x) BSWAP_64(x) + +#elif defined(__FreeBSD__) + +#include +#define bswap_32(x) bswap32(x) +#define bswap_64(x) bswap64(x) + +#elif defined(__OpenBSD__) + +#include +#define bswap_32(x) swap32(x) +#define bswap_64(x) swap64(x) + +#elif defined(__NetBSD__) + +#include +#include +#if defined(__BSWAP_RENAME) && !defined(__bswap_32) +#define bswap_32(x) bswap32(x) +#define bswap_64(x) bswap64(x) +#endif + +#else + +#include + +#endif + +#ifdef WORDS_BIGENDIAN +#define uint32_in_expected_order(x) (bswap_32(x)) +#define uint64_in_expected_order(x) (bswap_64(x)) +#else +#define uint32_in_expected_order(x) (x) +#define uint64_in_expected_order(x) (x) +#endif + +#if !defined(LIKELY) +#if HAVE_BUILTIN_EXPECT +#define LIKELY(x) (__builtin_expect(!!(x), 1)) +#else +#define LIKELY(x) (x) +#endif +#endif + +static uint64_t Fetch64(const char *p) { + return uint64_in_expected_order(UNALIGNED_LOAD64(p)); +} + +static uint32_t Fetch32(const char *p) { + return uint32_in_expected_order(UNALIGNED_LOAD32(p)); +} + +// Some primes between 2^63 and 2^64 for various uses. +static const uint64_t k0 = 0xc3a5c85c97cb3127ULL; +static const uint64_t k1 = 0xb492b66fbe98f273ULL; +static const uint64_t k2 = 0x9ae16a3b2f90404fULL; + +// Magic numbers for 32-bit hashing. Copied from Murmur3. +static const uint32_t c1 = 0xcc9e2d51; +static const uint32_t c2 = 0x1b873593; + +// A 32-bit to 32-bit integer hash copied from Murmur3. +static uint32_t fmix(uint32_t h) +{ + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + +static uint32_t Rotate32(uint32_t val, int shift) { + // Avoid shifting by 32: doing so yields an undefined result. + return shift == 0 ? val : ((val >> shift) | (val << (32 - shift))); +} + +#undef PERMUTE3 +#define PERMUTE3(a, b, c) do { std::swap(a, b); std::swap(a, c); } while (0) + +static uint32_t Mur(uint32_t a, uint32_t h) { + // Helper from Murmur3 for combining two 32-bit values. + a *= c1; + a = Rotate32(a, 17); + a *= c2; + h ^= a; + h = Rotate32(h, 19); + return h * 5 + 0xe6546b64; +} + +static uint32_t Hash32Len13to24(const char *s, size_t len) { + uint32_t a = Fetch32(s - 4 + (len >> 1)); + uint32_t b = Fetch32(s + 4); + uint32_t c = Fetch32(s + len - 8); + uint32_t d = Fetch32(s + (len >> 1)); + uint32_t e = Fetch32(s); + uint32_t f = Fetch32(s + len - 4); + uint32_t h = len; + + return fmix(Mur(f, Mur(e, Mur(d, Mur(c, Mur(b, Mur(a, h))))))); +} + +static uint32_t Hash32Len0to4(const char *s, size_t len) { + uint32_t b = 0; + uint32_t c = 9; + for (size_t i = 0; i < len; i++) { + signed char v = s[i]; + b = b * c1 + v; + c ^= b; + } + return fmix(Mur(b, Mur(len, c))); +} + +static uint32_t Hash32Len5to12(const char *s, size_t len) { + uint32_t a = len, b = len * 5, c = 9, d = b; + a += Fetch32(s); + b += Fetch32(s + len - 4); + c += Fetch32(s + ((len >> 1) & 4)); + return fmix(Mur(c, Mur(b, Mur(a, d)))); +} + +uint32_t city_hash32(const char *s, size_t len) { + if (len <= 24) { + return len <= 12 ? + (len <= 4 ? Hash32Len0to4(s, len) : Hash32Len5to12(s, len)) : + Hash32Len13to24(s, len); + } + + // len > 24 + uint32_t h = len, g = c1 * len, f = g; + uint32_t a0 = Rotate32(Fetch32(s + len - 4) * c1, 17) * c2; + uint32_t a1 = Rotate32(Fetch32(s + len - 8) * c1, 17) * c2; + uint32_t a2 = Rotate32(Fetch32(s + len - 16) * c1, 17) * c2; + uint32_t a3 = Rotate32(Fetch32(s + len - 12) * c1, 17) * c2; + uint32_t a4 = Rotate32(Fetch32(s + len - 20) * c1, 17) * c2; + h ^= a0; + h = Rotate32(h, 19); + h = h * 5 + 0xe6546b64; + h ^= a2; + h = Rotate32(h, 19); + h = h * 5 + 0xe6546b64; + g ^= a1; + g = Rotate32(g, 19); + g = g * 5 + 0xe6546b64; + g ^= a3; + g = Rotate32(g, 19); + g = g * 5 + 0xe6546b64; + f += a4; + f = Rotate32(f, 19); + f = f * 5 + 0xe6546b64; + size_t iters = (len - 1) / 20; + do { + uint32_t a0 = Rotate32(Fetch32(s) * c1, 17) * c2; + uint32_t a1 = Fetch32(s + 4); + uint32_t a2 = Rotate32(Fetch32(s + 8) * c1, 17) * c2; + uint32_t a3 = Rotate32(Fetch32(s + 12) * c1, 17) * c2; + uint32_t a4 = Fetch32(s + 16); + h ^= a0; + h = Rotate32(h, 18); + h = h * 5 + 0xe6546b64; + f += a1; + f = Rotate32(f, 19); + f = f * c1; + g += a2; + g = Rotate32(g, 18); + g = g * 5 + 0xe6546b64; + h ^= a3 + a1; + h = Rotate32(h, 19); + h = h * 5 + 0xe6546b64; + g ^= a4; + g = bswap_32(g) * 5; + h += a4 * 5; + h = bswap_32(h); + f += a0; + PERMUTE3(f, h, g); + s += 20; + } while (--iters != 0); + g = Rotate32(g, 11) * c1; + g = Rotate32(g, 17) * c1; + f = Rotate32(f, 11) * c1; + f = Rotate32(f, 17) * c1; + h = Rotate32(h + g, 19); + h = h * 5 + 0xe6546b64; + h = Rotate32(h, 17) * c1; + h = Rotate32(h + f, 19); + h = h * 5 + 0xe6546b64; + h = Rotate32(h, 17) * c1; + return h; +} + +// Bitwise right rotate. Normally this will compile to a single +// instruction, especially if the shift is a manifest constant. +static uint64_t Rotate(uint64_t val, int shift) { + // Avoid shifting by 64: doing so yields an undefined result. + return shift == 0 ? val : ((val >> shift) | (val << (64 - shift))); +} + +static uint64_t ShiftMix(uint64_t val) { + return val ^ (val >> 47); +} + +static uint64_t HashLen16(uint64_t u, uint64_t v) { + return Hash128to64(uint128(u, v)); +} + +static uint64_t HashLen16(uint64_t u, uint64_t v, uint64_t mul) { + // Murmur-inspired hashing. + uint64_t a = (u ^ v) * mul; + a ^= (a >> 47); + uint64_t b = (v ^ a) * mul; + b ^= (b >> 47); + b *= mul; + return b; +} + +static uint64_t HashLen0to16(const char *s, size_t len) { + if (len >= 8) { + uint64_t mul = k2 + len * 2; + uint64_t a = Fetch64(s) + k2; + uint64_t b = Fetch64(s + len - 8); + uint64_t c = Rotate(b, 37) * mul + a; + uint64_t d = (Rotate(a, 25) + b) * mul; + return HashLen16(c, d, mul); + } + if (len >= 4) { + uint64_t mul = k2 + len * 2; + uint64_t a = Fetch32(s); + return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul); + } + if (len > 0) { + uint8_t a = s[0]; + uint8_t b = s[len >> 1]; + uint8_t c = s[len - 1]; + uint32_t y = static_cast(a) + (static_cast(b) << 8); + uint32_t z = len + (static_cast(c) << 2); + return ShiftMix(y * k2 ^ z * k0) * k2; + } + return k2; +} + +// This probably works well for 16-byte strings as well, but it may be overkill +// in that case. +static uint64_t HashLen17to32(const char *s, size_t len) { + uint64_t mul = k2 + len * 2; + uint64_t a = Fetch64(s) * k1; + uint64_t b = Fetch64(s + 8); + uint64_t c = Fetch64(s + len - 8) * mul; + uint64_t d = Fetch64(s + len - 16) * k2; + return HashLen16(Rotate(a + b, 43) + Rotate(c, 30) + d, + a + Rotate(b + k2, 18) + c, mul); +} + +// Return a 16-byte hash for 48 bytes. Quick and dirty. +// Callers do best to use "random-looking" values for a and b. +static pair WeakHashLen32WithSeeds( + uint64_t w, uint64_t x, uint64_t y, uint64_t z, uint64_t a, uint64_t b) { + a += w; + b = Rotate(b + a + z, 21); + uint64_t c = a; + a += x; + a += y; + b += Rotate(a, 44); + return make_pair(a + z, b + c); +} + +// Return a 16-byte hash for s[0] ... s[31], a, and b. Quick and dirty. +static pair WeakHashLen32WithSeeds( + const char* s, uint64_t a, uint64_t b) { + return WeakHashLen32WithSeeds(Fetch64(s), + Fetch64(s + 8), + Fetch64(s + 16), + Fetch64(s + 24), + a, + b); +} + +// Return an 8-byte hash for 33 to 64 bytes. +static uint64_t HashLen33to64(const char *s, size_t len) { + uint64_t mul = k2 + len * 2; + uint64_t a = Fetch64(s) * k2; + uint64_t b = Fetch64(s + 8); + uint64_t c = Fetch64(s + len - 24); + uint64_t d = Fetch64(s + len - 32); + uint64_t e = Fetch64(s + 16) * k2; + uint64_t f = Fetch64(s + 24) * 9; + uint64_t g = Fetch64(s + len - 8); + uint64_t h = Fetch64(s + len - 16) * mul; + uint64_t u = Rotate(a + g, 43) + (Rotate(b, 30) + c) * 9; + uint64_t v = ((a + g) ^ d) + f + 1; + uint64_t w = bswap_64((u + v) * mul) + h; + uint64_t x = Rotate(e + f, 42) + c; + uint64_t y = (bswap_64((v + w) * mul) + g) * mul; + uint64_t z = e + f + c; + a = bswap_64((x + z) * mul + y) + b; + b = ShiftMix((z + a) * mul + d + h) * mul; + return b + x; +} + +uint64_t city_hash64(const char *s, size_t len) { + if (len <= 32) { + if (len <= 16) { + return HashLen0to16(s, len); + } else { + return HashLen17to32(s, len); + } + } else if (len <= 64) { + return HashLen33to64(s, len); + } + + // For strings over 64 bytes we hash the end first, and then as we + // loop we keep 56 bytes of state: v, w, x, y, and z. + uint64_t x = Fetch64(s + len - 40); + uint64_t y = Fetch64(s + len - 16) + Fetch64(s + len - 56); + uint64_t z = HashLen16(Fetch64(s + len - 48) + len, Fetch64(s + len - 24)); + pair v = WeakHashLen32WithSeeds(s + len - 64, len, z); + pair w = WeakHashLen32WithSeeds(s + len - 32, y + k1, x); + x = x * k1 + Fetch64(s); + + // Decrease len to the nearest multiple of 64, and operate on 64-byte chunks. + len = (len - 1) & ~static_cast(63); + do { + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + len -= 64; + } while (len != 0); + return HashLen16(HashLen16(v.first, w.first) + ShiftMix(y) * k1 + z, + HashLen16(v.second, w.second) + x); +} + +uint64_t CityHash64WithSeeds(const char *s, size_t len, + uint64_t seed0, uint64_t seed1) { + return HashLen16(city_hash64(s, len) - seed0, seed1); +} + +uint64_t CityHash64WithSeed(const char *s, size_t len, uint64_t seed) { + return CityHash64WithSeeds(s, len, k2, seed); +} + +// A subroutine for CityHash128(). Returns a decent 128-bit hash for strings +// of any length representable in signed long. Based on City and Murmur. +static uint128 CityMurmur(const char *s, size_t len, uint128 seed) { + uint64_t a = Uint128Low64(seed); + uint64_t b = Uint128High64(seed); + uint64_t c = 0; + uint64_t d = 0; + signed long l = len - 16; + if (l <= 0) { // len <= 16 + a = ShiftMix(a * k1) * k1; + c = b * k1 + HashLen0to16(s, len); + d = ShiftMix(a + (len >= 8 ? Fetch64(s) : c)); + } else { // len > 16 + c = HashLen16(Fetch64(s + len - 8) + k1, a); + d = HashLen16(b + len, c + Fetch64(s + len - 16)); + a += d; + do { + a ^= ShiftMix(Fetch64(s) * k1) * k1; + a *= k1; + b ^= a; + c ^= ShiftMix(Fetch64(s + 8) * k1) * k1; + c *= k1; + d ^= c; + s += 16; + l -= 16; + } while (l > 0); + } + a = HashLen16(a, c); + b = HashLen16(d, b); + return uint128(a ^ b, HashLen16(b, a)); +} + +uint128 CityHash128WithSeed(const char *s, size_t len, uint128 seed) { + if (len < 128) { + return CityMurmur(s, len, seed); + } + + // We expect len >= 128 to be the common case. Keep 56 bytes of state: + // v, w, x, y, and z. + pair v, w; + uint64_t x = Uint128Low64(seed); + uint64_t y = Uint128High64(seed); + uint64_t z = len * k1; + v.first = Rotate(y ^ k1, 49) * k1 + Fetch64(s); + v.second = Rotate(v.first, 42) * k1 + Fetch64(s + 8); + w.first = Rotate(y + z, 35) * k1 + x; + w.second = Rotate(x + Fetch64(s + 88), 53) * k1; + + // This is the same inner loop as CityHash64(), manually unrolled. + do { + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + x = Rotate(x + y + v.first + Fetch64(s + 8), 37) * k1; + y = Rotate(y + v.second + Fetch64(s + 48), 42) * k1; + x ^= w.second; + y += v.first + Fetch64(s + 40); + z = Rotate(z + w.first, 33) * k1; + v = WeakHashLen32WithSeeds(s, v.second * k1, x + w.first); + w = WeakHashLen32WithSeeds(s + 32, z + w.second, y + Fetch64(s + 16)); + std::swap(z, x); + s += 64; + len -= 128; + } while (LIKELY(len >= 128)); + x += Rotate(v.first + z, 49) * k0; + y = y * k0 + Rotate(w.second, 37); + z = z * k0 + Rotate(w.first, 27); + w.first *= 9; + v.first *= k0; + // If 0 < len < 128, hash up to 4 chunks of 32 bytes each from the end of s. + for (size_t tail_done = 0; tail_done < len; ) { + tail_done += 32; + y = Rotate(x + y, 42) * k0 + v.second; + w.first += Fetch64(s + len - tail_done + 16); + x = x * k0 + w.first; + z += w.second + Fetch64(s + len - tail_done); + w.second += v.first; + v = WeakHashLen32WithSeeds(s + len - tail_done, v.first + z, v.second); + v.first *= k0; + } + // At this point our 56 bytes of state should contain more than + // enough information for a strong 128-bit hash. We use two + // different 56-byte-to-8-byte hashes to get a 16-byte final result. + x = HashLen16(x, v.first); + y = HashLen16(y + z, w.first); + return uint128(HashLen16(x + v.second, w.second) + y, + HashLen16(x + w.second, y + v.second)); +} + +uint128 city_hash128(const char *s, size_t len) { + return len >= 16 ? + CityHash128WithSeed(s + 16, len - 16, + uint128(Fetch64(s), Fetch64(s + 8) + k0)) : + CityHash128WithSeed(s, len, uint128(k0, k1)); +} + +//#ifdef __SSE4_2__ +//#include +//#include + +// Requires len >= 240. +static void CityHashCrc256Long(const char *s, size_t len, + uint32_t seed, uint64_t *result) { + uint64_t a = Fetch64(s + 56) + k0; + uint64_t b = Fetch64(s + 96) + k0; + uint64_t c = result[0] = HashLen16(b, len); + uint64_t d = result[1] = Fetch64(s + 120) * k0 + len; + uint64_t e = Fetch64(s + 184) + seed; + uint64_t f = 0; + uint64_t g = 0; + uint64_t h = c + d; + uint64_t x = seed; + uint64_t y = 0; + uint64_t z = 0; + + // 240 bytes of input per iter. + size_t iters = len / 240; + len -= iters * 240; + do { +#undef CHUNK +#define CHUNK(r) \ + PERMUTE3(x, z, y); \ + b += Fetch64(s); \ + c += Fetch64(s + 8); \ + d += Fetch64(s + 16); \ + e += Fetch64(s + 24); \ + f += Fetch64(s + 32); \ + a += b; \ + h += f; \ + b += c; \ + f += d; \ + g += e; \ + e += z; \ + g += x; \ + z = _mm_crc32_u64(z, b + g); \ + y = _mm_crc32_u64(y, e + h); \ + x = _mm_crc32_u64(x, f + a); \ + e = Rotate(e, r); \ + c += e; \ + s += 40 + + CHUNK(0); PERMUTE3(a, h, c); + CHUNK(33); PERMUTE3(a, h, f); + CHUNK(0); PERMUTE3(b, h, f); + CHUNK(42); PERMUTE3(b, h, d); + CHUNK(0); PERMUTE3(b, h, e); + CHUNK(33); PERMUTE3(a, h, e); + } while (--iters > 0); + + while (len >= 40) { + CHUNK(29); + e ^= Rotate(a, 20); + h += Rotate(b, 30); + g ^= Rotate(c, 40); + f += Rotate(d, 34); + PERMUTE3(c, h, g); + len -= 40; + } + if (len > 0) { + s = s + len - 40; + CHUNK(33); + e ^= Rotate(a, 43); + h += Rotate(b, 42); + g ^= Rotate(c, 41); + f += Rotate(d, 40); + } + result[0] ^= h; + result[1] ^= g; + g += h; + a = HashLen16(a, g + z); + x += y << 32; + b += x; + c = HashLen16(c, z) + h; + d = HashLen16(d, e + result[0]); + g += e; + h += HashLen16(x, f); + e = HashLen16(a, d) + g; + z = HashLen16(b, c) + a; + y = HashLen16(g, h) + c; + result[0] = e + z + y + x; + a = ShiftMix((a + y) * k0) * k0 + b; + result[1] += a + result[0]; + a = ShiftMix(a * k0) * k0 + c; + result[2] = a + result[1]; + a = ShiftMix((a + e) * k0) * k0; + result[3] = a + result[2]; +} + +// Requires len < 240. +static void CityHashCrc256Short(const char *s, size_t len, uint64_t *result) { + char buf[240]; + memcpy(buf, s, len); + memset(buf + len, 0, 240 - len); + CityHashCrc256Long(buf, 240, ~static_cast(len), result); +} + +void CityHashCrc256(const char *s, size_t len, uint64_t *result) { + if (LIKELY(len >= 240)) { + CityHashCrc256Long(s, len, 0, result); + } else { + CityHashCrc256Short(s, len, result); + } +} + +array city_hash_crc_256(const char *s, size_t len) +{ + array buf; + CityHashCrc256( s, len, (uint64_t*)&buf ); + return buf; +} + +uint128 CityHashCrc128WithSeed(const char *s, size_t len, uint128 seed) { + if (len <= 900) { + return CityHash128WithSeed(s, len, seed); + } else { + uint64_t result[4]; + CityHashCrc256(s, len, result); + uint64_t u = Uint128High64(seed) + result[0]; + uint64_t v = Uint128Low64(seed) + result[1]; + return uint128(HashLen16(u, v + result[2]), + HashLen16(Rotate(v, 32), u * k0 + result[3])); + } +} + +uint128 city_hash_crc_128(const char *s, size_t len) { + if (len <= 900) { + return city_hash128(s, len); + } else { + uint64_t result[4]; + CityHashCrc256(s, len, result); + return uint128(result[2], result[3]); + } +} + +} // end namespace fc + +//#endif diff --git a/libraries/libfc/src/crypto/crc.cpp b/libraries/libfc/src/crypto/crc.cpp new file mode 100644 index 0000000000..3b95455038 --- /dev/null +++ b/libraries/libfc/src/crypto/crc.cpp @@ -0,0 +1,627 @@ +#include +#include +//#include +/* Tables generated with code like the following: + +#define CRCPOLY 0x82f63b78 // reversed 0x1EDC6F41 +#define CRCINIT 0xFFFFFFFF + +void init() { + for (uint32_t i = 0; i <= 0xFF; i++) { + uint32_t x = i; + for (uint32_t j = 0; j < 8; j++) + x = (x>>1) ^ (CRCPOLY & (-(int32_t)(x & 1))); + g_crc_slicing[0][i] = x; + } + + for (uint32_t i = 0; i <= 0xFF; i++) { + uint32_t c = g_crc_slicing[0][i]; + for (uint32_t j = 1; j < 8; j++) { + c = g_crc_slicing[0][c & 0xFF] ^ (c >> 8); + g_crc_slicing[j][i] = c; + } + } +} +*/ + +/* + * The following CRC lookup table was generated automagically + * using the following model parameters: + * + * Generator Polynomial = ................. 0x1EDC6F41 + * Generator Polynomial Length = .......... 32 bits + * Reflected Bits = ....................... TRUE + * Table Generation Offset = .............. 32 bits + * Number of Slices = ..................... 8 slices + * Slice Lengths = ........................ 8 8 8 8 8 8 8 8 + * Directory Name = ....................... .\ + * File Name = ............................ 8x256_tables.c + */ + +const uint32_t crc_tableil8_o32[256] = +{ + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351 +}; + +/* + * end of the CRC lookup table crc_tableil8_o32 + */ + + + +/* + * The following CRC lookup table was generated automagically + * using the following model parameters: + * + * Generator Polynomial = ................. 0x1EDC6F41 + * Generator Polynomial Length = .......... 32 bits + * Reflected Bits = ....................... TRUE + * Table Generation Offset = .............. 32 bits + * Number of Slices = ..................... 8 slices + * Slice Lengths = ........................ 8 8 8 8 8 8 8 8 + * Directory Name = ....................... .\ + * File Name = ............................ 8x256_tables.c + */ + +const uint32_t crc_tableil8_o40[256] = +{ + 0x00000000, 0x13A29877, 0x274530EE, 0x34E7A899, 0x4E8A61DC, 0x5D28F9AB, 0x69CF5132, 0x7A6DC945, + 0x9D14C3B8, 0x8EB65BCF, 0xBA51F356, 0xA9F36B21, 0xD39EA264, 0xC03C3A13, 0xF4DB928A, 0xE7790AFD, + 0x3FC5F181, 0x2C6769F6, 0x1880C16F, 0x0B225918, 0x714F905D, 0x62ED082A, 0x560AA0B3, 0x45A838C4, + 0xA2D13239, 0xB173AA4E, 0x859402D7, 0x96369AA0, 0xEC5B53E5, 0xFFF9CB92, 0xCB1E630B, 0xD8BCFB7C, + 0x7F8BE302, 0x6C297B75, 0x58CED3EC, 0x4B6C4B9B, 0x310182DE, 0x22A31AA9, 0x1644B230, 0x05E62A47, + 0xE29F20BA, 0xF13DB8CD, 0xC5DA1054, 0xD6788823, 0xAC154166, 0xBFB7D911, 0x8B507188, 0x98F2E9FF, + 0x404E1283, 0x53EC8AF4, 0x670B226D, 0x74A9BA1A, 0x0EC4735F, 0x1D66EB28, 0x298143B1, 0x3A23DBC6, + 0xDD5AD13B, 0xCEF8494C, 0xFA1FE1D5, 0xE9BD79A2, 0x93D0B0E7, 0x80722890, 0xB4958009, 0xA737187E, + 0xFF17C604, 0xECB55E73, 0xD852F6EA, 0xCBF06E9D, 0xB19DA7D8, 0xA23F3FAF, 0x96D89736, 0x857A0F41, + 0x620305BC, 0x71A19DCB, 0x45463552, 0x56E4AD25, 0x2C896460, 0x3F2BFC17, 0x0BCC548E, 0x186ECCF9, + 0xC0D23785, 0xD370AFF2, 0xE797076B, 0xF4359F1C, 0x8E585659, 0x9DFACE2E, 0xA91D66B7, 0xBABFFEC0, + 0x5DC6F43D, 0x4E646C4A, 0x7A83C4D3, 0x69215CA4, 0x134C95E1, 0x00EE0D96, 0x3409A50F, 0x27AB3D78, + 0x809C2506, 0x933EBD71, 0xA7D915E8, 0xB47B8D9F, 0xCE1644DA, 0xDDB4DCAD, 0xE9537434, 0xFAF1EC43, + 0x1D88E6BE, 0x0E2A7EC9, 0x3ACDD650, 0x296F4E27, 0x53028762, 0x40A01F15, 0x7447B78C, 0x67E52FFB, + 0xBF59D487, 0xACFB4CF0, 0x981CE469, 0x8BBE7C1E, 0xF1D3B55B, 0xE2712D2C, 0xD69685B5, 0xC5341DC2, + 0x224D173F, 0x31EF8F48, 0x050827D1, 0x16AABFA6, 0x6CC776E3, 0x7F65EE94, 0x4B82460D, 0x5820DE7A, + 0xFBC3FAF9, 0xE861628E, 0xDC86CA17, 0xCF245260, 0xB5499B25, 0xA6EB0352, 0x920CABCB, 0x81AE33BC, + 0x66D73941, 0x7575A136, 0x419209AF, 0x523091D8, 0x285D589D, 0x3BFFC0EA, 0x0F186873, 0x1CBAF004, + 0xC4060B78, 0xD7A4930F, 0xE3433B96, 0xF0E1A3E1, 0x8A8C6AA4, 0x992EF2D3, 0xADC95A4A, 0xBE6BC23D, + 0x5912C8C0, 0x4AB050B7, 0x7E57F82E, 0x6DF56059, 0x1798A91C, 0x043A316B, 0x30DD99F2, 0x237F0185, + 0x844819FB, 0x97EA818C, 0xA30D2915, 0xB0AFB162, 0xCAC27827, 0xD960E050, 0xED8748C9, 0xFE25D0BE, + 0x195CDA43, 0x0AFE4234, 0x3E19EAAD, 0x2DBB72DA, 0x57D6BB9F, 0x447423E8, 0x70938B71, 0x63311306, + 0xBB8DE87A, 0xA82F700D, 0x9CC8D894, 0x8F6A40E3, 0xF50789A6, 0xE6A511D1, 0xD242B948, 0xC1E0213F, + 0x26992BC2, 0x353BB3B5, 0x01DC1B2C, 0x127E835B, 0x68134A1E, 0x7BB1D269, 0x4F567AF0, 0x5CF4E287, + 0x04D43CFD, 0x1776A48A, 0x23910C13, 0x30339464, 0x4A5E5D21, 0x59FCC556, 0x6D1B6DCF, 0x7EB9F5B8, + 0x99C0FF45, 0x8A626732, 0xBE85CFAB, 0xAD2757DC, 0xD74A9E99, 0xC4E806EE, 0xF00FAE77, 0xE3AD3600, + 0x3B11CD7C, 0x28B3550B, 0x1C54FD92, 0x0FF665E5, 0x759BACA0, 0x663934D7, 0x52DE9C4E, 0x417C0439, + 0xA6050EC4, 0xB5A796B3, 0x81403E2A, 0x92E2A65D, 0xE88F6F18, 0xFB2DF76F, 0xCFCA5FF6, 0xDC68C781, + 0x7B5FDFFF, 0x68FD4788, 0x5C1AEF11, 0x4FB87766, 0x35D5BE23, 0x26772654, 0x12908ECD, 0x013216BA, + 0xE64B1C47, 0xF5E98430, 0xC10E2CA9, 0xD2ACB4DE, 0xA8C17D9B, 0xBB63E5EC, 0x8F844D75, 0x9C26D502, + 0x449A2E7E, 0x5738B609, 0x63DF1E90, 0x707D86E7, 0x0A104FA2, 0x19B2D7D5, 0x2D557F4C, 0x3EF7E73B, + 0xD98EEDC6, 0xCA2C75B1, 0xFECBDD28, 0xED69455F, 0x97048C1A, 0x84A6146D, 0xB041BCF4, 0xA3E32483 +}; + +/* + * end of the CRC lookup table crc_tableil8_o40 + */ + + + +/* + * The following CRC lookup table was generated automagically + * using the following model parameters: + * + * Generator Polynomial = ................. 0x1EDC6F41 + * Generator Polynomial Length = .......... 32 bits + * Reflected Bits = ....................... TRUE + * Table Generation Offset = .............. 32 bits + * Number of Slices = ..................... 8 slices + * Slice Lengths = ........................ 8 8 8 8 8 8 8 8 + * Directory Name = ....................... .\ + * File Name = ............................ 8x256_tables.c + */ + +const uint32_t crc_tableil8_o48[256] = +{ + 0x00000000, 0xA541927E, 0x4F6F520D, 0xEA2EC073, 0x9EDEA41A, 0x3B9F3664, 0xD1B1F617, 0x74F06469, + 0x38513EC5, 0x9D10ACBB, 0x773E6CC8, 0xD27FFEB6, 0xA68F9ADF, 0x03CE08A1, 0xE9E0C8D2, 0x4CA15AAC, + 0x70A27D8A, 0xD5E3EFF4, 0x3FCD2F87, 0x9A8CBDF9, 0xEE7CD990, 0x4B3D4BEE, 0xA1138B9D, 0x045219E3, + 0x48F3434F, 0xEDB2D131, 0x079C1142, 0xA2DD833C, 0xD62DE755, 0x736C752B, 0x9942B558, 0x3C032726, + 0xE144FB14, 0x4405696A, 0xAE2BA919, 0x0B6A3B67, 0x7F9A5F0E, 0xDADBCD70, 0x30F50D03, 0x95B49F7D, + 0xD915C5D1, 0x7C5457AF, 0x967A97DC, 0x333B05A2, 0x47CB61CB, 0xE28AF3B5, 0x08A433C6, 0xADE5A1B8, + 0x91E6869E, 0x34A714E0, 0xDE89D493, 0x7BC846ED, 0x0F382284, 0xAA79B0FA, 0x40577089, 0xE516E2F7, + 0xA9B7B85B, 0x0CF62A25, 0xE6D8EA56, 0x43997828, 0x37691C41, 0x92288E3F, 0x78064E4C, 0xDD47DC32, + 0xC76580D9, 0x622412A7, 0x880AD2D4, 0x2D4B40AA, 0x59BB24C3, 0xFCFAB6BD, 0x16D476CE, 0xB395E4B0, + 0xFF34BE1C, 0x5A752C62, 0xB05BEC11, 0x151A7E6F, 0x61EA1A06, 0xC4AB8878, 0x2E85480B, 0x8BC4DA75, + 0xB7C7FD53, 0x12866F2D, 0xF8A8AF5E, 0x5DE93D20, 0x29195949, 0x8C58CB37, 0x66760B44, 0xC337993A, + 0x8F96C396, 0x2AD751E8, 0xC0F9919B, 0x65B803E5, 0x1148678C, 0xB409F5F2, 0x5E273581, 0xFB66A7FF, + 0x26217BCD, 0x8360E9B3, 0x694E29C0, 0xCC0FBBBE, 0xB8FFDFD7, 0x1DBE4DA9, 0xF7908DDA, 0x52D11FA4, + 0x1E704508, 0xBB31D776, 0x511F1705, 0xF45E857B, 0x80AEE112, 0x25EF736C, 0xCFC1B31F, 0x6A802161, + 0x56830647, 0xF3C29439, 0x19EC544A, 0xBCADC634, 0xC85DA25D, 0x6D1C3023, 0x8732F050, 0x2273622E, + 0x6ED23882, 0xCB93AAFC, 0x21BD6A8F, 0x84FCF8F1, 0xF00C9C98, 0x554D0EE6, 0xBF63CE95, 0x1A225CEB, + 0x8B277743, 0x2E66E53D, 0xC448254E, 0x6109B730, 0x15F9D359, 0xB0B84127, 0x5A968154, 0xFFD7132A, + 0xB3764986, 0x1637DBF8, 0xFC191B8B, 0x595889F5, 0x2DA8ED9C, 0x88E97FE2, 0x62C7BF91, 0xC7862DEF, + 0xFB850AC9, 0x5EC498B7, 0xB4EA58C4, 0x11ABCABA, 0x655BAED3, 0xC01A3CAD, 0x2A34FCDE, 0x8F756EA0, + 0xC3D4340C, 0x6695A672, 0x8CBB6601, 0x29FAF47F, 0x5D0A9016, 0xF84B0268, 0x1265C21B, 0xB7245065, + 0x6A638C57, 0xCF221E29, 0x250CDE5A, 0x804D4C24, 0xF4BD284D, 0x51FCBA33, 0xBBD27A40, 0x1E93E83E, + 0x5232B292, 0xF77320EC, 0x1D5DE09F, 0xB81C72E1, 0xCCEC1688, 0x69AD84F6, 0x83834485, 0x26C2D6FB, + 0x1AC1F1DD, 0xBF8063A3, 0x55AEA3D0, 0xF0EF31AE, 0x841F55C7, 0x215EC7B9, 0xCB7007CA, 0x6E3195B4, + 0x2290CF18, 0x87D15D66, 0x6DFF9D15, 0xC8BE0F6B, 0xBC4E6B02, 0x190FF97C, 0xF321390F, 0x5660AB71, + 0x4C42F79A, 0xE90365E4, 0x032DA597, 0xA66C37E9, 0xD29C5380, 0x77DDC1FE, 0x9DF3018D, 0x38B293F3, + 0x7413C95F, 0xD1525B21, 0x3B7C9B52, 0x9E3D092C, 0xEACD6D45, 0x4F8CFF3B, 0xA5A23F48, 0x00E3AD36, + 0x3CE08A10, 0x99A1186E, 0x738FD81D, 0xD6CE4A63, 0xA23E2E0A, 0x077FBC74, 0xED517C07, 0x4810EE79, + 0x04B1B4D5, 0xA1F026AB, 0x4BDEE6D8, 0xEE9F74A6, 0x9A6F10CF, 0x3F2E82B1, 0xD50042C2, 0x7041D0BC, + 0xAD060C8E, 0x08479EF0, 0xE2695E83, 0x4728CCFD, 0x33D8A894, 0x96993AEA, 0x7CB7FA99, 0xD9F668E7, + 0x9557324B, 0x3016A035, 0xDA386046, 0x7F79F238, 0x0B899651, 0xAEC8042F, 0x44E6C45C, 0xE1A75622, + 0xDDA47104, 0x78E5E37A, 0x92CB2309, 0x378AB177, 0x437AD51E, 0xE63B4760, 0x0C158713, 0xA954156D, + 0xE5F54FC1, 0x40B4DDBF, 0xAA9A1DCC, 0x0FDB8FB2, 0x7B2BEBDB, 0xDE6A79A5, 0x3444B9D6, 0x91052BA8 +}; + +/* + * end of the CRC lookup table crc_tableil8_o48 + */ + + + +/* + * The following CRC lookup table was generated automagically + * using the following model parameters: + * + * Generator Polynomial = ................. 0x1EDC6F41 + * Generator Polynomial Length = .......... 32 bits + * Reflected Bits = ....................... TRUE + * Table Generation Offset = .............. 32 bits + * Number of Slices = ..................... 8 slices + * Slice Lengths = ........................ 8 8 8 8 8 8 8 8 + * Directory Name = ....................... .\ + * File Name = ............................ 8x256_tables.c + */ + +const uint32_t crc_tableil8_o56[256] = +{ + 0x00000000, 0xDD45AAB8, 0xBF672381, 0x62228939, 0x7B2231F3, 0xA6679B4B, 0xC4451272, 0x1900B8CA, + 0xF64463E6, 0x2B01C95E, 0x49234067, 0x9466EADF, 0x8D665215, 0x5023F8AD, 0x32017194, 0xEF44DB2C, + 0xE964B13D, 0x34211B85, 0x560392BC, 0x8B463804, 0x924680CE, 0x4F032A76, 0x2D21A34F, 0xF06409F7, + 0x1F20D2DB, 0xC2657863, 0xA047F15A, 0x7D025BE2, 0x6402E328, 0xB9474990, 0xDB65C0A9, 0x06206A11, + 0xD725148B, 0x0A60BE33, 0x6842370A, 0xB5079DB2, 0xAC072578, 0x71428FC0, 0x136006F9, 0xCE25AC41, + 0x2161776D, 0xFC24DDD5, 0x9E0654EC, 0x4343FE54, 0x5A43469E, 0x8706EC26, 0xE524651F, 0x3861CFA7, + 0x3E41A5B6, 0xE3040F0E, 0x81268637, 0x5C632C8F, 0x45639445, 0x98263EFD, 0xFA04B7C4, 0x27411D7C, + 0xC805C650, 0x15406CE8, 0x7762E5D1, 0xAA274F69, 0xB327F7A3, 0x6E625D1B, 0x0C40D422, 0xD1057E9A, + 0xABA65FE7, 0x76E3F55F, 0x14C17C66, 0xC984D6DE, 0xD0846E14, 0x0DC1C4AC, 0x6FE34D95, 0xB2A6E72D, + 0x5DE23C01, 0x80A796B9, 0xE2851F80, 0x3FC0B538, 0x26C00DF2, 0xFB85A74A, 0x99A72E73, 0x44E284CB, + 0x42C2EEDA, 0x9F874462, 0xFDA5CD5B, 0x20E067E3, 0x39E0DF29, 0xE4A57591, 0x8687FCA8, 0x5BC25610, + 0xB4868D3C, 0x69C32784, 0x0BE1AEBD, 0xD6A40405, 0xCFA4BCCF, 0x12E11677, 0x70C39F4E, 0xAD8635F6, + 0x7C834B6C, 0xA1C6E1D4, 0xC3E468ED, 0x1EA1C255, 0x07A17A9F, 0xDAE4D027, 0xB8C6591E, 0x6583F3A6, + 0x8AC7288A, 0x57828232, 0x35A00B0B, 0xE8E5A1B3, 0xF1E51979, 0x2CA0B3C1, 0x4E823AF8, 0x93C79040, + 0x95E7FA51, 0x48A250E9, 0x2A80D9D0, 0xF7C57368, 0xEEC5CBA2, 0x3380611A, 0x51A2E823, 0x8CE7429B, + 0x63A399B7, 0xBEE6330F, 0xDCC4BA36, 0x0181108E, 0x1881A844, 0xC5C402FC, 0xA7E68BC5, 0x7AA3217D, + 0x52A0C93F, 0x8FE56387, 0xEDC7EABE, 0x30824006, 0x2982F8CC, 0xF4C75274, 0x96E5DB4D, 0x4BA071F5, + 0xA4E4AAD9, 0x79A10061, 0x1B838958, 0xC6C623E0, 0xDFC69B2A, 0x02833192, 0x60A1B8AB, 0xBDE41213, + 0xBBC47802, 0x6681D2BA, 0x04A35B83, 0xD9E6F13B, 0xC0E649F1, 0x1DA3E349, 0x7F816A70, 0xA2C4C0C8, + 0x4D801BE4, 0x90C5B15C, 0xF2E73865, 0x2FA292DD, 0x36A22A17, 0xEBE780AF, 0x89C50996, 0x5480A32E, + 0x8585DDB4, 0x58C0770C, 0x3AE2FE35, 0xE7A7548D, 0xFEA7EC47, 0x23E246FF, 0x41C0CFC6, 0x9C85657E, + 0x73C1BE52, 0xAE8414EA, 0xCCA69DD3, 0x11E3376B, 0x08E38FA1, 0xD5A62519, 0xB784AC20, 0x6AC10698, + 0x6CE16C89, 0xB1A4C631, 0xD3864F08, 0x0EC3E5B0, 0x17C35D7A, 0xCA86F7C2, 0xA8A47EFB, 0x75E1D443, + 0x9AA50F6F, 0x47E0A5D7, 0x25C22CEE, 0xF8878656, 0xE1873E9C, 0x3CC29424, 0x5EE01D1D, 0x83A5B7A5, + 0xF90696D8, 0x24433C60, 0x4661B559, 0x9B241FE1, 0x8224A72B, 0x5F610D93, 0x3D4384AA, 0xE0062E12, + 0x0F42F53E, 0xD2075F86, 0xB025D6BF, 0x6D607C07, 0x7460C4CD, 0xA9256E75, 0xCB07E74C, 0x16424DF4, + 0x106227E5, 0xCD278D5D, 0xAF050464, 0x7240AEDC, 0x6B401616, 0xB605BCAE, 0xD4273597, 0x09629F2F, + 0xE6264403, 0x3B63EEBB, 0x59416782, 0x8404CD3A, 0x9D0475F0, 0x4041DF48, 0x22635671, 0xFF26FCC9, + 0x2E238253, 0xF36628EB, 0x9144A1D2, 0x4C010B6A, 0x5501B3A0, 0x88441918, 0xEA669021, 0x37233A99, + 0xD867E1B5, 0x05224B0D, 0x6700C234, 0xBA45688C, 0xA345D046, 0x7E007AFE, 0x1C22F3C7, 0xC167597F, + 0xC747336E, 0x1A0299D6, 0x782010EF, 0xA565BA57, 0xBC65029D, 0x6120A825, 0x0302211C, 0xDE478BA4, + 0x31035088, 0xEC46FA30, 0x8E647309, 0x5321D9B1, 0x4A21617B, 0x9764CBC3, 0xF54642FA, 0x2803E842 +}; + +/* + * end of the CRC lookup table crc_tableil8_o56 + */ + + + +/* + * The following CRC lookup table was generated automagically + * using the following model parameters: + * + * Generator Polynomial = ................. 0x1EDC6F41 + * Generator Polynomial Length = .......... 32 bits + * Reflected Bits = ....................... TRUE + * Table Generation Offset = .............. 32 bits + * Number of Slices = ..................... 8 slices + * Slice Lengths = ........................ 8 8 8 8 8 8 8 8 + * Directory Name = ....................... .\ + * File Name = ............................ 8x256_tables.c + */ + +const uint32_t crc_tableil8_o64[256] = +{ + 0x00000000, 0x38116FAC, 0x7022DF58, 0x4833B0F4, 0xE045BEB0, 0xD854D11C, 0x906761E8, 0xA8760E44, + 0xC5670B91, 0xFD76643D, 0xB545D4C9, 0x8D54BB65, 0x2522B521, 0x1D33DA8D, 0x55006A79, 0x6D1105D5, + 0x8F2261D3, 0xB7330E7F, 0xFF00BE8B, 0xC711D127, 0x6F67DF63, 0x5776B0CF, 0x1F45003B, 0x27546F97, + 0x4A456A42, 0x725405EE, 0x3A67B51A, 0x0276DAB6, 0xAA00D4F2, 0x9211BB5E, 0xDA220BAA, 0xE2336406, + 0x1BA8B557, 0x23B9DAFB, 0x6B8A6A0F, 0x539B05A3, 0xFBED0BE7, 0xC3FC644B, 0x8BCFD4BF, 0xB3DEBB13, + 0xDECFBEC6, 0xE6DED16A, 0xAEED619E, 0x96FC0E32, 0x3E8A0076, 0x069B6FDA, 0x4EA8DF2E, 0x76B9B082, + 0x948AD484, 0xAC9BBB28, 0xE4A80BDC, 0xDCB96470, 0x74CF6A34, 0x4CDE0598, 0x04EDB56C, 0x3CFCDAC0, + 0x51EDDF15, 0x69FCB0B9, 0x21CF004D, 0x19DE6FE1, 0xB1A861A5, 0x89B90E09, 0xC18ABEFD, 0xF99BD151, + 0x37516AAE, 0x0F400502, 0x4773B5F6, 0x7F62DA5A, 0xD714D41E, 0xEF05BBB2, 0xA7360B46, 0x9F2764EA, + 0xF236613F, 0xCA270E93, 0x8214BE67, 0xBA05D1CB, 0x1273DF8F, 0x2A62B023, 0x625100D7, 0x5A406F7B, + 0xB8730B7D, 0x806264D1, 0xC851D425, 0xF040BB89, 0x5836B5CD, 0x6027DA61, 0x28146A95, 0x10050539, + 0x7D1400EC, 0x45056F40, 0x0D36DFB4, 0x3527B018, 0x9D51BE5C, 0xA540D1F0, 0xED736104, 0xD5620EA8, + 0x2CF9DFF9, 0x14E8B055, 0x5CDB00A1, 0x64CA6F0D, 0xCCBC6149, 0xF4AD0EE5, 0xBC9EBE11, 0x848FD1BD, + 0xE99ED468, 0xD18FBBC4, 0x99BC0B30, 0xA1AD649C, 0x09DB6AD8, 0x31CA0574, 0x79F9B580, 0x41E8DA2C, + 0xA3DBBE2A, 0x9BCAD186, 0xD3F96172, 0xEBE80EDE, 0x439E009A, 0x7B8F6F36, 0x33BCDFC2, 0x0BADB06E, + 0x66BCB5BB, 0x5EADDA17, 0x169E6AE3, 0x2E8F054F, 0x86F90B0B, 0xBEE864A7, 0xF6DBD453, 0xCECABBFF, + 0x6EA2D55C, 0x56B3BAF0, 0x1E800A04, 0x269165A8, 0x8EE76BEC, 0xB6F60440, 0xFEC5B4B4, 0xC6D4DB18, + 0xABC5DECD, 0x93D4B161, 0xDBE70195, 0xE3F66E39, 0x4B80607D, 0x73910FD1, 0x3BA2BF25, 0x03B3D089, + 0xE180B48F, 0xD991DB23, 0x91A26BD7, 0xA9B3047B, 0x01C50A3F, 0x39D46593, 0x71E7D567, 0x49F6BACB, + 0x24E7BF1E, 0x1CF6D0B2, 0x54C56046, 0x6CD40FEA, 0xC4A201AE, 0xFCB36E02, 0xB480DEF6, 0x8C91B15A, + 0x750A600B, 0x4D1B0FA7, 0x0528BF53, 0x3D39D0FF, 0x954FDEBB, 0xAD5EB117, 0xE56D01E3, 0xDD7C6E4F, + 0xB06D6B9A, 0x887C0436, 0xC04FB4C2, 0xF85EDB6E, 0x5028D52A, 0x6839BA86, 0x200A0A72, 0x181B65DE, + 0xFA2801D8, 0xC2396E74, 0x8A0ADE80, 0xB21BB12C, 0x1A6DBF68, 0x227CD0C4, 0x6A4F6030, 0x525E0F9C, + 0x3F4F0A49, 0x075E65E5, 0x4F6DD511, 0x777CBABD, 0xDF0AB4F9, 0xE71BDB55, 0xAF286BA1, 0x9739040D, + 0x59F3BFF2, 0x61E2D05E, 0x29D160AA, 0x11C00F06, 0xB9B60142, 0x81A76EEE, 0xC994DE1A, 0xF185B1B6, + 0x9C94B463, 0xA485DBCF, 0xECB66B3B, 0xD4A70497, 0x7CD10AD3, 0x44C0657F, 0x0CF3D58B, 0x34E2BA27, + 0xD6D1DE21, 0xEEC0B18D, 0xA6F30179, 0x9EE26ED5, 0x36946091, 0x0E850F3D, 0x46B6BFC9, 0x7EA7D065, + 0x13B6D5B0, 0x2BA7BA1C, 0x63940AE8, 0x5B856544, 0xF3F36B00, 0xCBE204AC, 0x83D1B458, 0xBBC0DBF4, + 0x425B0AA5, 0x7A4A6509, 0x3279D5FD, 0x0A68BA51, 0xA21EB415, 0x9A0FDBB9, 0xD23C6B4D, 0xEA2D04E1, + 0x873C0134, 0xBF2D6E98, 0xF71EDE6C, 0xCF0FB1C0, 0x6779BF84, 0x5F68D028, 0x175B60DC, 0x2F4A0F70, + 0xCD796B76, 0xF56804DA, 0xBD5BB42E, 0x854ADB82, 0x2D3CD5C6, 0x152DBA6A, 0x5D1E0A9E, 0x650F6532, + 0x081E60E7, 0x300F0F4B, 0x783CBFBF, 0x402DD013, 0xE85BDE57, 0xD04AB1FB, 0x9879010F, 0xA0686EA3 +}; + +/* + * end of the CRC lookup table crc_tableil8_o64 + */ + + + +/* + * The following CRC lookup table was generated automagically + * using the following model parameters: + * + * Generator Polynomial = ................. 0x1EDC6F41 + * Generator Polynomial Length = .......... 32 bits + * Reflected Bits = ....................... TRUE + * Table Generation Offset = .............. 32 bits + * Number of Slices = ..................... 8 slices + * Slice Lengths = ........................ 8 8 8 8 8 8 8 8 + * Directory Name = ....................... .\ + * File Name = ............................ 8x256_tables.c + */ + +const uint32_t crc_tableil8_o72[256] = +{ + 0x00000000, 0xEF306B19, 0xDB8CA0C3, 0x34BCCBDA, 0xB2F53777, 0x5DC55C6E, 0x697997B4, 0x8649FCAD, + 0x6006181F, 0x8F367306, 0xBB8AB8DC, 0x54BAD3C5, 0xD2F32F68, 0x3DC34471, 0x097F8FAB, 0xE64FE4B2, + 0xC00C303E, 0x2F3C5B27, 0x1B8090FD, 0xF4B0FBE4, 0x72F90749, 0x9DC96C50, 0xA975A78A, 0x4645CC93, + 0xA00A2821, 0x4F3A4338, 0x7B8688E2, 0x94B6E3FB, 0x12FF1F56, 0xFDCF744F, 0xC973BF95, 0x2643D48C, + 0x85F4168D, 0x6AC47D94, 0x5E78B64E, 0xB148DD57, 0x370121FA, 0xD8314AE3, 0xEC8D8139, 0x03BDEA20, + 0xE5F20E92, 0x0AC2658B, 0x3E7EAE51, 0xD14EC548, 0x570739E5, 0xB83752FC, 0x8C8B9926, 0x63BBF23F, + 0x45F826B3, 0xAAC84DAA, 0x9E748670, 0x7144ED69, 0xF70D11C4, 0x183D7ADD, 0x2C81B107, 0xC3B1DA1E, + 0x25FE3EAC, 0xCACE55B5, 0xFE729E6F, 0x1142F576, 0x970B09DB, 0x783B62C2, 0x4C87A918, 0xA3B7C201, + 0x0E045BEB, 0xE13430F2, 0xD588FB28, 0x3AB89031, 0xBCF16C9C, 0x53C10785, 0x677DCC5F, 0x884DA746, + 0x6E0243F4, 0x813228ED, 0xB58EE337, 0x5ABE882E, 0xDCF77483, 0x33C71F9A, 0x077BD440, 0xE84BBF59, + 0xCE086BD5, 0x213800CC, 0x1584CB16, 0xFAB4A00F, 0x7CFD5CA2, 0x93CD37BB, 0xA771FC61, 0x48419778, + 0xAE0E73CA, 0x413E18D3, 0x7582D309, 0x9AB2B810, 0x1CFB44BD, 0xF3CB2FA4, 0xC777E47E, 0x28478F67, + 0x8BF04D66, 0x64C0267F, 0x507CEDA5, 0xBF4C86BC, 0x39057A11, 0xD6351108, 0xE289DAD2, 0x0DB9B1CB, + 0xEBF65579, 0x04C63E60, 0x307AF5BA, 0xDF4A9EA3, 0x5903620E, 0xB6330917, 0x828FC2CD, 0x6DBFA9D4, + 0x4BFC7D58, 0xA4CC1641, 0x9070DD9B, 0x7F40B682, 0xF9094A2F, 0x16392136, 0x2285EAEC, 0xCDB581F5, + 0x2BFA6547, 0xC4CA0E5E, 0xF076C584, 0x1F46AE9D, 0x990F5230, 0x763F3929, 0x4283F2F3, 0xADB399EA, + 0x1C08B7D6, 0xF338DCCF, 0xC7841715, 0x28B47C0C, 0xAEFD80A1, 0x41CDEBB8, 0x75712062, 0x9A414B7B, + 0x7C0EAFC9, 0x933EC4D0, 0xA7820F0A, 0x48B26413, 0xCEFB98BE, 0x21CBF3A7, 0x1577387D, 0xFA475364, + 0xDC0487E8, 0x3334ECF1, 0x0788272B, 0xE8B84C32, 0x6EF1B09F, 0x81C1DB86, 0xB57D105C, 0x5A4D7B45, + 0xBC029FF7, 0x5332F4EE, 0x678E3F34, 0x88BE542D, 0x0EF7A880, 0xE1C7C399, 0xD57B0843, 0x3A4B635A, + 0x99FCA15B, 0x76CCCA42, 0x42700198, 0xAD406A81, 0x2B09962C, 0xC439FD35, 0xF08536EF, 0x1FB55DF6, + 0xF9FAB944, 0x16CAD25D, 0x22761987, 0xCD46729E, 0x4B0F8E33, 0xA43FE52A, 0x90832EF0, 0x7FB345E9, + 0x59F09165, 0xB6C0FA7C, 0x827C31A6, 0x6D4C5ABF, 0xEB05A612, 0x0435CD0B, 0x308906D1, 0xDFB96DC8, + 0x39F6897A, 0xD6C6E263, 0xE27A29B9, 0x0D4A42A0, 0x8B03BE0D, 0x6433D514, 0x508F1ECE, 0xBFBF75D7, + 0x120CEC3D, 0xFD3C8724, 0xC9804CFE, 0x26B027E7, 0xA0F9DB4A, 0x4FC9B053, 0x7B757B89, 0x94451090, + 0x720AF422, 0x9D3A9F3B, 0xA98654E1, 0x46B63FF8, 0xC0FFC355, 0x2FCFA84C, 0x1B736396, 0xF443088F, + 0xD200DC03, 0x3D30B71A, 0x098C7CC0, 0xE6BC17D9, 0x60F5EB74, 0x8FC5806D, 0xBB794BB7, 0x544920AE, + 0xB206C41C, 0x5D36AF05, 0x698A64DF, 0x86BA0FC6, 0x00F3F36B, 0xEFC39872, 0xDB7F53A8, 0x344F38B1, + 0x97F8FAB0, 0x78C891A9, 0x4C745A73, 0xA344316A, 0x250DCDC7, 0xCA3DA6DE, 0xFE816D04, 0x11B1061D, + 0xF7FEE2AF, 0x18CE89B6, 0x2C72426C, 0xC3422975, 0x450BD5D8, 0xAA3BBEC1, 0x9E87751B, 0x71B71E02, + 0x57F4CA8E, 0xB8C4A197, 0x8C786A4D, 0x63480154, 0xE501FDF9, 0x0A3196E0, 0x3E8D5D3A, 0xD1BD3623, + 0x37F2D291, 0xD8C2B988, 0xEC7E7252, 0x034E194B, 0x8507E5E6, 0x6A378EFF, 0x5E8B4525, 0xB1BB2E3C +}; + +/* + * end of the CRC lookup table crc_tableil8_o72 + */ + + + +/* + * The following CRC lookup table was generated automagically + * using the following model parameters: + * + * Generator Polynomial = ................. 0x1EDC6F41 + * Generator Polynomial Length = .......... 32 bits + * Reflected Bits = ....................... TRUE + * Table Generation Offset = .............. 32 bits + * Number of Slices = ..................... 8 slices + * Slice Lengths = ........................ 8 8 8 8 8 8 8 8 + * Directory Name = ....................... .\ + * File Name = ............................ 8x256_tables.c + */ + +const uint32_t crc_tableil8_o80[256] = +{ + 0x00000000, 0x68032CC8, 0xD0065990, 0xB8057558, 0xA5E0C5D1, 0xCDE3E919, 0x75E69C41, 0x1DE5B089, + 0x4E2DFD53, 0x262ED19B, 0x9E2BA4C3, 0xF628880B, 0xEBCD3882, 0x83CE144A, 0x3BCB6112, 0x53C84DDA, + 0x9C5BFAA6, 0xF458D66E, 0x4C5DA336, 0x245E8FFE, 0x39BB3F77, 0x51B813BF, 0xE9BD66E7, 0x81BE4A2F, + 0xD27607F5, 0xBA752B3D, 0x02705E65, 0x6A7372AD, 0x7796C224, 0x1F95EEEC, 0xA7909BB4, 0xCF93B77C, + 0x3D5B83BD, 0x5558AF75, 0xED5DDA2D, 0x855EF6E5, 0x98BB466C, 0xF0B86AA4, 0x48BD1FFC, 0x20BE3334, + 0x73767EEE, 0x1B755226, 0xA370277E, 0xCB730BB6, 0xD696BB3F, 0xBE9597F7, 0x0690E2AF, 0x6E93CE67, + 0xA100791B, 0xC90355D3, 0x7106208B, 0x19050C43, 0x04E0BCCA, 0x6CE39002, 0xD4E6E55A, 0xBCE5C992, + 0xEF2D8448, 0x872EA880, 0x3F2BDDD8, 0x5728F110, 0x4ACD4199, 0x22CE6D51, 0x9ACB1809, 0xF2C834C1, + 0x7AB7077A, 0x12B42BB2, 0xAAB15EEA, 0xC2B27222, 0xDF57C2AB, 0xB754EE63, 0x0F519B3B, 0x6752B7F3, + 0x349AFA29, 0x5C99D6E1, 0xE49CA3B9, 0x8C9F8F71, 0x917A3FF8, 0xF9791330, 0x417C6668, 0x297F4AA0, + 0xE6ECFDDC, 0x8EEFD114, 0x36EAA44C, 0x5EE98884, 0x430C380D, 0x2B0F14C5, 0x930A619D, 0xFB094D55, + 0xA8C1008F, 0xC0C22C47, 0x78C7591F, 0x10C475D7, 0x0D21C55E, 0x6522E996, 0xDD279CCE, 0xB524B006, + 0x47EC84C7, 0x2FEFA80F, 0x97EADD57, 0xFFE9F19F, 0xE20C4116, 0x8A0F6DDE, 0x320A1886, 0x5A09344E, + 0x09C17994, 0x61C2555C, 0xD9C72004, 0xB1C40CCC, 0xAC21BC45, 0xC422908D, 0x7C27E5D5, 0x1424C91D, + 0xDBB77E61, 0xB3B452A9, 0x0BB127F1, 0x63B20B39, 0x7E57BBB0, 0x16549778, 0xAE51E220, 0xC652CEE8, + 0x959A8332, 0xFD99AFFA, 0x459CDAA2, 0x2D9FF66A, 0x307A46E3, 0x58796A2B, 0xE07C1F73, 0x887F33BB, + 0xF56E0EF4, 0x9D6D223C, 0x25685764, 0x4D6B7BAC, 0x508ECB25, 0x388DE7ED, 0x808892B5, 0xE88BBE7D, + 0xBB43F3A7, 0xD340DF6F, 0x6B45AA37, 0x034686FF, 0x1EA33676, 0x76A01ABE, 0xCEA56FE6, 0xA6A6432E, + 0x6935F452, 0x0136D89A, 0xB933ADC2, 0xD130810A, 0xCCD53183, 0xA4D61D4B, 0x1CD36813, 0x74D044DB, + 0x27180901, 0x4F1B25C9, 0xF71E5091, 0x9F1D7C59, 0x82F8CCD0, 0xEAFBE018, 0x52FE9540, 0x3AFDB988, + 0xC8358D49, 0xA036A181, 0x1833D4D9, 0x7030F811, 0x6DD54898, 0x05D66450, 0xBDD31108, 0xD5D03DC0, + 0x8618701A, 0xEE1B5CD2, 0x561E298A, 0x3E1D0542, 0x23F8B5CB, 0x4BFB9903, 0xF3FEEC5B, 0x9BFDC093, + 0x546E77EF, 0x3C6D5B27, 0x84682E7F, 0xEC6B02B7, 0xF18EB23E, 0x998D9EF6, 0x2188EBAE, 0x498BC766, + 0x1A438ABC, 0x7240A674, 0xCA45D32C, 0xA246FFE4, 0xBFA34F6D, 0xD7A063A5, 0x6FA516FD, 0x07A63A35, + 0x8FD9098E, 0xE7DA2546, 0x5FDF501E, 0x37DC7CD6, 0x2A39CC5F, 0x423AE097, 0xFA3F95CF, 0x923CB907, + 0xC1F4F4DD, 0xA9F7D815, 0x11F2AD4D, 0x79F18185, 0x6414310C, 0x0C171DC4, 0xB412689C, 0xDC114454, + 0x1382F328, 0x7B81DFE0, 0xC384AAB8, 0xAB878670, 0xB66236F9, 0xDE611A31, 0x66646F69, 0x0E6743A1, + 0x5DAF0E7B, 0x35AC22B3, 0x8DA957EB, 0xE5AA7B23, 0xF84FCBAA, 0x904CE762, 0x2849923A, 0x404ABEF2, + 0xB2828A33, 0xDA81A6FB, 0x6284D3A3, 0x0A87FF6B, 0x17624FE2, 0x7F61632A, 0xC7641672, 0xAF673ABA, + 0xFCAF7760, 0x94AC5BA8, 0x2CA92EF0, 0x44AA0238, 0x594FB2B1, 0x314C9E79, 0x8949EB21, 0xE14AC7E9, + 0x2ED97095, 0x46DA5C5D, 0xFEDF2905, 0x96DC05CD, 0x8B39B544, 0xE33A998C, 0x5B3FECD4, 0x333CC01C, + 0x60F48DC6, 0x08F7A10E, 0xB0F2D456, 0xD8F1F89E, 0xC5144817, 0xAD1764DF, 0x15121187, 0x7D113D4F +}; + +/* + * end of the CRC lookup table crc_tableil8_o80 + */ + + + +/* + * The following CRC lookup table was generated automagically + * using the following model parameters: + * + * Generator Polynomial = ................. 0x1EDC6F41 + * Generator Polynomial Length = .......... 32 bits + * Reflected Bits = ....................... TRUE + * Table Generation Offset = .............. 32 bits + * Number of Slices = ..................... 8 slices + * Slice Lengths = ........................ 8 8 8 8 8 8 8 8 + * Directory Name = ....................... .\ + * File Name = ............................ 8x256_tables.c + */ + +const uint32_t crc_tableil8_o88[256] = +{ + 0x00000000, 0x493C7D27, 0x9278FA4E, 0xDB448769, 0x211D826D, 0x6821FF4A, 0xB3657823, 0xFA590504, + 0x423B04DA, 0x0B0779FD, 0xD043FE94, 0x997F83B3, 0x632686B7, 0x2A1AFB90, 0xF15E7CF9, 0xB86201DE, + 0x847609B4, 0xCD4A7493, 0x160EF3FA, 0x5F328EDD, 0xA56B8BD9, 0xEC57F6FE, 0x37137197, 0x7E2F0CB0, + 0xC64D0D6E, 0x8F717049, 0x5435F720, 0x1D098A07, 0xE7508F03, 0xAE6CF224, 0x7528754D, 0x3C14086A, + 0x0D006599, 0x443C18BE, 0x9F789FD7, 0xD644E2F0, 0x2C1DE7F4, 0x65219AD3, 0xBE651DBA, 0xF759609D, + 0x4F3B6143, 0x06071C64, 0xDD439B0D, 0x947FE62A, 0x6E26E32E, 0x271A9E09, 0xFC5E1960, 0xB5626447, + 0x89766C2D, 0xC04A110A, 0x1B0E9663, 0x5232EB44, 0xA86BEE40, 0xE1579367, 0x3A13140E, 0x732F6929, + 0xCB4D68F7, 0x827115D0, 0x593592B9, 0x1009EF9E, 0xEA50EA9A, 0xA36C97BD, 0x782810D4, 0x31146DF3, + 0x1A00CB32, 0x533CB615, 0x8878317C, 0xC1444C5B, 0x3B1D495F, 0x72213478, 0xA965B311, 0xE059CE36, + 0x583BCFE8, 0x1107B2CF, 0xCA4335A6, 0x837F4881, 0x79264D85, 0x301A30A2, 0xEB5EB7CB, 0xA262CAEC, + 0x9E76C286, 0xD74ABFA1, 0x0C0E38C8, 0x453245EF, 0xBF6B40EB, 0xF6573DCC, 0x2D13BAA5, 0x642FC782, + 0xDC4DC65C, 0x9571BB7B, 0x4E353C12, 0x07094135, 0xFD504431, 0xB46C3916, 0x6F28BE7F, 0x2614C358, + 0x1700AEAB, 0x5E3CD38C, 0x857854E5, 0xCC4429C2, 0x361D2CC6, 0x7F2151E1, 0xA465D688, 0xED59ABAF, + 0x553BAA71, 0x1C07D756, 0xC743503F, 0x8E7F2D18, 0x7426281C, 0x3D1A553B, 0xE65ED252, 0xAF62AF75, + 0x9376A71F, 0xDA4ADA38, 0x010E5D51, 0x48322076, 0xB26B2572, 0xFB575855, 0x2013DF3C, 0x692FA21B, + 0xD14DA3C5, 0x9871DEE2, 0x4335598B, 0x0A0924AC, 0xF05021A8, 0xB96C5C8F, 0x6228DBE6, 0x2B14A6C1, + 0x34019664, 0x7D3DEB43, 0xA6796C2A, 0xEF45110D, 0x151C1409, 0x5C20692E, 0x8764EE47, 0xCE589360, + 0x763A92BE, 0x3F06EF99, 0xE44268F0, 0xAD7E15D7, 0x572710D3, 0x1E1B6DF4, 0xC55FEA9D, 0x8C6397BA, + 0xB0779FD0, 0xF94BE2F7, 0x220F659E, 0x6B3318B9, 0x916A1DBD, 0xD856609A, 0x0312E7F3, 0x4A2E9AD4, + 0xF24C9B0A, 0xBB70E62D, 0x60346144, 0x29081C63, 0xD3511967, 0x9A6D6440, 0x4129E329, 0x08159E0E, + 0x3901F3FD, 0x703D8EDA, 0xAB7909B3, 0xE2457494, 0x181C7190, 0x51200CB7, 0x8A648BDE, 0xC358F6F9, + 0x7B3AF727, 0x32068A00, 0xE9420D69, 0xA07E704E, 0x5A27754A, 0x131B086D, 0xC85F8F04, 0x8163F223, + 0xBD77FA49, 0xF44B876E, 0x2F0F0007, 0x66337D20, 0x9C6A7824, 0xD5560503, 0x0E12826A, 0x472EFF4D, + 0xFF4CFE93, 0xB67083B4, 0x6D3404DD, 0x240879FA, 0xDE517CFE, 0x976D01D9, 0x4C2986B0, 0x0515FB97, + 0x2E015D56, 0x673D2071, 0xBC79A718, 0xF545DA3F, 0x0F1CDF3B, 0x4620A21C, 0x9D642575, 0xD4585852, + 0x6C3A598C, 0x250624AB, 0xFE42A3C2, 0xB77EDEE5, 0x4D27DBE1, 0x041BA6C6, 0xDF5F21AF, 0x96635C88, + 0xAA7754E2, 0xE34B29C5, 0x380FAEAC, 0x7133D38B, 0x8B6AD68F, 0xC256ABA8, 0x19122CC1, 0x502E51E6, + 0xE84C5038, 0xA1702D1F, 0x7A34AA76, 0x3308D751, 0xC951D255, 0x806DAF72, 0x5B29281B, 0x1215553C, + 0x230138CF, 0x6A3D45E8, 0xB179C281, 0xF845BFA6, 0x021CBAA2, 0x4B20C785, 0x906440EC, 0xD9583DCB, + 0x613A3C15, 0x28064132, 0xF342C65B, 0xBA7EBB7C, 0x4027BE78, 0x091BC35F, 0xD25F4436, 0x9B633911, + 0xA777317B, 0xEE4B4C5C, 0x350FCB35, 0x7C33B612, 0x866AB316, 0xCF56CE31, 0x14124958, 0x5D2E347F, + 0xE54C35A1, 0xAC704886, 0x7734CFEF, 0x3E08B2C8, 0xC451B7CC, 0x8D6DCAEB, 0x56294D82, 0x1F1530A5 +}; + +/* + * end of the CRC lookup table crc_tableil8_o88 + */ + + + + +uint32_t crc32cSlicingBy8(uint32_t crc, const void* data, size_t length) { + const char* p_buf = (const char*) data; + + // Handle leading misaligned bytes + size_t initial_bytes = (sizeof(int32_t) - (intptr_t)p_buf) & (sizeof(int32_t) - 1); + if (length < initial_bytes) initial_bytes = length; + for (size_t li = 0; li < initial_bytes; li++) { + crc = crc_tableil8_o32[(crc ^ *p_buf++) & 0x000000FF] ^ (crc >> 8); + } + + length -= initial_bytes; + size_t running_length = length & ~(sizeof(uint64_t) - 1); + size_t end_bytes = length - running_length; + + for (size_t li = 0; li < running_length/8; li++) { + crc ^= *(uint32_t*) p_buf; + p_buf += 4; + uint32_t term1 = crc_tableil8_o88[crc & 0x000000FF] ^ + crc_tableil8_o80[(crc >> 8) & 0x000000FF]; + uint32_t term2 = crc >> 16; + crc = term1 ^ + crc_tableil8_o72[term2 & 0x000000FF] ^ + crc_tableil8_o64[(term2 >> 8) & 0x000000FF]; + term1 = crc_tableil8_o56[(*(uint32_t *)p_buf) & 0x000000FF] ^ + crc_tableil8_o48[((*(uint32_t *)p_buf) >> 8) & 0x000000FF]; + + term2 = (*(uint32_t *)p_buf) >> 16; + crc = crc ^ term1 ^ + crc_tableil8_o40[term2 & 0x000000FF] ^ + crc_tableil8_o32[(term2 >> 8) & 0x000000FF]; + p_buf += 4; + } + + for (size_t li=0; li < end_bytes; li++) { + crc = crc_tableil8_o32[(crc ^ *p_buf++) & 0x000000FF] ^ (crc >> 8); + } + + return crc; +} + +#define CRC32C_POLY 0x1EDC6F41 + +#define CRC32C(c,d) (c=(c>>8)^crc_c[(c^(d))&0xFF]) +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* Copyright 2001, D. Otis. Use this program, code or tables */ +/* extracted from it, as desired without restriction. */ +/* */ +/* 32 Bit Reflected CRC table generation for SCTP. */ +/* To accommodate serial byte data being shifted out least */ +/* significant bit first, the table's 32 bit words are reflected */ +/* which flips both byte and bit MS and LS positions. The CRC */ +/* is calculated MS bits first from the perspective of the serial*/ +/* stream. The x^32 term is implied and the x^0 term may also */ +/* be shown as +1. The polynomial code used is 0x1EDC6F41. */ +/* Castagnoli93 */ +/* x^32+x^28+x^27+x^26+x^25+x^23+x^22+x^20+x^19+x^18+x^14+x^13+ */ +/* x^11+x^10+x^9+x^8+x^6+x^0 */ +/* Guy Castagnoli Stefan Braeuer and Martin Herrman */ +/* "Optimization of Cyclic Redundancy-Check Codes */ +/* with 24 and 32 Parity Bits", */ +/* IEEE Transactions on Communications, Vol.41, No.6, June 1993 */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +static const uint32_t crc_c[256] = { + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, + 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, + 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, + 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, + 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, + 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, + 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, + 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, + 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, + 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, + 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, + 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, + 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, + 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, + 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, + 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, + 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, + 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, + 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, + 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, + 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, + 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, + 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, + 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, + 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, + 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, + 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, + 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, + 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, + 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, + 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, + 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, + 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, + 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, + 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, + 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, + 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, + 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, + 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, + 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, + 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, + 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, + 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, + 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, + 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, + 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, + 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, + 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, + 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, + 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, + 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, + 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, + 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, + 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351, + }; +#if !defined __SSE4_2__ || (defined __SSE4_2__ && !defined __x86_64__) + + +uint64_t _mm_crc32_u64(uint64_t a, uint64_t b ) +{ + return crc32cSlicingBy8(a, (unsigned char*)&b, sizeof(b)); +} +/* +//#include "citycrc.h" +#include +#include + +int main( int argc, char** argv ) +{ + uint64_t f = 0x1234; + uint64_t a = 0x5678; + uint32_t f1 = _mm_crc32_u64(f, a); + uint32_t f4 = crc32cSlicingBy8(f, (unsigned char*)&a, sizeof(a)); + std::cout< +#include + +namespace fc { + SSL_TYPE(ssl_dh, DH, DH_free) + + static bool validate( const ssl_dh& dh, bool& valid ) { + int check; + DH_check(dh,&check); + return valid = !(check /*& DH_CHECK_P_NOT_SAFE_PRIME*/); + } + + bool diffie_hellman::generate_params( int s, uint8_t g ) + { + ssl_dh dh; + DH_generate_parameters_ex(dh.obj, s, g, NULL); + ssl_bignum bn_p; + DH_get0_pqg(dh.obj, (const BIGNUM**)&bn_p.obj, NULL, NULL); + p.resize( BN_num_bytes( bn_p ) ); + if( p.size() ) + BN_bn2bin( bn_p, (unsigned char*)&p.front() ); + this->g = g; + return fc::validate( dh, valid ); + } + + bool diffie_hellman::validate() + { + if( !p.size() ) + return valid = false; + ssl_dh dh = DH_new(); + const auto bn_p = BN_bin2bn( (unsigned char*)&p.front(), p.size(), NULL ); + const auto bn_g = BN_bin2bn( (unsigned char*)&g, 1, NULL ); + DH_set0_pqg(dh.obj, bn_p, NULL, bn_g); + return fc::validate( dh, valid ); + } + + bool diffie_hellman::generate_pub_key() + { + if( !p.size() ) + return valid = false; + ssl_dh dh = DH_new(); + const auto bn_p = BN_bin2bn( (unsigned char*)&p.front(), p.size(), NULL ); + const auto bn_g = BN_bin2bn( (unsigned char*)&g, 1, NULL ); + DH_set0_pqg(dh.obj, bn_p, NULL, bn_g); + + if( !fc::validate( dh, valid ) ) + { + return false; + } + DH_generate_key(dh); + + ssl_bignum bn_pub_key; + ssl_bignum bn_priv_key; + DH_get0_key(dh.obj, (const BIGNUM**)&bn_pub_key.obj, (const BIGNUM**)&bn_priv_key.obj); + pub_key.resize( BN_num_bytes( bn_pub_key ) ); + priv_key.resize( BN_num_bytes( bn_priv_key ) ); + if( pub_key.size() ) + BN_bn2bin( bn_pub_key.obj, (unsigned char*)&pub_key.front() ); + if( priv_key.size() ) + BN_bn2bin( bn_priv_key.obj, (unsigned char*)&priv_key.front() ); + + return true; + } + bool diffie_hellman::compute_shared_key( const char* buf, uint32_t s ) { + ssl_dh dh = DH_new(); + auto bn_p = BN_bin2bn( (unsigned char*)&p.front(), p.size(), NULL ); + auto bn_pub_key = BN_bin2bn( (unsigned char*)&pub_key.front(), pub_key.size(), NULL ); + auto bn_priv_key = BN_bin2bn( (unsigned char*)&priv_key.front(), priv_key.size(), NULL ); + auto bn_g = BN_bin2bn( (unsigned char*)&g, 1, NULL ); + DH_set0_pqg(dh.obj, bn_p, NULL, bn_g); + DH_set0_key(dh.obj, bn_pub_key, bn_priv_key); + + int check; + DH_check(dh,&check); + if( !fc::validate( dh, valid ) ) + { + return false; + } + + ssl_bignum pk; + BN_bin2bn( (unsigned char*)buf, s, pk ); + shared_key.resize( DH_size(dh) ); + DH_compute_key( (unsigned char*)&shared_key.front(), pk, dh ); + + return true; + } + bool diffie_hellman::compute_shared_key( const std::vector& pubk ) { + return compute_shared_key( &pubk.front(), pubk.size() ); + } +} diff --git a/libraries/libfc/src/crypto/elliptic_common.cpp b/libraries/libfc/src/crypto/elliptic_common.cpp new file mode 100644 index 0000000000..4fa0925b93 --- /dev/null +++ b/libraries/libfc/src/crypto/elliptic_common.cpp @@ -0,0 +1,240 @@ +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +#elif defined(__FreeBSD__) +# include +#else +# include +#endif + +/* stuff common to all ecc implementations */ + +#define BTC_EXT_PUB_MAGIC (0x0488B21E) +#define BTC_EXT_PRIV_MAGIC (0x0488ADE4) + +namespace fc { namespace ecc { + + namespace detail { + typedef fc::array chr37; + + fc::sha256 _left( const fc::sha512& v ) + { + fc::sha256 result; + memcpy( result.data(), v.data(), 32 ); + return result; + } + + fc::sha256 _right( const fc::sha512& v ) + { + fc::sha256 result; + memcpy( result.data(), v.data() + 32, 32 ); + return result; + } + + static void _put( unsigned char** dest, unsigned int i) + { + *(*dest)++ = (i >> 24) & 0xff; + *(*dest)++ = (i >> 16) & 0xff; + *(*dest)++ = (i >> 8) & 0xff; + *(*dest)++ = i & 0xff; + } + + + static chr37 _derive_message( char first, const char* key32, int i ) + { + chr37 result; + unsigned char* dest = (unsigned char*) result.begin(); + *dest++ = first; + memcpy( dest, key32, 32 ); dest += 32; + _put( &dest, i ); + return result; + } + + chr37 _derive_message( const public_key_data& key, int i ) + { + return _derive_message( *key.begin(), key.begin() + 1, i ); + } + + + const ec_group& get_curve() + { + static const ec_group secp256k1( EC_GROUP_new_by_curve_name( NID_secp256k1 ) ); + return secp256k1; + } + + static private_key_secret _get_curve_order() + { + const ec_group& group = get_curve(); + bn_ctx ctx(BN_CTX_new()); + ssl_bignum order; + FC_ASSERT( EC_GROUP_get_order( group, order, ctx ) ); + private_key_secret bin; + FC_ASSERT( BN_num_bytes( order ) == static_cast(bin.data_size()) ); + FC_ASSERT( BN_bn2bin( order, (unsigned char*) bin.data() ) == static_cast(bin.data_size()) ); + return bin; + } + + const private_key_secret& get_curve_order() + { + static private_key_secret order = _get_curve_order(); + return order; + } + + static private_key_secret _get_half_curve_order() + { + const ec_group& group = get_curve(); + bn_ctx ctx(BN_CTX_new()); + ssl_bignum order; + FC_ASSERT( EC_GROUP_get_order( group, order, ctx ) ); + BN_rshift1( order, order ); + private_key_secret bin; + FC_ASSERT( BN_num_bytes( order ) == static_cast(bin.data_size()) ); + FC_ASSERT( BN_bn2bin( order, (unsigned char*) bin.data() ) == static_cast(bin.data_size()) ); + return bin; + } + + const private_key_secret& get_half_curve_order() + { + static private_key_secret half_order = _get_half_curve_order(); + return half_order; + } + } + + public_key public_key::from_key_data( const public_key_data &data ) { + return public_key(data); + } + + private_key private_key::child( const fc::sha256& offset )const + { + fc::sha256::encoder enc; + fc::raw::pack( enc, get_public_key() ); + fc::raw::pack( enc, offset ); + return generate_from_seed( get_secret(), enc.result() ); + } + + std::string public_key::to_base58( const public_key_data &key ) + { + uint32_t check = (uint32_t)sha256::hash(key.data, sizeof(key))._hash[0]; + static_assert(sizeof(key) + sizeof(check) == 37, ""); // hack around gcc bug: key.size() should be constexpr, but isn't + array data; + memcpy(data.data, key.begin(), key.size()); + memcpy(data.begin() + key.size(), (const char*)&check, sizeof(check)); + return fc::to_base58(data.begin(), data.size(), fc::yield_function_t()); + } + + public_key public_key::from_base58( const std::string& b58 ) + { + array data; + size_t s = fc::from_base58(b58, (char*)&data, sizeof(data) ); + FC_ASSERT( s == sizeof(data) ); + + public_key_data key; + uint32_t check = (uint32_t)sha256::hash(data.data, sizeof(key))._hash[0]; + FC_ASSERT( memcmp( (char*)&check, data.data + sizeof(key), sizeof(check) ) == 0 ); + memcpy( (char*)key.data, data.data, sizeof(key) ); + return from_key_data(key); + } + + unsigned int public_key::fingerprint() const + { + public_key_data key = serialize(); + ripemd160 hash = ripemd160::hash( sha256::hash( key.begin(), key.size() ) ); + unsigned char* fp = (unsigned char*) hash._hash; + return (fp[0] << 24) | (fp[1] << 16) | (fp[2] << 8) | fp[3]; + } + + bool public_key::is_canonical( const compact_signature& c ) { + return !(c.data[1] & 0x80) + && !(c.data[1] == 0 && !(c.data[2] & 0x80)) + && !(c.data[33] & 0x80) + && !(c.data[33] == 0 && !(c.data[34] & 0x80)); + } + + private_key private_key::generate_from_seed( const fc::sha256& seed, const fc::sha256& offset ) + { + ssl_bignum z; + BN_bin2bn((unsigned char*)&offset, sizeof(offset), z); + + ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); + bn_ctx ctx(BN_CTX_new()); + ssl_bignum order; + EC_GROUP_get_order(group, order, ctx); + + // secexp = (seed + z) % order + ssl_bignum secexp; + BN_bin2bn((unsigned char*)&seed, sizeof(seed), secexp); + BN_add(secexp, secexp, z); + BN_mod(secexp, secexp, order, ctx); + + fc::sha256 secret; + FC_ASSERT(BN_num_bytes(secexp) <= int64_t(sizeof(secret))); + auto shift = sizeof(secret) - BN_num_bytes(secexp); + BN_bn2bin(secexp, ((unsigned char*)&secret)+shift); + return regenerate( secret ); + } + + fc::sha256 private_key::get_secret( const EC_KEY * const k ) + { + if( !k ) + { + return fc::sha256(); + } + + fc::sha256 sec; + const BIGNUM* bn = EC_KEY_get0_private_key(k); + if( bn == NULL ) + { + FC_THROW_EXCEPTION( exception, "get private key failed" ); + } + int nbytes = BN_num_bytes(bn); + BN_bn2bin(bn, &((unsigned char*)&sec)[32-nbytes] ); + return sec; + } + + private_key private_key::generate() + { + EC_KEY* k = EC_KEY_new_by_curve_name( NID_secp256k1 ); + if( !k ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); + if( !EC_KEY_generate_key( k ) ) + { + FC_THROW_EXCEPTION( exception, "ecc key generation error" ); + + } + + return private_key( k ); + } + + +} + +void to_variant( const ecc::private_key& var, variant& vo ) +{ + vo = var.get_secret(); +} + +void from_variant( const variant& var, ecc::private_key& vo ) +{ + fc::sha256 sec; + from_variant( var, sec ); + vo = ecc::private_key::regenerate(sec); +} + +void to_variant( const ecc::public_key& var, variant& vo ) +{ + vo = var.serialize(); +} + +void from_variant( const variant& var, ecc::public_key& vo ) +{ + ecc::public_key_data dat; + from_variant( var, dat ); + vo = ecc::public_key(dat); +} + +} diff --git a/libraries/libfc/src/crypto/elliptic_impl_priv.cpp b/libraries/libfc/src/crypto/elliptic_impl_priv.cpp new file mode 100644 index 0000000000..1134c0b4b0 --- /dev/null +++ b/libraries/libfc/src/crypto/elliptic_impl_priv.cpp @@ -0,0 +1,108 @@ +#include + +#include +#include + +#include "_elliptic_impl_priv.hpp" + +/* used by mixed + secp256k1 */ + +namespace fc { namespace ecc { + namespace detail { + + private_key_impl::private_key_impl() BOOST_NOEXCEPT + { + _init_lib(); + } + + private_key_impl::private_key_impl( const private_key_impl& cpy ) BOOST_NOEXCEPT + { + _init_lib(); + this->_key = cpy._key; + } + + private_key_impl& private_key_impl::operator=( const private_key_impl& pk ) BOOST_NOEXCEPT + { + _key = pk._key; + return *this; + } + } + + static const private_key_secret empty_priv; + + private_key::private_key() {} + + private_key::private_key( const private_key& pk ) : my( pk.my ) {} + + private_key::private_key( private_key&& pk ) : my( std::move( pk.my ) ) {} + + private_key::~private_key() {} + + private_key& private_key::operator=( private_key&& pk ) + { + my = std::move( pk.my ); + return *this; + } + + private_key& private_key::operator=( const private_key& pk ) + { + my = pk.my; + return *this; + } + + private_key private_key::regenerate( const fc::sha256& secret ) + { + private_key self; + self.my->_key = secret; + return self; + } + + fc::sha256 private_key::get_secret()const + { + return my->_key; + } + + private_key::private_key( EC_KEY* k ) + { + my->_key = get_secret( k ); + EC_KEY_free(k); + } + + public_key private_key::get_public_key()const + { + FC_ASSERT( my->_key != empty_priv ); + public_key_data pub; + size_t pub_len = sizeof(pub); + secp256k1_pubkey secp_pub; + FC_ASSERT( secp256k1_ec_pubkey_create( detail::_get_context(), &secp_pub, (unsigned char*) my->_key.data() ) ); + secp256k1_ec_pubkey_serialize( detail::_get_context(), (unsigned char*)&pub, &pub_len, &secp_pub, SECP256K1_EC_COMPRESSED ); + FC_ASSERT( pub_len == pub.size() ); + return public_key(pub); + } + + static int extended_nonce_function( unsigned char *nonce32, const unsigned char *msg32, + const unsigned char *key32, const unsigned char* algo16, + void* data, unsigned int attempt ) { + unsigned int* extra = (unsigned int*) data; + (*extra)++; + return secp256k1_nonce_function_default( nonce32, msg32, key32, algo16, nullptr, *extra ); + } + + compact_signature private_key::sign_compact( const fc::sha256& digest, bool require_canonical )const + { + FC_ASSERT( my->_key != empty_priv ); + compact_signature result; + secp256k1_ecdsa_recoverable_signature secp_sig; + int recid; + unsigned int counter = 0; + do + { + FC_ASSERT( secp256k1_ecdsa_sign_recoverable( detail::_get_context(), &secp_sig, (unsigned char*) digest.data(), (unsigned char*) my->_key.data(), extended_nonce_function, &counter )); + secp256k1_ecdsa_recoverable_signature_serialize_compact( detail::_get_context(), result.data + 1, &recid, &secp_sig); + } while( require_canonical && !public_key::is_canonical( result ) ); + + result.begin()[0] = 27 + 4 + recid; + return result; + } + +}} diff --git a/libraries/libfc/src/crypto/elliptic_impl_pub.cpp b/libraries/libfc/src/crypto/elliptic_impl_pub.cpp new file mode 100644 index 0000000000..c9bbb08472 --- /dev/null +++ b/libraries/libfc/src/crypto/elliptic_impl_pub.cpp @@ -0,0 +1,358 @@ +#include +#include + +#include "_elliptic_impl_pub.hpp" + +/* used by mixed + openssl */ + +namespace fc { namespace ecc { + namespace detail { + + public_key_impl::public_key_impl() BOOST_NOEXCEPT + { + _init_lib(); + } + + public_key_impl::public_key_impl( const public_key_impl& cpy ) BOOST_NOEXCEPT + { + _init_lib(); + *this = cpy; + } + + public_key_impl::public_key_impl( public_key_impl&& cpy ) BOOST_NOEXCEPT + { + _init_lib(); + *this = cpy; + } + + public_key_impl::~public_key_impl() BOOST_NOEXCEPT + { + free_key(); + } + + public_key_impl& public_key_impl::operator=( const public_key_impl& pk ) BOOST_NOEXCEPT + { + if (pk._key == nullptr) + { + free_key(); + } else if ( _key == nullptr ) { + _key = EC_KEY_dup( pk._key ); + } else { + EC_KEY_copy( _key, pk._key ); + } + return *this; + } + + public_key_impl& public_key_impl::operator=( public_key_impl&& pk ) BOOST_NOEXCEPT + { + if ( this != &pk ) { + free_key(); + _key = pk._key; + pk._key = nullptr; + } + return *this; + } + + void public_key_impl::free_key() BOOST_NOEXCEPT + { + if( _key != nullptr ) + { + EC_KEY_free(_key); + _key = nullptr; + } + } + + // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields + // recid selects which key is recovered + // if check is non-zero, additional checks are performed + int public_key_impl::ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, + const unsigned char *msg, + int msglen, int recid, int check) + { + if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); + + int ret = 0; + BN_CTX *ctx = NULL; + + BIGNUM *x = NULL; + BIGNUM *e = NULL; + BIGNUM *order = NULL; + BIGNUM *sor = NULL; + BIGNUM *eor = NULL; + BIGNUM *field = NULL; + EC_POINT *R = NULL; + EC_POINT *O = NULL; + EC_POINT *Q = NULL; + BIGNUM *rr = NULL; + BIGNUM *zero = NULL; + int n = 0; + int i = recid / 2; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } + BN_CTX_start(ctx); + order = BN_CTX_get(ctx); + if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } + x = BN_CTX_get(ctx); + if (!BN_copy(x, order)) { ret=-1; goto err; } + if (!BN_mul_word(x, i)) { ret=-1; goto err; } + if (!BN_add(x, x, ecsig->r)) { ret=-1; goto err; } + field = BN_CTX_get(ctx); + if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } + if (BN_cmp(x, field) >= 0) { ret=0; goto err; } + if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } + if (check) + { + if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } + if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } + } + if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + n = EC_GROUP_get_degree(group); + e = BN_CTX_get(ctx); + if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } + if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); + zero = BN_CTX_get(ctx); + if (!BN_zero(zero)) { ret=-1; goto err; } + if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } + rr = BN_CTX_get(ctx); + if (!BN_mod_inverse(rr, ecsig->r, order, ctx)) { ret=-1; goto err; } + sor = BN_CTX_get(ctx); + if (!BN_mod_mul(sor, ecsig->s, rr, order, ctx)) { ret=-1; goto err; } + eor = BN_CTX_get(ctx); + if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } + if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } + if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } + + ret = 1; + + err: + if (ctx) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + if (R != NULL) EC_POINT_free(R); + if (O != NULL) EC_POINT_free(O); + if (Q != NULL) EC_POINT_free(Q); + return ret; + } + } + + public_key::public_key() {} + + public_key::public_key( const public_key& pk ) : my( pk.my ) {} + + public_key::public_key( public_key&& pk ) : my( std::move( pk.my ) ) {} + + public_key::~public_key() {} + + public_key& public_key::operator=( public_key&& pk ) + { + my = std::move(pk.my); + return *this; + } + + public_key& public_key::operator=( const public_key& pk ) + { + my = pk.my; + return *this; + } + + bool public_key::valid()const + { + return my->_key != nullptr; + } + + /* WARNING! This implementation is broken, it is actually equivalent to + * public_key::add()! + */ +// public_key public_key::mult( const fc::sha256& digest ) const +// { +// // get point from this public key +// const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); +// ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); +// +// ssl_bignum z; +// BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); +// +// // multiply by digest +// ssl_bignum one; +// BN_one(one); +// bn_ctx ctx(BN_CTX_new()); +// +// ec_point result(EC_POINT_new(group)); +// EC_POINT_mul(group, result, z, master_pub, one, ctx); +// +// public_key rtn; +// rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); +// EC_KEY_set_public_key(rtn.my->_key,result); +// +// return rtn; +// } + public_key public_key::add( const fc::sha256& digest )const + { + try { + ec_group group(EC_GROUP_new_by_curve_name(NID_secp256k1)); + bn_ctx ctx(BN_CTX_new()); + + fc::bigint digest_bi( (char*)&digest, sizeof(digest) ); + + ssl_bignum order; + EC_GROUP_get_order(group, order, ctx); + if( digest_bi > fc::bigint(order) ) + { + FC_THROW_EXCEPTION( exception, "digest > group order" ); + } + + + public_key digest_key = private_key::regenerate(digest).get_public_key(); + const EC_POINT* digest_point = EC_KEY_get0_public_key( digest_key.my->_key ); + + // get point from this public key + const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); + +// ssl_bignum z; +// BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); + + // multiply by digest +// ssl_bignum one; +// BN_one(one); + + ec_point result(EC_POINT_new(group)); + EC_POINT_add(group, result, digest_point, master_pub, ctx); + + if (EC_POINT_is_at_infinity(group, result)) + { + FC_THROW_EXCEPTION( exception, "point at infinity" ); + } + + + public_key rtn; + rtn.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + EC_KEY_set_public_key(rtn.my->_key,result); + return rtn; + } FC_RETHROW_EXCEPTIONS( debug, "digest: ${digest}", ("digest",digest) ); + } + + std::string public_key::to_base58() const + { + public_key_data key = serialize(); + return to_base58( key ); + } + +// signature private_key::sign( const fc::sha256& digest )const +// { +// unsigned int buf_len = ECDSA_size(my->_key); +//// fprintf( stderr, "%d %d\n", buf_len, sizeof(sha256) ); +// signature sig; +// assert( buf_len == sizeof(sig) ); +// +// if( !ECDSA_sign( 0, +// (const unsigned char*)&digest, sizeof(digest), +// (unsigned char*)&sig, &buf_len, my->_key ) ) +// { +// FC_THROW_EXCEPTION( exception, "signing error" ); +// } +// +// +// return sig; +// } +// bool public_key::verify( const fc::sha256& digest, const fc::ecc::signature& sig ) +// { +// return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); +// } + + public_key_data public_key::serialize()const + { + public_key_data dat; + if( !my->_key ) return dat; + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); + /*size_t nbytes = i2o_ECPublicKey( my->_key, nullptr ); */ + /*assert( nbytes == 33 )*/ + char* front = &dat.data[0]; + i2o_ECPublicKey( my->_key, (unsigned char**)&front ); // FIXME: questionable memory handling + return dat; + /* + EC_POINT* pub = EC_KEY_get0_public_key( my->_key ); + EC_GROUP* group = EC_KEY_get0_group( my->_key ); + EC_POINT_get_affine_coordinates_GFp( group, pub, self.my->_pub_x.get(), self.my->_pub_y.get(), nullptr ); + */ + } + public_key_point_data public_key::serialize_ecc_point()const + { + public_key_point_data dat; + if( !my->_key ) return dat; + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_UNCOMPRESSED ); + char* front = &dat.data[0]; + i2o_ECPublicKey( my->_key, (unsigned char**)&front ); // FIXME: questionable memory handling + return dat; + } + + public_key::public_key( const public_key_point_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(dat) ); + if( !my->_key ) + { + FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + } + } + public_key::public_key( const public_key_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(public_key_data) ); + if( !my->_key ) + { + FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + } + } + +// bool private_key::verify( const fc::sha256& digest, const fc::ecc::signature& sig ) +// { +// return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); +// } + + public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) + { + int nV = c.data[0]; + if (nV<27 || nV>=35) + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + + ECDSA_SIG *sig = ECDSA_SIG_new(); + BN_bin2bn(&c.data[1],32,sig->r); + BN_bin2bn(&c.data[33],32,sig->s); + + if( check_canonical ) + { + FC_ASSERT( is_canonical( c ), "signature is not canonical" ); + } + + my->_key = EC_KEY_new_by_curve_name(NID_secp256k1); + + if (nV >= 31) + { + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); + nV -= 4; +// fprintf( stderr, "compressed\n" ); + } + + if (detail::public_key_impl::ECDSA_SIG_recover_key_GFp(my->_key, sig, (unsigned char*)&digest, sizeof(digest), nV - 27, 0) == 1) + { + ECDSA_SIG_free(sig); + return; + } + ECDSA_SIG_free(sig); + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + } +}} diff --git a/libraries/libfc/src/crypto/elliptic_mixed.cpp b/libraries/libfc/src/crypto/elliptic_mixed.cpp new file mode 100644 index 0000000000..2dda3b5859 --- /dev/null +++ b/libraries/libfc/src/crypto/elliptic_mixed.cpp @@ -0,0 +1,38 @@ +#include + +#include +#include + +#include +#include +#include + +#include + +#include "_elliptic_impl_priv.hpp" +#include "_elliptic_impl_pub.hpp" + +namespace fc { namespace ecc { + namespace detail + { + const secp256k1_context_t* _get_context() { + static secp256k1_context_t* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + return ctx; + } + + void _init_lib() { + static const secp256k1_context_t* ctx = _get_context(); + } + } + + static const private_key_secret empty_priv; + fc::sha512 private_key::get_shared_secret( const public_key& other )const + { + FC_ASSERT( my->_key != empty_priv ); + FC_ASSERT( other.my->_key != nullptr ); + public_key_data pub(other.serialize()); + FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( detail::_get_context(), (unsigned char*) pub.begin(), pub.size(), (unsigned char*) my->_key.data() ) ); + return fc::sha512::hash( pub.begin() + 1, pub.size() - 1 ); + } + +} } diff --git a/libraries/libfc/src/crypto/elliptic_openssl.cpp b/libraries/libfc/src/crypto/elliptic_openssl.cpp new file mode 100644 index 0000000000..c982439c37 --- /dev/null +++ b/libraries/libfc/src/crypto/elliptic_openssl.cpp @@ -0,0 +1,250 @@ +#include + +#include +#include + +#include +#include +#include + +#include "_elliptic_impl_pub.hpp" + +namespace fc { namespace ecc { + namespace detail + { + class private_key_impl + { + public: + private_key_impl() BOOST_NOEXCEPT {} + + private_key_impl( const private_key_impl& cpy ) BOOST_NOEXCEPT + { + *this = cpy; + } + + private_key_impl( private_key_impl&& cpy ) BOOST_NOEXCEPT + { + *this = cpy; + } + + ~private_key_impl() BOOST_NOEXCEPT + { + free_key(); + } + + private_key_impl& operator=( const private_key_impl& pk ) BOOST_NOEXCEPT + { + if (pk._key == nullptr) + { + free_key(); + } else if ( _key == nullptr ) { + _key = EC_KEY_dup( pk._key ); + } else { + EC_KEY_copy( _key, pk._key ); + } + return *this; + } + + private_key_impl& operator=( private_key_impl&& pk ) BOOST_NOEXCEPT + { + if ( this != &pk ) { + free_key(); + _key = pk._key; + pk._key = nullptr; + } + return *this; + } + + EC_KEY* _key = nullptr; + + private: + void free_key() BOOST_NOEXCEPT + { + if( _key != nullptr ) + { + EC_KEY_free(_key); + _key = nullptr; + } + } + }; + } + + private_key::private_key() {} + + private_key::private_key( const private_key& pk ) : my( pk.my ) {} + + private_key::private_key( private_key&& pk ) : my( std::move( pk.my ) ) {} + + private_key::~private_key() {} + + private_key& private_key::operator=( private_key&& pk ) + { + my = std::move(pk.my); + return *this; + } + + private_key& private_key::operator=( const private_key& pk ) + { + my = pk.my; + return *this; + } + static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) + { + if (*olen < SHA512_DIGEST_LENGTH) { + return NULL; + } + *olen = SHA512_DIGEST_LENGTH; + return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); + } + + int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) + { + int ok = 0; + BN_CTX *ctx = NULL; + EC_POINT *pub_key = NULL; + + if (!eckey) return 0; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + + if ((ctx = BN_CTX_new()) == NULL) + goto err; + + pub_key = EC_POINT_new(group); + + if (pub_key == NULL) + goto err; + + if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) + goto err; + + EC_KEY_set_private_key(eckey,priv_key); + EC_KEY_set_public_key(eckey,pub_key); + + ok = 1; + + err: + + if (pub_key) EC_POINT_free(pub_key); + if (ctx != NULL) BN_CTX_free(ctx); + + return(ok); + } + + private_key private_key::regenerate( const fc::sha256& secret ) + { + private_key self; + self.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + if( !self.my->_key ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); + + ssl_bignum bn; + BN_bin2bn( (const unsigned char*)&secret, 32, bn ); + + if( !EC_KEY_regenerate_key(self.my->_key,bn) ) + { + FC_THROW_EXCEPTION( exception, "unable to regenerate key" ); + } + return self; + } + + fc::sha256 private_key::get_secret()const + { + return get_secret( my->_key ); + } + + private_key::private_key( EC_KEY* k ) + { + my->_key = k; + } + + public_key private_key::get_public_key()const + { + public_key pub; + pub.my->_key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + EC_KEY_set_public_key( pub.my->_key, EC_KEY_get0_public_key( my->_key ) ); + return pub; + } + + + fc::sha512 private_key::get_shared_secret( const public_key& other )const + { + FC_ASSERT( my->_key != nullptr ); + FC_ASSERT( other.my->_key != nullptr ); + fc::sha512 buf; + ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(other.my->_key), my->_key, ecies_key_derivation ); + return buf; + } + + compact_signature private_key::sign_compact( const fc::sha256& digest )const + { + try { + FC_ASSERT( my->_key != nullptr ); + auto my_pub_key = get_public_key().serialize(); // just for good measure + //ECDSA_SIG *sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); + public_key_data key_data; + while( true ) + { + ecdsa_sig sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); + + if (sig==nullptr) + FC_THROW_EXCEPTION( exception, "Unable to sign" ); + + compact_signature csig; + // memset( csig.data, 0, sizeof(csig) ); + + int nBitsR = BN_num_bits(sig->r); + int nBitsS = BN_num_bits(sig->s); + if (nBitsR <= 256 && nBitsS <= 256) + { + int nRecId = -1; + EC_KEY* key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + FC_ASSERT( key ); + EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); + for (int i=0; i<4; i++) + { + if (detail::public_key_impl::ECDSA_SIG_recover_key_GFp(key, sig, (unsigned char*)&digest, sizeof(digest), i, 1) == 1) + { + unsigned char* buffer = (unsigned char*) key_data.begin(); + i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling + if ( key_data == my_pub_key ) + { + nRecId = i; + break; + } + } + } + EC_KEY_free( key ); + + if (nRecId == -1) + { + FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); + } + unsigned char* result = nullptr; + auto bytes = i2d_ECDSA_SIG( sig, &result ); + auto lenR = result[3]; + auto lenS = result[5+lenR]; + //idump( (result[0])(result[1])(result[2])(result[3])(result[3+lenR])(result[4+lenR])(bytes)(lenR)(lenS) ); + if( lenR != 32 ) { free(result); continue; } + if( lenS != 32 ) { free(result); continue; } + //idump( (33-(nBitsR+7)/8) ); + //idump( (65-(nBitsS+7)/8) ); + //idump( (sizeof(csig) ) ); + memcpy( &csig.data[1], &result[4], lenR ); + memcpy( &csig.data[33], &result[6+lenR], lenS ); + //idump( (csig.data[33]) ); + //idump( (csig.data[1]) ); + free(result); + //idump( (nRecId) ); + csig.data[0] = nRecId+27+4;//(fCompressedPubKey ? 4 : 0); + /* + idump( (csig) ); + auto rlen = BN_bn2bin(sig->r,&csig.data[33-(nBitsR+7)/8]); + auto slen = BN_bn2bin(sig->s,&csig.data[65-(nBitsS+7)/8]); + idump( (rlen)(slen) ); + */ + } + return csig; + } // while true + } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); + } +} } diff --git a/libraries/libfc/src/crypto/elliptic_r1.cpp b/libraries/libfc/src/crypto/elliptic_r1.cpp new file mode 100644 index 0000000000..09943282b7 --- /dev/null +++ b/libraries/libfc/src/crypto/elliptic_r1.cpp @@ -0,0 +1,644 @@ +#include + +#include +#include + +#include +#include +#include + +namespace fc { namespace crypto { namespace r1 { + namespace detail + { + class public_key_impl + { + public: + public_key_impl() : _key(nullptr) {} + + ~public_key_impl() + { + if( _key != nullptr ) + { + EC_KEY_free(_key); + } + } + public_key_impl( const public_key_impl& cpy ) + { + _key = cpy._key ? EC_KEY_dup( cpy._key ) : nullptr; + } + EC_KEY* _key; + }; + class private_key_impl + { + public: + private_key_impl() : _key(nullptr) {} + + ~private_key_impl() + { + if( _key != nullptr ) + { + EC_KEY_free(_key); + } + } + private_key_impl( const private_key_impl& cpy ) + { + _key = cpy._key ? EC_KEY_dup( cpy._key ) : nullptr; + } + EC_KEY* _key; + }; + } + static void * ecies_key_derivation(const void *input, size_t ilen, void *output, size_t *olen) + { + if (*olen < SHA512_DIGEST_LENGTH) { + return NULL; + } + *olen = SHA512_DIGEST_LENGTH; + return (void*)SHA512((const unsigned char*)input, ilen, (unsigned char*)output); + } + + // Perform ECDSA key recovery (see SEC1 4.1.6) for curves over (mod p)-fields + // recid selects which key is recovered + // if check is non-zero, additional checks are performed + int ECDSA_SIG_recover_key_GFp(EC_KEY *eckey, ECDSA_SIG *ecsig, const unsigned char *msg, int msglen, int recid, int check) + { + if (!eckey) FC_THROW_EXCEPTION( exception, "null key" ); + + int ret = 0; + BN_CTX *ctx = NULL; + + BIGNUM *x = NULL; + BIGNUM *e = NULL; + BIGNUM *order = NULL; + BIGNUM *sor = NULL; + BIGNUM *eor = NULL; + BIGNUM *field = NULL; + EC_POINT *R = NULL; + EC_POINT *O = NULL; + EC_POINT *Q = NULL; + BIGNUM *rr = NULL; + BIGNUM *zero = NULL; + int n = 0; + int i = recid / 2; + + const BIGNUM *r, *s; + ECDSA_SIG_get0(ecsig, &r, &s); + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + if ((ctx = BN_CTX_new()) == NULL) { ret = -1; goto err; } + BN_CTX_start(ctx); + order = BN_CTX_get(ctx); + if (!EC_GROUP_get_order(group, order, ctx)) { ret = -2; goto err; } + x = BN_CTX_get(ctx); + if (!BN_copy(x, order)) { ret=-1; goto err; } + if (!BN_mul_word(x, i)) { ret=-1; goto err; } + if (!BN_add(x, x, r)) { ret=-1; goto err; } + field = BN_CTX_get(ctx); + if (!EC_GROUP_get_curve_GFp(group, field, NULL, NULL, ctx)) { ret=-2; goto err; } + if (BN_cmp(x, field) >= 0) { ret=0; goto err; } + if ((R = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_set_compressed_coordinates_GFp(group, R, x, recid % 2, ctx)) { ret=0; goto err; } + if (check) + { + if ((O = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + if (!EC_POINT_mul(group, O, NULL, R, order, ctx)) { ret=-2; goto err; } + if (!EC_POINT_is_at_infinity(group, O)) { ret = 0; goto err; } + } + if ((Q = EC_POINT_new(group)) == NULL) { ret = -2; goto err; } + n = EC_GROUP_get_degree(group); + e = BN_CTX_get(ctx); + if (!BN_bin2bn(msg, msglen, e)) { ret=-1; goto err; } + if (8*msglen > n) BN_rshift(e, e, 8-(n & 7)); + zero = BN_CTX_get(ctx); + BN_zero(zero); + if (!BN_mod_sub(e, zero, e, order, ctx)) { ret=-1; goto err; } + rr = BN_CTX_get(ctx); + if (!BN_mod_inverse(rr, r, order, ctx)) { ret=-1; goto err; } + sor = BN_CTX_get(ctx); + if (!BN_mod_mul(sor, s, rr, order, ctx)) { ret=-1; goto err; } + eor = BN_CTX_get(ctx); + if (!BN_mod_mul(eor, e, rr, order, ctx)) { ret=-1; goto err; } + if (!EC_POINT_mul(group, Q, eor, R, sor, ctx)) { ret=-2; goto err; } + if (!EC_KEY_set_public_key(eckey, Q)) { ret=-2; goto err; } + + ret = 1; + + err: + if (ctx) { + BN_CTX_end(ctx); + BN_CTX_free(ctx); + } + if (R != NULL) EC_POINT_free(R); + if (O != NULL) EC_POINT_free(O); + if (Q != NULL) EC_POINT_free(Q); + return ret; + } + + compact_signature signature_from_ecdsa(const EC_KEY* key, const public_key_data& pub_data, fc::ecdsa_sig& sig, const fc::sha256& d) { + //We can't use ssl_bignum here; _get0() does not transfer ownership to us; _set0() does transfer ownership to fc::ecdsa_sig + const BIGNUM *sig_r, *sig_s; + BIGNUM *r = BN_new(), *s = BN_new(); + ECDSA_SIG_get0(sig, &sig_r, &sig_s); + BN_copy(r, sig_r); + BN_copy(s, sig_s); + + //want to always use the low S value + const EC_GROUP* group = EC_KEY_get0_group(key); + ssl_bignum order, halforder; + EC_GROUP_get_order(group, order, nullptr); + BN_rshift1(halforder, order); + if(BN_cmp(s, halforder) > 0) + BN_sub(s, order, s); + + compact_signature csig; + + int nBitsR = BN_num_bits(r); + int nBitsS = BN_num_bits(s); + if(nBitsR > 256 || nBitsS > 256) + FC_THROW_EXCEPTION( exception, "Unable to sign" ); + + ECDSA_SIG_set0(sig, r, s); + + int nRecId = -1; + for (int i=0; i<4; i++) + { + public_key keyRec; + keyRec.my->_key = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 ); + if (ECDSA_SIG_recover_key_GFp(keyRec.my->_key, sig, (unsigned char*)&d, sizeof(d), i, 1) == 1) + { + if (keyRec.serialize() == pub_data ) + { + nRecId = i; + break; + } + } + } + if (nRecId == -1) + FC_THROW_EXCEPTION( exception, "unable to construct recoverable key"); + + csig.data[0] = nRecId+27+4; + BN_bn2bin(r,&csig.data[33-(nBitsR+7)/8]); + BN_bn2bin(s,&csig.data[65-(nBitsS+7)/8]); + + return csig; + } + + int static inline EC_KEY_regenerate_key(EC_KEY *eckey, const BIGNUM *priv_key) + { + int ok = 0; + BN_CTX *ctx = NULL; + EC_POINT *pub_key = NULL; + + if (!eckey) return 0; + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + + if ((ctx = BN_CTX_new()) == NULL) + goto err; + + pub_key = EC_POINT_new(group); + + if (pub_key == NULL) + goto err; + + if (!EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, ctx)) + goto err; + + EC_KEY_set_private_key(eckey,priv_key); + EC_KEY_set_public_key(eckey,pub_key); + + ok = 1; + + err: + + if (pub_key) EC_POINT_free(pub_key); + if (ctx != NULL) BN_CTX_free(ctx); + + return(ok); + } + +/* + public_key::public_key() + :my( new detail::public_key_impl() ) + { + } + + public_key::public_key( fc::bigint pub_x, fc::bigint pub_y ) + :my( new detail::public_key_impl() ) + { + } + + public_key::~public_key() + { + } + */ + + public_key public_key::mult( const fc::sha256& digest ) + { + // get point from this public key + const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); + ec_group group(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + + ssl_bignum z; + BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); + + // multiply by digest + ssl_bignum one; + BN_one(one); + bn_ctx ctx(BN_CTX_new()); + + ec_point result(EC_POINT_new(group)); + EC_POINT_mul(group, result, z, master_pub, one, ctx); + + public_key rtn; + rtn.my->_key = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 ); + EC_KEY_set_public_key(rtn.my->_key,result); + + return rtn; + } + bool public_key::valid()const + { + return my->_key != nullptr; + } + public_key public_key::add( const fc::sha256& digest )const + { + try { + ec_group group(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + bn_ctx ctx(BN_CTX_new()); + + fc::bigint digest_bi( (char*)&digest, sizeof(digest) ); + + ssl_bignum order; + EC_GROUP_get_order(group, order, ctx); + if( digest_bi > fc::bigint(order) ) + { + FC_THROW_EXCEPTION( exception, "digest > group order" ); + } + + + public_key digest_key = private_key::regenerate(digest).get_public_key(); + const EC_POINT* digest_point = EC_KEY_get0_public_key( digest_key.my->_key ); + + // get point from this public key + const EC_POINT* master_pub = EC_KEY_get0_public_key( my->_key ); + + ssl_bignum z; + BN_bin2bn((unsigned char*)&digest, sizeof(digest), z); + + // multiply by digest + ssl_bignum one; + BN_one(one); + + ec_point result(EC_POINT_new(group)); + EC_POINT_add(group, result, digest_point, master_pub, ctx); + + if (EC_POINT_is_at_infinity(group, result)) + { + FC_THROW_EXCEPTION( exception, "point at infinity" ); + } + + + public_key rtn; + rtn.my->_key = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 ); + EC_KEY_set_public_key(rtn.my->_key,result); + return rtn; + } FC_RETHROW_EXCEPTIONS( debug, "digest: ${digest}", ("digest",digest) ); + } + + std::string public_key::to_base58() const + { + public_key_data key = serialize(); + uint32_t check = (uint32_t)sha256::hash(key.data, sizeof(key))._hash[0]; + static_assert(sizeof(key) + sizeof(check) == 37, ""); // hack around gcc bug: key.size() should be constexpr, but isn't + array data; + memcpy(data.data, key.begin(), key.size()); + memcpy(data.begin() + key.size(), (const char*)&check, sizeof(check)); + return fc::to_base58(data.begin(), data.size(), fc::yield_function_t()); + } + + public_key public_key::from_base58( const std::string& b58 ) + { + array data; + size_t s = fc::from_base58(b58, (char*)&data, sizeof(data) ); + FC_ASSERT( s == sizeof(data) ); + + public_key_data key; + uint32_t check = (uint32_t)sha256::hash(data.data, sizeof(key))._hash[0]; + FC_ASSERT( memcmp( (char*)&check, data.data + sizeof(key), sizeof(check) ) == 0 ); + memcpy( (char*)key.data, data.data, sizeof(key) ); + return public_key(key); + } + + private_key::private_key() + {} + + private_key private_key::generate_from_seed( const fc::sha256& seed, const fc::sha256& offset ) + { + ssl_bignum z; + BN_bin2bn((unsigned char*)&offset, sizeof(offset), z); + + ec_group group(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + bn_ctx ctx(BN_CTX_new()); + ssl_bignum order; + EC_GROUP_get_order(group, order, ctx); + + // secexp = (seed + z) % order + ssl_bignum secexp; + BN_bin2bn((unsigned char*)&seed, sizeof(seed), secexp); + BN_add(secexp, secexp, z); + BN_mod(secexp, secexp, order, ctx); + + fc::sha256 secret; + FC_ASSERT(BN_num_bytes(secexp) <= int64_t(sizeof(secret))); + auto shift = sizeof(secret) - BN_num_bytes(secexp); + BN_bn2bin(secexp, ((unsigned char*)&secret)+shift); + return regenerate( secret ); + } + + private_key private_key::regenerate( const fc::sha256& secret ) + { + private_key self; + self.my->_key = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 ); + if( !self.my->_key ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); + + ssl_bignum bn; + BN_bin2bn( (const unsigned char*)&secret, 32, bn ); + + if( !EC_KEY_regenerate_key(self.my->_key,bn) ) + { + FC_THROW_EXCEPTION( exception, "unable to regenerate key" ); + } + return self; + } + + fc::sha256 private_key::get_secret()const + { + if( !my->_key ) + { + return fc::sha256(); + } + + fc::sha256 sec; + const BIGNUM* bn = EC_KEY_get0_private_key(my->_key); + if( bn == NULL ) + { + FC_THROW_EXCEPTION( exception, "get private key failed" ); + } + int nbytes = BN_num_bytes(bn); + BN_bn2bin(bn, &((unsigned char*)&sec)[32-nbytes] ); + return sec; + } + + private_key private_key::generate() + { + private_key self; + EC_KEY* k = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 ); + if( !k ) FC_THROW_EXCEPTION( exception, "Unable to generate EC key" ); + self.my->_key = k; + if( !EC_KEY_generate_key( self.my->_key ) ) + { + FC_THROW_EXCEPTION( exception, "ecc key generation error" ); + + } + +#if 0 + = bigint( EC_KEY_get0_private_key( k ); + EC_POINT* pub = EC_KEY_get0_public_key( k ); + EC_GROUP* group = EC_KEY_get0_group( k ); + + EC_POINT_get_affine_coordinates_GFp( group, pub, self.my->_pub_x.get(), self.my->_pub_y.get(), nullptr/*ctx*/ ); + + EC_KEY_free(k); +#endif + + return self; + } + + signature private_key::sign( const fc::sha256& digest )const + { + unsigned int buf_len = ECDSA_size(my->_key); +// fprintf( stderr, "%d %d\n", buf_len, sizeof(sha256) ); + signature sig; + FC_ASSERT( buf_len == sizeof(sig) ); + + if( !ECDSA_sign( 0, + (const unsigned char*)&digest, sizeof(digest), + (unsigned char*)&sig, &buf_len, my->_key ) ) + { + FC_THROW_EXCEPTION( exception, "signing error" ); + } + + + return sig; + } + bool public_key::verify( const fc::sha256& digest, const fc::crypto::r1::signature& sig ) + { + return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); + } + + public_key_data public_key::serialize()const + { + public_key_data dat; + if( !my->_key ) return dat; + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); + /*size_t nbytes = i2o_ECPublicKey( my->_key, nullptr ); */ + /*FC_ASSERT( nbytes == 33 )*/ + char* front = &dat.data[0]; + i2o_ECPublicKey( my->_key, (unsigned char**)&front ); + return dat; + /* + EC_POINT* pub = EC_KEY_get0_public_key( my->_key ); + EC_GROUP* group = EC_KEY_get0_group( my->_key ); + EC_POINT_get_affine_coordinates_GFp( group, pub, self.my->_pub_x.get(), self.my->_pub_y.get(), nullptr ); + */ + } + + public_key::public_key() + { + } + public_key::~public_key() + { + } + public_key::public_key( const public_key_point_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(dat) ); + if( !my->_key ) + { + FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + } + } + public_key::public_key( const public_key_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + my->_key = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 ); + my->_key = o2i_ECPublicKey( &my->_key, (const unsigned char**)&front, sizeof(public_key_data) ); + if( !my->_key ) + { + FC_THROW_EXCEPTION( exception, "error decoding public key", ("s", ERR_error_string( ERR_get_error(), nullptr) ) ); + } + } + } + + bool private_key::verify( const fc::sha256& digest, const fc::crypto::r1::signature& sig ) + { + return 1 == ECDSA_verify( 0, (unsigned char*)&digest, sizeof(digest), (unsigned char*)&sig, sizeof(sig), my->_key ); + } + + public_key private_key::get_public_key()const + { + public_key pub; + pub.my->_key = EC_KEY_new_by_curve_name( NID_X9_62_prime256v1 ); + EC_KEY_set_public_key( pub.my->_key, EC_KEY_get0_public_key( my->_key ) ); + return pub; + } + + + fc::sha512 private_key::get_shared_secret( const public_key& other )const + { + FC_ASSERT( my->_key != nullptr ); + FC_ASSERT( other.my->_key != nullptr ); + fc::sha512 buf; + ECDH_compute_key( (unsigned char*)&buf, sizeof(buf), EC_KEY_get0_public_key(other.my->_key), my->_key, ecies_key_derivation ); + return buf; + } + + private_key::~private_key() + { + } + + public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) + { + int nV = c.data[0]; + if (nV<27 || nV>=35) + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + + ecdsa_sig sig = ECDSA_SIG_new(); + BIGNUM *r = BN_new(), *s = BN_new(); + BN_bin2bn(&c.data[1],32,r); + BN_bin2bn(&c.data[33],32,s); + ECDSA_SIG_set0(sig, r, s); + + my->_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + + const EC_GROUP* group = EC_KEY_get0_group(my->_key); + ssl_bignum order, halforder; + EC_GROUP_get_order(group, order, nullptr); + BN_rshift1(halforder, order); + if(BN_cmp(s, halforder) > 0) + FC_THROW_EXCEPTION( exception, "invalid high s-value encountered in r1 signature" ); + + if (nV >= 31) + { + EC_KEY_set_conv_form( my->_key, POINT_CONVERSION_COMPRESSED ); + nV -= 4; +// fprintf( stderr, "compressed\n" ); + } + + if (ECDSA_SIG_recover_key_GFp(my->_key, sig, (unsigned char*)&digest, sizeof(digest), nV - 27, 0) == 1) + return; + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + } + + compact_signature private_key::sign_compact( const fc::sha256& digest )const + { + try { + FC_ASSERT( my->_key != nullptr ); + auto my_pub_key = get_public_key().serialize(); // just for good measure + ecdsa_sig sig = ECDSA_do_sign((unsigned char*)&digest, sizeof(digest), my->_key); + + if (sig==nullptr) + FC_THROW_EXCEPTION( exception, "Unable to sign" ); + + return signature_from_ecdsa(my->_key, my_pub_key, sig, digest); + } FC_RETHROW_EXCEPTIONS( warn, "sign ${digest}", ("digest", digest)("private_key",*this) ); + } + + private_key& private_key::operator=( private_key&& pk ) + { + if( my->_key ) + { + EC_KEY_free(my->_key); + } + my->_key = pk.my->_key; + pk.my->_key = nullptr; + return *this; + } + public_key::public_key( const public_key& pk ) + :my(pk.my) + { + } + public_key::public_key( public_key&& pk ) + :my( fc::move( pk.my) ) + { + } + private_key::private_key( const private_key& pk ) + :my(pk.my) + { + } + private_key::private_key( private_key&& pk ) + :my( fc::move( pk.my) ) + { + } + + public_key& public_key::operator=( public_key&& pk ) + { + if( my->_key ) + { + EC_KEY_free(my->_key); + } + my->_key = pk.my->_key; + pk.my->_key = nullptr; + return *this; + } + public_key& public_key::operator=( const public_key& pk ) + { + if( my->_key ) + { + EC_KEY_free(my->_key); + } + my->_key = EC_KEY_dup(pk.my->_key); + return *this; + } + private_key& private_key::operator=( const private_key& pk ) + { + if( my->_key ) + { + EC_KEY_free(my->_key); + } + my->_key = EC_KEY_dup(pk.my->_key); + return *this; + } + +} +} + void to_variant( const crypto::r1::private_key& var, variant& vo ) + { + vo = var.get_secret(); + } + void from_variant( const variant& var, crypto::r1::private_key& vo ) + { + fc::sha256 sec; + from_variant( var, sec ); + vo = crypto::r1::private_key::regenerate(sec); + } + + void to_variant( const crypto::r1::public_key& var, variant& vo ) + { + vo = var.serialize(); + } + void from_variant( const variant& var, crypto::r1::public_key& vo ) + { + crypto::r1::public_key_data dat; + from_variant( var, dat ); + vo = crypto::r1::public_key(dat); + } + + +} diff --git a/libraries/libfc/src/crypto/elliptic_secp256k1.cpp b/libraries/libfc/src/crypto/elliptic_secp256k1.cpp new file mode 100644 index 0000000000..459a8d7c86 --- /dev/null +++ b/libraries/libfc/src/crypto/elliptic_secp256k1.cpp @@ -0,0 +1,163 @@ +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#if _WIN32 +# include +#elif defined(__FreeBSD__) +# include +#else +# include +#endif + +#include "_elliptic_impl_priv.hpp" + +namespace fc { namespace ecc { + namespace detail + { + const secp256k1_context* _get_context() { + static secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + return ctx; + } + + void _init_lib() { + static const secp256k1_context* ctx = _get_context(); + (void)ctx; + } + + class public_key_impl + { + public: + public_key_impl() BOOST_NOEXCEPT + { + _init_lib(); + } + + public_key_impl( const public_key_impl& cpy ) BOOST_NOEXCEPT + : _key( cpy._key ) + { + _init_lib(); + } + + public_key_data _key; + }; + + typedef fc::array chr37; + chr37 _derive_message( const public_key_data& key, int i ); + fc::sha256 _left( const fc::sha512& v ); + fc::sha256 _right( const fc::sha512& v ); + const ec_group& get_curve(); + const private_key_secret& get_curve_order(); + const private_key_secret& get_half_curve_order(); + } + + static const public_key_data empty_pub; + + + fc::sha512 private_key::get_shared_secret( const public_key& other )const + { + static const private_key_secret empty_priv; + FC_ASSERT( my->_key != empty_priv ); + FC_ASSERT( other.my->_key != empty_pub ); + secp256k1_pubkey secp_pubkey; + FC_ASSERT( secp256k1_ec_pubkey_parse( detail::_get_context(), &secp_pubkey, (unsigned char*)other.serialize().data, other.serialize().size() ) ); + FC_ASSERT( secp256k1_ec_pubkey_tweak_mul( detail::_get_context(), &secp_pubkey, (unsigned char*) my->_key.data() ) ); + public_key_data serialized_result; + size_t serialized_result_sz = sizeof(serialized_result); + secp256k1_ec_pubkey_serialize(detail::_get_context(), (unsigned char*)&serialized_result.data, &serialized_result_sz, &secp_pubkey, SECP256K1_EC_COMPRESSED ); + FC_ASSERT( serialized_result_sz == sizeof(serialized_result) ); + return fc::sha512::hash( serialized_result.begin() + 1, serialized_result.size() - 1 ); + } + + + public_key::public_key() {} + + public_key::public_key( const public_key &pk ) : my( pk.my ) {} + + public_key::public_key( public_key &&pk ) : my( std::move( pk.my ) ) {} + + public_key::~public_key() {} + + public_key& public_key::operator=( const public_key& pk ) + { + my = pk.my; + return *this; + } + + public_key& public_key::operator=( public_key&& pk ) + { + my = pk.my; + return *this; + } + + bool public_key::valid()const + { + return my->_key != empty_pub; + } + + std::string public_key::to_base58() const + { + FC_ASSERT( my->_key != empty_pub ); + return to_base58( my->_key ); + } + + public_key_data public_key::serialize()const + { + FC_ASSERT( my->_key != empty_pub ); + return my->_key; + } + + public_key::public_key( const public_key_point_data& dat ) + { + const char* front = &dat.data[0]; + if( *front == 0 ){} + else + { + EC_KEY *key = EC_KEY_new_by_curve_name( NID_secp256k1 ); + key = o2i_ECPublicKey( &key, (const unsigned char**)&front, sizeof(dat) ); + FC_ASSERT( key ); + EC_KEY_set_conv_form( key, POINT_CONVERSION_COMPRESSED ); + unsigned char* buffer = (unsigned char*) my->_key.begin(); + i2o_ECPublicKey( key, &buffer ); // FIXME: questionable memory handling + EC_KEY_free( key ); + } + } + + public_key::public_key( const public_key_data& dat ) + { + my->_key = dat; + } + + public_key::public_key( const compact_signature& c, const fc::sha256& digest, bool check_canonical ) + { + int nV = c.data[0]; + if (nV<27 || nV>=35) + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + + if( check_canonical ) + { + FC_ASSERT( is_canonical( c ), "signature is not canonical" ); + } + + secp256k1_pubkey secp_pub; + secp256k1_ecdsa_recoverable_signature secp_sig; + + FC_ASSERT( secp256k1_ecdsa_recoverable_signature_parse_compact( detail::_get_context(), &secp_sig, (unsigned char*)c.begin() + 1, (*c.begin() - 27) & 3) ); + FC_ASSERT( secp256k1_ecdsa_recover( detail::_get_context(), &secp_pub, &secp_sig, (unsigned char*) digest.data() ) ); + + size_t serialized_result_sz = my->_key.size(); + secp256k1_ec_pubkey_serialize( detail::_get_context(), (unsigned char*)&my->_key.data, &serialized_result_sz, &secp_pub, SECP256K1_EC_COMPRESSED ); + FC_ASSERT( serialized_result_sz == my->_key.size() ); + } + +} } diff --git a/libraries/libfc/src/crypto/elliptic_webauthn.cpp b/libraries/libfc/src/crypto/elliptic_webauthn.cpp new file mode 100644 index 0000000000..cb1de59800 --- /dev/null +++ b/libraries/libfc/src/crypto/elliptic_webauthn.cpp @@ -0,0 +1,250 @@ +#include +#include +#include +#include + +#include +#include +#include + +#define RAPIDJSON_NAMESPACE_BEGIN namespace fc::crypto::webauthn::detail::rapidjson { +#define RAPIDJSON_NAMESPACE_END } +#include "rapidjson/reader.h" + +#include + +namespace fc { namespace crypto { namespace webauthn { + +namespace detail { +using namespace std::literals; + +struct webauthn_json_handler : public rapidjson::BaseReaderHandler, webauthn_json_handler> { + std::string found_challenge; + std::string found_origin; + std::string found_type; + + enum parse_stat_t { + EXPECT_FIRST_OBJECT_START, + EXPECT_FIRST_OBJECT_KEY, + EXPECT_FIRST_OBJECT_DONTCARE_VALUE, + EXPECT_CHALLENGE_VALUE, + EXPECT_ORIGIN_VALUE, + EXPECT_TYPE_VALUE, + IN_NESTED_CONTAINER + } current_state = EXPECT_FIRST_OBJECT_START; + unsigned current_nested_container_depth = 0; + + bool basic_stuff() { + if(current_state == IN_NESTED_CONTAINER) + return true; + if(current_state == EXPECT_FIRST_OBJECT_DONTCARE_VALUE) { + current_state = EXPECT_FIRST_OBJECT_KEY; + return true; + } + return false; + } + + bool Null() { + return basic_stuff(); + } + bool Bool(bool) { + return basic_stuff(); + } + bool Int(int) { + return basic_stuff(); + } + bool Uint(unsigned) { + return basic_stuff(); + } + bool Int64(int64_t) { + return basic_stuff(); + } + bool Uint64(uint64_t) { + return basic_stuff(); + } + bool Double(double) { + return basic_stuff(); + } + + bool String(const char* str, rapidjson::SizeType length, bool copy) { + switch(current_state) { + case EXPECT_FIRST_OBJECT_START: + case EXPECT_FIRST_OBJECT_KEY: + return false; + case EXPECT_CHALLENGE_VALUE: + found_challenge = std::string(str, length); + current_state = EXPECT_FIRST_OBJECT_KEY; + return true; + case EXPECT_ORIGIN_VALUE: + found_origin = std::string(str, length); + current_state = EXPECT_FIRST_OBJECT_KEY; + return true; + case EXPECT_TYPE_VALUE: + found_type = std::string(str, length); + current_state = EXPECT_FIRST_OBJECT_KEY; + return true; + case EXPECT_FIRST_OBJECT_DONTCARE_VALUE: + current_state = EXPECT_FIRST_OBJECT_KEY; + return true; + case IN_NESTED_CONTAINER: + return true; + } + } + + bool StartObject() { + switch(current_state) { + case EXPECT_FIRST_OBJECT_START: + current_state = EXPECT_FIRST_OBJECT_KEY; + return true; + case EXPECT_FIRST_OBJECT_DONTCARE_VALUE: + current_state = IN_NESTED_CONTAINER; + ++current_nested_container_depth; + return true; + case IN_NESTED_CONTAINER: + ++current_nested_container_depth; + return true; + case EXPECT_FIRST_OBJECT_KEY: + case EXPECT_CHALLENGE_VALUE: + case EXPECT_ORIGIN_VALUE: + case EXPECT_TYPE_VALUE: + return false; + } + } + bool Key(const char* str, rapidjson::SizeType length, bool copy) { + switch(current_state) { + case EXPECT_FIRST_OBJECT_START: + case EXPECT_FIRST_OBJECT_DONTCARE_VALUE: + case EXPECT_CHALLENGE_VALUE: + case EXPECT_ORIGIN_VALUE: + case EXPECT_TYPE_VALUE: + return false; + case EXPECT_FIRST_OBJECT_KEY: { + if("challenge"s == str) + current_state = EXPECT_CHALLENGE_VALUE; + else if("origin"s == str) + current_state = EXPECT_ORIGIN_VALUE; + else if("type"s == str) + current_state = EXPECT_TYPE_VALUE; + else + current_state = EXPECT_FIRST_OBJECT_DONTCARE_VALUE; + return true; + } + case IN_NESTED_CONTAINER: + return true; + } + } + bool EndObject(rapidjson::SizeType memberCount) { + switch(current_state) { + case EXPECT_FIRST_OBJECT_START: + case EXPECT_FIRST_OBJECT_DONTCARE_VALUE: + case EXPECT_CHALLENGE_VALUE: + case EXPECT_ORIGIN_VALUE: + case EXPECT_TYPE_VALUE: + return false; + case IN_NESTED_CONTAINER: + if(!--current_nested_container_depth) + current_state = EXPECT_FIRST_OBJECT_KEY; + return true; + case EXPECT_FIRST_OBJECT_KEY: + return true; + } + } + + bool StartArray() { + switch(current_state) { + case EXPECT_FIRST_OBJECT_DONTCARE_VALUE: + current_state = IN_NESTED_CONTAINER; + ++current_nested_container_depth; + return true; + case IN_NESTED_CONTAINER: + ++current_nested_container_depth; + return true; + case EXPECT_FIRST_OBJECT_START: + case EXPECT_FIRST_OBJECT_KEY: + case EXPECT_CHALLENGE_VALUE: + case EXPECT_ORIGIN_VALUE: + case EXPECT_TYPE_VALUE: + return false; + } + } + bool EndArray(rapidjson::SizeType elementCount) { + switch(current_state) { + case EXPECT_FIRST_OBJECT_START: + case EXPECT_FIRST_OBJECT_DONTCARE_VALUE: + case EXPECT_CHALLENGE_VALUE: + case EXPECT_ORIGIN_VALUE: + case EXPECT_TYPE_VALUE: + case EXPECT_FIRST_OBJECT_KEY: + return false; + case IN_NESTED_CONTAINER: + if(!--current_nested_container_depth) + current_state = EXPECT_FIRST_OBJECT_KEY; + return true; + } + } +}; +} //detail + + +public_key::public_key(const signature& c, const fc::sha256& digest, bool) { + detail::webauthn_json_handler handler; + detail::rapidjson::Reader reader; + detail::rapidjson::StringStream ss(c.client_json.c_str()); + FC_ASSERT(reader.Parse(ss, handler), "Failed to parse client data JSON"); + + FC_ASSERT(handler.found_type == "webauthn.get", "webauthn signature type not an assertion"); + + std::string challenge_bytes = fc::base64url_decode(handler.found_challenge); + FC_ASSERT(fc::sha256(challenge_bytes.data(), challenge_bytes.size()) == digest, "Wrong webauthn challenge"); + + char required_origin_scheme[] = "https://"; + size_t https_len = strlen(required_origin_scheme); + FC_ASSERT(handler.found_origin.compare(0, https_len, required_origin_scheme) == 0, "webauthn origin must begin with https://"); + rpid = handler.found_origin.substr(https_len, handler.found_origin.rfind(':')-https_len); + + constexpr static size_t min_auth_data_size = 37; + FC_ASSERT(c.auth_data.size() >= min_auth_data_size, "auth_data not as large as required"); + if(c.auth_data[32] & 0x01) + user_verification_type = user_presence_t::USER_PRESENCE_PRESENT; + if(c.auth_data[32] & 0x04) + user_verification_type = user_presence_t::USER_PRESENCE_VERIFIED; + + static_assert(min_auth_data_size >= sizeof(fc::sha256), "auth_data min size not enough to store a sha256"); + FC_ASSERT(memcmp(c.auth_data.data(), fc::sha256::hash(rpid).data(), sizeof(fc::sha256)) == 0, "webauthn rpid hash doesn't match origin"); + + //the signature (and thus public key we need to return) will be over + // sha256(auth_data || client_data_hash) + fc::sha256 client_data_hash = fc::sha256::hash(c.client_json); + fc::sha256::encoder e; + e.write((char*)c.auth_data.data(), c.auth_data.size()); + e.write(client_data_hash.data(), client_data_hash.data_size()); + fc::sha256 signed_digest = e.result(); + + //quite a bit of this copied from elliptic_r1, can probably commonize + int nV = c.compact_signature.data[0]; + if (nV<31 || nV>=35) + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); + ecdsa_sig sig = ECDSA_SIG_new(); + BIGNUM *r = BN_new(), *s = BN_new(); + BN_bin2bn(&c.compact_signature.data[1],32,r); + BN_bin2bn(&c.compact_signature.data[33],32,s); + ECDSA_SIG_set0(sig, r, s); + + fc::ec_key key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + nV -= 4; + + if (r1::ECDSA_SIG_recover_key_GFp(key, sig, (uint8_t*)signed_digest.data(), signed_digest.data_size(), nV - 27, 0) == 1) { + const EC_POINT* point = EC_KEY_get0_public_key(key); + const EC_GROUP* group = EC_KEY_get0_group(key); + size_t sz = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, (uint8_t*)public_key_data.data, public_key_data.size(), NULL); + if(sz == public_key_data.size()) + return; + } + FC_THROW_EXCEPTION( exception, "unable to reconstruct public key from signature" ); +} + +void public_key::post_init() { + FC_ASSERT(rpid.length(), "webauthn pubkey must have non empty rpid"); +} + +}}} \ No newline at end of file diff --git a/libraries/libfc/src/crypto/equihash.cpp b/libraries/libfc/src/crypto/equihash.cpp new file mode 100644 index 0000000000..ce855ab344 --- /dev/null +++ b/libraries/libfc/src/crypto/equihash.cpp @@ -0,0 +1,47 @@ +#include + +#include + +#define EQUIHASH_NONCE 2 + +namespace fc { namespace equihash { + + _POW::Seed sha_to_seed( sha256 seed ) + { + _POW::Seed new_seed; + + // Seed is 128 bits. Half of sha256 to create seed. Should still have enough randomness + new_seed.v[0] = (unsigned int) seed._hash[0]; + new_seed.v[0] ^= (unsigned int) seed._hash[2]; + new_seed.v[1] = (unsigned int)( seed._hash[0] >> 32 ); + new_seed.v[1] ^= (unsigned int)( seed._hash[2] >> 32 ); + new_seed.v[2] = (unsigned int) seed._hash[1]; + new_seed.v[2] ^= (unsigned int) seed._hash[3]; + new_seed.v[3] = (unsigned int)( seed._hash[1] >> 32 ); + new_seed.v[3] ^= (unsigned int)( seed._hash[3] >> 32 ); + + return new_seed; + } + + bool proof::is_valid() const + { + _POW::Proof test( n, k, sha_to_seed( seed ), EQUIHASH_NONCE, inputs ); + return test.Test(); + + } + + proof proof::hash( uint32_t n, uint32_t k, sha256 seed ) + { + auto hash = _POW::Equihash( n, k, sha_to_seed( seed ) ); + auto result = hash.FindProof( EQUIHASH_NONCE ); + + proof p; + p.n = n; + p.k = k; + p.seed = seed; + p.inputs = result.inputs; + + return p; + } + +} } // fc::equihash diff --git a/libraries/libfc/src/crypto/hex.cpp b/libraries/libfc/src/crypto/hex.cpp new file mode 100644 index 0000000000..67ef919281 --- /dev/null +++ b/libraries/libfc/src/crypto/hex.cpp @@ -0,0 +1,49 @@ +#include +#include + +namespace fc { + + uint8_t from_hex( char c ) { + if( c >= '0' && c <= '9' ) + return c - '0'; + if( c >= 'a' && c <= 'f' ) + return c - 'a' + 10; + if( c >= 'A' && c <= 'F' ) + return c - 'A' + 10; + FC_THROW_EXCEPTION( exception, "Invalid hex character '${c}'", ("c", fc::string(&c,1) ) ); + return 0; + } + + std::string to_hex( const char* d, uint32_t s ) + { + std::string r; + const char* to_hex="0123456789abcdef"; + uint8_t* c = (uint8_t*)d; + for( uint32_t i = 0; i < s; ++i ) + (r += to_hex[(c[i]>>4)]) += to_hex[(c[i] &0x0f)]; + return r; + } + + size_t from_hex( const fc::string& hex_str, char* out_data, size_t out_data_len ) { + fc::string::const_iterator i = hex_str.begin(); + uint8_t* out_pos = (uint8_t*)out_data; + uint8_t* out_end = out_pos + out_data_len; + while( i != hex_str.end() && out_end != out_pos ) { + *out_pos = from_hex( *i ) << 4; + ++i; + if( i != hex_str.end() ) { + *out_pos |= from_hex( *i ); + ++i; + } + ++out_pos; + } + return out_pos - (uint8_t*)out_data; + } + std::string to_hex( const std::vector& data ) + { + if( data.size() ) + return to_hex( data.data(), data.size() ); + return ""; + } + +} diff --git a/libraries/libfc/src/crypto/k1_recover.cpp b/libraries/libfc/src/crypto/k1_recover.cpp new file mode 100644 index 0000000000..cc755da60e --- /dev/null +++ b/libraries/libfc/src/crypto/k1_recover.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +namespace fc { + + const secp256k1_context* k1_recover_context() { + static secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + return ctx; + } + + std::variant k1_recover(const bytes& signature, const bytes& digest) { + const secp256k1_context* context{k1_recover_context()}; + FC_ASSERT(context != nullptr); + + if (signature.size() != 65 || digest.size() != 32) { + return k1_recover_error::input_error; + } + + int recid = signature[0]; + if (recid<27 || recid>=35) return k1_recover_error::invalid_signature; + recid = (recid - 27) & 3; + + secp256k1_ecdsa_recoverable_signature sig; + if (!secp256k1_ecdsa_recoverable_signature_parse_compact(context, &sig, (const unsigned char*)&signature[1], recid)) { + return k1_recover_error::invalid_signature; + } + + secp256k1_pubkey pub_key; + if (!secp256k1_ecdsa_recover(context, &pub_key, &sig, (const unsigned char*)&digest[0])) { + return k1_recover_error::recover_error; + } + + size_t kOutLen{65}; + bytes out(kOutLen, '\0'); + secp256k1_ec_pubkey_serialize(context, (unsigned char*)&out[0], &kOutLen, &pub_key, SECP256K1_EC_UNCOMPRESSED); + return out; + } +} diff --git a/libraries/libfc/src/crypto/modular_arithmetic.cpp b/libraries/libfc/src/crypto/modular_arithmetic.cpp new file mode 100644 index 0000000000..58f061f85e --- /dev/null +++ b/libraries/libfc/src/crypto/modular_arithmetic.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +namespace fc { + + std::variant modexp(const bytes& _base, const bytes& _exponent, const bytes& _modulus) + { + if (_modulus.size() == 0) { + return modular_arithmetic_error::modulus_len_zero; + } + + auto output = bytes(_modulus.size(), '\0'); + + mpz_t base, exponent, modulus; + mpz_inits(base, exponent, modulus, nullptr); + + if (_base.size()) { + mpz_import(base, _base.size(), 1, 1, 0, 0, _base.data()); + } + + if (_exponent.size()) { + mpz_import(exponent, _exponent.size(), 1, 1, 0, 0, _exponent.data()); + } + + mpz_import(modulus, _modulus.size(), 1, 1, 0, 0, _modulus.data()); + + if (mpz_sgn(modulus) == 0) { + mpz_clears(base, exponent, modulus, nullptr); + return output; + } + + mpz_t result; + mpz_init(result); + + mpz_powm(result, base, exponent, modulus); + // export as little-endian + mpz_export(output.data(), nullptr, -1, 1, 0, 0, result); + // and convert to big-endian + std::reverse(output.begin(), output.end()); + + mpz_clears(base, exponent, modulus, result, nullptr); + + return output; + } + +} diff --git a/libraries/libfc/src/crypto/pke.cpp b/libraries/libfc/src/crypto/pke.cpp new file mode 100644 index 0000000000..4d57bf44d3 --- /dev/null +++ b/libraries/libfc/src/crypto/pke.cpp @@ -0,0 +1,364 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + + namespace detail { + class pke_impl + { + public: + pke_impl():rsa(nullptr){} + ~pke_impl() + { + if( rsa != nullptr ) + RSA_free(rsa); + } + RSA* rsa; + }; + } // detail + + public_key::operator bool()const { return !!my; } + private_key::operator bool()const { return !!my; } + + public_key::public_key() + {} + + public_key::public_key( const bytes& d ) + :my( std::make_shared() ) + { + string pem = "-----BEGIN RSA PUBLIC KEY-----\n"; + auto b64 = fc::base64_encode( (const unsigned char*)d.data(), d.size() ); + for( size_t i = 0; i < b64.size(); i += 64 ) + pem += b64.substr( i, 64 ) + "\n"; + pem += "-----END RSA PUBLIC KEY-----\n"; + // fc::cerr<rsa = PEM_read_bio_RSAPublicKey(mem, NULL, NULL, NULL ); + BIO_free(mem); + } + public_key::public_key( const public_key& k ) + :my(k.my) + { + } + + public_key::public_key( public_key&& k ) + :my(std::move(k.my)) + { + } + + public_key::~public_key() { } + + public_key& public_key::operator=(const public_key& p ) + { + my = p.my; return *this; + } + public_key& public_key::operator=( public_key&& p ) + { + my = std::move(p.my); return *this; + } + bool public_key::verify( const sha1& digest, const array& sig )const + { + return 0 != RSA_verify( NID_sha1, (const uint8_t*)&digest, 20, + (uint8_t*)&sig, 2048/8, my->rsa ); + } + + bool public_key::verify( const sha1& digest, const signature& sig )const + { + static_assert( sig.size() == 2048/8, "" ); + return 0 != RSA_verify( NID_sha1, (const uint8_t*)&digest, 20, + (uint8_t*)sig.data(), 2048/8, my->rsa ); + } + bool public_key::verify( const sha256& digest, const signature& sig )const + { + static_assert( sig.size() == 2048/8, "" ); + return 0 != RSA_verify( NID_sha256, (const uint8_t*)&digest, 32, + (uint8_t*)sig.data(), 2048/8, my->rsa ); + } + bytes public_key::encrypt( const char* b, size_t l )const + { + FC_ASSERT( my && my->rsa ); + bytes out( RSA_size(my->rsa) ); //, char(0) ); + int rtn = RSA_public_encrypt( l, + (unsigned char*)b, + (unsigned char*)out.data(), + my->rsa, RSA_PKCS1_OAEP_PADDING ); + if( rtn >= 0 ) { + out.resize(rtn); + return out; + } + FC_THROW_EXCEPTION( exception, "openssl: ${message}", ("message",fc::string(ERR_error_string( ERR_get_error(),NULL))) ); + } + + bytes public_key::encrypt( const bytes& in )const + { + FC_ASSERT( my && my->rsa ); + bytes out( RSA_size(my->rsa) ); //, char(0) ); + int rtn = RSA_public_encrypt( in.size(), + (unsigned char*)in.data(), + (unsigned char*)out.data(), + my->rsa, RSA_PKCS1_OAEP_PADDING ); + fc::cerr<<"rtn: "<= 0 ) { + out.resize(rtn); + return out; + } + FC_THROW_EXCEPTION( exception, "openssl: ${message}", ("message",fc::string(ERR_error_string( ERR_get_error(),NULL))) ); + } + bytes public_key::decrypt( const bytes& in )const + { + FC_ASSERT( my && my->rsa ); + bytes out( RSA_size(my->rsa) );//, char(0) ); + int rtn = RSA_public_decrypt( in.size(), + (unsigned char*)in.data(), + (unsigned char*)out.data(), + my->rsa, RSA_PKCS1_OAEP_PADDING ); + if( rtn >= 0 ) { + out.resize(rtn); + return out; + } + FC_THROW_EXCEPTION( exception, "openssl: ${message}", ("message",fc::string(ERR_error_string( ERR_get_error(),NULL))) ); + } + + bytes public_key::serialize()const + { + bytes ba; + if( !my ) { return ba; } + + BIO *mem = BIO_new(BIO_s_mem()); + int e = PEM_write_bio_RSAPublicKey( mem, my->rsa ); + if( e != 1 ) + { + BIO_free(mem); + FC_THROW_EXCEPTION( exception, "openssl: ${message}", ("message",fc::string(ERR_error_string( ERR_get_error(),NULL))) ); + } + char* dat; + uint32_t l = BIO_get_mem_data( mem, &dat ); + + fc::stringstream ss( string( dat, l ) ); + fc::stringstream key; + fc::string tmp; + fc::getline( ss, tmp ); + fc::getline( ss, tmp ); + while( tmp.size() && tmp[0] != '-' ) + { + key << tmp; + fc::getline( ss, tmp ); + } + auto str = key.str(); + str = fc::base64_decode( str ); + ba = bytes( str.begin(), str.end() ); + + BIO_free(mem); + return ba; + } + + private_key::private_key() + { + } + private_key::private_key( const bytes& d ) + :my( std::make_shared() ) + { + + string pem = "-----BEGIN RSA PRIVATE KEY-----\n"; + auto b64 = fc::base64_encode( (const unsigned char*)d.data(), d.size() ); + for( size_t i = 0; i < b64.size(); i += 64 ) + pem += b64.substr( i, 64 ) + "\n"; + pem += "-----END RSA PRIVATE KEY-----\n"; + // fc::cerr<rsa = PEM_read_bio_RSAPrivateKey(mem, NULL, NULL, NULL ); + BIO_free(mem); + + FC_ASSERT( my->rsa, "read private key" ); + } + + private_key::private_key( const private_key& k ) + :my(k.my) + { + } + private_key::private_key( private_key&& k ) + :my(std::move(k.my) ) + { + } + private_key::~private_key() { } + + private_key& private_key::operator=(const private_key& p ) + { + my = p.my; return *this; + } + private_key& private_key::operator=(private_key&& p ) + { + my = std::move(p.my); return *this; + } + + void private_key::sign( const sha1& digest, array& sig )const + { + FC_ASSERT( (size_t(RSA_size(my->rsa)) <= sizeof(sig)), "Invalid RSA size" ); + uint32_t slen = 0; + if( 1 != RSA_sign( NID_sha1, (uint8_t*)&digest, + 20, (unsigned char*)&sig, &slen, my->rsa ) ) + { + FC_THROW_EXCEPTION( exception, "rsa sign failed with ${message}", ("message",fc::string(ERR_error_string( ERR_get_error(),NULL))) ); + } + } + + signature private_key::sign( const sha1& digest )const + { + if( !my ) FC_THROW_EXCEPTION( assert_exception, "!null" ); + signature sig; + sig.resize( RSA_size(my->rsa) ); + + uint32_t slen = 0; + if( 1 != RSA_sign( NID_sha1, (uint8_t*)digest.data(), + 20, (unsigned char*)sig.data(), &slen, my->rsa ) ) + { + FC_THROW_EXCEPTION( exception, "rsa sign failed with ${message}", ("message",fc::string(ERR_error_string( ERR_get_error(),NULL))) ); + } + return sig; + } + signature private_key::sign( const sha256& digest )const + { + if( !my ) FC_THROW_EXCEPTION( assert_exception, "!null" ); + signature sig; + sig.resize( RSA_size(my->rsa) ); + + uint32_t slen = 0; + if( 1 != RSA_sign( NID_sha256, (uint8_t*)digest.data(), + 32, (unsigned char*)sig.data(), &slen, my->rsa ) ) + { + FC_THROW_EXCEPTION( exception, "rsa sign failed with ${message}", ("message",fc::string(ERR_error_string( ERR_get_error(),NULL))) ); + } + return sig; + } + + + bytes private_key::encrypt( const bytes& in )const + { + if( !my ) FC_THROW_EXCEPTION( assert_exception, "!null" ); + bytes out; + out.resize( RSA_size(my->rsa) ); + int rtn = RSA_private_encrypt( in.size(), + (unsigned char*)in.data(), + (unsigned char*)out.data(), + my->rsa, RSA_PKCS1_OAEP_PADDING ); + if( rtn >= 0 ) { + out.resize(rtn); + return out; + } + + FC_THROW_EXCEPTION( exception, "encrypt failed" ); + } + + bytes private_key::decrypt( const char* in, size_t l )const + { + if( !my ) FC_THROW_EXCEPTION( assert_exception, "!null" ); + bytes out; + out.resize( RSA_size(my->rsa) ); + int rtn = RSA_private_decrypt( l, + (unsigned char*)in, + (unsigned char*)out.data(), + my->rsa, RSA_PKCS1_OAEP_PADDING ); + if( rtn >= 0 ) { + out.resize(rtn); + return out; + } + FC_THROW_EXCEPTION( exception, "decrypt failed" ); + } + bytes private_key::decrypt( const bytes& in )const + { + if( !my ) FC_THROW_EXCEPTION( assert_exception, "!null" ); + bytes out; + out.resize( RSA_size(my->rsa) ); + int rtn = RSA_private_decrypt( in.size(), + (unsigned char*)in.data(), + (unsigned char*)out.data(), + my->rsa, RSA_PKCS1_OAEP_PADDING ); + if( rtn >= 0 ) { + out.resize(rtn); + return out; + } + FC_THROW_EXCEPTION( exception, "decrypt failed" ); + } + + bytes private_key::serialize()const + { + bytes ba; + if( !my ) { return ba; } + + BIO *mem = BIO_new(BIO_s_mem()); + int e = PEM_write_bio_RSAPrivateKey( mem, my->rsa, NULL, NULL, 0, NULL, NULL ); + if( e != 1 ) + { + BIO_free(mem); + FC_THROW_EXCEPTION( exception, "Error writing private key, ${message}", ("message",fc::string(ERR_error_string( ERR_get_error(),NULL))) ); + } + char* dat; + uint32_t l = BIO_get_mem_data( mem, &dat ); + // return bytes( dat, dat + l ); + + stringstream ss( string( dat, l ) ); + stringstream key; + string tmp; + fc::getline( ss, tmp ); + fc::getline( ss, tmp ); + + while( tmp.size() && tmp[0] != '-' ) + { + key << tmp; + fc::getline( ss, tmp ); + } + auto str = key.str(); + str = fc::base64_decode( str ); + ba = bytes( str.begin(), str.end() ); + // ba = bytes( dat, dat + l ); + BIO_free(mem); + return ba; + } + + void generate_key_pair( public_key& pub, private_key& priv ) + { + static bool init = true; + if( init ) { ERR_load_crypto_strings(); init = false; } + + pub.my = std::make_shared(); + priv.my = pub.my; + pub.my->rsa = RSA_generate_key( 2048, 65537, NULL, NULL ); + } + + /** encodes the big int as base64 string, or a number */ + void to_variant( const public_key& bi, variant& v ) + { + v = bi.serialize(); + } + + /** decodes the big int as base64 string, or a number */ + void from_variant( const variant& v, public_key& bi ) + { + bi = public_key( v.as >() ); + } + + + /** encodes the big int as base64 string, or a number */ + void to_variant( const private_key& bi, variant& v ) + { + v = bi.serialize(); + } + + /** decodes the big int as base64 string, or a number */ + void from_variant( const variant& v, private_key& bi ) + { + bi = private_key( v.as >() ); + } + +} // fc diff --git a/libraries/libfc/src/crypto/private_key.cpp b/libraries/libfc/src/crypto/private_key.cpp new file mode 100644 index 0000000000..e6faf3b42d --- /dev/null +++ b/libraries/libfc/src/crypto/private_key.cpp @@ -0,0 +1,154 @@ +#include +#include +#include + +namespace fc { namespace crypto { + using namespace std; + + struct public_key_visitor : visitor { + template + public_key::storage_type operator()(const KeyType& key) const + { + return public_key::storage_type(key.get_public_key()); + } + }; + + public_key private_key::get_public_key() const + { + return public_key(std::visit(public_key_visitor(), _storage)); + } + + struct sign_visitor : visitor { + sign_visitor( const sha256& digest, bool require_canonical ) + :_digest(digest) + ,_require_canonical(require_canonical) + {} + + template + signature::storage_type operator()(const KeyType& key) const + { + return signature::storage_type(key.sign(_digest, _require_canonical)); + } + + const sha256& _digest; + bool _require_canonical; + }; + + signature private_key::sign( const sha256& digest, bool require_canonical ) const + { + return signature(std::visit(sign_visitor(digest, require_canonical), _storage)); + } + + struct generate_shared_secret_visitor : visitor { + generate_shared_secret_visitor( const public_key::storage_type& pub_storage ) + :_pub_storage(pub_storage) + {} + + template + sha512 operator()(const KeyType& key) const + { + using PublicKeyType = typename KeyType::public_key_type; + return key.generate_shared_secret(std::template get(_pub_storage)); + } + + const public_key::storage_type& _pub_storage; + }; + + sha512 private_key::generate_shared_secret( const public_key& pub ) const + { + return std::visit(generate_shared_secret_visitor(pub._storage), _storage); + } + + template + string to_wif( const Data& secret, const fc::yield_function_t& yield ) + { + const size_t size_of_data_to_hash = sizeof(typename Data::data_type) + 1; + const size_t size_of_hash_bytes = 4; + char data[size_of_data_to_hash + size_of_hash_bytes]; + data[0] = (char)0x80; // this is the Bitcoin MainNet code + memcpy(&data[1], (const char*)&secret.serialize(), sizeof(typename Data::data_type)); + sha256 digest = sha256::hash(data, size_of_data_to_hash); + digest = sha256::hash(digest); + memcpy(data + size_of_data_to_hash, (char*)&digest, size_of_hash_bytes); + return to_base58(data, sizeof(data), yield); + } + + template + Data from_wif( const string& wif_key ) + { + auto wif_bytes = from_base58(wif_key); + FC_ASSERT(wif_bytes.size() >= 5); + auto key_bytes = vector(wif_bytes.begin() + 1, wif_bytes.end() - 4); + fc::sha256 check = fc::sha256::hash(wif_bytes.data(), wif_bytes.size() - 4); + fc::sha256 check2 = fc::sha256::hash(check); + + FC_ASSERT(memcmp( (char*)&check, wif_bytes.data() + wif_bytes.size() - 4, 4 ) == 0 || + memcmp( (char*)&check2, wif_bytes.data() + wif_bytes.size() - 4, 4 ) == 0 ); + + return Data(fc::variant(key_bytes).as()); + } + + static private_key::storage_type priv_parse_base58(const string& base58str) + { + const auto pivot = base58str.find('_'); + + if (pivot == std::string::npos) { + // wif import + using default_type = std::variant_alternative_t<0, private_key::storage_type>; + return private_key::storage_type(from_wif(base58str)); + } else { + constexpr auto prefix = config::private_key_base_prefix; + const auto prefix_str = base58str.substr(0, pivot); + FC_ASSERT(prefix == prefix_str, "Private Key has invalid prefix: ${str}", ("str", base58str)("prefix_str", prefix_str)); + + auto data_str = base58str.substr(pivot + 1); + FC_ASSERT(!data_str.empty(), "Private Key has no data: ${str}", ("str", base58str)); + return base58_str_parser::apply(data_str); + } + } + + private_key::private_key(const std::string& base58str) + :_storage(priv_parse_base58(base58str)) + {} + + std::string private_key::to_string(const fc::yield_function_t& yield) const + { + auto which = _storage.index(); + + if (which == 0) { + using default_type = std::variant_alternative_t<0, private_key::storage_type>; + return to_wif(std::template get(_storage), yield); + } + + auto data_str = std::visit(base58str_visitor(yield), _storage); + return std::string(config::private_key_base_prefix) + "_" + data_str; + } + + std::ostream& operator<<(std::ostream& s, const private_key& k) { + s << "private_key(" << k.to_string() << ')'; + return s; + } + + bool operator == ( const private_key& p1, const private_key& p2) { + return eq_comparator::apply(p1._storage, p2._storage); + } + + bool operator < ( const private_key& p1, const private_key& p2) + { + return less_comparator::apply(p1._storage, p2._storage); + } +} } // fc::crypto + +namespace fc +{ + void to_variant(const fc::crypto::private_key& var, variant& vo, const fc::yield_function_t& yield) + { + vo = var.to_string(yield); + } + + void from_variant(const variant& var, fc::crypto::private_key& vo) + { + vo = fc::crypto::private_key(var.as_string()); + } + +} // fc diff --git a/libraries/libfc/src/crypto/public_key.cpp b/libraries/libfc/src/crypto/public_key.cpp new file mode 100644 index 0000000000..766835e748 --- /dev/null +++ b/libraries/libfc/src/crypto/public_key.cpp @@ -0,0 +1,118 @@ +#include +#include +#include + +namespace fc { namespace crypto { + + struct recovery_visitor : fc::visitor { + recovery_visitor(const sha256& digest, bool check_canonical) + :_digest(digest) + ,_check_canonical(check_canonical) + {} + + template + public_key::storage_type operator()(const SignatureType& s) const { + return public_key::storage_type(s.recover(_digest, _check_canonical)); + } + + const sha256& _digest; + bool _check_canonical; + }; + + public_key::public_key( const signature& c, const sha256& digest, bool check_canonical ) + :_storage(std::visit(recovery_visitor(digest, check_canonical), c._storage)) + { + } + + size_t public_key::which() const { + return _storage.index(); + } + + static public_key::storage_type parse_base58(const std::string& base58str) + { + constexpr auto legacy_prefix = config::public_key_legacy_prefix; + if(prefix_matches(legacy_prefix, base58str) && base58str.find('_') == std::string::npos ) { + auto sub_str = base58str.substr(const_strlen(legacy_prefix)); + using default_type = typename std::variant_alternative_t<0, public_key::storage_type>; //public_key::storage_type::template type_at<0>; + using data_type = default_type::data_type; + using wrapper = checksummed_data; + auto bin = fc::from_base58(sub_str); + FC_ASSERT(bin.size() == sizeof(data_type) + sizeof(uint32_t), ""); + auto wrapped = fc::raw::unpack(bin); + FC_ASSERT(wrapper::calculate_checksum(wrapped.data) == wrapped.check); + return public_key::storage_type(default_type(wrapped.data)); + } else { + constexpr auto prefix = config::public_key_base_prefix; + + const auto pivot = base58str.find('_'); + FC_ASSERT(pivot != std::string::npos, "No delimiter in string, cannot determine data type: ${str}", ("str", base58str)); + + const auto prefix_str = base58str.substr(0, pivot); + FC_ASSERT(prefix == prefix_str, "Public Key has invalid prefix: ${str}", ("str", base58str)("prefix_str", prefix_str)); + + auto data_str = base58str.substr(pivot + 1); + FC_ASSERT(!data_str.empty(), "Public Key has no data: ${str}", ("str", base58str)); + return base58_str_parser::apply(data_str); + } + } + + public_key::public_key(const std::string& base58str) + :_storage(parse_base58(base58str)) + {} + + struct is_valid_visitor : public fc::visitor { + template< typename KeyType > + bool operator()( const KeyType& key )const { + return key.valid(); + } + }; + + bool public_key::valid()const + { + return std::visit(is_valid_visitor(), _storage); + } + + std::string public_key::to_string(const fc::yield_function_t& yield) const + { + auto data_str = std::visit(base58str_visitor(yield), _storage); + + auto which = _storage.index(); + if (which == 0) { + return std::string(config::public_key_legacy_prefix) + data_str; + } else { + return std::string(config::public_key_base_prefix) + "_" + data_str; + } + } + + std::ostream& operator<<(std::ostream& s, const public_key& k) { + s << "public_key(" << k.to_string() << ')'; + return s; + } + + bool operator == ( const public_key& p1, const public_key& p2) { + return eq_comparator::apply(p1._storage, p2._storage); + } + + bool operator != ( const public_key& p1, const public_key& p2) { + return !(p1 == p2); + } + + bool operator < ( const public_key& p1, const public_key& p2) + { + return less_comparator::apply(p1._storage, p2._storage); + } +} } // fc::crypto + +namespace fc +{ + using namespace std; + void to_variant(const fc::crypto::public_key& var, fc::variant& vo, const fc::yield_function_t& yield) + { + vo = var.to_string(yield); + } + + void from_variant(const fc::variant& var, fc::crypto::public_key& vo) + { + vo = fc::crypto::public_key(var.as_string()); + } +} // fc diff --git a/libraries/libfc/src/crypto/rand.cpp b/libraries/libfc/src/crypto/rand.cpp new file mode 100644 index 0000000000..a14d3272dd --- /dev/null +++ b/libraries/libfc/src/crypto/rand.cpp @@ -0,0 +1,21 @@ +#include +#include +#include +#include + + +namespace fc { + +void rand_bytes(char* buf, int count) +{ + int result = RAND_bytes((unsigned char*)buf, count); + if (result != 1) + FC_THROW("Error calling OpenSSL's RAND_bytes(): ${code}", ("code", (uint32_t)ERR_get_error())); +} + +void rand_pseudo_bytes(char* buf, int count) +{ + rand_bytes(buf, count); +} + +} // namespace fc diff --git a/libraries/libfc/src/crypto/ripemd160.cpp b/libraries/libfc/src/crypto/ripemd160.cpp new file mode 100644 index 0000000000..93c21c65ea --- /dev/null +++ b/libraries/libfc/src/crypto/ripemd160.cpp @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "_digest_common.hpp" + +namespace fc +{ + +ripemd160::ripemd160() { memset( _hash, 0, sizeof(_hash) ); } +ripemd160::ripemd160( const string& hex_str ) { + auto bytes_written = fc::from_hex( hex_str, (char*)_hash, sizeof(_hash) ); + if( bytes_written < sizeof(_hash) ) + memset( (char*)_hash + bytes_written, 0, (sizeof(_hash) - bytes_written) ); +} + +string ripemd160::str()const { + return fc::to_hex( (char*)_hash, sizeof(_hash) ); +} +ripemd160::operator string()const { return str(); } + +char* ripemd160::data()const { return (char*)&_hash[0]; } + + +struct ripemd160::encoder::impl { + impl() + { + memset( (char*)&ctx, 0, sizeof(ctx) ); + } + RIPEMD160_CTX ctx; +}; + +ripemd160::encoder::~encoder() {} +ripemd160::encoder::encoder() { + reset(); +} + +ripemd160 ripemd160::hash( const fc::sha512& h ) +{ + return hash( (const char*)&h, sizeof(h) ); +} +ripemd160 ripemd160::hash( const fc::sha256& h ) +{ + return hash( (const char*)&h, sizeof(h) ); +} +ripemd160 ripemd160::hash( const char* d, uint32_t dlen ) { + encoder e; + e.write(d,dlen); + return e.result(); +} +ripemd160 ripemd160::hash( const string& s ) { + return hash( s.c_str(), s.size() ); +} + +void ripemd160::encoder::write( const char* d, uint32_t dlen ) { + RIPEMD160_Update( &my->ctx, d, dlen); +} +ripemd160 ripemd160::encoder::result() { + ripemd160 h; + RIPEMD160_Final((uint8_t*)h.data(), &my->ctx ); + return h; +} +void ripemd160::encoder::reset() { + RIPEMD160_Init( &my->ctx); +} + +ripemd160 operator << ( const ripemd160& h1, uint32_t i ) { + ripemd160 result; + fc::detail::shift_l( h1.data(), result.data(), result.data_size(), i ); + return result; +} +ripemd160 operator ^ ( const ripemd160& h1, const ripemd160& h2 ) { + ripemd160 result; + result._hash[0] = h1._hash[0] ^ h2._hash[0]; + result._hash[1] = h1._hash[1] ^ h2._hash[1]; + result._hash[2] = h1._hash[2] ^ h2._hash[2]; + result._hash[3] = h1._hash[3] ^ h2._hash[3]; + result._hash[4] = h1._hash[4] ^ h2._hash[4]; + return result; +} +bool operator >= ( const ripemd160& h1, const ripemd160& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) >= 0; +} +bool operator > ( const ripemd160& h1, const ripemd160& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) > 0; +} +bool operator < ( const ripemd160& h1, const ripemd160& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) < 0; +} +bool operator != ( const ripemd160& h1, const ripemd160& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) != 0; +} +bool operator == ( const ripemd160& h1, const ripemd160& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) == 0; +} + + void to_variant( const ripemd160& bi, variant& v ) + { + v = std::vector( (const char*)&bi, ((const char*)&bi) + sizeof(bi) ); + } + void from_variant( const variant& v, ripemd160& bi ) + { + std::vector ve = v.as< std::vector >(); + if( ve.size() ) + { + memcpy(bi.data(), ve.data(), fc::min(ve.size(),sizeof(bi)) ); + } + else + memset( bi.data(), char(0), sizeof(bi) ); + } + +} // fc diff --git a/libraries/libfc/src/crypto/sha1.cpp b/libraries/libfc/src/crypto/sha1.cpp new file mode 100644 index 0000000000..3d294446f4 --- /dev/null +++ b/libraries/libfc/src/crypto/sha1.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include +#include +#include "_digest_common.hpp" + +namespace fc +{ + +sha1::sha1() { memset( _hash, 0, sizeof(_hash) ); } +sha1::sha1( const string& hex_str ) { + auto bytes_written = fc::from_hex( hex_str, (char*)_hash, sizeof(_hash) ); + if( bytes_written < sizeof(_hash) ) + memset( (char*)_hash + bytes_written, 0, (sizeof(_hash) - bytes_written) ); +} + +string sha1::str()const { + return fc::to_hex( (char*)_hash, sizeof(_hash) ); +} +sha1::operator string()const { return str(); } + +char* sha1::data() { return (char*)&_hash[0]; } +const char* sha1::data()const { return (char*)&_hash[0]; } + + +struct sha1::encoder::impl { + SHA_CTX ctx; +}; + +sha1::encoder::~encoder() {} +sha1::encoder::encoder() { + reset(); +} + +sha1 sha1::hash( const char* d, uint32_t dlen ) { + encoder e; + e.write(d,dlen); + return e.result(); +} +sha1 sha1::hash( const string& s ) { + return hash( s.c_str(), s.size() ); +} + +void sha1::encoder::write( const char* d, uint32_t dlen ) { + SHA1_Update( &my->ctx, d, dlen); +} +sha1 sha1::encoder::result() { + sha1 h; + SHA1_Final((uint8_t*)h.data(), &my->ctx ); + return h; +} +void sha1::encoder::reset() { + SHA1_Init( &my->ctx); +} + +sha1 operator << ( const sha1& h1, uint32_t i ) { + sha1 result; + fc::detail::shift_l( h1.data(), result.data(), result.data_size(), i ); + return result; +} +sha1 operator ^ ( const sha1& h1, const sha1& h2 ) { + sha1 result; + result._hash[0] = h1._hash[0] ^ h2._hash[0]; + result._hash[1] = h1._hash[1] ^ h2._hash[1]; + result._hash[2] = h1._hash[2] ^ h2._hash[2]; + result._hash[3] = h1._hash[3] ^ h2._hash[3]; + result._hash[4] = h1._hash[4] ^ h2._hash[4]; + return result; +} +bool operator >= ( const sha1& h1, const sha1& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) >= 0; +} +bool operator > ( const sha1& h1, const sha1& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) > 0; +} +bool operator < ( const sha1& h1, const sha1& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) < 0; +} +bool operator != ( const sha1& h1, const sha1& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) != 0; +} +bool operator == ( const sha1& h1, const sha1& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) == 0; +} + + void to_variant( const sha1& bi, variant& v ) + { + v = std::vector( (const char*)&bi, ((const char*)&bi) + sizeof(bi) ); + } + void from_variant( const variant& v, sha1& bi ) + { + std::vector ve = v.as< std::vector >(); + if( ve.size() ) + { + memcpy(bi.data(), ve.data(), fc::min(ve.size(),sizeof(bi)) ); + } + else + memset( bi.data(), char(0), sizeof(bi) ); + } + +} // fc diff --git a/libraries/libfc/src/crypto/sha224.cpp b/libraries/libfc/src/crypto/sha224.cpp new file mode 100644 index 0000000000..c1e6df337a --- /dev/null +++ b/libraries/libfc/src/crypto/sha224.cpp @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include +#include "_digest_common.hpp" + +namespace fc { + + sha224::sha224() { memset( _hash, 0, sizeof(_hash) ); } + sha224::sha224( const string& hex_str ) { + auto bytes_written = fc::from_hex( hex_str, (char*)_hash, sizeof(_hash) ); + if( bytes_written < sizeof(_hash) ) + memset( (char*)_hash + bytes_written, 0, (sizeof(_hash) - bytes_written) ); + } + + string sha224::str()const { + return fc::to_hex( (char*)_hash, sizeof(_hash) ); + } + sha224::operator string()const { return str(); } + + char* sha224::data() { return (char*)&_hash[0]; } + const char* sha224::data() const { return (const char*)&_hash[0]; } + + struct sha224::encoder::impl { + SHA256_CTX ctx; + }; + + sha224::encoder::~encoder() {} + sha224::encoder::encoder() { + reset(); + } + + sha224 sha224::hash( const char* d, uint32_t dlen ) { + encoder e; + e.write(d,dlen); + return e.result(); + } + sha224 sha224::hash( const string& s ) { + return hash( s.c_str(), s.size() ); + } + + void sha224::encoder::write( const char* d, uint32_t dlen ) { + SHA224_Update( &my->ctx, d, dlen); + } + sha224 sha224::encoder::result() { + sha224 h; + SHA224_Final((uint8_t*)h.data(), &my->ctx ); + return h; + } + void sha224::encoder::reset() { + SHA224_Init( &my->ctx); + } + + sha224 operator << ( const sha224& h1, uint32_t i ) { + sha224 result; + fc::detail::shift_l( h1.data(), result.data(), result.data_size(), i ); + return result; + } + sha224 operator ^ ( const sha224& h1, const sha224& h2 ) { + sha224 result; + for( uint32_t i = 0; i < 7; ++i ) + result._hash[i] = h1._hash[i] ^ h2._hash[i]; + return result; + } + bool operator >= ( const sha224& h1, const sha224& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(sha224) ) >= 0; + } + bool operator > ( const sha224& h1, const sha224& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(sha224) ) > 0; + } + bool operator < ( const sha224& h1, const sha224& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(sha224) ) < 0; + } + bool operator != ( const sha224& h1, const sha224& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(sha224) ) != 0; + } + bool operator == ( const sha224& h1, const sha224& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(sha224) ) == 0; + } + + void to_variant( const sha224& bi, variant& v ) + { + v = std::vector( (const char*)&bi, ((const char*)&bi) + sizeof(bi) ); + } + void from_variant( const variant& v, sha224& bi ) + { + std::vector ve = v.as< std::vector >(); + if( ve.size() ) + { + memcpy(bi.data(), ve.data(), fc::min(ve.size(),sizeof(bi)) ); + } + else + memset( bi.data(), char(0), sizeof(bi) ); + } + + template<> + unsigned int hmac::internal_block_size() const { return 64; } +} diff --git a/libraries/libfc/src/crypto/sha256.cpp b/libraries/libfc/src/crypto/sha256.cpp new file mode 100644 index 0000000000..1ffe4c2ad0 --- /dev/null +++ b/libraries/libfc/src/crypto/sha256.cpp @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "_digest_common.hpp" + +namespace fc { + + sha256::sha256() { memset( _hash, 0, sizeof(_hash) ); } + sha256::sha256( const char *data, size_t size ) { + if (size != sizeof(_hash)) + FC_THROW_EXCEPTION( exception, "sha256: size mismatch" ); + memcpy(_hash, data, size ); + } + sha256::sha256( const string& hex_str ) { + auto bytes_written = fc::from_hex( hex_str, (char*)_hash, sizeof(_hash) ); + if( bytes_written < sizeof(_hash) ) + memset( (char*)_hash + bytes_written, 0, (sizeof(_hash) - bytes_written) ); + } + + string sha256::str()const { + return fc::to_hex( (char*)_hash, sizeof(_hash) ); + } + sha256::operator string()const { return str(); } + + const char* sha256::data()const { return (const char*)&_hash[0]; } + char* sha256::data() { return (char*)&_hash[0]; } + + + struct sha256::encoder::impl { + SHA256_CTX ctx; + }; + + sha256::encoder::~encoder() {} + sha256::encoder::encoder() { + reset(); + } + + sha256 sha256::hash( const char* d, uint32_t dlen ) { + encoder e; + e.write(d,dlen); + return e.result(); + } + + sha256 sha256::hash( const string& s ) { + return hash( s.c_str(), s.size() ); + } + + sha256 sha256::hash( const sha256& s ) + { + return hash( s.data(), sizeof( s._hash ) ); + } + + void sha256::encoder::write( const char* d, uint32_t dlen ) { + SHA256_Update( &my->ctx, d, dlen); + } + sha256 sha256::encoder::result() { + sha256 h; + SHA256_Final((uint8_t*)h.data(), &my->ctx ); + return h; + } + void sha256::encoder::reset() { + SHA256_Init( &my->ctx); + } + + sha256 operator << ( const sha256& h1, uint32_t i ) { + sha256 result; + fc::detail::shift_l( h1.data(), result.data(), result.data_size(), i ); + return result; + } + sha256 operator >> ( const sha256& h1, uint32_t i ) { + sha256 result; + fc::detail::shift_r( h1.data(), result.data(), result.data_size(), i ); + return result; + } + sha256 operator ^ ( const sha256& h1, const sha256& h2 ) { + sha256 result; + result._hash[0] = h1._hash[0] ^ h2._hash[0]; + result._hash[1] = h1._hash[1] ^ h2._hash[1]; + result._hash[2] = h1._hash[2] ^ h2._hash[2]; + result._hash[3] = h1._hash[3] ^ h2._hash[3]; + return result; + } + bool operator >= ( const sha256& h1, const sha256& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) >= 0; + } + bool operator > ( const sha256& h1, const sha256& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) > 0; + } + bool operator < ( const sha256& h1, const sha256& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) < 0; + } + bool operator != ( const sha256& h1, const sha256& h2 ) { + return !(h1 == h2); + } + bool operator == ( const sha256& h1, const sha256& h2 ) { + // idea to not use memcmp, from: + // https://lemire.me/blog/2018/08/22/avoid-lexicographical-comparisons-when-testing-for-string-equality/ + return + h1._hash[0] == h2._hash[0] && + h1._hash[1] == h2._hash[1] && + h1._hash[2] == h2._hash[2] && + h1._hash[3] == h2._hash[3]; + } + + uint32_t sha256::approx_log_32()const + { + uint16_t lzbits = clz(); + if( lzbits >= 0x100 ) + return 0; + uint8_t nzbits = 0xFF-lzbits; + size_t offset = (size_t) (lzbits >> 3); + uint8_t* my_bytes = (uint8_t*) data(); + size_t n = data_size(); + uint32_t y = (uint32_t( my_bytes[offset ] ) << 0x18) + | (uint32_t(offset+1 < n ? my_bytes[offset+1] : 0) << 0x10) + | (uint32_t(offset+2 < n ? my_bytes[offset+2] : 0) << 0x08) + | (uint32_t(offset+3 < n ? my_bytes[offset+3] : 0) ) + ; + // + // lzbits&7 == 7 : 00000001 iff nzbits&7 == 0 + // lzbits&7 == 6 : 0000001x iff nzbits&7 == 1 + // lzbits&7 == 5 : 000001xx iff nzbits&7 == 2 + // + y >>= (nzbits & 7); + y ^= 1 << 0x18; + y |= uint32_t( nzbits ) << 0x18; + return y; + } + + void sha256::set_to_inverse_approx_log_32( uint32_t x ) + { + uint8_t nzbits = uint8_t( x >> 0x18 ); + _hash[0] = 0; + _hash[1] = 0; + _hash[2] = 0; + _hash[3] = 0; + if( nzbits == 0 ) + return; + uint8_t x0 = uint8_t((x ) & 0xFF); + uint8_t x1 = uint8_t((x >> 0x08) & 0xFF); + uint8_t x2 = uint8_t((x >> 0x10) & 0xFF); + uint8_t* my_bytes = (uint8_t*) data(); + my_bytes[0x1F] = x0; + my_bytes[0x1E] = x1; + my_bytes[0x1D] = x2; + my_bytes[0x1C] = 1; + + if( nzbits <= 0x18 ) + { + (*this) = (*this) >> (0x18 - nzbits); + } + else + (*this) = (*this) << (nzbits - 0x18); + return; + } + + double sha256::inverse_approx_log_32_double( uint32_t x ) + { + uint8_t nzbits = uint8_t( x >> 0x18 ); + if( nzbits == 0 ) + return 0.0; + uint32_t b = 1 << 0x18; + uint32_t y = (x & (b-1)) | b; + return std::ldexp( y, int( nzbits ) - 0x18 ); + } + + uint16_t sha256::clz()const + { + const uint8_t* my_bytes = (uint8_t*) data(); + size_t size = data_size(); + size_t lzbits = 0; + static const uint8_t char2lzbits[] = { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + size_t i = 0; + + while( true ) + { + uint8_t c = my_bytes[i]; + lzbits += char2lzbits[c]; + if( c != 0 ) + break; + ++i; + if( i >= size ) + return 0x100; + } + + return lzbits; + } + + void to_variant( const sha256& bi, variant& v ) + { + v = std::vector( (const char*)&bi, ((const char*)&bi) + sizeof(bi) ); + } + void from_variant( const variant& v, sha256& bi ) + { + std::vector ve = v.as< std::vector >(); + if( ve.size() ) + { + memcpy(bi.data(), ve.data(), fc::min(ve.size(),sizeof(bi)) ); + } + else + memset( bi.data(), char(0), sizeof(bi) ); + } + + uint64_t hash64(const char* buf, size_t len) + { + sha256 sha_value = sha256::hash(buf,len); + return sha_value._hash[0]; + } + + template<> + unsigned int hmac::internal_block_size() const { return 64; } +} //end namespace fc diff --git a/libraries/libfc/src/crypto/sha3.cpp b/libraries/libfc/src/crypto/sha3.cpp new file mode 100644 index 0000000000..d3333323c1 --- /dev/null +++ b/libraries/libfc/src/crypto/sha3.cpp @@ -0,0 +1,287 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "_digest_common.hpp" + +namespace fc +{ + +template struct overloaded : Ts... { using Ts::operator()...; }; +template overloaded(Ts...) -> overloaded; + +#if defined(__BYTE_ORDER__) +#if defined(__ORDER_LITTLE_ENDIAN__) +inline constexpr bool is_little_endian = true; +#else +inline constexpr bool is_little_endian = false; +#endif +#else +#error "sha3 implementation needs __BYTE_ORDER__ and __ORDER_LITTLE/BIG_ENDIAN__ defined" +#endif + +#if defined(__builtin_rotateleft64) +__attribute__ ((always_inline)) +inline uint64_t rotl64(uint64_t x, uint64_t y) { return __builtin_rotateleft64(x, y); } +#else +// gcc should recognize this better than clang +__attribute__ ((always_inline)) +inline uint64_t rotl64(uint64_t x, uint64_t y) { return (x << y) | (x >> (64-y)); } +#endif + +#ifdef __clang__ + #define VECTORIZE_HINT _Pragma("clang loop vectorize(enable) interleave(enable)") +#else + #define VECTORIZE_HINT +#endif + +/* This implementation is an amalgam from tiny_sha3 (https://github.com/mjosaarinen/tiny_sha3) and + * SHA3UIF (https://github.com/brainhub/SHA3UIF) and documentation. + * This implementation is quite slow comparative to openssl 1.1.1. Once we require a version greater + * than or equal to 1.1.1, we should replace with their primitives. + */ +struct sha3_impl { + sha3_impl() { init(); } + + static constexpr uint8_t number_of_rounds = 24; + static constexpr uint8_t number_of_words = 25; + static constexpr uint8_t digest_size = 32; + static constexpr uint64_t round_constants[number_of_rounds] = { + UINT64_C(0x0000000000000001), UINT64_C(0x0000000000008082), UINT64_C(0x800000000000808a), UINT64_C(0x8000000080008000), + UINT64_C(0x000000000000808b), UINT64_C(0x0000000080000001), UINT64_C(0x8000000080008081), UINT64_C(0x8000000000008009), + UINT64_C(0x000000000000008a), UINT64_C(0x0000000000000088), UINT64_C(0x0000000080008009), UINT64_C(0x000000008000000a), + UINT64_C(0x000000008000808b), UINT64_C(0x800000000000008b), UINT64_C(0x8000000000008089), UINT64_C(0x8000000000008003), + UINT64_C(0x8000000000008002), UINT64_C(0x8000000000000080), UINT64_C(0x000000000000800a), UINT64_C(0x800000008000000a), + UINT64_C(0x8000000080008081), UINT64_C(0x8000000000008080), UINT64_C(0x0000000080000001), UINT64_C(0x8000000080008008)}; + static constexpr uint8_t rot_constants[number_of_rounds] = {1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44}; + static constexpr uint8_t pi_lanes[number_of_rounds] = {10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1}; + + void update_step() + { + uint64_t bc[5]; + + if constexpr (!is_little_endian) + { + uint8_t *v; + // convert the buffer to little endian + for (std::size_t i; i < number_of_words; i++) + { + v = reinterpret_cast(words + i); + words[i] = ((uint64_t)v[0]) | (((uint64_t)v[1]) << 8) | + (((uint64_t)v[2]) << 16) | (((uint64_t)v[3]) << 24) | + (((uint64_t)v[4]) << 32) | (((uint64_t)v[5]) << 40) | + (((uint64_t)v[6]) << 48) | (((uint64_t)v[7]) << 56); + } + } + + VECTORIZE_HINT for (std::size_t i = 0; i < number_of_rounds; i++) + { + // theta + VECTORIZE_HINT for (std::size_t j = 0; j < 5; j++) + bc[j] = words[j] ^ words[j + 5] ^ words[j + 10] ^ words[j + 15] ^ words[j + 20]; + + uint64_t t; + VECTORIZE_HINT for (std::size_t j = 0; j < 5; j++) + { + t = bc[(j + 4) % 5] ^ rotl64(bc[(j + 1) % 5], 1); + VECTORIZE_HINT for (std::size_t k = 0; k < number_of_words; k += 5) + words[k + j] ^= t; + } + + // rho pi + t = words[1]; + VECTORIZE_HINT for (std::size_t j = 0; j < number_of_rounds; j++) + { + uint8_t p = pi_lanes[j]; + bc[0] = words[p]; + words[p] = rotl64(t, rot_constants[j]); + t = bc[0]; + } + + // chi + VECTORIZE_HINT for (std::size_t j = 0; j < number_of_words; j += 5) + { + VECTORIZE_HINT for (std::size_t k = 0; k < 5; k++) + bc[k] = words[k + j]; + VECTORIZE_HINT for (std::size_t k = 0; k < 5; k++) + words[k + j] ^= (~bc[(k + 1) % 5]) & bc[(k + 2) % 5]; + } + + // iota + words[0] ^= round_constants[i]; + } + + if constexpr (!is_little_endian) + { + uint8_t *v; + uint64_t tmp; + // convert back to big endian + for (std::size_t i = 0; i < sizeof(words); i++) + { + v = (uint8_t *)(words + i); + tmp = words[i]; + v[0] = tmp & 0xFF; + v[1] = (tmp >> 8) & 0xFF; + v[2] = (tmp >> 16) & 0xFF; + v[3] = (tmp >> 24) & 0xFF; + v[4] = (tmp >> 32) & 0xFF; + v[5] = (tmp >> 40) & 0xFF; + v[6] = (tmp >> 48) & 0xFF; + v[7] = (tmp >> 56) & 0xFF; + } + } + } + + void init() { + memset((char *)this, 0, sizeof(*this)); + size = 136; + } + + void update(const uint8_t* data, std::size_t len) { + int j = point; + for (std::size_t i = 0; i < len; i++) + { + bytes[j++] ^= data[i]; + if (j >= size) + { + update_step(); + j = 0; + } + } + point = j; + } + + void finalize(char* buffer) { + bytes[point] ^= keccak ? 0x01 : 0x06; + bytes[size-1] ^= 0x80; + update_step(); + memcpy(buffer, (const char*)bytes, digest_size); + } + + union { + uint8_t bytes[number_of_words*8]; + uint64_t words[number_of_words*5]; // this is greater than 25, because in the theta portion we need a wide berth + }; + bool keccak = false; + int point; + int size; +}; + +sha3::sha3() +{ + memset(_hash, 0, sizeof(_hash)); +} +sha3::sha3(const char *data, size_t size) +{ + if (size != sizeof(_hash)) + FC_THROW_EXCEPTION(exception, "sha3: size mismatch"); + memcpy(_hash, data, size); +} +sha3::sha3(const string &hex_str) +{ + auto bytes_written = fc::from_hex(hex_str, (char *)_hash, sizeof(_hash)); + if (bytes_written < sizeof(_hash)) + memset((char *)_hash + bytes_written, 0, (sizeof(_hash) - bytes_written)); +} + +string sha3::str() const +{ + return fc::to_hex((char *)_hash, sizeof(_hash)); +} +sha3::operator string() const { return str(); } + +const char *sha3::data() const { return (const char *)&_hash[0]; } +char *sha3::data() { return (char *)&_hash[0]; } + +struct sha3::encoder::impl +{ + sha3_impl ctx; +}; + +sha3::encoder::~encoder() {} +sha3::encoder::encoder() +{ + reset(); +} + +void sha3::encoder::write(const char *d, uint32_t dlen) +{ + my->ctx.update((const uint8_t*)d, dlen); +} +sha3 sha3::encoder::result(bool is_nist) +{ + sha3 h; + my->ctx.keccak = !is_nist; + my->ctx.finalize((char*)h.data()); + return h; +} +void sha3::encoder::reset() +{ + my->ctx.init(); +} + +sha3 operator<<(const sha3 &h1, uint32_t i) +{ + sha3 result; + fc::detail::shift_l(h1.data(), result.data(), result.data_size(), i); + return result; +} +sha3 operator>>(const sha3 &h1, uint32_t i) +{ + sha3 result; + fc::detail::shift_r(h1.data(), result.data(), result.data_size(), i); + return result; +} +sha3 operator^(const sha3 &h1, const sha3 &h2) +{ + sha3 result; + result._hash[0] = h1._hash[0] ^ h2._hash[0]; + result._hash[1] = h1._hash[1] ^ h2._hash[1]; + result._hash[2] = h1._hash[2] ^ h2._hash[2]; + result._hash[3] = h1._hash[3] ^ h2._hash[3]; + return result; +} +bool operator>=(const sha3 &h1, const sha3 &h2) +{ + return memcmp(h1._hash, h2._hash, sizeof(h1._hash)) >= 0; +} +bool operator>(const sha3 &h1, const sha3 &h2) +{ + return memcmp(h1._hash, h2._hash, sizeof(h1._hash)) > 0; +} +bool operator<(const sha3 &h1, const sha3 &h2) +{ + return memcmp(h1._hash, h2._hash, sizeof(h1._hash)) < 0; +} +bool operator!=(const sha3 &h1, const sha3 &h2) +{ + return !(h1 == h2); +} +bool operator==(const sha3 &h1, const sha3 &h2) +{ + // idea to not use memcmp, from: + // https://lemire.me/blog/2018/08/22/avoid-lexicographical-comparisons-when-testing-for-string-equality/ + return h1._hash[0] == h2._hash[0] && + h1._hash[1] == h2._hash[1] && + h1._hash[2] == h2._hash[2] && + h1._hash[3] == h2._hash[3]; +} + +void to_variant(const sha3 &bi, variant &v) +{ + v = std::vector((const char *)&bi, ((const char *)&bi) + sizeof(bi)); +} +void from_variant(const variant &v, sha3 &bi) +{ + const auto &ve = v.as>(); + if (ve.size()) + memcpy(bi.data(), ve.data(), fc::min(ve.size(), sizeof(bi))); + else + memset(bi.data(), char(0), sizeof(bi)); +} +} // namespace fc \ No newline at end of file diff --git a/libraries/libfc/src/crypto/sha512.cpp b/libraries/libfc/src/crypto/sha512.cpp new file mode 100644 index 0000000000..82e439a1eb --- /dev/null +++ b/libraries/libfc/src/crypto/sha512.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include +#include +#include +#include "_digest_common.hpp" + +namespace fc { + + sha512::sha512() { memset( _hash, 0, sizeof(_hash) ); } + sha512::sha512( const string& hex_str ) { + auto bytes_written = fc::from_hex( hex_str, (char*)_hash, sizeof(_hash) ); + if( bytes_written < sizeof(_hash) ) + memset( (char*)_hash + bytes_written, 0, (sizeof(_hash) - bytes_written) ); + } + + string sha512::str()const { + return fc::to_hex( (char*)_hash, sizeof(_hash) ); + } + sha512::operator string()const { return str(); } + + char* sha512::data() { return (char*)&_hash[0]; } + const char* sha512::data()const { return (const char*)&_hash[0]; } + + + struct sha512::encoder::impl { + SHA512_CTX ctx; + }; + + sha512::encoder::~encoder() {} + sha512::encoder::encoder() { + reset(); + } + + sha512 sha512::hash( const char* d, uint32_t dlen ) { + encoder e; + e.write(d,dlen); + return e.result(); + } + sha512 sha512::hash( const string& s ) { + return hash( s.c_str(), s.size() ); + } + + void sha512::encoder::write( const char* d, uint32_t dlen ) { + SHA512_Update( &my->ctx, d, dlen); + } + sha512 sha512::encoder::result() { + sha512 h; + SHA512_Final((uint8_t*)h.data(), &my->ctx ); + return h; + } + void sha512::encoder::reset() { + SHA512_Init( &my->ctx); + } + + sha512 operator << ( const sha512& h1, uint32_t i ) { + sha512 result; + fc::detail::shift_l( h1.data(), result.data(), result.data_size(), i ); + return result; + } + sha512 operator ^ ( const sha512& h1, const sha512& h2 ) { + sha512 result; + result._hash[0] = h1._hash[0] ^ h2._hash[0]; + result._hash[1] = h1._hash[1] ^ h2._hash[1]; + result._hash[2] = h1._hash[2] ^ h2._hash[2]; + result._hash[3] = h1._hash[3] ^ h2._hash[3]; + result._hash[4] = h1._hash[4] ^ h2._hash[4]; + result._hash[5] = h1._hash[5] ^ h2._hash[5]; + result._hash[6] = h1._hash[6] ^ h2._hash[6]; + result._hash[7] = h1._hash[7] ^ h2._hash[7]; + return result; + } + bool operator >= ( const sha512& h1, const sha512& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) >= 0; + } + bool operator > ( const sha512& h1, const sha512& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) > 0; + } + bool operator < ( const sha512& h1, const sha512& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) < 0; + } + bool operator != ( const sha512& h1, const sha512& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) != 0; + } + bool operator == ( const sha512& h1, const sha512& h2 ) { + return memcmp( h1._hash, h2._hash, sizeof(h1._hash) ) == 0; + } + + void to_variant( const sha512& bi, variant& v ) + { + v = std::vector( (const char*)&bi, ((const char*)&bi) + sizeof(bi) ); + } + void from_variant( const variant& v, sha512& bi ) + { + std::vector ve = v.as< std::vector >(); + if( ve.size() ) + { + memcpy(bi.data(), ve.data(), fc::min(ve.size(),sizeof(bi)) ); + } + else + memset( bi.data(), char(0), sizeof(bi) ); + } + + template<> + unsigned int hmac::internal_block_size() const { return 128; } +} diff --git a/libraries/libfc/src/crypto/signature.cpp b/libraries/libfc/src/crypto/signature.cpp new file mode 100644 index 0000000000..2d3a2927b1 --- /dev/null +++ b/libraries/libfc/src/crypto/signature.cpp @@ -0,0 +1,97 @@ +#include +#include +#include + +namespace fc { namespace crypto { + struct hash_visitor : public fc::visitor { + template + size_t operator()(const SigType& sig) const { + static_assert(sizeof(sig._data.data) == 65, "sig size is expected to be 65"); + //signatures are two bignums: r & s. Just add up least significant digits of the two + return *(size_t*)&sig._data.data[32-sizeof(size_t)] + *(size_t*)&sig._data.data[64-sizeof(size_t)]; + } + + size_t operator()(const webauthn::signature& sig) const { + return sig.get_hash(); + } + }; + + static signature::storage_type sig_parse_base58(const std::string& base58str) + { try { + constexpr auto prefix = config::signature_base_prefix; + + const auto pivot = base58str.find('_'); + FC_ASSERT(pivot != std::string::npos, "No delimiter in string, cannot determine type: ${str}", ("str", base58str)); + + const auto prefix_str = base58str.substr(0, pivot); + FC_ASSERT(prefix == prefix_str, "Signature Key has invalid prefix: ${str}", ("str", base58str)("prefix_str", prefix_str)); + + auto data_str = base58str.substr(pivot + 1); + FC_ASSERT(!data_str.empty(), "Signature has no data: ${str}", ("str", base58str)); + return base58_str_parser::apply(data_str); + } FC_RETHROW_EXCEPTIONS( warn, "error parsing signature", ("str", base58str ) ) } + + signature::signature(const std::string& base58str) + :_storage(sig_parse_base58(base58str)) + {} + + size_t signature::which() const { + return _storage.index(); + } + + template struct overloaded : Ts... { using Ts::operator()...; }; + template overloaded(Ts...) -> overloaded; + + size_t signature::variable_size() const { + return std::visit(overloaded { + [&](const auto& k1r1) { + return static_cast(0); + }, + [&](const webauthn::signature& wa) { + return static_cast(wa.variable_size()); + } + }, _storage); + } + + std::string signature::to_string(const fc::yield_function_t& yield) const + { + auto data_str = std::visit(base58str_visitor(yield), _storage); + yield(); + return std::string(config::signature_base_prefix) + "_" + data_str; + } + + std::ostream& operator<<(std::ostream& s, const signature& k) { + s << "signature(" << k.to_string() << ')'; + return s; + } + + bool operator == ( const signature& p1, const signature& p2) { + return eq_comparator::apply(p1._storage, p2._storage); + } + + bool operator != ( const signature& p1, const signature& p2) { + return !eq_comparator::apply(p1._storage, p2._storage); + } + + bool operator < ( const signature& p1, const signature& p2) + { + return less_comparator::apply(p1._storage, p2._storage); + } + + size_t hash_value(const signature& b) { + return std::visit(hash_visitor(), b._storage); + } +} } // eosio::blockchain + +namespace fc +{ + void to_variant(const fc::crypto::signature& var, fc::variant& vo, const fc::yield_function_t& yield) + { + vo = var.to_string(yield); + } + + void from_variant(const fc::variant& var, fc::crypto::signature& vo) + { + vo = fc::crypto::signature(var.as_string()); + } +} // fc diff --git a/libraries/libfc/src/exception.cpp b/libraries/libfc/src/exception.cpp new file mode 100644 index 0000000000..a9d9de221b --- /dev/null +++ b/libraries/libfc/src/exception.cpp @@ -0,0 +1,357 @@ +#include +#include +#include +#include + +#include + +namespace fc +{ + FC_REGISTER_EXCEPTIONS( (timeout_exception) + (file_not_found_exception) + (parse_error_exception) + (invalid_arg_exception) + (invalid_operation_exception) + (key_not_found_exception) + (bad_cast_exception) + (out_of_range_exception) + (canceled_exception) + (assert_exception) + (eof_exception) + (unknown_host_exception) + (null_optional) + (udt_exception) + (aes_exception) + (overflow_exception) + (underflow_exception) + (divide_by_zero_exception) + ) + + namespace detail + { + class exception_impl + { + public: + std::string _name; + std::string _what; + int64_t _code; + log_messages _elog; + }; + } + exception::exception( log_messages&& msgs, int64_t code, + const std::string& name_value, + const std::string& what_value ) + :my( new detail::exception_impl() ) + { + my->_code = code; + my->_what = what_value; + my->_name = name_value; + my->_elog = fc::move(msgs); + } + + exception::exception( + const log_messages& msgs, + int64_t code, + const std::string& name_value, + const std::string& what_value ) + :my( new detail::exception_impl() ) + { + my->_code = code; + my->_what = what_value; + my->_name = name_value; + my->_elog = msgs; + } + + unhandled_exception::unhandled_exception( log_message&& m, std::exception_ptr e ) + :exception( fc::move(m) ) + { + _inner = e; + } + unhandled_exception::unhandled_exception( const exception& r ) + :exception(r) + { + } + unhandled_exception::unhandled_exception( log_messages m ) + :exception() + { my->_elog = fc::move(m); } + + std::exception_ptr unhandled_exception::get_inner_exception()const { return _inner; } + + NO_RETURN void unhandled_exception::dynamic_rethrow_exception()const + { + if( !(_inner == std::exception_ptr()) ) std::rethrow_exception( _inner ); + else { fc::exception::dynamic_rethrow_exception(); } + } + + std::shared_ptr unhandled_exception::dynamic_copy_exception()const + { + auto e = std::make_shared( *this ); + e->_inner = _inner; + return e; + } + + exception::exception( int64_t code, + const std::string& name_value, + const std::string& what_value ) + :my( new detail::exception_impl() ) + { + my->_code = code; + my->_what = what_value; + my->_name = name_value; + } + + exception::exception( log_message&& msg, + int64_t code, + const std::string& name_value, + const std::string& what_value ) + :my( new detail::exception_impl() ) + { + my->_code = code; + my->_what = what_value; + my->_name = name_value; + my->_elog.push_back( fc::move( msg ) ); + } + exception::exception( const exception& c ) + :my( new detail::exception_impl(*c.my) ) + { } + exception::exception( exception&& c ) + :my( fc::move(c.my) ){} + + const char* exception::name()const throw() { return my->_name.c_str(); } + const char* exception::what()const noexcept { return my->_what.c_str(); } + int64_t exception::code()const throw() { return my->_code; } + + exception::~exception(){} + + void to_variant( const exception& e, variant& v ) + { + v = mutable_variant_object( "code", e.code() ) + ( "name", e.name() ) + ( "message", e.what() ) + ( "stack", e.get_log() ); + + } + void from_variant( const variant& v, exception& ll ) + { + auto obj = v.get_object(); + if( obj.contains( "stack" ) ) + ll.my->_elog = obj["stack"].as(); + if( obj.contains( "code" ) ) + ll.my->_code = obj["code"].as_int64(); + if( obj.contains( "name" ) ) + ll.my->_name = obj["name"].as_string(); + if( obj.contains( "message" ) ) + ll.my->_what = obj["message"].as_string(); + } + + const log_messages& exception::get_log()const { return my->_elog; } + void exception::append_log( log_message m ) + { + my->_elog.emplace_back( fc::move(m) ); + } + + /** + * Generates a detailed string including file, line, method, + * and other information that is generally only useful for + * developers. + */ + string exception::to_detail_string( log_level ll )const + { + const auto deadline = fc::time_point::now() + format_time_limit; + std::stringstream ss; + try { + try { + ss << variant( my->_code ).as_string(); + } catch( std::bad_alloc& ) { + throw; + } catch( ... ) { + ss << "<- exception in to_detail_string."; + } + ss << " " << my->_name << ": " << my->_what << "\n"; + for( auto itr = my->_elog.begin(); itr != my->_elog.end(); ++itr ) { + try { + ss << itr->get_message() << "\n"; //fc::format_string( itr->get_format(), itr->get_data() ) <<"\n"; + ss << " " << json::to_string( itr->get_data(), deadline ) << "\n"; + ss << " " << itr->get_context().to_string() << "\n"; + } catch( std::bad_alloc& ) { + throw; + } catch( const fc::timeout_exception& e) { + ss << "<- timeout exception in to_detail_string: " << e.what() << "\n"; + break; + } catch( ... ) { + ss << "<- exception in to_detail_string.\n"; + } + } + } catch( std::bad_alloc& ) { + throw; + } catch( ... ) { + ss << "<- exception in to_detail_string.\n"; + } + return ss.str(); + } + + /** + * Generates a user-friendly error report. + */ + string exception::to_string( log_level ll )const + { + const auto deadline = fc::time_point::now() + format_time_limit; + std::stringstream ss; + try { + ss << my->_what; + try { + ss << " (" << variant( my->_code ).as_string() << ")\n"; + } catch( std::bad_alloc& ) { + throw; + } catch( ... ) { + ss << "<- exception in to_string.\n"; + } + for( auto itr = my->_elog.begin(); itr != my->_elog.end(); ++itr ) { + try { + FC_CHECK_DEADLINE(deadline); + ss << fc::format_string( itr->get_format(), itr->get_data(), true) << "\n"; + // ss << " " << itr->get_context().to_string() <<"\n"; + } catch( std::bad_alloc& ) { + throw; + } catch( const fc::timeout_exception& e) { + ss << "<- timeout exception in to_string: " << e.what(); + break; + } catch( ... ) { + ss << "<- exception in to_string.\n"; + } + } + return ss.str(); + } catch( std::bad_alloc& ) { + throw; + } catch( ... ) { + ss << "<- exception in to_string.\n"; + } + return ss.str(); + } + + /** + * Generates a user-friendly error report. + */ + string exception::top_message( )const + { + for( auto itr = my->_elog.begin(); itr != my->_elog.end(); ++itr ) + { + auto s = fc::format_string( itr->get_format(), itr->get_data() ); + if (!s.empty()) { + return s; + } + } + return string(); + } + + void NO_RETURN exception_factory::rethrow( const exception& e )const + { + auto itr = _registered_exceptions.find( e.code() ); + if( itr != _registered_exceptions.end() ) + itr->second->rethrow( e ); + throw e; + } + /** + * Rethrows the exception restoring the proper type based upon + * the error code. This is used to propagate exception types + * across conversions to/from JSON + */ + NO_RETURN void exception::dynamic_rethrow_exception()const + { + exception_factory::instance().rethrow( *this ); + } + + exception_ptr exception::dynamic_copy_exception()const + { + return std::make_shared(*this); + } + + fc::string except_str() + { + return boost::current_exception_diagnostic_information(); + } + + void throw_bad_enum_cast( int64_t i, const char* e ) + { + FC_THROW_EXCEPTION( bad_cast_exception, + "invalid index '${key}' in enum '${enum}'", + ("key",i)("enum",e) ); + } + void throw_bad_enum_cast( const char* k, const char* e ) + { + FC_THROW_EXCEPTION( bad_cast_exception, + "invalid name '${key}' in enum '${enum}'", + ("key",k)("enum",e) ); + } + + bool assert_optional(bool is_valid ) + { + if( !is_valid ) + throw null_optional(); + return true; + } + exception& exception::operator=( const exception& copy ) + { + *my = *copy.my; + return *this; + } + + exception& exception::operator=( exception&& copy ) + { + my = std::move(copy.my); + return *this; + } + + void record_assert_trip( + const char* filename, + uint32_t lineno, + const char* expr + ) + { + fc::mutable_variant_object assert_trip_info = + fc::mutable_variant_object() + ("source_file", filename) + ("source_lineno", lineno) + ("expr", expr) + ; + /* TODO: restore this later + std::cout + << "FC_ASSERT triggered: " + << fc::json::to_string( assert_trip_info ) << "\n"; + */ + return; + } + + bool enable_record_assert_trip = false; + + std_exception_wrapper::std_exception_wrapper( log_message&& m, std::exception_ptr e, + const std::string& name_value, + const std::string& what_value) + :exception( fc::move(m), exception_code::std_exception_code, name_value, what_value ) + { + _inner = {std::move(e)}; + } + + std_exception_wrapper std_exception_wrapper::from_current_exception(const std::exception& e) + { + return std_exception_wrapper{FC_LOG_MESSAGE(warn, "rethrow ${what}: ", ("what",e.what())), + std::current_exception(), + BOOST_CORE_TYPEID(e).name(), + e.what()}; + } + + std::exception_ptr std_exception_wrapper::get_inner_exception()const { return _inner; } + + NO_RETURN void std_exception_wrapper::dynamic_rethrow_exception()const + { + if( !(_inner == std::exception_ptr()) ) std::rethrow_exception( _inner ); + else { fc::exception::dynamic_rethrow_exception(); } + } + + std::shared_ptr std_exception_wrapper::dynamic_copy_exception()const + { + auto e = std::make_shared( *this ); + e->_inner = _inner; + return e; + } +} // fc diff --git a/libraries/libfc/src/filesystem.cpp b/libraries/libfc/src/filesystem.cpp new file mode 100644 index 0000000000..65c1ddf76d --- /dev/null +++ b/libraries/libfc/src/filesystem.cpp @@ -0,0 +1,624 @@ +//#define BOOST_NO_SCOPED_ENUMS +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#ifdef _WIN32 +# include +# include +# include +#else + #include + #include + #include +# ifdef FC_HAS_SIMPLE_FILE_LOCK + #include + #include +# endif +#endif + +namespace fc { + // when converting to and from a variant, store utf-8 in the variant + void to_variant( const fc::path& path_to_convert, variant& variant_output ) + { + std::wstring wide_string = path_to_convert.generic_wstring(); + std::string utf8_string; + fc::encodeUtf8(wide_string, &utf8_string); + variant_output = utf8_string; + + //std::string path = t.to_native_ansi_path(); + //std::replace(path.begin(), path.end(), '\\', '/'); + //v = path; + } + + void from_variant( const fc::variant& variant_to_convert, fc::path& path_output ) + { + std::wstring wide_string; + fc::decodeUtf8(variant_to_convert.as_string(), &wide_string); + path_output = path(wide_string); + } + + // Note: we can do this cast because the separator should be an ASCII character + char path::separator_char = static_cast(boost::filesystem::path("/").make_preferred().native()[0]); + + path::path(){} + path::~path(){}; + path::path( const boost::filesystem::path& p ) + :_p(p){} + + path::path( const char* p ) + :_p(p){} + path::path( const fc::string& p ) + :_p(p.c_str()){} + + path::path(const std::wstring& p) + :_p(p) {} + + path::path( const path& p ) + :_p(p){} + + path::path( path&& p ) + :_p(std::move(p)){} + + path& path::operator =( const path& p ) { + *_p = *p._p; + return *this; + } + path& path::operator =( path&& p ) { + *_p = fc::move( *p._p ); + return *this; + } + + bool operator <( const fc::path& l, const fc::path& r ) { return *l._p < *r._p; } + bool operator ==( const fc::path& l, const fc::path& r ) { return *l._p == *r._p; } + bool operator !=( const fc::path& l, const fc::path& r ) { return *l._p != *r._p; } + + path& path::operator /=( const fc::path& p ) { + *_p /= *p._p; + return *this; + } + path operator /( const fc::path& p, const fc::path& o ) { + path tmp; + tmp = *p._p / *o._p; + return tmp; + } + + path::operator boost::filesystem::path& () { + return *_p; + } + path::operator const boost::filesystem::path& ()const { + return *_p; + } + fc::string path::generic_string()const { + return _p->generic_string(); + } + + fc::string path::preferred_string() const + { + return boost::filesystem::path(*_p).make_preferred().string(); + } + + std::wstring path::wstring() const + { + return _p->wstring(); + } + + std::wstring path::generic_wstring() const + { + return _p->generic_wstring(); + } + + std::wstring path::preferred_wstring() const + { + return boost::filesystem::path(*_p).make_preferred().wstring(); + } + + std::string path::to_native_ansi_path() const + { + std::wstring path = generic_wstring(); + +#ifdef WIN32 + const size_t maxPath = 32*1024; + std::vector short_path; + short_path.resize(maxPath + 1); + + wchar_t* buffer = short_path.data(); + DWORD res = GetShortPathNameW(path.c_str(), buffer, maxPath); + if(res != 0) + path = buffer; +#endif + std::string filePath; + fc::encodeUtf8(path, &filePath); + return filePath; + } + + /** + * @todo use iterators instead of indexes for + * faster performance + */ + fc::string path::windows_string()const { + std::string result = _p->generic_string(); + std::replace(result.begin(), result.end(), '/', '\\'); + return result; + } + + fc::string path::string()const { + return _p->string(); + } + fc::path path::filename()const { + return _p->filename(); + } + void path::replace_extension( const fc::path& e ) { + _p->replace_extension(e); + } + fc::path path::extension()const { + return _p->extension(); + } + fc::path path::stem()const { + return _p->stem(); + } + fc::path path::parent_path()const { + return _p->parent_path(); + } + bool path::is_relative()const { return _p->is_relative(); } + bool path::is_absolute()const { return _p->is_absolute(); } + + bool path::empty() const { return _p->empty(); } + + directory_iterator::directory_iterator( const fc::path& p ) + :_p(p){} + + directory_iterator::directory_iterator(){} + directory_iterator::~directory_iterator(){} + + fc::path directory_iterator::operator*()const { return boost::filesystem::path(*(*_p)); } + detail::path_wrapper directory_iterator::operator->() const { return detail::path_wrapper(boost::filesystem::path(*(*_p))); } + directory_iterator& directory_iterator::operator++(int) { (*_p)++; return *this; } + directory_iterator& directory_iterator::operator++() { (*_p)++; return *this; } + + bool operator==( const directory_iterator& r, const directory_iterator& l) { + return *r._p == *l._p; + } + bool operator!=( const directory_iterator& r, const directory_iterator& l) { + return *r._p != *l._p; + } + + + recursive_directory_iterator::recursive_directory_iterator( const fc::path& p ) + :_p(p){} + + recursive_directory_iterator::recursive_directory_iterator(){} + recursive_directory_iterator::~recursive_directory_iterator(){} + + fc::path recursive_directory_iterator::operator*()const { return boost::filesystem::path(*(*_p)); } + recursive_directory_iterator& recursive_directory_iterator::operator++(int) { (*_p)++; return *this; } + recursive_directory_iterator& recursive_directory_iterator::operator++() { (*_p)++; return *this; } + + void recursive_directory_iterator::pop() { (*_p).pop(); } + int recursive_directory_iterator::level() { return _p->level(); } + + bool operator==( const recursive_directory_iterator& r, const recursive_directory_iterator& l) { + return *r._p == *l._p; + } + bool operator!=( const recursive_directory_iterator& r, const recursive_directory_iterator& l) { + return *r._p != *l._p; + } + + + bool exists( const path& p ) { return boost::filesystem::exists(p); } + void create_directories( const path& p ) { + try { + boost::filesystem::create_directories(p); + } catch ( ... ) { + FC_THROW( "Unable to create directories ${path}", ("path", p )("inner", fc::except_str() ) ); + } + } + bool is_directory( const path& p ) { return boost::filesystem::is_directory(p); } + bool is_regular_file( const path& p ) { return boost::filesystem::is_regular_file(p); } + uint64_t file_size( const path& p ) { return boost::filesystem::file_size(p); } + + uint64_t directory_size(const path& p) + { + try { + FC_ASSERT( is_directory( p ) ); + + recursive_directory_iterator end; + uint64_t size = 0; + for( recursive_directory_iterator itr( p ); itr != end; ++itr ) + { + if( is_regular_file( *itr ) ) + size += file_size( *itr ); + } + + return size; + } catch ( ... ) { + FC_THROW( "Unable to calculate size of directory ${path}", ("path", p )("inner", fc::except_str() ) ); + } + } + + void remove_all( const path& p ) { boost::filesystem::remove_all(p); } + void copy( const path& f, const path& t ) { + boost::system::error_code ec; + try { + #if BOOST_VERSION > 107300 + if (exists(t)){ + throw boost::system::system_error(boost::system::errc::make_error_code(boost::system::errc::errc_t::file_exists)); + } + if ( boost::filesystem::is_directory( f ) ) { + boost::filesystem::copy(boost::filesystem::path(f), + boost::filesystem::path(t), + boost::filesystem::copy_options::directories_only, + ec ); + } else { + boost::filesystem::copy(boost::filesystem::path(f), + boost::filesystem::path(t), + boost::filesystem::copy_options::none, + ec ); + } + #else + boost::filesystem::copy( boost::filesystem::path(f), boost::filesystem::path(t), ec ); + #endif + } catch ( boost::system::system_error& e ) { + FC_THROW( "Copy from ${srcfile} to ${dstfile} failed because ${reason}", + ("srcfile",f)("dstfile",t)("reason",e.what() ) ); + } catch ( ... ) { + FC_THROW( "Copy from ${srcfile} to ${dstfile} failed", + ("srcfile",f)("dstfile",t)("inner", fc::except_str() ) ); + } + if( ec ) { + FC_THROW( "Copy from ${srcfile} to ${dstfile} failed because ${reason}, category: ${cat}", + ("srcfile",f)("dstfile",t)("reason", ec.message())("cat", ec.category().name()) ); + } + } + void resize_file( const path& f, size_t t ) + { + try { + boost::filesystem::resize_file( f, t ); + } + catch ( boost::system::system_error& e ) + { + FC_THROW( "Resize file '${f}' to size ${s} failed: ${reason}", + ("f",f)("s",t)( "reason", e.what() ) ); + } + catch ( ... ) + { + FC_THROW( "Resize file '${f}' to size ${s} failed: ${reason}", + ("f",f)("s",t)( "reason", fc::except_str() ) ); + } + } + + // setuid, setgid not implemented. + // translates octal permission like 0755 to S_ stuff defined in sys/stat.h + // no-op on Windows. + void chmod( const path& p, int perm ) + { +#ifndef WIN32 + mode_t actual_perm = + ((perm & 0400) ? S_IRUSR : 0) + | ((perm & 0200) ? S_IWUSR : 0) + | ((perm & 0100) ? S_IXUSR : 0) + + | ((perm & 0040) ? S_IRGRP : 0) + | ((perm & 0020) ? S_IWGRP : 0) + | ((perm & 0010) ? S_IXGRP : 0) + + | ((perm & 0004) ? S_IROTH : 0) + | ((perm & 0002) ? S_IWOTH : 0) + | ((perm & 0001) ? S_IXOTH : 0) + ; + + int result = ::chmod( p.string().c_str(), actual_perm ); + if( result != 0 ) + FC_THROW( "chmod operation failed on ${p}", ("p",p) ); +#endif + return; + } + + void rename( const path& f, const path& t ) { + try { + boost::filesystem::rename( boost::filesystem::path(f), boost::filesystem::path(t) ); + } catch ( boost::system::system_error& ) { + try{ + boost::filesystem::copy( boost::filesystem::path(f), boost::filesystem::path(t) ); + boost::filesystem::remove( boost::filesystem::path(f)); + } catch ( boost::system::system_error& e ) { + FC_THROW( "Rename from ${srcfile} to ${dstfile} failed because ${reason}", + ("srcfile",f)("dstfile",t)("reason",e.what() ) ); + } + } catch ( ... ) { + FC_THROW( "Rename from ${srcfile} to ${dstfile} failed", + ("srcfile",f)("dstfile",t)("inner", fc::except_str() ) ); + } + } + void create_hard_link( const path& f, const path& t ) { + try { + boost::filesystem::create_hard_link( f, t ); + } catch ( ... ) { + FC_THROW( "Unable to create hard link from '${from}' to '${to}'", + ( "from", f )("to",t)("exception", fc::except_str() ) ); + } + } + bool remove( const path& f ) { + try { + return boost::filesystem::remove( f ); + } catch ( ... ) { + FC_THROW( "Unable to remove '${path}'", ( "path", f )("exception", fc::except_str() ) ); + } + } + fc::path canonical( const fc::path& p ) { + try { + return boost::filesystem::canonical(p); + } catch ( ... ) { + FC_THROW( "Unable to resolve path '${path}'", ( "path", p )("exception", fc::except_str() ) ); + } + } + fc::path absolute( const fc::path& p ) { return boost::filesystem::absolute(p); } + path unique_path() { return boost::filesystem::unique_path(); } + path temp_directory_path() { return boost::filesystem::temp_directory_path(); } + + // Return path when appended to a_From will resolve to same as a_To + fc::path make_relative(const fc::path& from, const fc::path& to) { + boost::filesystem::path a_From = boost::filesystem::absolute(from); + boost::filesystem::path a_To = boost::filesystem::absolute(to); + boost::filesystem::path ret; + boost::filesystem::path::const_iterator itrFrom(a_From.begin()), itrTo(a_To.begin()); + // Find common base + for( boost::filesystem::path::const_iterator toEnd( a_To.end() ), fromEnd( a_From.end() ) ; itrFrom != fromEnd && itrTo != toEnd && *itrFrom == *itrTo; ++itrFrom, ++itrTo ); + // Navigate backwards in directory to reach previously found base + for( boost::filesystem::path::const_iterator fromEnd( a_From.end() ); itrFrom != fromEnd; ++itrFrom ) { + if( (*itrFrom) != "." ) + ret /= ".."; + } + // Now navigate down the directory branch + for (; itrTo != a_To.end(); ++itrTo) + ret /= *itrTo; + return ret; + } + + temp_file::temp_file(const fc::path& p, bool create) + : temp_file_base(p / fc::unique_path()) + { + if (fc::exists(*_path)) + { + FC_THROW( "Name collision: ${path}", ("path", _path->string()) ); + } + if (create) + { + std::ofstream ofs(_path->generic_string().c_str(), std::ofstream::out | std::ofstream::binary); + ofs.close(); + } + } + + temp_file::temp_file(temp_file&& other) + : temp_file_base(std::move(other._path)) + { + } + + temp_file& temp_file::operator=(temp_file&& other) + { + if (this != &other) + { + remove(); + _path = std::move(other._path); + } + return *this; + } + + temp_directory::temp_directory(const fc::path& p) + : temp_file_base(p / fc::unique_path()) + { + if (fc::exists(*_path)) + { + FC_THROW( "Name collision: ${path}", ("path", _path->string()) ); + } + fc::create_directories(*_path); + } + + temp_directory::temp_directory(temp_directory&& other) + : temp_file_base(std::move(other._path)) + { + } + + temp_directory& temp_directory::operator=(temp_directory&& other) + { + if (this != &other) + { + remove(); + _path = std::move(other._path); + } + return *this; + } + + const fc::path& temp_file_base::path() const + { + if (!_path) + { + FC_THROW( "Temporary directory has been released." ); + } + return *_path; + } + + void temp_file_base::remove() + { + if (_path) + { + try + { + fc::remove_all(*_path); + } + catch (...) + { + // eat errors on cleanup + } + release(); + } + } + + void temp_file_base::release() + { + _path = std::optional(); + } + + const fc::path& home_path() + { + static fc::path p = []() + { +#ifdef WIN32 + HANDLE access_token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &access_token)) + FC_ASSERT(false, "Unable to open an access token for the current process"); + wchar_t user_profile_dir[MAX_PATH]; + DWORD user_profile_dir_len = sizeof(user_profile_dir); + BOOL success = GetUserProfileDirectoryW(access_token, user_profile_dir, &user_profile_dir_len); + CloseHandle(access_token); + if (!success) + FC_ASSERT(false, "Unable to get the user profile directory"); + return fc::path(std::wstring(user_profile_dir)); +#else + char* home = getenv( "HOME" ); + if( nullptr == home ) + { + struct passwd* pwd = getpwuid(getuid()); + if( pwd ) + { + return fc::path( std::string( pwd->pw_dir ) ); + } + FC_ASSERT( home != nullptr, "The HOME environment variable is not set" ); + } + return fc::path( std::string(home) ); +#endif + }(); + return p; + } + + const fc::path& app_path() + { +#ifdef __APPLE__ + static fc::path appdir = [](){ return home_path() / "Library" / "Application Support"; }(); +#elif defined( WIN32 ) + static fc::path appdir = [](){ + wchar_t app_data_dir[MAX_PATH]; + + if (!SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, app_data_dir))) + FC_ASSERT(false, "Unable to get the current AppData directory"); + return fc::path(std::wstring(app_data_dir)); + }(); +#else + static fc::path appdir = home_path() / ".local/share"; +#endif + return appdir; + } + + const fc::path& current_path() + { + static fc::path appCurrentPath = boost::filesystem::current_path(); + return appCurrentPath; + } + + +#ifdef FC_HAS_SIMPLE_FILE_LOCK + class simple_lock_file::impl + { + public: +#ifdef _WIN32 + HANDLE file_handle; +#else + int file_handle; +#endif + bool is_locked; + path lock_file_path; + + impl(const path& lock_file_path); + ~impl(); + + bool try_lock(); + void unlock(); + }; + + simple_lock_file::impl::impl(const path& lock_file_path) : +#ifdef _WIN32 + file_handle(INVALID_HANDLE_VALUE), +#else + file_handle(-1), +#endif + is_locked(false), + lock_file_path(lock_file_path) + {} + + simple_lock_file::impl::~impl() + { + unlock(); + } + + bool simple_lock_file::impl::try_lock() + { +#ifdef _WIN32 + HANDLE fh = CreateFileA(lock_file_path.to_native_ansi_path().c_str(), + GENERIC_READ | GENERIC_WRITE, + 0, 0, + OPEN_ALWAYS, 0, NULL); + if (fh == INVALID_HANDLE_VALUE) + return false; + is_locked = true; + file_handle = fh; + return true; +#else + int fd = open(lock_file_path.string().c_str(), O_RDWR|O_CREAT, 0644); + if (fd < 0) + return false; + if (flock(fd, LOCK_EX|LOCK_NB) == -1) + { + close(fd); + return false; + } + is_locked = true; + file_handle = fd; + return true; +#endif + } + + void simple_lock_file::impl::unlock() + { +#ifdef WIN32 + CloseHandle(file_handle); + file_handle = INVALID_HANDLE_VALUE; + is_locked = false; +#else + flock(file_handle, LOCK_UN); + close(file_handle); + file_handle = -1; + is_locked = false; +#endif + } + + + simple_lock_file::simple_lock_file(const path& lock_file_path) : + my(new impl(lock_file_path)) + { + } + + simple_lock_file::~simple_lock_file() + { + } + + bool simple_lock_file::try_lock() + { + return my->try_lock(); + } + + void simple_lock_file::unlock() + { + my->unlock(); + } +#endif // FC_HAS_SIMPLE_FILE_LOCK + +} diff --git a/libraries/libfc/src/git_revision.cpp.in b/libraries/libfc/src/git_revision.cpp.in new file mode 100644 index 0000000000..40977b76f7 --- /dev/null +++ b/libraries/libfc/src/git_revision.cpp.in @@ -0,0 +1,11 @@ +#include + +#define FC_GIT_REVISION_SHA "@FC_GIT_REVISION_SHA@" +#define FC_GIT_REVISION_UNIX_TIMESTAMP @FC_GIT_REVISION_UNIX_TIMESTAMP@ + +namespace fc { + +const char* const git_revision_sha = FC_GIT_REVISION_SHA; +const uint32_t git_revision_unix_timestamp = FC_GIT_REVISION_UNIX_TIMESTAMP; + +} // end namespace fc diff --git a/libraries/libfc/src/interprocess/file_mapping.cpp b/libraries/libfc/src/interprocess/file_mapping.cpp new file mode 100644 index 0000000000..91c9297b86 --- /dev/null +++ b/libraries/libfc/src/interprocess/file_mapping.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +namespace fc { + + + file_mapping::file_mapping( const char* file, mode_t m ) : + my(file, m == read_only ? boost::interprocess::read_only : boost::interprocess::read_write ) + {} + + file_mapping::~file_mapping() {} + + + + mapped_region::mapped_region( const file_mapping& fm, mode_t m, uint64_t start, size_t size ) : + my( *fm.my, m == read_only ? boost::interprocess::read_only : boost::interprocess::read_write ,start, size) + {} + + mapped_region::mapped_region( const file_mapping& fm, mode_t m ) : + my( *fm.my, m == read_only ? boost::interprocess::read_only : boost::interprocess::read_write) + {} + + mapped_region::~mapped_region(){} + + void* mapped_region::get_address() const + { + return my->get_address(); + } + + void mapped_region::flush() + { + my->flush(); + } + + size_t mapped_region::get_size() const + { + return my->get_size(); + } +} diff --git a/libraries/libfc/src/interprocess/mmap_struct.cpp b/libraries/libfc/src/interprocess/mmap_struct.cpp new file mode 100644 index 0000000000..99174d7ae9 --- /dev/null +++ b/libraries/libfc/src/interprocess/mmap_struct.cpp @@ -0,0 +1,43 @@ +#include + +#include + +#include +#include +#include + +namespace fc +{ + namespace detail + { + size_t mmap_struct_base::size()const { return _mapped_region->get_size(); } + void mmap_struct_base::flush() + { + _mapped_region->flush(); + } + + void mmap_struct_base::open( const fc::path& file, size_t s, bool create ) + { + if( !fc::exists( file ) || fc::file_size(file) != s ) + { + std::ofstream out( file.generic_string().c_str() ); + char buffer[1024]; + memset( buffer, 0, sizeof(buffer) ); + + size_t bytes_left = s; + while( bytes_left > 0 ) + { + size_t to_write = std::min(bytes_left, sizeof(buffer) ); + out.write( buffer, to_write ); + bytes_left -= to_write; + } + } + + std::string filePath = file.to_native_ansi_path(); + + _file_mapping.reset( new fc::file_mapping( filePath.c_str(), fc::read_write ) ); + _mapped_region.reset( new fc::mapped_region( *_file_mapping, fc::read_write, 0, s ) ); + } + } // namespace fc + +} // namespace fc diff --git a/libraries/libfc/src/interprocess/process.cpp b/libraries/libfc/src/interprocess/process.cpp new file mode 100644 index 0000000000..fcafc5c708 --- /dev/null +++ b/libraries/libfc/src/interprocess/process.cpp @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + namespace bp = boost::process; + namespace io = boost::iostreams; + +fc::path find_executable_in_path( const fc::string name ) { + try { + return fc::string(bp::find_executable_in_path( std::string(name), "" )); + } catch (...) { + const char* p = std::getenv("PATH"); + FC_THROW( "Unable to find executable ${exe} in path.", + ("exe", name) + ("inner", fc::except_str() ) + ("PATH", fc::string(p!=nullptr?p:"") ) ); + } + return fc::path(); +} + + +class process::impl +{ + public: + impl() + :stat( fc::asio::default_io_service() ){} + + ~impl() + { + try + { + if( _in ) + { + _in->close(); + } + if( _exited.valid() && !_exited.ready()) + { + //child->terminate(); + _exited.wait(); + } + } + catch(...) + { + wlog( "caught exception cleaning up process: ${except_str}", ("except_str",fc::except_str()) ); + } + } + + std::shared_ptr child; + + std::shared_ptr _inp; + std::shared_ptr _outp; + std::shared_ptr _errp; + + buffered_istream_ptr _out; + buffered_istream_ptr _err; + buffered_ostream_ptr _in; + + bp::status stat; + bp::context pctx; + + fc::future _exited; +}; + +process::process() +:my( new process::impl() ){} +process::~process(){} + +iprocess& process::exec( const fc::path& exe, + std::vector args, + const fc::path& work_dir, int opt ) +{ + + my->pctx.work_dir = work_dir.string(); + my->pctx.suppress_console = (opt & suppress_console) != 0; + + if( opt&open_stdout) + my->pctx.streams[boost::process::stdout_id] = bp::behavior::async_pipe(); + else + my->pctx.streams[boost::process::stdout_id] = bp::behavior::null(); + + + if( opt& open_stderr ) + my->pctx.streams[boost::process::stderr_id] = bp::behavior::async_pipe(); + else + my->pctx.streams[boost::process::stderr_id] = bp::behavior::null(); + + if( opt& open_stdin ) + my->pctx.streams[boost::process::stdin_id] = bp::behavior::async_pipe(); + else + my->pctx.streams[boost::process::stdin_id] = bp::behavior::close(); + + /* + std::vector a; + a.reserve(size_t(args.size())); + for( uint32_t i = 0; i < args.size(); ++i ) { + a.push_back( fc::move(args[i]) ); + } + */ + my->child.reset( new bp::child( bp::create_child( exe.string(), fc::move(args), my->pctx ) ) ); + + if( opt & open_stdout ) { + bp::handle outh = my->child->get_handle( bp::stdout_id ); + my->_outp.reset( new bp::pipe( fc::asio::default_io_service(), outh.release() ) ); + } + if( opt & open_stderr ) { + bp::handle errh = my->child->get_handle( bp::stderr_id ); + my->_errp.reset( new bp::pipe( fc::asio::default_io_service(), errh.release() ) ); + } + if( opt & open_stdin ) { + bp::handle inh = my->child->get_handle( bp::stdin_id ); + my->_inp.reset( new bp::pipe( fc::asio::default_io_service(), inh.release() ) ); + } + + + promise::ptr p(new promise("process")); + my->stat.async_wait( my->child->get_id(), [=]( const boost::system::error_code& ec, int exit_code ) + { + //slog( "process::result %d", exit_code ); + if( !ec ) { + #ifdef BOOST_POSIX_API + if( WIFEXITED(exit_code) ) p->set_value( WEXITSTATUS(exit_code) ); + else + { + p->set_exception( + fc::exception_ptr( new fc::exception( + FC_LOG_MESSAGE( error, "process exited with: ${message} ", + ("message", strsignal(WTERMSIG(exit_code))) ) ) ) ); + } + #else + p->set_value(exit_code); + #endif + } + else + { + p->set_exception( + fc::exception_ptr( new fc::exception( + FC_LOG_MESSAGE( error, "process exited with: ${message} ", + ("message", boost::system::system_error(ec).what())) ) ) ); + } + }); + if( opt & open_stdin ) + my->_in = std::make_shared(std::make_shared>(my->_inp)); + if( opt & open_stdout ) + my->_out = std::make_shared(std::make_shared>(my->_outp)); + if( opt & open_stderr ) + my->_err = std::make_shared(std::make_shared>(my->_errp)); + my->_exited = p; + return *this; +} + +/** + * Forcefully kills the process. + */ +void process::kill() { + my->child->terminate(); +} + +/** + * @brief returns a stream that writes to the process' stdin + */ +fc::buffered_ostream_ptr process::in_stream() { + return my->_in; +} + +/** + * @brief returns a stream that reads from the process' stdout + */ +fc::buffered_istream_ptr process::out_stream() { + return my->_out; +} +/** + * @brief returns a stream that reads from the process' stderr + */ +fc::buffered_istream_ptr process::err_stream() { + return my->_err; +} + +int process::result(const microseconds& timeout /* = microseconds::maximum() */) +{ + return my->_exited.wait(timeout); +} + +} diff --git a/libraries/libfc/src/io/buffered_iostream.cpp b/libraries/libfc/src/io/buffered_iostream.cpp new file mode 100644 index 0000000000..0760621293 --- /dev/null +++ b/libraries/libfc/src/io/buffered_iostream.cpp @@ -0,0 +1,209 @@ +#include +#include +#include +#include + +#include + +namespace fc +{ + namespace detail + { + class buffered_istream_impl + { + public: + buffered_istream_impl( istream_ptr is ) : + _istr(fc::move(is)) +#ifndef NDEBUG + ,_shared_read_buffer_in_use(false) +#endif + {} + + istream_ptr _istr; + boost::asio::streambuf _rdbuf; + std::shared_ptr _shared_read_buffer; +#ifndef NDEBUG + bool _shared_read_buffer_in_use; +#endif + }; + static const size_t minimum_read_size = 1024; + } + + buffered_istream::buffered_istream( istream_ptr is ) + :my( new detail::buffered_istream_impl( fc::move(is) ) ) + { + FC_ASSERT( my->_istr != nullptr, " this shouldn't be null" ); + } + + buffered_istream::buffered_istream( buffered_istream&& o ) + :my( fc::move(o.my) ){} + + buffered_istream& buffered_istream::operator=( buffered_istream&& i ) + { + my = fc::move(i.my); + return *this; + } + + buffered_istream::~buffered_istream(){} + + size_t buffered_istream::readsome( char* buf, size_t len ) + { + size_t bytes_from_rdbuf = static_cast(my->_rdbuf.sgetn(buf, len)); + if (bytes_from_rdbuf) + return bytes_from_rdbuf; + + + if( len > detail::minimum_read_size ) + return my->_istr->readsome(buf,len); + + char tmp[detail::minimum_read_size]; + size_t bytes_read = my->_istr->readsome( tmp, detail::minimum_read_size ); + + size_t bytes_to_deliver_immediately = std::min(bytes_read,len); + + memcpy( buf, tmp, bytes_to_deliver_immediately ); + + + if( bytes_read > len ) + { + my->_rdbuf.sputn( tmp + len, bytes_read - len ); + } + + return bytes_to_deliver_immediately; + } + + size_t buffered_istream::readsome( const std::shared_ptr& buf, size_t len, size_t offset ) + { + size_t bytes_from_rdbuf = static_cast(my->_rdbuf.sgetn(buf.get() + offset, len)); + if (bytes_from_rdbuf) + return bytes_from_rdbuf; + + + if( len > detail::minimum_read_size ) + return my->_istr->readsome(buf.get() + offset, len); + +#ifndef NDEBUG + // This code was written with the assumption that you'd only be making one call to readsome + // at a time so it reuses _shared_read_buffer. If you really need to make concurrent calls to + // readsome(), you'll need to prevent reusing _shared_read_buffer here + struct check_buffer_in_use { + bool& _buffer_in_use; + check_buffer_in_use(bool& buffer_in_use) : _buffer_in_use(buffer_in_use) { assert(!_buffer_in_use); _buffer_in_use = true; } + ~check_buffer_in_use() { assert(_buffer_in_use); _buffer_in_use = false; } + } buffer_in_use_checker(my->_shared_read_buffer_in_use); +#endif + + if (!my->_shared_read_buffer) + my->_shared_read_buffer.reset(new char[detail::minimum_read_size], [](char* p){ delete[] p; }); + size_t bytes_read = my->_istr->readsome( my->_shared_read_buffer, detail::minimum_read_size, 0 ); + + size_t bytes_to_deliver_immediately = std::min(bytes_read,len); + + memcpy( buf.get() + offset, my->_shared_read_buffer.get(), bytes_to_deliver_immediately ); + + if( bytes_read > len ) + { + my->_rdbuf.sputn( my->_shared_read_buffer.get() + len, bytes_read - len ); + } + + return bytes_to_deliver_immediately; + } + + char buffered_istream::peek()const + { + if( my->_rdbuf.size() ) + { + return my->_rdbuf.sgetc(); + } + + char tmp[detail::minimum_read_size]; + size_t bytes_read = my->_istr->readsome( tmp, detail::minimum_read_size ); + my->_rdbuf.sputn( tmp, bytes_read ); + + if( my->_rdbuf.size() ) + { + return my->_rdbuf.sgetc(); + } + FC_THROW_EXCEPTION( assert_exception, + "at least one byte should be available, or eof should have been thrown" ); + } + + + namespace detail + { + class buffered_ostream_impl + { + public: + buffered_ostream_impl( ostream_ptr os ) : + _ostr(fc::move(os)) +#ifndef NDEBUG + ,_shared_write_buffer_in_use(false) +#endif + {} + + ostream_ptr _ostr; + boost::asio::streambuf _rdbuf; + std::shared_ptr _shared_write_buffer; +#ifndef NDEBUG + bool _shared_write_buffer_in_use; +#endif + }; + } + + buffered_ostream::buffered_ostream( ostream_ptr os, size_t bufsize ) + :my( new detail::buffered_ostream_impl( fc::move(os) ) ) + { + } + + buffered_ostream::buffered_ostream( buffered_ostream&& o ) + :my( fc::move(o.my) ){} + + buffered_ostream& buffered_ostream::operator=( buffered_ostream&& i ) + { + my = fc::move(i.my); + return *this; + } + + buffered_ostream::~buffered_ostream(){} + + size_t buffered_ostream::writesome( const char* buf, size_t len ) + { + size_t written = static_cast(my->_rdbuf.sputn( buf, len )); + if( written < len ) { flush(); } + return written + static_cast(my->_rdbuf.sputn( buf+written, len-written )); + } + + size_t buffered_ostream::writesome( const std::shared_ptr& buf, size_t len, size_t offset ) + { + return writesome(buf.get() + offset, len); + } + + void buffered_ostream::flush() + { +#ifndef NDEBUG + // This code was written with the assumption that you'd only be making one call to flush + // at a time so it reuses _shared_write_buffer. If you really need to make concurrent calls to + // flush(), you'll need to prevent reusing _shared_write_buffer here + struct check_buffer_in_use { + bool& _buffer_in_use; + check_buffer_in_use(bool& buffer_in_use) : _buffer_in_use(buffer_in_use) { assert(!_buffer_in_use); _buffer_in_use = true; } + ~check_buffer_in_use() { assert(_buffer_in_use); _buffer_in_use = false; } + } buffer_in_use_checker(my->_shared_write_buffer_in_use); +#endif + const size_t write_buffer_size = 2048; + if (!my->_shared_write_buffer) + my->_shared_write_buffer.reset(new char[write_buffer_size], [](char* p){ delete[] p; }); + + while( size_t bytes_from_rdbuf = static_cast(my->_rdbuf.sgetn(my->_shared_write_buffer.get(), write_buffer_size)) ) + my->_ostr->write( my->_shared_write_buffer, bytes_from_rdbuf ); + my->_ostr->flush(); + } + + void buffered_ostream::close() + { + flush(); + my->_ostr->close(); + } + + +} diff --git a/libraries/libfc/src/io/console.cpp b/libraries/libfc/src/io/console.cpp new file mode 100644 index 0000000000..c0cda1809a --- /dev/null +++ b/libraries/libfc/src/io/console.cpp @@ -0,0 +1,46 @@ +#include +#include + +namespace fc { + +#ifdef WIN32 +#include + +void set_console_echo( bool enable_echo ) +{ + auto stdin_handle = GetStdHandle( STD_INPUT_HANDLE ); + DWORD mode = 0; + GetConsoleMode( stdin_handle, &mode ); + if( enable_echo ) + { + SetConsoleMode( stdin_handle, mode | ENABLE_ECHO_INPUT ); + } + else + { + SetConsoleMode( stdin_handle, mode & (~ENABLE_ECHO_INPUT) ); + } +} + +#else // NOT WIN32 +#include +#include + +void set_console_echo( bool enable_echo ) +{ + termios oldt; + tcgetattr(STDIN_FILENO, &oldt); + termios newt = oldt; + if( enable_echo ) + { + newt.c_lflag |= ECHO; + } + else + { + newt.c_lflag &= ~ECHO; + } + tcsetattr(STDIN_FILENO, TCSANOW, &newt); +} + +#endif // WIN32 + +} // namespace fc diff --git a/libraries/libfc/src/io/datastream.cpp b/libraries/libfc/src/io/datastream.cpp new file mode 100644 index 0000000000..cfe1104e4e --- /dev/null +++ b/libraries/libfc/src/io/datastream.cpp @@ -0,0 +1,7 @@ +#include +#include + +NO_RETURN void fc::detail::throw_datastream_range_error(char const* method, size_t len, int64_t over) +{ + FC_THROW_EXCEPTION( out_of_range_exception, "${method} datastream of length ${len} over by ${over}", ("method",fc::string(method))("len",len)("over",over) ); +} diff --git a/libraries/libfc/src/io/fstream.cpp b/libraries/libfc/src/io/fstream.cpp new file mode 100644 index 0000000000..222a93f4b1 --- /dev/null +++ b/libraries/libfc/src/io/fstream.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace fc { + + void read_file_contents( const fc::path& filename, std::string& result ) + { + const boost::filesystem::path& bfp = filename; + boost::filesystem::ifstream f( bfp, std::ios::in | std::ios::binary ); + FC_ASSERT(f, "Failed to open ${filename}", ("filename", filename)); + // don't use fc::stringstream here as we need something with override for << rdbuf() + std::stringstream ss; + ss << f.rdbuf(); + FC_ASSERT(f, "Failed reading ${filename}", ("filename", filename)); + result = ss.str(); + } + +} // namespace fc diff --git a/libraries/libfc/src/io/json.cpp b/libraries/libfc/src/io/json.cpp new file mode 100644 index 0000000000..01d57b084c --- /dev/null +++ b/libraries/libfc/src/io/json.cpp @@ -0,0 +1,861 @@ +#include +//#include +//#include +#include +//#include +#include +#include +#include +#include + +#include + +namespace fc +{ + // forward declarations of provided functions + template variant variant_from_stream( T& in, uint32_t max_depth ); + template char parseEscape( T& in ); + template std::string stringFromStream( T& in ); + template bool skip_white_space( T& in ); + template std::string stringFromToken( T& in ); + template variant_object objectFromStream( T& in, uint32_t max_depth ); + template variants arrayFromStream( T& in, uint32_t max_depth ); + template variant number_from_stream( T& in ); + template variant token_from_stream( T& in ); + template void to_stream( T& os, const variants& a, const json::yield_function_t& yield, json::output_formatting format ); + template void to_stream( T& os, const variant_object& o, const json::yield_function_t& yield, json::output_formatting format ); + template void to_stream( T& os, const variant& v, const json::yield_function_t& yield, json::output_formatting format ); + std::string pretty_print( const std::string& v, uint8_t indent ); +} + +#include + +namespace fc +{ + template + char parseEscape( T& in ) + { + if( in.peek() == '\\' ) + { + try { + in.get(); + switch( in.peek() ) + { + case 't': + in.get(); + return '\t'; + case 'n': + in.get(); + return '\n'; + case 'r': + in.get(); + return '\r'; + case '\\': + in.get(); + return '\\'; + default: + return in.get(); + } + } FC_RETHROW_EXCEPTIONS( info, "Stream ended with '\\'" ); + } + FC_THROW_EXCEPTION( parse_error_exception, "Expected '\\'" ); + } + + template + bool skip_white_space( T& in ) + { + bool skipped = false; + while( true ) + { + switch( in.peek() ) + { + case ' ': + case '\t': + case '\n': + case '\r': + skipped = true; + in.get(); + break; + case '\0': + FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); + break; + default: + return skipped; + } + } + } + + template + std::string stringFromStream( T& in ) + { + std::stringstream token; + try + { + char c = in.peek(); + + if( c != '"' ) + FC_THROW_EXCEPTION( parse_error_exception, + "Expected '\"' but read '${char}'", + ("char", string(&c, (&c) + 1) ) ); + in.get(); + while( !in.eof() ) + { + switch( c = in.peek() ) + { + case '\\': + token << parseEscape( in ); + break; + case 0x04: + FC_THROW_EXCEPTION( parse_error_exception, "EOF before closing '\"' in string '${token}'", + ("token", token.str() ) ); + case '"': + in.get(); + return token.str(); + default: + token << c; + in.get(); + } + } + FC_THROW_EXCEPTION( parse_error_exception, "EOF before closing '\"' in string '${token}'", + ("token", token.str() ) ); + } FC_RETHROW_EXCEPTIONS( warn, "while parsing token '${token}'", + ("token", token.str() ) ); + } + template + std::string stringFromToken( T& in ) + { + std::stringstream token; + try + { + char c = in.peek(); + + while( !in.eof() ) + { + switch( c = in.peek() ) + { + case '\\': + token << parseEscape( in ); + break; + case '\t': + case ' ': + case '\n': + in.get(); + return token.str(); + case '\0': + FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); + default: + if( isalnum( c ) || c == '_' || c == '-' || c == '.' || c == ':' || c == '/' ) + { + token << c; + in.get(); + } + else return token.str(); + } + } + return token.str(); + } + catch( const fc::eof_exception& eof ) + { + return token.str(); + } + catch (const std::ios_base::failure&) + { + return token.str(); + } + + FC_RETHROW_EXCEPTIONS( warn, "while parsing token '${token}'", + ("token", token.str() ) ); + } + + template + variant_object objectFromStream( T& in, uint32_t max_depth ) + { + mutable_variant_object obj; + try + { + char c = in.peek(); + if( c != '{' ) + FC_THROW_EXCEPTION( parse_error_exception, + "Expected '{', but read '${char}'", + ("char",string(&c, &c + 1)) ); + in.get(); + while( in.peek() != '}' ) + { + if( in.peek() == ',' ) + { + in.get(); + continue; + } + if( skip_white_space(in) ) continue; + string key = stringFromStream( in ); + skip_white_space(in); + if( in.peek() != ':' ) + { + FC_THROW_EXCEPTION( parse_error_exception, "Expected ':' after key \"${key}\"", + ("key", key) ); + } + in.get(); + auto val = variant_from_stream( in, max_depth - 1 ); + + obj(std::move(key),std::move(val)); + //skip_white_space(in); + } + if( in.peek() == '}' ) + { + in.get(); + return obj; + } + FC_THROW_EXCEPTION( parse_error_exception, "Expected '}' after ${variant}", ("variant", obj ) ); + } + catch( const fc::eof_exception& e ) + { + FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF: ${e}", ("e", e.to_detail_string() ) ); + } + catch( const std::ios_base::failure& e ) + { + FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF: ${e}", ("e", e.what() ) ); + } FC_RETHROW_EXCEPTIONS( warn, "Error parsing object" ); + } + + template + variants arrayFromStream( T& in, uint32_t max_depth ) + { + variants ar; + try + { + if( in.peek() != '[' ) + FC_THROW_EXCEPTION( parse_error_exception, "Expected '['" ); + in.get(); + skip_white_space(in); + + while( in.peek() != ']' ) + { + if( in.peek() == ',' ) + { + in.get(); + continue; + } + if( skip_white_space(in) ) continue; + ar.push_back( variant_from_stream( in, max_depth - 1) ); + skip_white_space(in); + } + if( in.peek() != ']' ) + FC_THROW_EXCEPTION( parse_error_exception, "Expected ']' after parsing ${variant}", + ("variant", ar) ); + + in.get(); + } FC_RETHROW_EXCEPTIONS( warn, "Attempting to parse array ${array}", + ("array", ar ) ); + return ar; + } + + template + variant number_from_stream( T& in ) + { + std::stringstream ss; + + bool dot = false; + bool neg = false; + if( in.peek() == '-') + { + neg = true; + ss.put( in.get() ); + } + bool done = false; + + try + { + while( !done ) + { + char c = in.peek(); + switch( c ) + { + case '.': + if (dot) + FC_THROW_EXCEPTION(parse_error_exception, "Can't parse a number with two decimal places"); + dot = true; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ss.put( in.get() ); + break; + case '\0': + FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); + default: + if( isalnum( c ) ) + { + return ss.str() + stringFromToken( in ); + } + done = true; + break; + } + } + } + catch (fc::eof_exception&) + { + } + catch (const std::ios_base::failure&) + { + } + std::string str = ss.str(); + if (str == "-." || str == "." || str == "-") // check the obviously wrong things we could have encountered + FC_THROW_EXCEPTION(parse_error_exception, "Can't parse token \"${token}\" as a JSON numeric constant", ("token", str)); + if( dot ) + return parser_type == json::parse_type::legacy_parser_with_string_doubles ? variant(str) : variant(to_double(str)); + if( neg ) + return to_int64(str); + return to_uint64(str); + } + template + variant token_from_stream( T& in ) + { + std::stringstream ss; + ss.exceptions( std::ifstream::badbit ); + bool received_eof = false; + bool done = false; + + try + { + char c; + while((c = in.peek()) && !done) + { + switch( c ) + { + case 'n': + case 'u': + case 'l': + case 't': + case 'r': + case 'e': + case 'f': + case 'a': + case 's': + ss.put( in.get() ); + break; + default: + done = true; + break; + } + } + } + catch (fc::eof_exception&) + { + received_eof = true; + } + catch (const std::ios_base::failure&) + { + received_eof = true; + } + + // we can get here either by processing a delimiter as in "null," + // an EOF like "null", or an invalid token like "nullZ" + std::string str = ss.str(); + if( str == "null" ) + return variant(); + if( str == "true" ) + return true; + if( str == "false" ) + return false; + else + { + if (received_eof) + { + if (str.empty()) + FC_THROW_EXCEPTION( parse_error_exception, "Unexpected EOF" ); + else + return str; + } + else + { + // if we've reached this point, we've either seen a partial + // token ("tru") or something our simple parser couldn't + // make out ("falfe") + // A strict JSON parser would signal this as an error, but we + // will just treat the malformed token as an un-quoted string. + return str + stringFromToken(in);; + } + } + } + + + template + variant variant_from_stream( T& in, uint32_t max_depth ) + { + if( max_depth == 0 ) + FC_THROW_EXCEPTION( parse_error_exception, "Too many nested items in JSON input!" ); + skip_white_space(in); + variant var; + while( 1 ) + { + signed char c = in.peek(); + switch( c ) + { + case ' ': + case '\t': + case '\n': + case '\r': + in.get(); + continue; + case '"': + return stringFromStream( in ); + case '{': + return objectFromStream( in, max_depth - 1 ); + case '[': + return arrayFromStream( in, max_depth - 1 ); + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return number_from_stream( in ); + // null, true, false, or 'warning' / string + case 'n': + case 't': + case 'f': + return token_from_stream( in ); + case 0x04: // ^D end of transmission + case EOF: + case '\0': + FC_THROW_EXCEPTION( eof_exception, "unexpected end of file" ); + default: + FC_THROW_EXCEPTION( parse_error_exception, "Unexpected char '${c}' in \"${s}\"", + ("c", c)("s", stringFromToken(in)) ); + } + } + return variant(); + } + + variant json::from_string( const std::string& utf8_str, const json::parse_type ptype, const uint32_t max_depth ) + { try { + std::stringstream in( utf8_str ); + //in.exceptions( std::ifstream::eofbit ); + switch( ptype ) + { + case parse_type::legacy_parser: + return variant_from_stream( in, max_depth ); + case parse_type::legacy_parser_with_string_doubles: + return variant_from_stream( in, max_depth ); + case parse_type::strict_parser: + return json_relaxed::variant_from_stream( in, max_depth ); + case parse_type::relaxed_parser: + return json_relaxed::variant_from_stream( in, max_depth ); + default: + FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", static_cast(ptype)) ); + } + } FC_RETHROW_EXCEPTIONS( warn, "", ("str",utf8_str) ) } + + variants json::variants_from_string( const std::string& utf8_str, const json::parse_type ptype, const uint32_t max_depth ) + { try { + variants result; + std::stringstream in( utf8_str ); + //in.exceptions( std::ifstream::eofbit ); + try { + while( true ) + { + // result.push_back( variant_from_stream( in )); + result.push_back(json_relaxed::variant_from_stream( in, max_depth )); + } + } catch ( const fc::eof_exception& ){} + return result; + } FC_RETHROW_EXCEPTIONS( warn, "", ("str",utf8_str) ) } + /* + void toUTF8( const char str, std::ostream& os ) + { + // validate str == valid utf8 + utf8::replace_invalid( &str, &str + 1, std::ostream_iterator(os) ); + } + + void toUTF8( const wchar_t c, std::ostream& os ) + { + utf8::utf16to8( &c, (&c)+1, std::ostream_iterator(os) ); + } + */ + + /** + * Convert '\t', '\r', '\n', '\\' and '"' to "\t\r\n\\\"" if escape_control_chars == true + * Convert all other < 32 & 127 ascii to escaped unicode "\u00xx" + * Removes invalid utf8 characters + * Escapes Control sequence Introducer 0x9b to \u009b + * All other characters unmolested. + */ + std::string escape_string( const std::string_view& str, const json::yield_function_t& yield, bool escape_control_chars ) + { + string r; + const auto init_size = str.size(); + r.reserve( init_size + 13 ); // allow for a few escapes + size_t i = 0; + for( auto itr = str.begin(); itr != str.end(); ++i,++itr ) + { + if( i % json::escape_string_yield_check_count == 0 ) yield( init_size + r.size() ); + switch( *itr ) + { + case '\x00': r += "\\u0000"; break; + case '\x01': r += "\\u0001"; break; + case '\x02': r += "\\u0002"; break; + case '\x03': r += "\\u0003"; break; + case '\x04': r += "\\u0004"; break; + case '\x05': r += "\\u0005"; break; + case '\x06': r += "\\u0006"; break; + case '\x07': r += "\\u0007"; break; // \a is not valid JSON + case '\x08': r += "\\u0008"; break; // \b + // case '\x09': r += "\\u0009"; break; // \t + // case '\x0a': r += "\\u000a"; break; // \n + case '\x0b': r += "\\u000b"; break; + case '\x0c': r += "\\u000c"; break; // \f + // case '\x0d': r += "\\u000d"; break; // \r + case '\x0e': r += "\\u000e"; break; + case '\x0f': r += "\\u000f"; break; + case '\x10': r += "\\u0010"; break; + case '\x11': r += "\\u0011"; break; + case '\x12': r += "\\u0012"; break; + case '\x13': r += "\\u0013"; break; + case '\x14': r += "\\u0014"; break; + case '\x15': r += "\\u0015"; break; + case '\x16': r += "\\u0016"; break; + case '\x17': r += "\\u0017"; break; + case '\x18': r += "\\u0018"; break; + case '\x19': r += "\\u0019"; break; + case '\x1a': r += "\\u001a"; break; + case '\x1b': r += "\\u001b"; break; + case '\x1c': r += "\\u001c"; break; + case '\x1d': r += "\\u001d"; break; + case '\x1e': r += "\\u001e"; break; + case '\x1f': r += "\\u001f"; break; + + case '\x7f': r += "\\u007f"; break; + + // if escape_control_chars=true these fall-through to default + case '\t': // \x09 + if( escape_control_chars ) { + r += "\\t"; + break; + } + case '\n': // \x0a + if( escape_control_chars ) { + r += "\\n"; + break; + } + case '\r': // \x0d + if( escape_control_chars ) { + r += "\\r"; + break; + } + case '\\': + if( escape_control_chars ) { + r += "\\\\"; + break; + } + case '\"': + if( escape_control_chars ) { + r += "\\\""; + break; + } + default: + r += *itr; + } + } + + return is_valid_utf8( r ) ? r : prune_invalid_utf8( r ); + } + + template + void to_stream( T& os, const variants& a, const json::yield_function_t& yield, const json::output_formatting format ) + { + yield(os.tellp()); + os << '['; + auto itr = a.begin(); + + while( itr != a.end() ) + { + to_stream( os, *itr, yield, format ); + ++itr; + if( itr != a.end() ) + os << ','; + } + os << ']'; + } + + template + void to_stream( T& os, const variant_object& o, const json::yield_function_t& yield, const json::output_formatting format ) + { + yield(os.tellp()); + os << '{'; + auto itr = o.begin(); + + while( itr != o.end() ) + { + os << '"' << escape_string( itr->key(), yield ) << '"'; + os << ':'; + to_stream( os, itr->value(), yield, format ); + ++itr; + if( itr != o.end() ) + os << ','; + } + os << '}'; + } + + template + void to_stream( T& os, const variant& v, const json::yield_function_t& yield, const json::output_formatting format ) + { + yield(os.tellp()); + switch( v.get_type() ) + { + case variant::null_type: + os << "null"; + return; + case variant::int64_type: + { + int64_t i = v.as_int64(); + if( format == json::output_formatting::stringify_large_ints_and_doubles && + i > 0xffffffff ) + os << '"'< 0xffffffff ) + os << '"'<( p, ifstream::binary ); + //auto tmp = std::make_shared( p.generic_string().c_str(), std::ios::binary ); + //buffered_istream bi( tmp ); + boost::filesystem::ifstream bi( p, std::ios::binary ); + switch( ptype ) + { + case json::parse_type::legacy_parser: + return variant_from_stream( bi, max_depth ); + case json::parse_type::legacy_parser_with_string_doubles: + return variant_from_stream( bi, max_depth ); + case json::parse_type::strict_parser: + return json_relaxed::variant_from_stream( bi, max_depth ); + case json::parse_type::relaxed_parser: + return json_relaxed::variant_from_stream( bi, max_depth ); + default: + FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", static_cast(ptype)) ); + } + } + /* + variant json::from_stream( buffered_istream& in, parse_type ptype, uint32_t max_depth ) + { + switch( ptype ) + { + case legacy_parser: + return variant_from_stream( in, max_depth ); + case legacy_parser_with_string_doubles: + return variant_from_stream( in, max_depth ); + case strict_parser: + return json_relaxed::variant_from_stream( in, max_depth ); + case relaxed_parser: + return json_relaxed::variant_from_stream( in, max_depth ); + default: + FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", ptype) ); + } + } + */ + + bool json::is_valid( const std::string& utf8_str, const json::parse_type ptype, const uint32_t max_depth ) + { + if( utf8_str.size() == 0 ) return false; + std::stringstream in( utf8_str ); + switch( ptype ) + { + case json::parse_type::legacy_parser: + variant_from_stream( in, max_depth ); + break; + case json::parse_type::legacy_parser_with_string_doubles: + variant_from_stream( in, max_depth ); + break; + case json::parse_type::strict_parser: + json_relaxed::variant_from_stream( in, max_depth ); + break; + case json::parse_type::relaxed_parser: + json_relaxed::variant_from_stream( in, max_depth ); + break; + default: + FC_ASSERT( false, "Unknown JSON parser type {ptype}", ("ptype", static_cast(ptype)) ); + } + try { in.peek(); } catch ( const eof_exception& e ) { return true; } + return false; + } + +} // fc diff --git a/libraries/libfc/src/io/sstream.cpp b/libraries/libfc/src/io/sstream.cpp new file mode 100644 index 0000000000..9c4739e3bb --- /dev/null +++ b/libraries/libfc/src/io/sstream.cpp @@ -0,0 +1,115 @@ +#include +#include +#include +#include +#include + +namespace fc { + class stringstream::impl { + public: + impl( fc::string&s ) + :ss( s ) + { ss.exceptions( std::stringstream::badbit ); } + + impl( const fc::string&s ) + :ss( s ) + { ss.exceptions( std::stringstream::badbit ); } + + impl(){ss.exceptions( std::stringstream::badbit ); } + + std::stringstream ss; + }; + + stringstream::stringstream( fc::string& s ) + :my(s) { + } + stringstream::stringstream( const fc::string& s ) + :my(s) { + } + stringstream::stringstream(){} + stringstream::~stringstream(){} + + + fc::string stringstream::str(){ + return my->ss.str();//.c_str();//*reinterpret_cast(&st); + } + + void stringstream::str(const fc::string& s) { + my->ss.str(s); + } + + void stringstream::clear() { + my->ss.clear(); + } + + + bool stringstream::eof()const { + return my->ss.eof(); + } + size_t stringstream::writesome( const char* buf, size_t len ) { + my->ss.write(buf,len); + if( my->ss.eof() ) + { + FC_THROW_EXCEPTION( eof_exception, "stringstream" ); + } + return len; + } + size_t stringstream::writesome( const std::shared_ptr& buf, size_t len, size_t offset ) + { + return writesome(buf.get() + offset, len); + } + + size_t stringstream::readsome( char* buf, size_t len ) { + size_t r = static_cast(my->ss.readsome(buf,len)); + if( my->ss.eof() || r == 0 ) + { + FC_THROW_EXCEPTION( eof_exception, "stringstream" ); + } + return r; + } + size_t stringstream::readsome( const std::shared_ptr& buf, size_t len, size_t offset ) + { + return readsome(buf.get() + offset, len); + } + + + void stringstream::close(){ my->ss.flush(); }; + void stringstream::flush(){ my->ss.flush(); }; + + /* + istream& stringstream::read( char* buf, size_t len ) { + my->ss.read(buf,len); + return *this; + } + istream& stringstream::read( int64_t& v ) { my->ss >> v; return *this; } + istream& stringstream::read( uint64_t& v ) { my->ss >> v; return *this; } + istream& stringstream::read( int32_t& v ) { my->ss >> v; return *this; } + istream& stringstream::read( uint32_t& v ) { my->ss >> v; return *this; } + istream& stringstream::read( int16_t& v ) { my->ss >> v; return *this; } + istream& stringstream::read( uint16_t& v ) { my->ss >> v; return *this; } + istream& stringstream::read( int8_t& v ) { my->ss >> v; return *this; } + istream& stringstream::read( uint8_t& v ) { my->ss >> v; return *this; } + istream& stringstream::read( float& v ) { my->ss >> v; return *this; } + istream& stringstream::read( double& v ) { my->ss >> v; return *this; } + istream& stringstream::read( bool& v ) { my->ss >> v; return *this; } + istream& stringstream::read( char& v ) { my->ss >> v; return *this; } + istream& stringstream::read( fc::string& v ) { my->ss >> *reinterpret_cast(&v); return *this; } + + ostream& stringstream::write( const fc::string& s) { + my->ss.write( s.c_str(), s.size() ); + return *this; + } + */ + + char stringstream::peek() + { + char c = my->ss.peek(); + if( my->ss.eof() ) + { + FC_THROW_EXCEPTION( eof_exception, "stringstream" ); + } + return c; + } +} + + diff --git a/libraries/libfc/src/io/varint.cpp b/libraries/libfc/src/io/varint.cpp new file mode 100644 index 0000000000..a3eaac22aa --- /dev/null +++ b/libraries/libfc/src/io/varint.cpp @@ -0,0 +1,10 @@ +#include +#include + +namespace fc +{ +void to_variant( const signed_int& var, variant& vo ) { vo = var.value; } +void from_variant( const variant& var, signed_int& vo ) { vo.value = static_cast(var.as_int64()); } +void to_variant( const unsigned_int& var, variant& vo ) { vo = var.value; } +void from_variant( const variant& var, unsigned_int& vo ) { vo.value = static_cast(var.as_uint64()); } +} diff --git a/libraries/libfc/src/log/appender.cpp b/libraries/libfc/src/log/appender.cpp new file mode 100644 index 0000000000..b042442720 --- /dev/null +++ b/libraries/libfc/src/log/appender.cpp @@ -0,0 +1,15 @@ +#include +#include +#include +#include +#include + + +namespace fc { + + static bool reg_console_appender = log_config::register_appender( "console" ); + static bool reg_gelf_appender = log_config::register_appender( "gelf" ); + static bool reg_dmlog_appender = log_config::register_appender( "dmlog" ); + + +} // namespace fc diff --git a/libraries/libfc/src/log/console_appender.cpp b/libraries/libfc/src/log/console_appender.cpp new file mode 100644 index 0000000000..45992f9dbd --- /dev/null +++ b/libraries/libfc/src/log/console_appender.cpp @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#endif +#define COLOR_CONSOLE 1 +#include "console_defines.h" +#include +#include +#include +#include + + +namespace fc { + + class console_appender::impl { + public: + config cfg; + color::type lc[log_level::off+1]; + bool use_syslog_header{getenv("JOURNAL_STREAM") != nullptr}; +#ifdef WIN32 + HANDLE console_handle; +#endif + }; + + console_appender::console_appender( const variant& args ) + :my(new impl) + { + configure( args.as() ); + } + + console_appender::console_appender( const config& cfg ) + :my(new impl) + { + configure( cfg ); + } + console_appender::console_appender() + :my(new impl){} + + + void console_appender::configure( const config& console_appender_config ) + { try { +#ifdef WIN32 + my->console_handle = INVALID_HANDLE_VALUE; +#endif + my->cfg = console_appender_config; +#ifdef WIN32 + if (my->cfg.stream == stream::std_error) + my->console_handle = GetStdHandle(STD_ERROR_HANDLE); + else if (my->cfg.stream == stream::std_out) + my->console_handle = GetStdHandle(STD_OUTPUT_HANDLE); +#endif + + for( int i = 0; i < log_level::off+1; ++i ) + my->lc[i] = color::console_default; + for( auto itr = my->cfg.level_colors.begin(); itr != my->cfg.level_colors.end(); ++itr ) + my->lc[itr->level] = itr->color; + } FC_CAPTURE_AND_RETHROW( (console_appender_config) ) } + + console_appender::~console_appender() {} + + #ifdef WIN32 + static WORD + #else + static const char* + #endif + get_console_color(console_appender::color::type t ) { + switch( t ) { + case console_appender::color::red: return CONSOLE_RED; + case console_appender::color::green: return CONSOLE_GREEN; + case console_appender::color::brown: return CONSOLE_BROWN; + case console_appender::color::blue: return CONSOLE_BLUE; + case console_appender::color::magenta: return CONSOLE_MAGENTA; + case console_appender::color::cyan: return CONSOLE_CYAN; + case console_appender::color::white: return CONSOLE_WHITE; + case console_appender::color::console_default: + default: + return CONSOLE_DEFAULT; + } + } + + string fixed_size( size_t s, const string& str ) { + if( str.size() == s ) return str; + if( str.size() > s ) return str.substr( 0, s ); + string tmp = str; + tmp.append( s - str.size(), ' ' ); + return tmp; + } + + void console_appender::log( const log_message& m ) { + //fc::string message = fc::format_string( m.get_format(), m.get_data() ); + //fc::variant lmsg(m); + + FILE* out = my->cfg.stream == stream::std_error ? stderr : stdout; + + //fc::string fmt_str = fc::format_string( cfg.format, mutable_variant_object(m.get_context())( "message", message) ); + + const log_context context = m.get_context(); + std::string file_line = context.get_file().substr( 0, 22 ); + file_line += ':'; + file_line += fixed_size( 6, fc::to_string( context.get_line_number() ) ); + + std::string line; + line.reserve( 256 ); + if(my->use_syslog_header) { + switch(m.get_context().get_log_level()) { + case log_level::error: + line += "<3>"; + break; + case log_level::warn: + line += "<4>"; + break; + case log_level::info: + line += "<6>"; + break; + case log_level::debug: + line += "<7>"; + break; + } + } + line += fixed_size( 5, context.get_log_level().to_string() ); line += ' '; + // use now() instead of context.get_timestamp() because log_message construction can include user provided long running calls + line += string( time_point::now() ); line += ' '; + line += fixed_size( 9, context.get_thread_name() ); line += ' '; + line += fixed_size( 29, file_line ); line += ' '; + + auto me = context.get_method(); + // strip all leading scopes... + if( me.size() ) { + uint32_t p = 0; + for( uint32_t i = 0;i < me.size(); ++i ) { + if( me[i] == ':' ) p = i; + } + + if( me[p] == ':' ) ++p; + line += fixed_size( 20, context.get_method().substr( p, 20 ) ); line += ' '; + } + line += "] "; + line += fc::format_string( m.get_format(), m.get_data() ); + + print( line, my->lc[context.get_log_level()] ); + + fprintf( out, "\n" ); + + if( my->cfg.flush ) fflush( out ); + } + + void console_appender::print( const std::string& text, color::type text_color ) + { + FILE* out = my->cfg.stream == stream::std_error ? stderr : stdout; + + #ifdef WIN32 + if (my->console_handle != INVALID_HANDLE_VALUE) + SetConsoleTextAttribute(my->console_handle, get_console_color(text_color)); + #else + if(isatty(fileno(out))) fprintf( out, "%s", get_console_color( text_color ) ); + #endif + + if( text.size() ) + fprintf( out, "%s", text.c_str() ); //fmt_str.c_str() ); + + #ifdef WIN32 + if (my->console_handle != INVALID_HANDLE_VALUE) + SetConsoleTextAttribute(my->console_handle, CONSOLE_DEFAULT); + #else + if(isatty(fileno(out))) fprintf( out, "%s", CONSOLE_DEFAULT ); + #endif + + if( my->cfg.flush ) fflush( out ); + } + +} diff --git a/libraries/libfc/src/log/console_defines.h b/libraries/libfc/src/log/console_defines.h new file mode 100644 index 0000000000..545768c104 --- /dev/null +++ b/libraries/libfc/src/log/console_defines.h @@ -0,0 +1,561 @@ +#ifndef _MACE_CMT_CONSOLE_DEFINES_H_ +#define _MACE_CMT_CONSOLE_DEFINES_H_ + +/// @cond INTERNAL_DEV +/** + @file console_defines.h + This header contains definitions for console styles and colors. + @ingroup tconsole +*/ + + +/** + @defgroup console_styles Console Styles + @brief Defines styles that can be used within text printed to the Console. + + Note that styles will not show up when printing to files (in fact, you will + get a lot of garbage characters if you do this). Also, not all consoles + support styled text. + @{ +*/ +#if COLOR_CONSOLE + +#ifndef WIN32 + +/** + @def CONSOLE_DEFAULT + @brief Sets all styles and colors to the console defaults. + + (const char*) +*/ +#define CONSOLE_DEFAULT "\033[0m" +/** + @def CONSOLE_BOLD + @brief Print bold console text (or brighten foreground color if present). + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BOLD "\033[1m" + +/** + @def CONSOLE_HALF_BRIGHT + @brief Print half-bright console text. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_HALF_BRIGHT "\033[2m" + +/** + @def CONSOLE_ITALIC + @brief Print italic console text. + + @ingroup tconsole + (const char*) Typically not supported. + + (const char*) +*/ +#define CONSOLE_ITALIC "\033[3m" + +/** + @def CONSOLE_UNDERLINE + @brief Print underlined console text. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_UNDERLINE "\033[4m" + +/** + @def CONSOLE_BLINK + @brief Print blinking console text. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BLINK "\033[5m" + +/** + @def CONSOLE_RAPID_BLINK + @brief Print rapidly blinking console text. Typically not supported. + + (const char*) +*/ +#define CONSOLE_RAPID_BLINK "\033[6m" + +/** + @def CONSOLE_REVERSED + @brief Print console text with foreground and background colors reversed. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_REVERSED "\033[7m" + +/** + @def CONSOLE_CONCEALED + @brief Print concealed console text. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_CONCEALED "\033[8m" + +/** + @def CONSOLE_STRIKETHROUGH + @brief Print strikethrough console text. Typically not supported. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_STRIKETHROUGH "\033[9m" + +/// @} + + +/** + @defgroup console_colors Console Colors + @brief Defines colors that can be used within text printed to the Console. + + @ingroup tconsole + Note that colors will not show up when printing to files (in fact, you will + get a lot of garbage characters if you do this). Also, not all consoles + support colored text. + @{ +*/ + +/** + @def CONSOLE_BLACK + @brief Print text with black foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BLACK "\033[30m" + +/** + @def CONSOLE_RED + @brief Print text with red foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_RED "\033[31m" + +/** + @def CONSOLE_GREEN + @brief Print text with green foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_GREEN "\033[32m" + +/** + @def CONSOLE_BROWN + @brief Print text with brown foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BROWN "\033[33m" + +/** + @def CONSOLE_BLUE + @brief Print text with blue foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BLUE "\033[34m" + +/** + @def CONSOLE_MAGENTA + @brief Print text with magenta foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_MAGENTA "\033[35m" + +/** + @def CONSOLE_CYAN + @brief Print text with cyan foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_CYAN "\033[36m" + +/** + @def CONSOLE_WHITE + @brief Print text with white foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_WHITE "\033[37m" + +/** + @def CONSOLE_BLACK_BG + @brief Print text with black background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BLACK_BG "\033[40m" + +/** + @def CONSOLE_RED_BG + @brief Print text with red background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_RED_BG "\033[41m" + +/** + @def CONSOLE_GREEN_BG + @brief Print text with green background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_GREEN_BG "\033[42m" + +/** + @def CONSOLE_BROWN_BG + @brief Print text with brown background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BROWN_BG "\033[43m" + +/** + @def CONSOLE_BLUE_BG + @brief Print text with blue background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BLUE_BG "\033[44m" + +/** + @def CONSOLE_MAGENTA_BG + @brief Print text with magenta background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_MAGENTA_BG "\033[45m" + +/** + @def CONSOLE_CYAN_BG + @brief Print text with cyan background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_CYAN_BG "\033[46m" + +/** + @def CONSOLE_WHITE_BG + @brief Print text with white background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_WHITE_BG "\033[47m" + + + +#else // WIN32 +#include +#include +//#include +#include + +/** + @def CONSOLE_DEFAULT + @brief Sets all styles and colors to the console defaults. + + (const char*) +*/ +#define CONSOLE_DEFAULT (FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_GREEN) +/** + @def CONSOLE_BOLD + @brief Print bold console text (or brighten foreground color if present). + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BOLD FOREGROUND_INTENSITY + +/** + @def CONSOLE_HALF_BRIGHT + @brief Print half-bright console text. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_HALF_BRIGHT + +/** + @def CONSOLE_ITALIC + @brief Print italic console text. + + @ingroup tconsole + (const char*) Typically not supported. + + (const char*) +*/ +#define CONSOLE_ITALIC + +/** + @def CONSOLE_UNDERLINE + @brief Print underlined console text. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_UNDERLINE COMMON_LVB_UNDERSCORE + +/** + @def CONSOLE_BLINK + @brief Print blinking console text. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BLINK + +/** + @def CONSOLE_RAPID_BLINK + @brief Print rapidly blinking console text. Typically not supported. + + (const char*) +*/ +#define CONSOLE_RAPID_BLINK + +/** + @def CONSOLE_REVERSED + @brief Print console text with foreground and background colors reversed. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_REVERSED CONSOLE_LVB_REVERSE_VIDEO + +/** + @def CONSOLE_CONCEALED + @brief Print concealed console text. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_CONCEALED + +/** + @def CONSOLE_STRIKETHROUGH + @brief Print strikethrough console text. Typically not supported. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_STRIKETHROUGH + +/// @} + + +/** + @defgroup console_colors Console Colors + @brief Defines colors that can be used within text printed to the Console. + + @ingroup tconsole + Note that colors will not show up when printing to files (in fact, you will + get a lot of garbage characters if you do this). Also, not all consoles + support colored text. + @{ +*/ + +/** + @def CONSOLE_BLACK + @brief Print text with black foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BLACK 0 + +/** + @def CONSOLE_RED + @brief Print text with red foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_RED FOREGROUND_RED + +/** + @def CONSOLE_GREEN + @brief Print text with green foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_GREEN FOREGROUND_GREEN + +/** + @def CONSOLE_BROWN + @brief Print text with brown foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BROWN (FOREGROUND_RED | FOREGROUND_GREEN) + +/** + @def CONSOLE_BLUE + @brief Print text with blue foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BLUE FOREGROUND_BLUE + +/** + @def CONSOLE_MAGENTA + @brief Print text with magenta foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_MAGENTA (CONSOLE_RED | CONSOLE_BLUE) + +/** + @def CONSOLE_CYAN + @brief Print text with cyan foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_CYAN (CONSOLE_BLUE | CONSOLE_GREEN) + +/** + @def CONSOLE_WHITE + @brief Print text with white foreground. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_WHITE (CONSOLE_RED | CONSOLE_BLUE | CONSOLE_GREEN) + +/** + @def CONSOLE_BLACK_BG + @brief Print text with black background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BLACK_BG 0 + +/** + @def CONSOLE_RED_BG + @brief Print text with red background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_RED_BG (BACKGROUND_RED) + +/** + @def CONSOLE_GREEN_BG + @brief Print text with green background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_GREEN_BG (BACKGROUND_GREEN) + +/** + @def CONSOLE_BROWN_BG + @brief Print text with brown background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BROWN_BG (BACKGROUND_RED | BACKGROUND_GREEN) + +/** + @def CONSOLE_BLUE_BG + @brief Print text with blue background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_BLUE_BG (BACKGROUND_BLUE) + +/** + @def CONSOLE_MAGENTA_BG + @brief Print text with magenta background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_MAGENTA_BG "\033[45m" + +/** + @def CONSOLE_CYAN_BG + @brief Print text with cyan background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_CYAN_BG "\033[46m" + +/** + @def CONSOLE_WHITE_BG + @brief Print text with white background. + + @ingroup tconsole + (const char*) +*/ +#define CONSOLE_WHITE_BG "\033[47m" +#endif + + +/// @} +#else // On Window's no color output WIN32 +#define CONSOLE_DEFAULT "" +#define CONSOLE_BOLD "" +#define CONSOLE_HALF_BRIGHT "" +#define CONSOLE_ITALIC "" +#define CONSOLE_UNDERLINE "" +#define CONSOLE_BLINK "" +#define CONSOLE_RAPID_BLINK "" +#define CONSOLE_REVERSED "" +#define CONSOLE_CONCEALED "" +#define CONSOLE_STRIKETHROUGH "" +#define CONSOLE_BLACK "" +#define CONSOLE_RED "" +#define CONSOLE_GREEN "" +#define CONSOLE_BROWN "" +#define CONSOLE_BLUE "" +#define CONSOLE_MAGENTA "" +#define CONSOLE_CYAN "" +#define CONSOLE_WHITE "" +#define CONSOLE_BLACK_BG "" +#define CONSOLE_RED_BG "" +#define CONSOLE_GREEN_BG "" +#define CONSOLE_BROWN_BG "" +#define CONSOLE_BLUE_BG "" +#define CONSOLE_MAGENTA_BG "" +#define CONSOLE_CYAN_BG "" +#define CONSOLE_WHITE_BG "" + +/// @} +/// @endcond INTERNAL_DEV +#endif // NOT DEFINED WIN32 +#endif // _BOOST_CMT_CONSOLE_DEFINES_H_ diff --git a/libraries/libfc/src/log/dmlog_appender.cpp b/libraries/libfc/src/log/dmlog_appender.cpp new file mode 100644 index 0000000000..bc499246d5 --- /dev/null +++ b/libraries/libfc/src/log/dmlog_appender.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#ifndef WIN32 +#include +#include +#endif +#include +#include +#include +#include +#include +#include + +namespace fc { + class dmlog_appender::impl { + public: + bool is_stopped = false; + FILE* out = nullptr; + bool owns_out = false; + }; + + dmlog_appender::dmlog_appender( const std::optional& args ) + :dmlog_appender(){ + if (!args || args->file == "-") + { + my->out = stdout; + } + else + { + my->out = std::fopen(args->file.c_str(), "a"); + if (my->out) + { + std::setbuf(my->out, nullptr); + my->owns_out = true; + } + else + { + FC_THROW("Failed to open deep mind log file ${name}", ("name", args->file)); + } + } + } + + dmlog_appender::dmlog_appender( const variant& args ) + :dmlog_appender(args.as>()){} + + dmlog_appender::dmlog_appender() + :my(new impl){} + + dmlog_appender::~dmlog_appender() { + if (my->owns_out) + { + std::fclose(my->out); + } + } + + void dmlog_appender::initialize( boost::asio::io_service& io_service ) {} + + void dmlog_appender::log( const log_message& m ) { + FILE* out = my->out; + + string message = format_string( "DMLOG " + m.get_format() + "\n", m.get_data() ); + + auto remaining_size = message.size(); + auto message_ptr = message.c_str(); + while (!my->is_stopped && remaining_size) { + auto written = fwrite(message_ptr, sizeof(char), remaining_size, out); + + // EINTR shouldn't happen anymore, but keep this detection, just in case. + if(written == 0 && errno != EINTR) + { + my->is_stopped = true; + } + + if(written != remaining_size) + { + fprintf(stderr, "DMLOG FPRINTF_FAILED failed written=%lu remaining=%lu %d %s\n", written, remaining_size, ferror(out), strerror(errno)); + clearerr(out); + } + + if(my->is_stopped) + { + fprintf(stderr, "DMLOG FPRINTF_FAILURE_TERMINATED\n"); + // Depending on the error, we might have already gotten a SIGPIPE + // An extra signal is harmless, though. Use a process targeted + // signal (not raise) because the SIGTERM may be blocked in this + // thread. + kill(getpid(), SIGTERM); + } + + message_ptr = &message_ptr[written]; + remaining_size -= written; + } + } +} diff --git a/libraries/libfc/src/log/gelf_appender.cpp b/libraries/libfc/src/log/gelf_appender.cpp new file mode 100644 index 0000000000..6fbcf7248a --- /dev/null +++ b/libraries/libfc/src/log/gelf_appender.cpp @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace fc +{ + namespace detail + { + boost::asio::ip::udp::endpoint to_asio_ep( const fc::ip::endpoint& e ) + { + return boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4(e.get_address()), e.port() ); + } + } + + const std::vector gelf_appender::config::reserved_field_names = { + "_id", // per GELF specification + "_timestamp_ns", // Remaining names all populated by appender + "_log_id", + "_line", + "_file", + "_method_name", + "_thread_name", + "_task_name" + }; + + const std::regex gelf_appender::config::user_field_name_pattern{"^_[\\w\\.\\-]*$"}; // per GELF specification + + class gelf_appender::impl + { + public: + config cfg; + std::optional gelf_endpoint; + udp_socket gelf_socket; + + impl(const variant& c) + { + mutable_variant_object mvo; + from_variant(c, mvo); + + cfg.endpoint = mvo["endpoint"].as(); + mvo.erase("endpoint"); + cfg.host = mvo["host"].as(); + mvo.erase("host"); + cfg.user_fields = mvo; + + for(auto&& field_name : config::reserved_field_names) { + if (cfg.user_fields.contains(field_name.c_str())) { + FC_THROW_EXCEPTION(invalid_arg_exception, "Field name '${field_name}' is reserved", + ("field_name", field_name)); + } + } + for(auto&& field : cfg.user_fields) { + if (!std::regex_match(field.key(), config::user_field_name_pattern)) { + FC_THROW_EXCEPTION(invalid_arg_exception, "Field name '${field_name} must begin with an underscore and contain only letters, numbers, underscores, dashes, and dots.", + ("field_name", field.key())); + } + } + } + + ~impl() + { + } + }; + + gelf_appender::gelf_appender(const variant& args) : + my(new impl(args)) + { + } + + void gelf_appender::initialize(boost::asio::io_service &io_service) + { + try + { + try + { + // if it's a numeric address:port, this will parse it + my->gelf_endpoint = detail::to_asio_ep(ip::endpoint::from_string(my->cfg.endpoint)); + } + catch (...) + { + } + if (!my->gelf_endpoint) + { + // couldn't parse as a numeric ip address, try resolving as a DNS name. + // This can yield, so don't do it in the catch block above + string::size_type colon_pos = my->cfg.endpoint.find(':'); + try + { + uint16_t port = boost::lexical_cast(my->cfg.endpoint.substr(colon_pos + 1, my->cfg.endpoint.size())); + + string hostname = my->cfg.endpoint.substr( 0, colon_pos ); + auto endpoints = resolve(io_service, hostname, port); + if (endpoints.empty()) + FC_THROW_EXCEPTION(unknown_host_exception, "The logging destination host name can not be resolved: ${hostname}", + ("hostname", hostname)); + my->gelf_endpoint = endpoints.back(); + } + catch (const boost::bad_lexical_cast&) + { + FC_THROW("Bad port: ${port}", ("port", my->cfg.endpoint.substr(colon_pos + 1, my->cfg.endpoint.size()))); + } + } + + if (my->gelf_endpoint) + { + my->gelf_socket.initialize(io_service); + my->gelf_socket.open(); + std::cerr << "opened GELF socket to endpoint " << my->cfg.endpoint << "\n"; + } + } + catch (...) + { + std::cerr << "error opening GELF socket to endpoint " << my->cfg.endpoint << "\n"; + } + } + + gelf_appender::~gelf_appender() + {} + + void gelf_appender::log(const log_message& message) + { + if (!my->gelf_endpoint) + return; + + log_context context = message.get_context(); + + mutable_variant_object gelf_message; + gelf_message["version"] = "1.1"; + gelf_message["host"] = my->cfg.host; + gelf_message["short_message"] = format_string(message.get_format(), message.get_data(), true); + + // use now() instead of context.get_timestamp() because log_message construction can include user provided long running calls + const auto time_ns = time_point::now().time_since_epoch().count(); + gelf_message["timestamp"] = time_ns / 1000000.; + gelf_message["_timestamp_ns"] = time_ns; + + static uint64_t gelf_log_counter; + gelf_message["_log_id"] = fc::to_string(++gelf_log_counter); + + switch (context.get_log_level()) + { + case log_level::debug: + gelf_message["level"] = 7; // debug + break; + case log_level::info: + gelf_message["level"] = 6; // info + break; + case log_level::warn: + gelf_message["level"] = 4; // warning + break; + case log_level::error: + gelf_message["level"] = 3; // error + break; + case log_level::all: + case log_level::off: + // these shouldn't be used in log messages, but do something deterministic just in case + gelf_message["level"] = 6; // info + break; + } + + if (!context.get_context().empty()) + gelf_message["context"] = context.get_context(); + gelf_message["_line"] = context.get_line_number(); + gelf_message["_file"] = context.get_file(); + gelf_message["_method_name"] = context.get_method(); + gelf_message["_thread_name"] = context.get_thread_name(); + if (!context.get_task_name().empty()) + gelf_message["_task_name"] = context.get_task_name(); + + for(auto&& field : my->cfg.user_fields) { + gelf_message[field.key()] = field.value(); + } + + string gelf_message_as_string = json::to_string(gelf_message, + fc::time_point::now() + fc::exception::format_time_limit, + json::output_formatting::legacy_generator); // GELF 1.1 specifies unstringified numbers + gelf_message_as_string = zlib_compress(gelf_message_as_string); + + // packets are sent by UDP, and they tend to disappear if they + // get too large. It's hard to find any solid numbers on how + // large they can be before they get dropped -- datagrams can + // be up to 64k, but anything over 512 is not guaranteed. + // You can play with this number, intermediate values like + // 1400 and 8100 are likely to work on most intranets. + const unsigned max_payload_size = 512; + + if (gelf_message_as_string.size() <= max_payload_size) + { + // no need to split + std::shared_ptr send_buffer(new char[gelf_message_as_string.size()], + [](char* p){ delete[] p; }); + memcpy(send_buffer.get(), gelf_message_as_string.c_str(), + gelf_message_as_string.size()); + + my->gelf_socket.send_to(send_buffer, gelf_message_as_string.size(), + *my->gelf_endpoint); + } + else + { + // split the message + // we need to generate an 8-byte ID for this message. + // city hash should do + uint64_t message_id = city_hash64(gelf_message_as_string.c_str(), gelf_message_as_string.size()); + const unsigned header_length = 2 /* magic */ + 8 /* msg id */ + 1 /* seq */ + 1 /* count */; + const unsigned body_length = max_payload_size - header_length; + unsigned total_number_of_packets = (gelf_message_as_string.size() + body_length - 1) / body_length; + unsigned bytes_sent = 0; + unsigned number_of_packets_sent = 0; + while (bytes_sent < gelf_message_as_string.size()) + { + unsigned bytes_to_send = std::min((unsigned)gelf_message_as_string.size() - bytes_sent, + body_length); + + std::shared_ptr send_buffer(new char[max_payload_size], + [](char* p){ delete[] p; }); + char* ptr = send_buffer.get(); + // magic number for chunked message + *(unsigned char*)ptr++ = 0x1e; + *(unsigned char*)ptr++ = 0x0f; + + // message id + memcpy(ptr, (char*)&message_id, sizeof(message_id)); + ptr += sizeof(message_id); + + *(unsigned char*)(ptr++) = number_of_packets_sent; + *(unsigned char*)(ptr++) = total_number_of_packets; + memcpy(ptr, gelf_message_as_string.c_str() + bytes_sent, + bytes_to_send); + my->gelf_socket.send_to(send_buffer, header_length + bytes_to_send, + *my->gelf_endpoint); + ++number_of_packets_sent; + bytes_sent += bytes_to_send; + } + FC_ASSERT(number_of_packets_sent == total_number_of_packets); + } + } +} // fc diff --git a/libraries/libfc/src/log/log_message.cpp b/libraries/libfc/src/log/log_message.cpp new file mode 100644 index 0000000000..f270d36b4b --- /dev/null +++ b/libraries/libfc/src/log/log_message.cpp @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include +#include + +namespace fc +{ + const string& get_thread_name(); + namespace detail + { + class log_context_impl + { + public: + log_level level; + string file; + uint64_t line; + string method; + string thread_name; + string task_name; + string hostname; + string context; + time_point timestamp; + }; + + class log_message_impl + { + public: + log_message_impl( log_context&& ctx ) + :context( std::move(ctx) ){} + log_message_impl(){} + + log_context context; + string format; + variant_object args; + }; + } + + + + log_context::log_context() + :my( std::make_shared() ){} + + log_context::log_context( log_level ll, const char* file, uint64_t line, + const char* method ) + :my( std::make_shared() ) + { + my->level = ll; + my->file = fc::path(file).filename().generic_string(); // TODO truncate filename + my->line = line; + my->method = method; + my->timestamp = time_point::now(); + my->thread_name = fc::get_thread_name(); + } + + log_context::log_context( const variant& v ) + :my( std::make_shared() ) + { + auto obj = v.get_object(); + my->level = obj["level"].as(); + my->file = obj["file"].as_string(); + my->line = obj["line"].as_uint64(); + my->method = obj["method"].as_string(); + my->hostname = obj["hostname"].as_string(); + my->thread_name = obj["thread_name"].as_string(); + if (obj.contains("task_name")) + my->task_name = obj["task_name"].as_string(); + my->timestamp = obj["timestamp"].as(); + if( obj.contains( "context" ) ) + my->context = obj["context"].as(); + } + + fc::string log_context::to_string()const + { + return my->thread_name + " " + my->file + ":" + fc::to_string(my->line) + " " + my->method; + + } + + void log_context::append_context( const fc::string& s ) + { + if (!my->context.empty()) + my->context += " -> "; + my->context += s; + } + + log_context::~log_context(){} + + + void to_variant( const log_context& l, variant& v ) + { + v = l.to_variant(); + } + + void from_variant( const variant& l, log_context& c ) + { + c = log_context(l); + } + + void from_variant( const variant& l, log_message& c ) + { + c = log_message(l); + } + void to_variant( const log_message& m, variant& v ) + { + v = m.to_variant(); + } + + void to_variant( log_level e, variant& v ) + { + switch( e ) + { + case log_level::all: + v = "all"; + return; + case log_level::debug: + v = "debug"; + return; + case log_level::info: + v = "info"; + return; + case log_level::warn: + v = "warn"; + return; + case log_level::error: + v = "error"; + return; + case log_level::off: + v = "off"; + return; + } + } + void from_variant( const variant& v, log_level& e ) + { + try + { + if( v.as_string() == "all" ) e = log_level::all; + else if( v.as_string() == "debug" ) e = log_level::debug; + else if( v.as_string() == "info" ) e = log_level::info; + else if( v.as_string() == "warn" ) e = log_level::warn; + else if( v.as_string() == "error" ) e = log_level::error; + else if( v.as_string() == "off" ) e = log_level::off; + else FC_THROW_EXCEPTION( bad_cast_exception, "Failed to cast from Variant to log_level" ); + } FC_RETHROW_EXCEPTIONS( error, + "Expected 'all|debug|info|warn|error|off', but got '${variant}'", + ("variant",v) ); + } + + string log_level::to_string()const { + switch( value ) + { + case log_level::all: + return "all"; + case log_level::debug: + return "debug"; + case log_level::info: + return "info"; + case log_level::warn: + return "warn"; + case log_level::error: + return "error"; + case log_level::off: + return "off"; + } + return "unknown"; + } + + string log_context::get_file()const { return my->file; } + uint64_t log_context::get_line_number()const { return my->line; } + string log_context::get_method()const { return my->method; } + string log_context::get_thread_name()const { return my->thread_name; } + string log_context::get_task_name()const { return my->task_name; } + string log_context::get_host_name()const { return my->hostname; } + time_point log_context::get_timestamp()const { return my->timestamp; } + log_level log_context::get_log_level()const{ return my->level; } + string log_context::get_context()const { return my->context; } + + + variant log_context::to_variant()const + { + mutable_variant_object o; + o( "level", variant(my->level) ) + ( "file", my->file ) + ( "line", my->line ) + ( "method", my->method ) + ( "hostname", my->hostname ) + ( "thread_name", my->thread_name ) + ( "timestamp", variant(my->timestamp) ); + + if( my->context.size() ) + o( "context", my->context ); + + return o; + } + + log_message::~log_message(){} + log_message::log_message() + :my( std::make_shared() ){} + + log_message::log_message( log_context ctx, std::string format, variant_object args ) + :my( std::make_shared(std::move(ctx)) ) + { + my->format = std::move(format); + my->args = std::move(args); + } + + log_message::log_message( const variant& v ) + :my( std::make_shared( log_context( v.get_object()["context"] ) ) ) + { + my->format = v.get_object()["format"].as_string(); + my->args = v.get_object()["data"].get_object(); + } + + variant log_message::to_variant()const + { + return mutable_variant_object( "context", my->context ) + ( "format", my->format ) + ( "data", my->args ); + } + + log_context log_message::get_context()const { return my->context; } + string log_message::get_format()const { return my->format; } + variant_object log_message::get_data()const { return my->args; } + + string log_message::get_message()const + { + return format_string( my->format, my->args ); + } + + string log_message::get_limited_message()const + { + const bool minimize = true; + return format_string( my->format, my->args, minimize ); + } + + +} // fc + diff --git a/libraries/libfc/src/log/logger.cpp b/libraries/libfc/src/log/logger.cpp new file mode 100644 index 0000000000..edc029c346 --- /dev/null +++ b/libraries/libfc/src/log/logger.cpp @@ -0,0 +1,109 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + + class logger::impl { + public: + impl() + :_parent(nullptr),_enabled(true),_additivity(false),_level(log_level::warn){} + fc::string _name; + logger _parent; + bool _enabled; + bool _additivity; + log_level _level; + + std::vector _appenders; + }; + + + logger::logger() + :my( new impl() ){} + + logger::logger(nullptr_t){} + + logger::logger( const string& name, const logger& parent ) + :my( new impl() ) + { + my->_name = name; + my->_parent = parent; + } + + + logger::logger( const logger& l ) + :my(l.my){} + + logger::logger( logger&& l ) + :my(fc::move(l.my)){} + + logger::~logger(){} + + logger& logger::operator=( const logger& l ){ + my = l.my; + return *this; + } + logger& logger::operator=( logger&& l ){ + fc_swap(my,l.my); + return *this; + } + bool operator==( const logger& l, std::nullptr_t ) { return !l.my; } + bool operator!=( const logger& l, std::nullptr_t ) { return !!l.my; } + + bool logger::is_enabled( log_level e )const { + return e >= my->_level; + } + + void logger::log( log_message m ) { + std::unique_lock g( log_config::get().log_mutex ); + m.get_context().append_context( my->_name ); + + for( auto itr = my->_appenders.begin(); itr != my->_appenders.end(); ++itr ) { + try { + (*itr)->log( m ); + } catch( fc::exception& er ) { + std::cerr << "ERROR: logger::log fc::exception: " << er.to_detail_string() << std::endl; + } catch( const std::exception& e ) { + std::cerr << "ERROR: logger::log std::exception: " << e.what() << std::endl; + } catch( ... ) { + std::cerr << "ERROR: logger::log unknown exception: " << std::endl; + } + } + + if( my->_additivity && my->_parent != nullptr) { + logger parent = my->_parent; + g.unlock(); + parent.log( m ); + } + } + + void logger::set_name( const fc::string& n ) { my->_name = n; } + const fc::string& logger::name()const { return my->_name; } + + logger logger::get( const fc::string& s ) { + return log_config::get_logger( s ); + } + + void logger::update( const fc::string& name, logger& log ) { + log_config::update_logger( name, log ); + } + + logger logger::get_parent()const { return my->_parent; } + logger& logger::set_parent(const logger& p) { my->_parent = p; return *this; } + + log_level logger::get_log_level()const { return my->_level; } + logger& logger::set_log_level(log_level ll) { my->_level = ll; return *this; } + + void logger::add_appender( const std::shared_ptr& a ) { + my->_appenders.push_back(a); + } + + bool configure_logging( const logging_config& cfg ); + bool do_default_config = configure_logging( logging_config::default_config() ); + +} // namespace fc diff --git a/libraries/libfc/src/log/logger_config.cpp b/libraries/libfc/src/log/logger_config.cpp new file mode 100644 index 0000000000..6d84843629 --- /dev/null +++ b/libraries/libfc/src/log/logger_config.cpp @@ -0,0 +1,159 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + + log_config& log_config::get() { + // allocate dynamically which will leak on exit but allow loggers to be used until the very end of execution + static log_config* the = new log_config; + return *the; + } + + bool log_config::register_appender( const fc::string& type, const appender_factory::ptr& f ) + { + std::lock_guard g( log_config::get().log_mutex ); + log_config::get().appender_factory_map[type] = f; + return true; + } + + logger log_config::get_logger( const fc::string& name ) { + std::lock_guard g( log_config::get().log_mutex ); + return log_config::get().logger_map[name]; + } + + void log_config::update_logger( const fc::string& name, logger& log ) { + std::lock_guard g( log_config::get().log_mutex ); + if( log_config::get().logger_map.find( name ) != log_config::get().logger_map.end() ) { + log = log_config::get().logger_map[name]; + } else { + // no entry for logger, so setup with default logger if it exists, otherwise do nothing since default logger not configured + if( log_config::get().logger_map.find( DEFAULT_LOGGER ) != log_config::get().logger_map.end() ) { + log = log_config::get().logger_map[DEFAULT_LOGGER]; + log_config::get().logger_map.emplace( name, log ); + } + } + } + + void log_config::initialize_appenders( boost::asio::io_service& ios ) { + std::lock_guard g( log_config::get().log_mutex ); + for( auto& iter : log_config::get().appender_map ) + iter.second->initialize( ios ); + } + + void configure_logging( const fc::path& lc ) { + configure_logging( fc::json::from_file(lc) ); + } + bool configure_logging( const logging_config& cfg ) { + return log_config::configure_logging( cfg ); + } + + bool log_config::configure_logging( const logging_config& cfg ) { + try { + static bool reg_console_appender = log_config::register_appender( "console" ); + static bool reg_gelf_appender = log_config::register_appender( "gelf" ); + static bool reg_dmlog_appender = log_config::register_appender( "dmlog" ); + + std::lock_guard g( log_config::get().log_mutex ); + log_config::get().logger_map.clear(); + log_config::get().appender_map.clear(); + + for( size_t i = 0; i < cfg.appenders.size(); ++i ) { + // create appender + auto fact_itr = log_config::get().appender_factory_map.find( cfg.appenders[i].type ); + if( fact_itr == log_config::get().appender_factory_map.end() ) { + //wlog( "Unknown appender type '%s'", type.c_str() ); + continue; + } + auto ap = fact_itr->second->create( cfg.appenders[i].args ); + log_config::get().appender_map[cfg.appenders[i].name] = ap; + } + for( size_t i = 0; i < cfg.loggers.size(); ++i ) { + auto lgr = log_config::get().logger_map[cfg.loggers[i].name]; + + // TODO: finish configure logger here... + if( cfg.loggers[i].parent ) { + lgr.set_parent( log_config::get().logger_map[*cfg.loggers[i].parent] ); + } + lgr.set_name(cfg.loggers[i].name); + if( cfg.loggers[i].level ) lgr.set_log_level( *cfg.loggers[i].level ); + + + for( auto a = cfg.loggers[i].appenders.begin(); a != cfg.loggers[i].appenders.end(); ++a ){ + auto ap_it = log_config::get().appender_map.find(*a); + if( ap_it != log_config::get().appender_map.end() ) { + lgr.add_appender( ap_it->second ); + } + } + } + return reg_console_appender || reg_gelf_appender || reg_dmlog_appender; + } catch ( exception& e ) + { + std::cerr< +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace fc { + +zipkin_config& zipkin_config::get() { + static zipkin_config the_one; + return the_one; +} + +void zipkin_config::init( const std::string& url, const std::string& service_name, uint32_t timeout_us ) { + get().zip = std::make_unique( url, service_name, timeout_us ); +} + +zipkin& zipkin_config::get_zipkin() { + if( !get().zip ) { + FC_THROW_EXCEPTION( fc::assert_exception, "uninitialized zipkin" ); + } + return *get().zip; +} + +void zipkin_config::shutdown() { + if( zipkin* z = get_zipkin_() ) { + z->shutdown(); + } +} + +uint64_t zipkin_config::get_next_unique_id() { + if( !get().zip ) { + FC_THROW_EXCEPTION( fc::assert_exception, "uninitialized zipkin" ); + } + return get().zip->get_next_unique_id(); +} + +class zipkin::impl { +public: + static constexpr uint32_t max_consecutive_errors = 9; + + const std::string zipkin_url; + const std::string service_name; + const uint32_t timeout_us; + std::mutex mtx; + uint64_t next_id = 0; + http_client http; + std::atomic consecutive_errors = 0; + std::atomic stopped = 0; + std::optional endpoint; + std::thread thread; + boost::asio::io_context ctx; + boost::asio::io_context::strand work_strand{ctx}; + boost::asio::executor_work_guard work_guard = boost::asio::make_work_guard(ctx); + + impl( std::string url, std::string service_name, uint32_t timeout_us ) + : zipkin_url( std::move(url) ) + , service_name( std::move(service_name) ) + , timeout_us( timeout_us ) { + } + + void init(); + void shutdown(); + + void log( zipkin_span::span_data&& span ); + + ~impl(); +}; + +void zipkin::impl::init() { + thread = std::thread( [this]() { + fc::set_os_thread_name( "zipkin" ); + while( true ) { + try { + ctx.run(); + break; + } FC_LOG_AND_DROP(); + } + } ); +} + +zipkin::impl::~impl() { + try { + shutdown(); + } catch (...) {} +} + +void zipkin::impl::shutdown() { + if( stopped ^= 1 ) return; + work_guard.reset(); // drain the queue + thread.join(); +} + +zipkin::zipkin( const std::string& url, const std::string& service_name, uint32_t timeout_us ) : + my( new impl( url, service_name, timeout_us ) ) { + my->init(); +} + +uint64_t zipkin::get_next_unique_id() { + std::scoped_lock g( my->mtx ); + if( my->next_id == 0 ) { + std::mt19937_64 engine( std::random_device{}() ); + std::uniform_int_distribution distribution(1); + my->next_id = distribution( engine ); + } + return my->next_id++; +} + +void zipkin::shutdown() { + my->shutdown(); +} + +fc::variant create_zipkin_variant( zipkin_span::span_data&& span, const std::string& service_name ) { + // https://zipkin.io/zipkin-api/ + // std::string traceId; // [a-f0-9]{16,32} unique id for trace, all children spans shared same id + // std::string name; // logical operation, should have low cardinality + // std::string parentId; // The parent span id, or absent if root span + // std::string id // a-f0-9]{16} + // int64_t timestamp // epoch microseconds of start of span + // int64_t duration // microseconds of span + + uint64_t trace_id; + if( span.parent_id != 0 ) { + trace_id = span.parent_id; + } else { + trace_id = span.id; + } + + fc::mutable_variant_object mvo; + mvo( "id", fc::to_hex( reinterpret_cast(&span.id), sizeof( span.id ) ) ); + mvo( "traceId", fc::to_hex( reinterpret_cast(&trace_id), sizeof( trace_id ) ) ); + if( span.parent_id != 0 ) { + mvo( "parentId", fc::to_hex( reinterpret_cast(&span.parent_id), sizeof( span.parent_id ) ) ); + } + mvo( "name", std::move( span.name ) ); + mvo( "timestamp", span.start.time_since_epoch().count() ); + mvo( "duration", (span.stop - span.start).count() ); + mvo( "localEndpoint", fc::variant_object( "serviceName", service_name ) ); + + mvo( "tags", std::move( span.tags ) ); + span.id = 0; // stop destructor of span from calling log again + + // /api/v2/spans takes an array of spans + fc::variants result; + result.emplace_back( std::move( mvo ) ); + + return result; +} + +void zipkin::log( zipkin_span::span_data&& span ) { + if( my->consecutive_errors > my->max_consecutive_errors || my->stopped ) + return; + + boost::asio::post(my->work_strand, [this, span{std::move(span)}]() mutable { + my->log( std::move( span ) ); + }); +} + +void zipkin::impl::log( zipkin_span::span_data&& span ) { + if( consecutive_errors > max_consecutive_errors ) + return; + + try { + auto deadline = fc::time_point::now() + fc::microseconds( timeout_us ); + if( !endpoint ) { + endpoint = url( zipkin_url ); + dlog( "connecting to zipkin: ${p}", ("p", *endpoint) ); + } + + http.post_sync( *endpoint, create_zipkin_variant( std::move( span ), service_name ), deadline ); + + consecutive_errors = 0; + return; + } catch( const fc::exception& e ) { + wlog( "unable to connect to zipkin: ${u}, error: ${e}", ("u", zipkin_url)("e", e.to_detail_string()) ); + } catch( const std::exception& e ) { + wlog( "unable to connect to zipkin: ${u}, error: ${e}", ("u", zipkin_url)("e", e.what()) ); + } catch( ... ) { + wlog( "unable to connect to zipkin: ${u}, error: unknown", ("u", zipkin_url) ); + } + ++consecutive_errors; +} + +uint64_t zipkin_span::to_id( const fc::sha256& id ) { + // avoid 0 since id of 0 is used as a flag + return id._hash[3] == 0 ? 1 : id._hash[3]; +} + +zipkin_span::~zipkin_span() { + if( data.id == 0 ) + return; + try { + if( zipkin_config::is_enabled() ) { + data.stop = time_point::now(); + zipkin_config::get_zipkin().log( std::move( data ) ); + } + } catch( ... ) {} +} + +} // fc diff --git a/libraries/libfc/src/mock_time.cpp b/libraries/libfc/src/mock_time.cpp new file mode 100644 index 0000000000..ade2b53458 --- /dev/null +++ b/libraries/libfc/src/mock_time.cpp @@ -0,0 +1,33 @@ +#include +#include + +namespace fc { + +bool mock_time_traits::mock_enabled_ = false; +const boost::posix_time::ptime mock_time_traits::epoch_{ boost::gregorian::date( 1970, 1, 1 ) }; +std::atomic mock_time_traits::now_{}; + +mock_time_traits::time_type mock_time_traits::now() noexcept { + return epoch_ + boost::posix_time::microseconds( now_.load() ); +} + +void mock_time_traits::set_now( time_type t ) { + fc::time_point now( fc::microseconds( (t - epoch_).total_microseconds() ) ); + set_now( now ); +} + +void mock_time_traits::set_now( const fc::time_point& now ) { + now_ = now.time_since_epoch().count(); + if( !mock_enabled_ ) mock_enabled_ = true; + + // After modifying the clock, we need to sleep the thread to give the io_service + // the opportunity to poll and notice the change in clock time. + // See to_posix_duration() + std::this_thread::sleep_for( std::chrono::milliseconds( 2 ) ); +} + +fc::time_point mock_time_traits::fc_now() { + return fc::time_point( fc::microseconds( ( mock_time_traits::now() - epoch_ ).total_microseconds() ) ); +} + +} //namespace fc diff --git a/libraries/libfc/src/network/LICENSE.go b/libraries/libfc/src/network/LICENSE.go new file mode 100644 index 0000000000..6a66aea5ea --- /dev/null +++ b/libraries/libfc/src/network/LICENSE.go @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/libraries/libfc/src/network/gntp.cpp b/libraries/libfc/src/network/gntp.cpp new file mode 100644 index 0000000000..0c522d3137 --- /dev/null +++ b/libraries/libfc/src/network/gntp.cpp @@ -0,0 +1,291 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace fc +{ + namespace detail + { + static std::string calc_sha1_base32_of_buffer(const std::string& buffer) + { + sha1::encoder sha1_encoder; + sha1_encoder.write(buffer.c_str(), buffer.size()); + sha1 sha1_result = sha1_encoder.result(); + string sha1_result_base32 = to_base32((char*)&sha1_result, sizeof(sha1_result)); + return sha1_result_base32.c_str(); + } + + + class gntp_icon_impl + { + public: + std::string _icon_bytes; + std::string _sha1_hash; + + gntp_icon_impl(const char* buffer, size_t length) : + _icon_bytes(buffer, length), + _sha1_hash(calc_sha1_base32_of_buffer(_icon_bytes)) + { + } + }; + + class gntp_notifier_impl + { + public: + gntp_notifier_impl(const std::string& host_to_notify = "127.0.0.1", uint16_t port = 23053, const std::optional& password = std::optional()); + + // there's no API to change these right now, it will always notify localhost at the default GNTP port + std::string hostname; + uint16_t port; + std::optional password; + + std::string application_name; + gntp_icon_ptr application_icon; + + gntp_notification_type_list notification_types; // list of all notification types we're registered to send + + std::optional endpoint; // cache the last endpoint we've connected to + + bool connection_failed; // true after we've tried to connect and failed + bool is_registered; // true after we've registered + + void send_gntp_message(const std::string& message); + }; + + gntp_notifier_impl::gntp_notifier_impl(const std::string& host_to_notify /* = "127.0.0.1" */, uint16_t port /* = 23053 */, + const std::optional& password /* = std::optional() */) : + hostname(host_to_notify), + port(port), + password(password), + connection_failed(false), + is_registered(false) + { + } + + void gntp_notifier_impl::send_gntp_message(const std::string& message) + { + std::shared_ptr sock(new boost::asio::ip::tcp::socket(asio::default_io_service())); + + bool connected = false; + if (endpoint) + { + // we've successfully connected before, connect to the same endpoint that worked last time + try + { + asio::tcp::connect(*sock, *endpoint); + connected = true; + } + catch (exception& er) + { + ilog("Failed to connect to GNTP service using an endpoint that previously worked: ${error_report}", + ("error_report", er.to_detail_string())); + sock->close(); + // clear the cached endpoint and fall through to the full connection procedure + endpoint = std::optional(); + } + catch (...) + { + ilog("Failed to connect to GNTP service using an endpoint that previously worked"); + sock->close(); + // clear the cached endpoint and fall through to the full connection procedure + endpoint = std::optional(); + } + } + if (!connected) + { + // do the full connection procedure + auto eps = asio::tcp::resolve(hostname, boost::lexical_cast(port)); + if (eps.size() == 0) + FC_THROW("Unable to resolve host '${host}'", ("host", hostname)); + + for (uint32_t i = 0; i < eps.size(); ++i) + { + try + { + boost::system::error_code ec; + ilog("Attempting to connect to GNTP srvice"); + asio::tcp::connect(*sock, eps[i]); + endpoint = eps[i]; + connected = true; + break; + } + catch (const exception& er) + { + ilog("Failed to connect to GNTP service: ${error_reprot}", + ("error_report", er.to_detail_string()) ); + sock->close(); + } + catch (...) + { + ilog("Failed to connect to GNTP service"); + sock->close(); + } + } + } + if (!connected) + FC_THROW("Unable to connect to any resolved endpoint for ${host}:${port}", + ("host", hostname)("port", port)); + try + { + asio::ostream write_stream(sock); + write_stream.write(message.c_str(), message.size()); + write_stream.flush(); + write_stream.close(); + } + catch (exception& er) + { + FC_RETHROW_EXCEPTION(er, warn, "Caught an exception while sending data to GNTP service"); + } + catch (...) + { + FC_THROW("Caught an exception while sending data to GNTP service"); + } + } + } // end namespace detail + + gntp_icon::gntp_icon(const char* buffer, size_t length) : + my(new detail::gntp_icon_impl(buffer, length)) + { + } + gntp_icon::~gntp_icon() + { + } + + gntp_notifier::gntp_notifier(const std::string& host_to_notify /* = "127.0.0.1" */, uint16_t port /* = 23053 */, + const std::optional& password /* = std::optional() */) : + my(new detail::gntp_notifier_impl(host_to_notify, port, password)) + { + } + + gntp_notifier::~gntp_notifier() + { + } + + void gntp_notifier::set_application_name(std::string appName) + { + my->application_name = appName; + } + void gntp_notifier::set_application_icon(const gntp_icon_ptr& icon) + { + my->application_icon = icon; + } + void gntp_notifier::add_notification_type(const gntp_notification_type& notification_type) + { + my->notification_types.push_back(notification_type); + } + + void gntp_notifier::register_notifications() + { + // this call will reset any errors + my->connection_failed = false; + my->is_registered = false; + + std::ostringstream message; + std::set icons_used; + + message << "GNTP/1.0 REGISTER NONE\r\n"; + message << "Application-Name: " << my->application_name << "\r\n"; + if (my->application_icon) + { + message << "Application-Icon: x-growl-resource://" << my->application_icon->my->_sha1_hash << "\r\n"; + icons_used.insert(my->application_icon); + } + + message << "Notifications-Count: " << my->notification_types.size() << "\r\n"; + for (const gntp_notification_type& notification_type : my->notification_types) + { + message << "\r\n"; + message << "Notification-Name: " << notification_type.name << "\r\n"; + if (!notification_type.display_name.empty()) + message << "Notification-Display-Name: " << notification_type.display_name << "\r\n"; + if (notification_type.icon) + { + message << "Notification-Icon: x-growl-resource://" << notification_type.icon->my->_sha1_hash << "\r\n"; + icons_used.insert(notification_type.icon); + } + message << "Notification-Enabled: " << (notification_type.enabled ? "True" : "False") << "\r\n"; + } + if (!icons_used.empty()) + { + message << "\r\n"; + for (const gntp_icon_ptr& icon : icons_used) + { + message << "Identifier: " << icon->my->_sha1_hash << "\r\n"; + message << "Length: " << icon->my->_icon_bytes.size() << "\r\n"; + message << "\r\n"; + message << icon->my->_icon_bytes; + message << "\r\n"; + } + } + + message << "\r\n\r\n"; + try + { + my->send_gntp_message(message.str()); + my->is_registered = true; + } + catch (const exception&) + { + my->connection_failed = true; + } + } + gntp_guid gntp_notifier::send_notification(std::string name, std::string title, std::string text, + const gntp_icon_ptr& icon, std::optional coalescingId /* = std::optional() */) + { + if (my->connection_failed) + return gntp_guid(); + if (!my->is_registered) + return gntp_guid(); + + gntp_guid notification_id; + rand_pseudo_bytes(notification_id.data(), 20); + + std::ostringstream message; + message << "GNTP/1.0 NOTIFY NONE"; + if (my->password) + { + char salt[16]; + rand_pseudo_bytes(salt, sizeof(salt)); + std::string salted_password = *my->password + std::string(salt, 16); + sha256 key = sha256::hash(salted_password); + sha256 keyhash = sha256::hash(key.data(), 32); + message << " SHA256:" << boost::to_upper_copy(to_hex(keyhash.data(), 32)) << "." << boost::to_upper_copy(to_hex(salt, sizeof(salt))); + } + message << "\r\n"; + message << "Application-Name: " << my->application_name << "\r\n"; + message << "Notification-Name: " << name << "\r\n"; + message << "Notification-ID: " << notification_id.str() << "\r\n"; + message << "Notification-Coalescing-ID: " << (coalescingId ? coalescingId->str() : notification_id.str()) << "\r\n"; + message << "Notification-Title: " << title << "\r\n"; + message << "Notification-Text: " << text << "\r\n"; + if (icon) + message << "Notification-Icon: x-growl-resource://" << icon->my->_sha1_hash << "\r\n"; + + if (icon) + { + message << "\r\n"; + message << "Identifier: " << icon->my->_sha1_hash << "\r\n"; + message << "Length: " << icon->my->_icon_bytes.size() << "\r\n"; + message << "\r\n"; + message << icon->my->_icon_bytes; + message << "\r\n"; + } + message << "\r\n\r\n"; + my->send_gntp_message(message.str()); + return notification_id; + } + +} // namespace fc \ No newline at end of file diff --git a/libraries/libfc/src/network/http/http_client.cpp b/libraries/libfc/src/network/http/http_client.cpp new file mode 100644 index 0000000000..34adb7b411 --- /dev/null +++ b/libraries/libfc/src/network/http/http_client.cpp @@ -0,0 +1,454 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from +namespace ssl = boost::asio::ssl; // from +namespace local = boost::asio::local; + +namespace fc { + +/** + * mapping of protocols to their standard ports + */ +static const std::map default_proto_ports = { + {"http", 80}, + {"https", 443} +}; + +class http_client_impl { +public: + using host_key = std::tuple; + using raw_socket_ptr = std::unique_ptr; + using ssl_socket_ptr = std::unique_ptr>; + using unix_socket_ptr = std::unique_ptr; + using connection = std::variant; + using connection_map = std::map; + using unix_url_split_map = std::map; + using error_code = boost::system::error_code; + using deadline_type = boost::posix_time::ptime; + + http_client_impl() + :_ioc() + ,_sslc(ssl::context::sslv23_client) + { + set_verify_peers(true); + } + + void add_cert(const std::string& cert_pem_string) { + error_code ec; + _sslc.add_certificate_authority(boost::asio::buffer(cert_pem_string.data(), cert_pem_string.size()), ec); + FC_ASSERT(!ec, "Failed to add cert: ${msg}", ("msg", ec.message())); + } + + void set_verify_peers(bool enabled) { + if (enabled) { + _sslc.set_verify_mode(ssl::verify_peer); + } else { + _sslc.set_verify_mode(ssl::verify_none); + } + } + + template + error_code sync_do_with_deadline( SyncReadStream& s, deadline_type deadline, Fn f, CancelFn cf ) { + bool timer_expired = false; + boost::asio::deadline_timer timer(_ioc); + + timer.expires_at(deadline); + bool timer_cancelled = false; + timer.async_wait([&timer_expired, &timer_cancelled] (const error_code&) { + // the only non-success error_code this is called with is operation_aborted but since + // we could have queued "success" when we cancelled the timer, we set a flag at the + // safer scope and only respect that. + if (!timer_cancelled) { + timer_expired = true; + } + }); + + std::optional f_result; + f(f_result); + + _ioc.restart(); + while (_ioc.run_one()) + { + if (f_result) { + timer_cancelled = true; + timer.cancel(); + } else if (timer_expired) { + cf(); + } + } + + if (!timer_expired) { + return *f_result; + } else { + return error_code(boost::system::errc::timed_out, boost::system::system_category()); + } + } + + template + error_code sync_do_with_deadline( SyncReadStream& s, deadline_type deadline, Fn f) { + return sync_do_with_deadline(s, deadline, f, [&s](){ + s.lowest_layer().cancel(); + }); + }; + + template + error_code sync_connect_with_timeout( SyncReadStream& s, const std::string& host, const std::string& port, const deadline_type& deadline ) { + tcp::resolver local_resolver(_ioc); + bool cancelled = false; + + auto res = sync_do_with_deadline(s, deadline, [&local_resolver, &cancelled, &s, &host, &port](std::optional& final_ec){ + local_resolver.async_resolve(host, port, [&cancelled, &s, &final_ec](const error_code& ec, tcp::resolver::results_type resolved ){ + if (ec) { + final_ec.emplace(ec); + return; + } + + if (!cancelled) { + boost::asio::async_connect(s, resolved.begin(), resolved.end(), [&final_ec](const error_code& ec, tcp::resolver::iterator ){ + final_ec.emplace(ec); + }); + } + }); + },[&local_resolver, &cancelled](){ + cancelled = true; + local_resolver.cancel(); + }); + + return res; + }; + + template + error_code sync_write_with_timeout(SyncReadStream& s, http::request& req, const deadline_type& deadline ) { + return sync_do_with_deadline(s, deadline, [&s, &req](std::optional& final_ec){ + http::async_write(s, req, [&final_ec]( const error_code& ec, std::size_t ) { + final_ec.emplace(ec); + }); + }); + } + + template + error_code sync_read_with_timeout(SyncReadStream& s, boost::beast::flat_buffer& buffer, http::response& res, const deadline_type& deadline ) { + return sync_do_with_deadline(s, deadline, [&s, &buffer, &res](std::optional& final_ec){ + http::async_read(s, buffer, res, [&final_ec]( const error_code& ec, std::size_t ) { + final_ec.emplace(ec); + }); + }); + } + + host_key url_to_host_key( const url& dest ) { + FC_ASSERT(dest.host(), "Provided URL has no host"); + uint16_t port = 80; + if (dest.port()) { + port = *dest.port(); + } + + return std::make_tuple(dest.proto(), *dest.host(), port); + } + + connection_map::iterator create_unix_connection( const url& dest, const deadline_type& deadline) { + auto key = url_to_host_key(dest); + auto socket = std::make_unique(_ioc); + + error_code ec; + socket->connect(local::stream_protocol::endpoint(*dest.host()), ec); + FC_ASSERT(!ec, "Failed to connect: ${message}", ("message",ec.message())); + + auto res = _connections.emplace(std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple(std::move(socket))); + + return res.first; + } + + connection_map::iterator create_raw_connection( const url& dest, const deadline_type& deadline ) { + auto key = url_to_host_key(dest); + auto socket = std::make_unique(_ioc); + + error_code ec = sync_connect_with_timeout(*socket, *dest.host(), dest.port() ? std::to_string(*dest.port()) : "80", deadline); + FC_ASSERT(!ec, "Failed to connect: ${message}", ("message",ec.message())); + + auto res = _connections.emplace(std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple(std::move(socket))); + + return res.first; + } + + connection_map::iterator create_ssl_connection( const url& dest, const deadline_type& deadline ) { + auto key = url_to_host_key(dest); + auto ssl_socket = std::make_unique>(_ioc, _sslc); + + // Set SNI Hostname (many hosts need this to handshake successfully) + if(!SSL_set_tlsext_host_name(ssl_socket->native_handle(), dest.host()->c_str())) + { + error_code ec{static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category()}; + FC_THROW("Unable to set SNI Host Name: ${msg}", ("msg", ec.message())); + } + + ssl_socket->set_verify_callback(boost::asio::ssl::rfc2818_verification(*dest.host())); + + error_code ec = sync_connect_with_timeout(ssl_socket->next_layer(), *dest.host(), dest.port() ? std::to_string(*dest.port()) : "443", deadline); + if (!ec) { + ec = sync_do_with_deadline(ssl_socket->next_layer(), deadline, [&ssl_socket](std::optional& final_ec) { + ssl_socket->async_handshake(ssl::stream_base::client, [&final_ec](const error_code& ec) { + final_ec.emplace(ec); + }); + }); + } + FC_ASSERT(!ec, "Failed to connect: ${message}", ("message",ec.message())); + + auto res = _connections.emplace(std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple(std::move(ssl_socket))); + + return res.first; + } + + connection_map::iterator create_connection( const url& dest, const deadline_type& deadline ) { + if (dest.proto() == "http") { + return create_raw_connection(dest, deadline); + } else if (dest.proto() == "https") { + return create_ssl_connection(dest, deadline); + } else if (dest.proto() == "unix") { + return create_unix_connection(dest, deadline); + } else { + FC_THROW("Unknown protocol ${proto}", ("proto", dest.proto())); + } + } + + struct check_closed_visitor : public visitor { + bool operator() ( const raw_socket_ptr& ptr ) const { + return !ptr->is_open(); + } + + bool operator() ( const ssl_socket_ptr& ptr ) const { + return !ptr->lowest_layer().is_open(); + } + + bool operator() ( const unix_socket_ptr& ptr) const { + return !ptr->is_open(); + } + }; + + bool check_closed( const connection_map::iterator& conn_itr ) { + if (std::visit(check_closed_visitor(), conn_itr->second)) { + _connections.erase(conn_itr); + return true; + } else { + return false; + } + } + + connection_map::iterator get_connection( const url& dest, const deadline_type& deadline ) { + auto key = url_to_host_key(dest); + auto conn_itr = _connections.find(key); + if (conn_itr == _connections.end() || check_closed(conn_itr)) { + return create_connection(dest, deadline); + } else { + return conn_itr; + } + } + + struct write_request_visitor : visitor { + write_request_visitor(http_client_impl* that, http::request& req, const deadline_type& deadline) + :that(that) + ,req(req) + ,deadline(deadline) + {} + + template + error_code operator() ( S& stream ) const { + return that->sync_write_with_timeout(*stream, req, deadline); + } + + http_client_impl* that; + http::request& req; + const deadline_type& deadline; + }; + + struct read_response_visitor : visitor { + read_response_visitor(http_client_impl* that, boost::beast::flat_buffer& buffer, http::response& res, const deadline_type& deadline) + :that(that) + ,buffer(buffer) + ,res(res) + ,deadline(deadline) + {} + + template + error_code operator() ( S& stream ) const { + return that->sync_read_with_timeout(*stream, buffer, res, deadline); + } + + http_client_impl* that; + boost::beast::flat_buffer& buffer; + http::response& res; + const deadline_type& deadline; + }; + + variant post_sync(const url& dest, const variant& payload, const fc::time_point& _deadline) { + static const deadline_type epoch(boost::gregorian::date(1970, 1, 1)); + auto deadline = epoch + boost::posix_time::microseconds(_deadline.time_since_epoch().count()); + FC_ASSERT(dest.host(), "No host set on URL"); + + string path = dest.path() ? dest.path()->generic_string() : "/"; + if (dest.query()) { + path = path + "?" + *dest.query(); + } + + string host_str = *dest.host(); + if (dest.port()) { + auto port = *dest.port(); + auto proto_iter = default_proto_ports.find(dest.proto()); + if (proto_iter != default_proto_ports.end() && proto_iter->second != port) { + host_str = host_str + ":" + std::to_string(port); + } + } + + http::request req{http::verb::post, path, 11}; + req.set(http::field::host, host_str); + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + req.set(http::field::content_type, "application/json"); + req.keep_alive(true); + req.body() = json::to_string(payload, _deadline); + req.prepare_payload(); + + auto conn_iter = get_connection(dest, deadline); + auto eraser = make_scoped_exit([this, &conn_iter](){ + _connections.erase(conn_iter); + }); + + // Send the HTTP request to the remote host + error_code ec = std::visit(write_request_visitor(this, req, deadline), conn_iter->second); + FC_ASSERT(!ec, "Failed to send request: ${message}", ("message",ec.message())); + + // This buffer is used for reading and must be persisted + boost::beast::flat_buffer buffer; + + // Declare a container to hold the response + http::response res; + + // Receive the HTTP response + ec = std::visit(read_response_visitor(this, buffer, res, deadline), conn_iter->second); + FC_ASSERT(!ec, "Failed to read response: ${message}", ("message",ec.message())); + + // if the connection can be kept open, keep it open + if (res.keep_alive()) { + eraser.cancel(); + } + + fc::variant result; + if( !res.body().empty() ) { + try { + result = json::from_string( res.body() ); + } catch( ... ) {} + } + if (res.result() == http::status::internal_server_error) { + fc::exception_ptr excp; + try { + auto err_var = result.get_object()["error"].get_object(); + excp = std::make_shared(err_var["code"].as_int64(), err_var["name"].as_string(), err_var["what"].as_string()); + + if (err_var.contains("details")) { + for (const auto& dvar : err_var["details"].get_array()) { + excp->append_log(FC_LOG_MESSAGE(error, dvar.get_object()["message"].as_string())); + } + } + } catch( ... ) { + + } + + if (excp) { + throw *excp; + } else { + FC_THROW("Request failed with 500 response, but response was not parseable"); + } + } else if (res.result() == http::status::not_found) { + FC_THROW("URL not found: ${url}", ("url", (std::string)dest)); + } + + return result; + } + + /* + Unix URLs work a little special here. They'll originally be in the format of + unix:///home/username/eosio-wallet/keosd.sock/v1/wallet/sign_digest + for example. When the fc::url is given to http_client in post_sync(), this will + have proto=unix and host=/home/username/eosio-wallet/keosd.sock/v1/wallet/sign_digest + + At this point we still don't know what part of the above string is the unix socket path + and which part is the path to access on the server. This function discovers that + host=/home/username/eosio-wallet/keosd.sock and path=/v1/wallet/sign_digest + and creates another fc::url that will be used downstream of the http_client::post_sync() + call. + */ + const fc::url& get_unix_url(const std::string& full_url) { + unix_url_split_map::const_iterator found = _unix_url_paths.find(full_url); + if(found != _unix_url_paths.end()) + return found->second; + + boost::filesystem::path socket_file(full_url); + if(socket_file.is_relative()) + FC_THROW_EXCEPTION( parse_error_exception, "socket url cannot be relative (${url})", ("url", socket_file.string())); + if(socket_file.empty()) + FC_THROW_EXCEPTION( parse_error_exception, "missing socket url"); + boost::filesystem::path url_path; + do { + if(boost::filesystem::status(socket_file).type() == boost::filesystem::socket_file) + break; + url_path = socket_file.filename() / url_path; + socket_file = socket_file.remove_filename(); + } while(!socket_file.empty()); + if(socket_file.empty()) + FC_THROW_EXCEPTION( parse_error_exception, "couldn't discover socket path"); + url_path = "/" / url_path; + return _unix_url_paths.emplace(full_url, fc::url("unix", socket_file.string(), ostring(), ostring(), url_path.string(), ostring(), ovariant_object(), std::optional())).first->second; + } + + boost::asio::io_context _ioc; + ssl::context _sslc; + connection_map _connections; + unix_url_split_map _unix_url_paths; +}; + + +http_client::http_client() +:_my(new http_client_impl()) +{ + +} + +variant http_client::post_sync(const url& dest, const variant& payload, const fc::time_point& deadline) { + if(dest.proto() == "unix") + return _my->post_sync(_my->get_unix_url(*dest.host()), payload, deadline); + else + return _my->post_sync(dest, payload, deadline); +} + +void http_client::add_cert(const std::string& cert_pem_string) { + _my->add_cert(cert_pem_string); +} + +void http_client::set_verify_peers(bool enabled) { + _my->set_verify_peers(enabled); +} + +http_client::~http_client() { + +} + +} diff --git a/libraries/libfc/src/network/ip.cpp b/libraries/libfc/src/network/ip.cpp new file mode 100644 index 0000000000..1961659e4e --- /dev/null +++ b/libraries/libfc/src/network/ip.cpp @@ -0,0 +1,158 @@ +#include +#include +#include +#include +#include +#include + +namespace fc { namespace ip { + + address::address( uint32_t ip ) + :_ip(ip){} + + address::address( const fc::string& s ) + { + try + { + _ip = boost::asio::ip::address_v4::from_string(s.c_str()).to_ulong(); + } + FC_RETHROW_EXCEPTIONS(error, "Error parsing IP address ${address}", ("address", s)) + } + + bool operator==( const address& a, const address& b ) { + return uint32_t(a) == uint32_t(b); + } + bool operator!=( const address& a, const address& b ) { + return uint32_t(a) != uint32_t(b); + } + + address& address::operator=( const fc::string& s ) + { + try + { + _ip = boost::asio::ip::address_v4::from_string(s.c_str()).to_ulong(); + } + FC_RETHROW_EXCEPTIONS(error, "Error parsing IP address ${address}", ("address", s)) + return *this; + } + + address::operator fc::string()const + { + try + { + return boost::asio::ip::address_v4(_ip).to_string().c_str(); + } + FC_RETHROW_EXCEPTIONS(error, "Error parsing IP address to string") + } + address::operator uint32_t()const { + return _ip; + } + + + endpoint::endpoint() + :_port(0){ } + endpoint::endpoint(const address& a, uint16_t p) + :_port(p),_ip(a){} + + bool operator==( const endpoint& a, const endpoint& b ) { + return a._port == b._port && a._ip == b._ip; + } + bool operator!=( const endpoint& a, const endpoint& b ) { + return a._port != b._port || a._ip != b._ip; + } + + bool operator< ( const endpoint& a, const endpoint& b ) + { + return uint32_t(a.get_address()) < uint32_t(b.get_address()) || + (uint32_t(a.get_address()) == uint32_t(b.get_address()) && + uint32_t(a.port()) < uint32_t(b.port())); + } + + uint16_t endpoint::port()const { return _port; } + const address& endpoint::get_address()const { return _ip; } + + endpoint endpoint::from_string( const string& endpoint_string ) + { + try + { + endpoint ep; + auto pos = endpoint_string.find(':'); + ep._ip = boost::asio::ip::address_v4::from_string(endpoint_string.substr( 0, pos ) ).to_ulong(); + ep._port = boost::lexical_cast( endpoint_string.substr( pos+1, endpoint_string.size() ) ); + return ep; + } + FC_RETHROW_EXCEPTIONS(warn, "error converting string to IP endpoint") + } + + endpoint::operator string()const + { + try + { + return string(_ip) + ':' + fc::string(boost::lexical_cast(_port).c_str()); + } + FC_RETHROW_EXCEPTIONS(warn, "error converting IP endpoint to string") + } + + /** + * @return true if the ip is in the following ranges: + * + * 10.0.0.0 to 10.255.255.255 + * 172.16.0.0 to 172.31.255.255 + * 192.168.0.0 to 192.168.255.255 + * 169.254.0.0 to 169.254.255.255 + * + */ + bool address::is_private_address()const + { + static address min10_ip("10.0.0.0"); + static address max10_ip("10.255.255.255"); + static address min172_ip("172.16.0.0"); + static address max172_ip("172.31.255.255"); + static address min192_ip("192.168.0.0"); + static address max192_ip("192.168.255.255"); + static address min169_ip("169.254.0.0"); + static address max169_ip("169.254.255.255"); + if( _ip >= min10_ip._ip && _ip <= max10_ip._ip ) return true; + if( _ip >= min172_ip._ip && _ip <= max172_ip._ip ) return true; + if( _ip >= min192_ip._ip && _ip <= max192_ip._ip ) return true; + if( _ip >= min169_ip._ip && _ip <= max169_ip._ip ) return true; + return false; + } + + /** + * 224.0.0.0 to 239.255.255.255 + */ + bool address::is_multicast_address()const + { + static address min_ip("224.0.0.0"); + static address max_ip("239.255.255.255"); + return _ip >= min_ip._ip && _ip <= max_ip._ip; + } + + /** !private & !multicast */ + bool address::is_public_address()const + { + return !( is_private_address() || is_multicast_address() ); + } + +} // namespace ip + + void to_variant( const ip::endpoint& var, variant& vo ) + { + vo = fc::string(var); + } + void from_variant( const variant& var, ip::endpoint& vo ) + { + vo = ip::endpoint::from_string(var.as_string()); + } + + void to_variant( const ip::address& var, variant& vo ) + { + vo = fc::string(var); + } + void from_variant( const variant& var, ip::address& vo ) + { + vo = ip::address(var.as_string()); + } + +} diff --git a/libraries/libfc/src/network/ntp.cpp b/libraries/libfc/src/network/ntp.cpp new file mode 100644 index 0000000000..c73880278c --- /dev/null +++ b/libraries/libfc/src/network/ntp.cpp @@ -0,0 +1,265 @@ +#include +#include +#include +#include +#include + +#include +#include "../byteswap.hpp" + +#include +#include + +namespace fc +{ + namespace detail { + using boost::fibers::future; + + class ntp_impl + { + public: + /** vector < host, port > */ + fc::thread _ntp_thread; + std::vector< std::pair< std::string, uint16_t> > _ntp_hosts; + future _read_loop_done; + udp_socket _sock; + uint32_t _request_interval_sec; + uint32_t _retry_failed_request_interval_sec; + fc::time_point _last_valid_ntp_reply_received_time; + + std::atomic_bool _last_ntp_delta_initialized; + std::atomic _last_ntp_delta_microseconds; + + future _request_time_task_done; + + std::shared_ptr> _scheduled_request_time; + + ntp_impl() : + _ntp_thread("ntp"), + _request_interval_sec( 60*60 /* 1 hr */), + _retry_failed_request_interval_sec(60 * 5), + _last_ntp_delta_microseconds(0) + { + _last_ntp_delta_initialized = false; + _ntp_hosts.push_back( std::make_pair( "pool.ntp.org",123 ) ); + } + + ~ntp_impl() + { + _sock.close(); + if( _scheduled_request_time ) { + _scheduled_request_time->cancel(); + _scheduled_request_time->get_future().wait(); + _scheduled_request_time.reset(); + } + } + + fc::time_point ntp_timestamp_to_fc_time_point(uint64_t ntp_timestamp_net_order) + { + uint64_t ntp_timestamp_host = bswap_64(ntp_timestamp_net_order); + uint32_t fractional_seconds = ntp_timestamp_host & 0xffffffff; + uint32_t microseconds = (uint32_t)((((uint64_t)fractional_seconds * 1000000) + (uint64_t(1) << 31)) >> 32); + uint32_t seconds_since_1900 = ntp_timestamp_host >> 32; + uint32_t seconds_since_epoch = seconds_since_1900 - 2208988800; + return fc::time_point() + fc::seconds(seconds_since_epoch) + fc::microseconds(microseconds); + } + + uint64_t fc_time_point_to_ntp_timestamp(const fc::time_point& fc_timestamp) + { + uint64_t microseconds_since_epoch = (uint64_t)fc_timestamp.time_since_epoch().count(); + uint32_t seconds_since_epoch = (uint32_t)(microseconds_since_epoch / 1000000); + uint32_t seconds_since_1900 = seconds_since_epoch + 2208988800; + uint32_t microseconds = microseconds_since_epoch % 1000000; + uint32_t fractional_seconds = (uint32_t)((((uint64_t)microseconds << 32) + (uint64_t(1) << 31)) / 1000000); + uint64_t ntp_timestamp_net_order = ((uint64_t)seconds_since_1900 << 32) + fractional_seconds; + return bswap_64(ntp_timestamp_net_order); + } + + void request_now() + { + FC_ASSERT(_ntp_thread.is_current()); + for( auto item : _ntp_hosts ) + { + try + { + wlog( "resolving... ${r}", ("r", item) ); + auto eps = resolve( item.first, item.second ); + for( auto ep : eps ) + { + wlog( "sending request to ${ep}", ("ep",ep) ); + std::shared_ptr send_buffer(new char[48], [](char* p){ delete[] p; }); + std::array packet_to_send { {010,0,0,0,0,0,0,0,0} }; + memcpy(send_buffer.get(), packet_to_send.data(), packet_to_send.size()); + uint64_t* send_buf_as_64_array = (uint64_t*)send_buffer.get(); + send_buf_as_64_array[5] = fc_time_point_to_ntp_timestamp(fc::time_point::now()); // 5 = Transmit Timestamp + _sock.send_to(send_buffer, packet_to_send.size(), ep); + break; + } + } + catch (const fc::canceled_exception&) + { + throw; + } + catch ( const std::bad_alloc& ) + { + throw; + } + catch ( const boost::interprocess::bad_alloc& ) + { + throw; + } + // this could fail to resolve but we want to go on to other hosts.. + catch ( const fc::exception& e ) + { + elog( "${e}", ("e",e.to_detail_string() ) ); + } + catch ( const std::exception& e ) + { + elog( "${e}", ("e",e.what() ) ); + } + } + } // request_now + + // started for first time in ntp() constructor, canceled in ~ntp() destructor + // this task gets invoked every _retry_failed_request_interval_sec (currently 5 min), and if + // _request_interval_sec (currently 1 hour) has passed since the last successful update, + // it sends a new request + void request_time_task() + { + request_now(); + } // request_loop + + void start_read_loop() + { + _read_loop_done = _ntp_thread.async( [this](){ read_loop(); } ); + } + + void read_loop() + { + FC_ASSERT(_ntp_thread.is_current()); + + uint32_t receive_buffer_size = sizeof(uint64_t) * 1024; + std::shared_ptr receive_buffer(new char[receive_buffer_size], [](char* p){ delete[] p; }); + uint64_t* recv_buf = (uint64_t*)receive_buffer.get(); + + // if you start the read while loop here, the recieve_from call will throw "invalid argument" on win32, + // so instead we start the loop after making our first request + _sock.open(); + _request_time_task_done = fc::async( [&](){ request_time_task(); } ); + + while( true ) + { + fc::ip::endpoint from; + try + { + _sock.receive_from( receive_buffer, receive_buffer_size, from ); + // wlog("received ntp reply from ${from}",("from",from) ); + } FC_RETHROW_EXCEPTIONS(error, "Error reading from NTP socket"); + + fc::time_point receive_time = fc::time_point::now(); + fc::time_point origin_time = ntp_timestamp_to_fc_time_point(recv_buf[3]); + fc::time_point server_receive_time = ntp_timestamp_to_fc_time_point(recv_buf[4]); + fc::time_point server_transmit_time = ntp_timestamp_to_fc_time_point(recv_buf[5]); + + fc::microseconds offset(((server_receive_time - origin_time) + + (server_transmit_time - receive_time)).count() / 2); + fc::microseconds round_trip_delay((receive_time - origin_time) - + (server_transmit_time - server_receive_time)); + //wlog("origin_time = ${origin_time}, server_receive_time = ${server_receive_time}, server_transmit_time = ${server_transmit_time}, receive_time = ${receive_time}", + // ("origin_time", origin_time)("server_receive_time", server_receive_time)("server_transmit_time", server_transmit_time)("receive_time", receive_time)); + // wlog("ntp offset: ${offset}, round_trip_delay ${delay}", ("offset", offset)("delay", round_trip_delay)); + + //if the reply we just received has occurred more than a second after our last time request (it was more than a second ago since our last request) + if( round_trip_delay > fc::microseconds(300000) ) + { + wlog("received stale ntp reply requested at ${request_time}, send a new time request", ("request_time", origin_time)); + request_now(); //request another reply and ignore this one + } + else //we think we have a timely reply, process it + { + if( offset < fc::seconds(60*60*24) && offset > fc::seconds(-60*60*24) ) + { + _last_ntp_delta_microseconds = offset.count(); + _last_ntp_delta_initialized = true; + fc::microseconds ntp_delta_time = fc::microseconds(_last_ntp_delta_microseconds); + _last_valid_ntp_reply_received_time = receive_time; + wlog("ntp_delta_time updated to ${delta_time} us", ("delta_time",ntp_delta_time) ); + } + else + elog( "NTP time and local time vary by more than a day! ntp:${ntp_time} local:${local}", + ("ntp_time", receive_time + offset)("local", fc::time_point::now()) ); + } + } + wlog("exiting ntp read_loop"); + } //end read_loop() + + void reschedule() { + if( _scheduled_request_time ) + _scheduled_request_time->cancel(); + + _scheduled_request_time = _ntp_thread.schedule( + [&](){ + request_now(); + reschedule(); + }, + fc::time_point::now() + fc::seconds(_request_interval_sec) ); + } + }; //ntp_impl + + } // namespace detail + + + + ntp::ntp() + :my( new detail::ntp_impl() ) + { + my->start_read_loop(); + } + + ntp::~ntp() + { + ilog( "shutting down ntp" ); + my->_ntp_thread.async([=](){ + my->_sock.close(); + if( my->_scheduled_request_time ) { + ilog( "wait cancel scheduled request " ); + my->_scheduled_request_time->cancel(); + my->_scheduled_request_time->get_future().wait(); + my->_scheduled_request_time.reset(); + } + ilog( "wait request time task " ); + my->_request_time_task_done.wait(); + ilog( "wait read loop " ); + my->_read_loop_done.wait(); + }).wait(); + my->_ntp_thread.quit(); + ilog( "joining ntp" ); + my->_ntp_thread.join(); + } + + + void ntp::add_server( const std::string& hostname, uint16_t port) + { + my->_ntp_thread.async( [=](){ my->_ntp_hosts.push_back( std::make_pair(hostname,port) ); }).wait(); + } + + void ntp::set_request_interval( uint32_t interval_sec ) + { + my->_request_interval_sec = interval_sec; + my->_retry_failed_request_interval_sec = std::min(my->_retry_failed_request_interval_sec, interval_sec); + my->reschedule(); + } + + void ntp::request_now() + { + my->_ntp_thread.async( [=](){ my->request_now(); } ).get(); + } + + std::optional ntp::get_time()const + { + if( my->_last_ntp_delta_initialized ) + return fc::time_point::now() + fc::microseconds(my->_last_ntp_delta_microseconds); + return std::optional(); + } + +} //namespace fc diff --git a/libraries/libfc/src/network/platform_root_ca.cpp b/libraries/libfc/src/network/platform_root_ca.cpp new file mode 100644 index 0000000000..31c393fcda --- /dev/null +++ b/libraries/libfc/src/network/platform_root_ca.cpp @@ -0,0 +1,126 @@ +/** + * @file + * @copyright defined in LICENSE.txt; Parts of this file Copyright (c) 2009 The Go Authors + */ +#include +#include + +#include + +#include + +#if defined(__APPLE__) +#include +#include +#endif + +namespace fc { + +#if defined(__APPLE__) +static void add_macos_root_cas(boost::asio::ssl::context& ctx) { + boost::container::flat_set trusted_certs; + boost::container::flat_set untrusted_certs; + + SecTrustSettingsDomain domains[] = { kSecTrustSettingsDomainSystem, + kSecTrustSettingsDomainAdmin, + kSecTrustSettingsDomainUser }; + + unsigned int number_domains = sizeof(domains)/sizeof(domains[0]); + + for (unsigned int i = 0; i < number_domains; i++) { + CFArrayRef certs; + OSStatus err = SecTrustSettingsCopyCertificates(domains[i], &certs); + if(err != noErr) + continue; + + for(CFIndex j = 0; j < CFArrayGetCount(certs); ++j) { + CFArrayRef trustSettings = nullptr; + SecCertificateRef cert = (SecCertificateRef)CFArrayGetValueAtIndex(certs, j); + if(cert == nullptr) + continue; + + bool untrusted{false}, trust_as_root{i == 0}, trust_root{false}; + if(i != 0) { + for (unsigned int k = i; k < number_domains; k++) { + CFArrayRef domainTrustSettings = nullptr; + err = SecTrustSettingsCopyTrustSettings(cert, domains[k], &domainTrustSettings); + if (err == errSecSuccess && domainTrustSettings != nullptr) { + if(trustSettings) + CFRelease(trustSettings); + trustSettings = domainTrustSettings; + } + } + if(trustSettings == nullptr) + continue; + if(CFArrayGetCount(trustSettings) == 0) + trust_as_root = true; + else for(CFIndex k = 0; k < CFArrayGetCount(trustSettings); k++) { + CFNumberRef cfNum; + CFDictionaryRef tSetting = (CFDictionaryRef)CFArrayGetValueAtIndex(trustSettings, k); + if(CFDictionaryGetValueIfPresent(tSetting, kSecTrustSettingsResult, (const void**)&cfNum)){ + SInt32 result = 0; + CFNumberGetValue(cfNum, kCFNumberSInt32Type, &result); + if(result == kSecTrustSettingsResultDeny) + untrusted = true; + else if (result == kSecTrustSettingsResultTrustAsRoot) + trust_as_root = true; + else if (result == kSecTrustSettingsResultTrustRoot) + trust_root = true; + } + } + CFRelease(trustSettings); + } + + //double check that these manually trusted ones are actually CAs + if(trust_root) { + CFErrorRef errRef = nullptr; + CFDataRef subjectName = SecCertificateCopyNormalizedSubjectContent(cert, &errRef); + if(errRef != nullptr) { + CFRelease(errRef); + continue; + } + CFDataRef issuerName = SecCertificateCopyNormalizedIssuerContent(cert, &errRef); + if(errRef != nullptr) { + CFRelease(subjectName); + CFRelease(errRef); + continue; + } + Boolean equal = CFEqual(subjectName, issuerName); + CFRelease(subjectName); + CFRelease(issuerName); + if(!equal) + continue; + } + + CFDataRef certAsPEM; + err = SecKeychainItemExport(cert, kSecFormatX509Cert, kSecItemPemArmour, nullptr, &certAsPEM); + if(err != noErr) + continue; + if(certAsPEM) { + if(!trust_root && !trust_as_root) + untrusted_certs.emplace((const char*)CFDataGetBytePtr(certAsPEM), CFDataGetLength(certAsPEM)); + else + trusted_certs.emplace((const char*)CFDataGetBytePtr(certAsPEM), CFDataGetLength(certAsPEM)); + CFRelease(certAsPEM); + } + } + CFRelease(certs); + } + for(const auto& untrusted : untrusted_certs) + trusted_certs.erase(untrusted); + boost::system::error_code dummy; + for(const auto& trusted : trusted_certs) + ctx.add_certificate_authority(boost::asio::const_buffer(trusted.data(), trusted.size()), dummy); +} +#endif + +void add_platform_root_cas_to_context(boost::asio::ssl::context& ctx) { +#if defined( __APPLE__ ) + add_macos_root_cas(ctx); +#elif defined( _WIN32 ) + FC_THROW("HTTPS on Windows not supported"); +#else + ctx.set_default_verify_paths(); +#endif +} +} \ No newline at end of file diff --git a/libraries/libfc/src/network/rate_limiting.cpp b/libraries/libfc/src/network/rate_limiting.cpp new file mode 100644 index 0000000000..a2351948cf --- /dev/null +++ b/libraries/libfc/src/network/rate_limiting.cpp @@ -0,0 +1,547 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc +{ + + namespace detail + { + // data about a read or write we're managing + class rate_limited_operation + { + public: + size_t length; + size_t offset; + size_t permitted_length; + promise::ptr completion_promise; + + rate_limited_operation(size_t length, + size_t offset, + promise::ptr&& completion_promise) : + length(length), + offset(offset), + permitted_length(0), + completion_promise(completion_promise) + {} + + virtual void perform_operation() = 0; + }; + + class rate_limited_tcp_write_operation : public rate_limited_operation + { + public: + boost::asio::ip::tcp::socket& socket; + const char* raw_buffer; + std::shared_ptr shared_buffer; + + // QUESTION: Why would this version of the constructor ever be called if it will abort the program if built as DEBUG? + // Commenting out for now since this file is not even included in the fc library build. + /* + rate_limited_tcp_write_operation(boost::asio::ip::tcp::socket& socket, + const char* buffer, + size_t length, + size_t offset, + promise::ptr completion_promise) : + rate_limited_operation(length, offset, std::move(completion_promise)), + socket(socket), + raw_buffer(buffer) + { + assert(false); + } + */ + rate_limited_tcp_write_operation(boost::asio::ip::tcp::socket& socket, + const std::shared_ptr& buffer, + size_t length, + size_t offset, + promise::ptr completion_promise) : + rate_limited_operation(length, offset, std::move(completion_promise)), + socket(socket), + raw_buffer(nullptr), + shared_buffer(buffer) + {} + virtual void perform_operation() override + { + if (raw_buffer) + asio::async_write_some(socket, + raw_buffer, permitted_length, + completion_promise); + else + asio::async_write_some(socket, + shared_buffer, permitted_length, offset, + completion_promise); + } + }; + + class rate_limited_tcp_read_operation : public rate_limited_operation + { + public: + boost::asio::ip::tcp::socket& socket; + char* raw_buffer; + std::shared_ptr shared_buffer; + + rate_limited_tcp_read_operation(boost::asio::ip::tcp::socket& socket, + char* buffer, + size_t length, + size_t offset, + promise::ptr completion_promise) : + rate_limited_operation(length, offset, std::move(completion_promise)), + socket(socket), + raw_buffer(buffer) + {} + rate_limited_tcp_read_operation(boost::asio::ip::tcp::socket& socket, + const std::shared_ptr& buffer, + size_t length, + size_t offset, + promise::ptr completion_promise) : + rate_limited_operation(length, offset, std::move(completion_promise)), + socket(socket), + raw_buffer(nullptr), + shared_buffer(buffer) + {} + virtual void perform_operation() override + { + if (raw_buffer) + asio::async_read_some(socket, + raw_buffer, permitted_length, + completion_promise); + else + asio::async_read_some(socket, + shared_buffer, permitted_length, offset, + completion_promise); + + } + }; + + struct is_operation_shorter + { + // less than operator designed to bring the shortest operations to the end + bool operator()(const rate_limited_operation* lhs, const rate_limited_operation* rhs) + { + return lhs->length > rhs->length; + } + }; + + class average_rate_meter + { + private: + mutable double _average_rate; + mutable uint32_t _unaccounted_bytes; + mutable time_point _last_update_time; + microseconds _time_constant; + + void update_const(uint32_t bytes_transferred = 0) const; + public: + average_rate_meter(const microseconds& time_constant = seconds(10)); + void set_time_constant(const microseconds& time_constant); + void update(uint32_t bytes_transferred = 0); + uint32_t get_average_rate() const; + }; + average_rate_meter::average_rate_meter(const microseconds& time_constant) : + _average_rate(0.), + _unaccounted_bytes(0), + _last_update_time(time_point_sec::min()), + _time_constant(time_constant) + {} + void average_rate_meter::set_time_constant(const microseconds& time_constant) + { + _time_constant = time_constant; + } + void average_rate_meter::update(uint32_t bytes_transferred /* = 0 */) + { + update_const(bytes_transferred); + } + void average_rate_meter::update_const(uint32_t bytes_transferred /* = 0 */) const + { + time_point now = time_point::now(); + if (now <= _last_update_time) + _unaccounted_bytes += bytes_transferred; + else + { + microseconds time_since_last_update = now - _last_update_time; + if (time_since_last_update > _time_constant) + _average_rate = bytes_transferred / (_time_constant.count() / (double)seconds(1).count()); + else + { + bytes_transferred += _unaccounted_bytes; + double seconds_since_last_update = time_since_last_update.count() / (double)seconds(1).count(); + double rate_since_last_update = bytes_transferred / seconds_since_last_update; + double alpha = time_since_last_update.count() / (double)_time_constant.count(); + _average_rate = rate_since_last_update * alpha + _average_rate * (1.0 - alpha); + } + _last_update_time = now; + _unaccounted_bytes = 0; + } + } + uint32_t average_rate_meter::get_average_rate() const + { + update_const(); + return (uint32_t)_average_rate; + } + + class rate_limiting_group_impl : public tcp_socket_io_hooks + { + public: + uint32_t _upload_bytes_per_second; + uint32_t _download_bytes_per_second; + uint32_t _burstiness_in_seconds; + + microseconds _granularity; // how often to add tokens to the bucket + uint32_t _read_tokens; + uint32_t _unused_read_tokens; // gets filled with tokens for unused bytes (if I'm allowed to read 200 bytes and I try to read 200 bytes, but can only read 50, tokens for the other 150 get returned here) + uint32_t _write_tokens; + uint32_t _unused_write_tokens; + + typedef std::list rate_limited_operation_list; + rate_limited_operation_list _read_operations_in_progress; + rate_limited_operation_list _read_operations_for_next_iteration; + rate_limited_operation_list _write_operations_in_progress; + rate_limited_operation_list _write_operations_for_next_iteration; + + time_point _last_read_iteration_time; + time_point _last_write_iteration_time; + + future _process_pending_reads_loop_complete; + promise::ptr _new_read_operation_available_promise; + future _process_pending_writes_loop_complete; + promise::ptr _new_write_operation_available_promise; + + average_rate_meter _actual_upload_rate; + average_rate_meter _actual_download_rate; + + rate_limiting_group_impl(uint32_t upload_bytes_per_second, uint32_t download_bytes_per_second, + uint32_t burstiness_in_seconds = 1); + ~rate_limiting_group_impl(); + + virtual size_t readsome(boost::asio::ip::tcp::socket& socket, char* buffer, size_t length) override; + virtual size_t readsome(boost::asio::ip::tcp::socket& socket, const std::shared_ptr& buffer, size_t length, size_t offset) override; + template + size_t readsome_impl(boost::asio::ip::tcp::socket& socket, const BufferType& buffer, size_t length, size_t offset); + virtual size_t writesome(boost::asio::ip::tcp::socket& socket, const char* buffer, size_t length) override; + virtual size_t writesome(boost::asio::ip::tcp::socket& socket, const std::shared_ptr& buffer, size_t length, size_t offset) override; + template + size_t writesome_impl(boost::asio::ip::tcp::socket& socket, const BufferType& buffer, size_t length, size_t offset); + + void process_pending_reads(); + void process_pending_writes(); + void process_pending_operations(time_point& last_iteration_start_time, + uint32_t& limit_bytes_per_second, + rate_limited_operation_list& operations_in_progress, + rate_limited_operation_list& operations_for_next_iteration, + uint32_t& tokens, + uint32_t& unused_tokens); + }; + + rate_limiting_group_impl::rate_limiting_group_impl(uint32_t upload_bytes_per_second, uint32_t download_bytes_per_second, + uint32_t burstiness_in_seconds) : + _upload_bytes_per_second(upload_bytes_per_second), + _download_bytes_per_second(download_bytes_per_second), + _burstiness_in_seconds(burstiness_in_seconds), + _granularity(milliseconds(50)), + _read_tokens(_download_bytes_per_second), + _unused_read_tokens(0), + _write_tokens(_upload_bytes_per_second), + _unused_write_tokens(0) + { + } + + rate_limiting_group_impl::~rate_limiting_group_impl() + { + try + { + _process_pending_reads_loop_complete.cancel_and_wait(); + } + catch (...) + { + } + try + { + _process_pending_writes_loop_complete.cancel_and_wait(); + } + catch (...) + { + } + } + + size_t rate_limiting_group_impl::readsome(boost::asio::ip::tcp::socket& socket, const std::shared_ptr& buffer, size_t length, size_t offset) + { + return readsome_impl(socket, buffer, length, offset); + } + + size_t rate_limiting_group_impl::readsome(boost::asio::ip::tcp::socket& socket, char* buffer, size_t length) + { + return readsome_impl(socket, buffer, length, 0); + } + + template + size_t rate_limiting_group_impl::readsome_impl(boost::asio::ip::tcp::socket& socket, const BufferType& buffer, size_t length, size_t offset) + { + size_t bytes_read; + if (_download_bytes_per_second) + { + promise::ptr completion_promise(new promise("rate_limiting_group_impl::readsome")); + rate_limited_tcp_read_operation read_operation(socket, buffer, length, offset, completion_promise); + _read_operations_for_next_iteration.push_back(&read_operation); + + // launch the read processing loop it if isn't running, or signal it to resume if it's paused. + if (!_process_pending_reads_loop_complete.valid() || _process_pending_reads_loop_complete.ready()) + _process_pending_reads_loop_complete = async([=](){ process_pending_reads(); }, "process_pending_reads" ); + else if (_new_read_operation_available_promise) + _new_read_operation_available_promise->set_value(); + + try + { + bytes_read = completion_promise->wait(); + } + catch (...) + { + _read_operations_for_next_iteration.remove(&read_operation); + _read_operations_in_progress.remove(&read_operation); + throw; + } + _unused_read_tokens += read_operation.permitted_length - bytes_read; + } + else + bytes_read = asio::read_some(socket, buffer, length, offset); + + _actual_download_rate.update(bytes_read); + + return bytes_read; + } + + size_t rate_limiting_group_impl::writesome(boost::asio::ip::tcp::socket& socket, const char* buffer, size_t length) + { + return writesome_impl(socket, buffer, length, 0); + } + + size_t rate_limiting_group_impl::writesome(boost::asio::ip::tcp::socket& socket, const std::shared_ptr& buffer, size_t length, size_t offset) + { + return writesome_impl(socket, buffer, length, offset); + } + + template + size_t rate_limiting_group_impl::writesome_impl(boost::asio::ip::tcp::socket& socket, const BufferType& buffer, size_t length, size_t offset) + { + size_t bytes_written; + if (_upload_bytes_per_second) + { + promise::ptr completion_promise(new promise("rate_limiting_group_impl::writesome")); + rate_limited_tcp_write_operation write_operation(socket, buffer, length, offset, completion_promise); + _write_operations_for_next_iteration.push_back(&write_operation); + + // launch the write processing loop it if isn't running, or signal it to resume if it's paused. + if (!_process_pending_writes_loop_complete.valid() || _process_pending_writes_loop_complete.ready()) + _process_pending_writes_loop_complete = async([=](){ process_pending_writes(); }, "process_pending_writes"); + else if (_new_write_operation_available_promise) + _new_write_operation_available_promise->set_value(); + + try + { + bytes_written = completion_promise->wait(); + } + catch (...) + { + _write_operations_for_next_iteration.remove(&write_operation); + _write_operations_in_progress.remove(&write_operation); + throw; + } + _unused_write_tokens += write_operation.permitted_length - bytes_written; + } + else + bytes_written = asio::write_some(socket, buffer, length, offset); + + _actual_upload_rate.update(bytes_written); + + return bytes_written; + } + + void rate_limiting_group_impl::process_pending_reads() + { + for (;;) + { + process_pending_operations(_last_read_iteration_time, _download_bytes_per_second, + _read_operations_in_progress, _read_operations_for_next_iteration, _read_tokens, _unused_read_tokens); + + _new_read_operation_available_promise = new promise("rate_limiting_group_impl::process_pending_reads"); + try + { + if (_read_operations_in_progress.empty()) + _new_read_operation_available_promise->wait(); + else + _new_read_operation_available_promise->wait(_granularity); + } + catch (const timeout_exception&) + { + } + _new_read_operation_available_promise.reset(); + } + } + void rate_limiting_group_impl::process_pending_writes() + { + for (;;) + { + process_pending_operations(_last_write_iteration_time, _upload_bytes_per_second, + _write_operations_in_progress, _write_operations_for_next_iteration, _write_tokens, _unused_write_tokens); + + _new_write_operation_available_promise = new promise("rate_limiting_group_impl::process_pending_writes"); + try + { + if (_write_operations_in_progress.empty()) + _new_write_operation_available_promise->wait(); + else + _new_write_operation_available_promise->wait(_granularity); + } + catch (const timeout_exception&) + { + } + _new_write_operation_available_promise.reset(); + } + } + void rate_limiting_group_impl::process_pending_operations(time_point& last_iteration_start_time, + uint32_t& limit_bytes_per_second, + rate_limited_operation_list& operations_in_progress, + rate_limited_operation_list& operations_for_next_iteration, + uint32_t& tokens, + uint32_t& unused_tokens) + { + // lock here for multithreaded + std::copy(operations_for_next_iteration.begin(), + operations_for_next_iteration.end(), + std::back_inserter(operations_in_progress)); + operations_for_next_iteration.clear(); + + // find out how much time since our last read/write + time_point this_iteration_start_time = time_point::now(); + if (limit_bytes_per_second) // the we are limiting up/download speed + { + microseconds time_since_last_iteration = this_iteration_start_time - last_iteration_start_time; + if (time_since_last_iteration > seconds(1)) + time_since_last_iteration = seconds(1); + else if (time_since_last_iteration < microseconds(0)) + time_since_last_iteration = microseconds(0); + + tokens += (uint32_t)((limit_bytes_per_second * time_since_last_iteration.count()) / 1000000); + tokens += unused_tokens; + unused_tokens = 0; + tokens = std::min(tokens, limit_bytes_per_second * _burstiness_in_seconds); + + if (tokens) + { + // sort the pending reads/writes in order of the number of bytes they need to write, smallest first + std::vector operations_sorted_by_length; + operations_sorted_by_length.reserve(operations_in_progress.size()); + for (rate_limited_operation* operation_data : operations_in_progress) + operations_sorted_by_length.push_back(operation_data); + std::sort(operations_sorted_by_length.begin(), operations_sorted_by_length.end(), is_operation_shorter()); + + // figure out how many bytes each reader/writer is allowed to read/write + uint32_t bytes_remaining_to_allocate = tokens; + while (!operations_sorted_by_length.empty()) + { + uint32_t bytes_permitted_for_this_operation = bytes_remaining_to_allocate / operations_sorted_by_length.size(); + uint32_t bytes_allocated_for_this_operation = std::min(operations_sorted_by_length.back()->length, bytes_permitted_for_this_operation); + operations_sorted_by_length.back()->permitted_length = bytes_allocated_for_this_operation; + bytes_remaining_to_allocate -= bytes_allocated_for_this_operation; + operations_sorted_by_length.pop_back(); + } + tokens = bytes_remaining_to_allocate; + + // kick off the reads/writes in first-come order + for (auto iter = operations_in_progress.begin(); iter != operations_in_progress.end();) + { + if ((*iter)->permitted_length > 0) + { + rate_limited_operation* operation_to_perform = *iter; + iter = operations_in_progress.erase(iter); + operation_to_perform->perform_operation(); + } + else + ++iter; + } + } + } + else // down/upload speed is unlimited + { + // we shouldn't end up here often. If the rate is unlimited, we should just execute + // the operation immediately without being queued up. This should only be hit if + // we change from a limited rate to unlimited + for (auto iter = operations_in_progress.begin(); + iter != operations_in_progress.end();) + { + rate_limited_operation* operation_to_perform = *iter; + iter = operations_in_progress.erase(iter); + operation_to_perform->permitted_length = operation_to_perform->length; + operation_to_perform->perform_operation(); + } + } + last_iteration_start_time = this_iteration_start_time; + } + + } + + rate_limiting_group::rate_limiting_group(uint32_t upload_bytes_per_second, uint32_t download_bytes_per_second, uint32_t burstiness_in_seconds /* = 1 */) : + my(new detail::rate_limiting_group_impl(upload_bytes_per_second, download_bytes_per_second, burstiness_in_seconds)) + { + } + + rate_limiting_group::~rate_limiting_group() + { + } + + uint32_t rate_limiting_group::get_actual_upload_rate() const + { + return my->_actual_upload_rate.get_average_rate(); + } + + uint32_t rate_limiting_group::get_actual_download_rate() const + { + return my->_actual_download_rate.get_average_rate(); + } + + void rate_limiting_group::set_actual_rate_time_constant(microseconds time_constant) + { + my->_actual_upload_rate.set_time_constant(time_constant); + my->_actual_download_rate.set_time_constant(time_constant); + } + + void rate_limiting_group::set_upload_limit(uint32_t upload_bytes_per_second) + { + my->_upload_bytes_per_second = upload_bytes_per_second; + } + + uint32_t rate_limiting_group::get_upload_limit() const + { + return my->_upload_bytes_per_second; + } + + void rate_limiting_group::set_download_limit(uint32_t download_bytes_per_second) + { + my->_download_bytes_per_second = download_bytes_per_second; + } + + uint32_t rate_limiting_group::get_download_limit() const + { + return my->_download_bytes_per_second; + } + + void rate_limiting_group::add_tcp_socket(tcp_socket* tcp_socket_to_limit) + { + tcp_socket_to_limit->set_io_hooks(my.get()); + } + + void rate_limiting_group::remove_tcp_socket(tcp_socket* tcp_socket_to_stop_limiting) + { + tcp_socket_to_stop_limiting->set_io_hooks(NULL); + } + + +} // namespace fc diff --git a/libraries/libfc/src/network/resolve.cpp b/libraries/libfc/src/network/resolve.cpp new file mode 100644 index 0000000000..5d204f4ff6 --- /dev/null +++ b/libraries/libfc/src/network/resolve.cpp @@ -0,0 +1,33 @@ +#include +#include + +namespace fc +{ + std::vector resolve(boost::asio::io_service& io_service, + const std::string& host, uint16_t port) + { + using q = boost::asio::ip::udp::resolver::query; + using b = boost::asio::ip::resolver_query_base; + boost::asio::ip::udp::resolver res(io_service); + boost::system::error_code ec; + auto ep = res.resolve(q(host, std::to_string(uint64_t(port)), + b::address_configured | b::numeric_service), ec); + if(!ec) + { + std::vector eps; + while(ep != boost::asio::ip::udp::resolver::iterator()) + { + if(ep->endpoint().address().is_v4()) + { + eps.push_back(*ep); + } + // TODO: add support for v6 + ++ep; + } + return eps; + } + FC_THROW_EXCEPTION(unknown_host_exception, + "name resolution failed: ${reason}", + ("reason", ec.message())); + } +} diff --git a/libraries/libfc/src/network/tcp_socket.cpp b/libraries/libfc/src/network/tcp_socket.cpp new file mode 100644 index 0000000000..eae2e2fa68 --- /dev/null +++ b/libraries/libfc/src/network/tcp_socket.cpp @@ -0,0 +1,344 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined _WIN32 || defined WIN32 || defined OS_WIN64 || defined _WIN64 || defined WIN64 || defined WINNT +# include +#endif + +namespace fc { + using boost::fibers::future; + + namespace detail + { + bool have_so_reuseport = true; + } + + class tcp_socket::impl : public tcp_socket_io_hooks { + public: + impl() : + _sock(fc::asio::default_io_service()), + _io_hooks(this) + {} + ~impl() + { + if( _sock.is_open() ) + try + { + _sock.close(); + } + catch( ... ) + {} + if( _read_in_progress.valid() ) + try + { + _read_in_progress.get(); + } + catch ( ... ) + { + } + if( _write_in_progress.valid() ) + try + { + _write_in_progress.get(); + } + catch ( ... ) + { + } + } + virtual size_t readsome(boost::asio::ip::tcp::socket& socket, char* buffer, size_t length) override; + virtual size_t readsome(boost::asio::ip::tcp::socket& socket, const std::shared_ptr& buffer, size_t length, size_t offset) override; + virtual size_t writesome(boost::asio::ip::tcp::socket& socket, const char* buffer, size_t length) override; + virtual size_t writesome(boost::asio::ip::tcp::socket& socket, const std::shared_ptr& buffer, size_t length, size_t offset) override; + + future _write_in_progress; + future _read_in_progress; + boost::asio::ip::tcp::socket _sock; + tcp_socket_io_hooks* _io_hooks; + }; + + size_t tcp_socket::impl::readsome(boost::asio::ip::tcp::socket& socket, char* buffer, size_t length) + { + return (_read_in_progress = fc::asio::read_some(socket, buffer, length)).get(); + } + size_t tcp_socket::impl::readsome(boost::asio::ip::tcp::socket& socket, const std::shared_ptr& buffer, size_t length, size_t offset) + { + return (_read_in_progress = fc::asio::read_some(socket, buffer, length, offset)).get(); + } + size_t tcp_socket::impl::writesome(boost::asio::ip::tcp::socket& socket, const char* buffer, size_t length) + { + return (_write_in_progress = fc::asio::write_some(socket, buffer, length)).get(); + } + size_t tcp_socket::impl::writesome(boost::asio::ip::tcp::socket& socket, const std::shared_ptr& buffer, size_t length, size_t offset) + { + return (_write_in_progress = fc::asio::write_some(socket, buffer, length, offset)).get(); + } + + + void tcp_socket::open() + { + my->_sock.open(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0).protocol()); + } + + bool tcp_socket::is_open()const { + return my->_sock.is_open(); + } + + tcp_socket::tcp_socket(){}; + + tcp_socket::~tcp_socket(){}; + + void tcp_socket::flush() {} + void tcp_socket::close() { + try { + if( is_open() ) + { + my->_sock.close(); + } + } FC_RETHROW_EXCEPTIONS( warn, "error closing tcp socket" ); + } + + bool tcp_socket::eof()const { + return !my->_sock.is_open(); + } + + size_t tcp_socket::writesome(const char* buf, size_t len) + { + return my->_io_hooks->writesome(my->_sock, buf, len); + } + + size_t tcp_socket::writesome(const std::shared_ptr& buf, size_t len, size_t offset) + { + return my->_io_hooks->writesome(my->_sock, buf, len, offset); + } + + fc::ip::endpoint tcp_socket::remote_endpoint()const + { + try + { + auto rep = my->_sock.remote_endpoint(); + return fc::ip::endpoint(rep.address().to_v4().to_ulong(), rep.port() ); + } + FC_RETHROW_EXCEPTIONS( warn, "error getting socket's remote endpoint" ); + } + + + fc::ip::endpoint tcp_socket::local_endpoint() const + { + try + { + auto boost_local_endpoint = my->_sock.local_endpoint(); + return fc::ip::endpoint(boost_local_endpoint.address().to_v4().to_ulong(), boost_local_endpoint.port() ); + } + FC_RETHROW_EXCEPTIONS( warn, "error getting socket's local endpoint" ); + } + + size_t tcp_socket::readsome( char* buf, size_t len ) + { + return my->_io_hooks->readsome(my->_sock, buf, len); + } + + size_t tcp_socket::readsome( const std::shared_ptr& buf, size_t len, size_t offset ) { + return my->_io_hooks->readsome(my->_sock, buf, len, offset); + } + + void tcp_socket::connect_to( const fc::ip::endpoint& remote_endpoint ) { + fc::asio::tcp::connect(my->_sock, fc::asio::tcp::endpoint( boost::asio::ip::address_v4(remote_endpoint.get_address()), remote_endpoint.port() ) ); + } + + void tcp_socket::bind(const fc::ip::endpoint& local_endpoint) + { + try + { + my->_sock.bind(boost::asio::ip::tcp::endpoint(boost::asio::ip::address_v4(local_endpoint.get_address()), + local_endpoint.port())); + } + catch (const std::exception& except) + { + elog("Exception binding outgoing connection to desired local endpoint ${endpoint}: ${what}", ("endpoint", local_endpoint)("what", except.what())); + FC_THROW("error binding to ${endpoint}: ${what}", ("endpoint", local_endpoint)("what", except.what())); + } + } + + void tcp_socket::enable_keep_alives(const fc::microseconds& interval) + { + if (interval.count()) + { + boost::asio::socket_base::keep_alive option(true); + my->_sock.set_option(option); +#if defined _WIN32 || defined WIN32 || defined OS_WIN64 || defined _WIN64 || defined WIN64 || defined WINNT + struct tcp_keepalive keepalive_settings; + keepalive_settings.onoff = 1; + keepalive_settings.keepalivetime = (ULONG)(interval.count() / fc::milliseconds(1).count()); + keepalive_settings.keepaliveinterval = (ULONG)(interval.count() / fc::milliseconds(1).count()); + + DWORD dwBytesRet = 0; + if (WSAIoctl(my->_sock.native(), SIO_KEEPALIVE_VALS, &keepalive_settings, sizeof(keepalive_settings), + NULL, 0, &dwBytesRet, NULL, NULL) == SOCKET_ERROR) + wlog("Error setting TCP keepalive values"); +#elif !defined(__clang__) || (__clang_major__ >= 6) + // This should work for modern Linuxes and for OSX >= Mountain Lion + int timeout_sec = interval.count() / fc::seconds(1).count(); + if (setsockopt(my->_sock.native(), IPPROTO_TCP, + #if defined( __APPLE__ ) + TCP_KEEPALIVE, + #else + TCP_KEEPIDLE, + #endif + (char*)&timeout_sec, sizeof(timeout_sec)) < 0) + wlog("Error setting TCP keepalive idle time"); +# if !defined(__APPLE__) || defined(TCP_KEEPINTVL) // TCP_KEEPINTVL not defined before 10.9 + if (setsockopt(my->_sock.native(), IPPROTO_TCP, TCP_KEEPINTVL, + (char*)&timeout_sec, sizeof(timeout_sec)) < 0) + wlog("Error setting TCP keepalive interval"); +# endif // !__APPLE__ || TCP_KEEPINTVL +#endif // !WIN32 + } + else + { + boost::asio::socket_base::keep_alive option(false); + my->_sock.set_option(option); + } + } + + void tcp_socket::set_io_hooks(tcp_socket_io_hooks* new_hooks) + { + my->_io_hooks = new_hooks ? new_hooks : &*my; + } + + void tcp_socket::set_reuse_address(bool enable /* = true */) + { + FC_ASSERT(my->_sock.is_open()); + boost::asio::socket_base::reuse_address option(enable); + my->_sock.set_option(option); +#if defined(__APPLE__) || defined(__linux__) +# ifndef SO_REUSEPORT +# define SO_REUSEPORT 15 +# endif + // OSX needs SO_REUSEPORT in addition to SO_REUSEADDR. + // This probably needs to be set for any BSD + if (detail::have_so_reuseport) + { + int reuseport_value = 1; + if (setsockopt(my->_sock.native(), SOL_SOCKET, SO_REUSEPORT, + (char*)&reuseport_value, sizeof(reuseport_value)) < 0) + { + if (errno == ENOPROTOOPT) + detail::have_so_reuseport = false; + else + wlog("Error setting SO_REUSEPORT"); + } + } +#endif // __APPLE__ + } + + + class tcp_server::impl { + public: + impl() + :_accept( fc::asio::default_io_service() ) + { + _accept.open(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0).protocol()); + } + + ~impl(){ + try { + _accept.close(); + } + catch ( boost::system::system_error& ) + { + wlog( "unexpected exception ${e}", ("e", fc::except_str()) ); + } + } + + boost::asio::ip::tcp::acceptor _accept; + }; + void tcp_server::close() { + if( my && my->_accept.is_open() ) + my->_accept.close(); + delete my; + my = nullptr; + } + tcp_server::tcp_server() + :my(nullptr) { + } + tcp_server::~tcp_server() { + delete my; + } + + + void tcp_server::accept( tcp_socket& s ) + { + try + { + FC_ASSERT( my != nullptr ); + fc::asio::tcp::accept( my->_accept, s.my->_sock ); + } FC_RETHROW_EXCEPTIONS( warn, "Unable to accept connection on socket." ); + } + void tcp_server::set_reuse_address(bool enable /* = true */) + { + if( !my ) + my = new impl; + boost::asio::ip::tcp::acceptor::reuse_address option(enable); + my->_accept.set_option(option); +#if defined(__APPLE__) || (defined(__linux__) && defined(SO_REUSEPORT)) + // OSX needs SO_REUSEPORT in addition to SO_REUSEADDR. + // This probably needs to be set for any BSD + if (detail::have_so_reuseport) + { + int reuseport_value = 1; + if (setsockopt(my->_accept.native(), SOL_SOCKET, SO_REUSEPORT, + (char*)&reuseport_value, sizeof(reuseport_value)) < 0) + { + if (errno == ENOPROTOOPT) + detail::have_so_reuseport = false; + else + wlog("Error setting SO_REUSEPORT"); + } + } +#endif // __APPLE__ + } + void tcp_server::listen( uint16_t port ) + { + if( !my ) + my = new impl; + try + { + my->_accept.bind(boost::asio::ip::tcp::endpoint(boost::asio::ip::address_v4(), port)); + my->_accept.listen(256); + } + FC_RETHROW_EXCEPTIONS(warn, "error listening on socket"); + } + void tcp_server::listen( const fc::ip::endpoint& ep ) + { + if( !my ) + my = new impl; + try + { + my->_accept.bind(boost::asio::ip::tcp::endpoint(boost::asio::ip::address_v4::from_string((string)ep.get_address()), ep.port())); + my->_accept.listen(); + } + FC_RETHROW_EXCEPTIONS(warn, "error listening on socket"); + } + + fc::ip::endpoint tcp_server::get_local_endpoint() const + { + FC_ASSERT( my != nullptr ); + return fc::ip::endpoint(my->_accept.local_endpoint().address().to_v4().to_ulong(), + my->_accept.local_endpoint().port() ); + } + + uint16_t tcp_server::get_port()const + { + FC_ASSERT( my != nullptr ); + return my->_accept.local_endpoint().port(); + } + + + +} // namespace fc diff --git a/libraries/libfc/src/network/udp_socket.cpp b/libraries/libfc/src/network/udp_socket.cpp new file mode 100644 index 0000000000..318e1ff0b1 --- /dev/null +++ b/libraries/libfc/src/network/udp_socket.cpp @@ -0,0 +1,111 @@ +#include +#include + + +namespace fc +{ + class udp_socket::impl + { + public: + impl(){} + ~impl(){} + + std::shared_ptr _sock; + }; + + udp_socket::udp_socket() + : my(new impl()) + { + } + + void udp_socket::initialize(boost::asio::io_service& service) + { + my->_sock.reset(new boost::asio::ip::udp::socket(service)); + } + + udp_socket::~udp_socket() + { + try + { + if(my->_sock) + my->_sock->close(); //close boost socket to make any pending reads run their completion handler + } + catch (...) //avoid destructor throw and likely this is just happening because socket wasn't open. + { + } + } + + void udp_socket::send_to(const char* buffer, size_t length, boost::asio::ip::udp::endpoint& to) + { + try + { + my->_sock->send_to(boost::asio::buffer(buffer, length), to); + return; + } + catch(const boost::system::system_error& e) + { + if(e.code() == boost::asio::error::would_block) + { + auto send_buffer_ptr = std::make_shared>(buffer, buffer+length); + my->_sock->async_send_to(boost::asio::buffer(send_buffer_ptr.get(), length), to, + [send_buffer_ptr](const boost::system::error_code& /*ec*/, std::size_t /*bytes_transferred*/) + { + // Swallow errors. Currently only used for GELF logging, so depend on local + // log to catch anything that doesn't make it across the network. + }); + } + // All other exceptions ignored. + } + } + + void udp_socket::send_to(const std::shared_ptr& buffer, size_t length, + boost::asio::ip::udp::endpoint& to) + { + try + { + my->_sock->send_to(boost::asio::buffer(buffer.get(), length), to); + return; + } + catch(const boost::system::system_error& e) + { + if(e.code() == boost::asio::error::would_block) + { + auto preserved_buffer_ptr = buffer; + my->_sock->async_send_to(boost::asio::buffer(preserved_buffer_ptr.get(), length), to, + [preserved_buffer_ptr](const boost::system::error_code& /*ec*/, std::size_t /*bytes_transferred*/) + { + // Swallow errors. Currently only used for GELF logging, so depend on local + // log to catch anything that doesn't make it across the network. + }); + } + // All other exceptions ignored. + } + } + + void udp_socket::open() + { + my->_sock->open(boost::asio::ip::udp::v4()); + my->_sock->non_blocking(true); + } + + void udp_socket::close() + { + my->_sock->close(); + } + + const boost::asio::ip::udp::endpoint udp_socket::local_endpoint() const + { + return my->_sock->local_endpoint(); + } + + void udp_socket::connect(const boost::asio::ip::udp::endpoint& e) + { + my->_sock->connect(e); + } + + void udp_socket::set_reuse_address( bool s ) + { + my->_sock->set_option( boost::asio::ip::udp::socket::reuse_address(s) ); + } + +} diff --git a/libraries/libfc/src/network/udt_socket.cpp b/libraries/libfc/src/network/udt_socket.cpp new file mode 100644 index 0000000000..3806ee7870 --- /dev/null +++ b/libraries/libfc/src/network/udt_socket.cpp @@ -0,0 +1,413 @@ +#include +#include +#include +#include +#include +#include + +#ifndef WIN32 +# include +#endif + +namespace fc { + + void check_udt_errors() + { + UDT::ERRORINFO& error_info = UDT::getlasterror(); + if( error_info.getErrorCode() ) + { + std::string error_message = error_info.getErrorMessage(); + error_info.clear(); + FC_CAPTURE_AND_THROW( udt_exception, (error_message) ); + } + } + + class udt_epoll_service + { + public: + udt_epoll_service() + :_epoll_thread("udt_epoll") + { + UDT::startup(); + check_udt_errors(); + _epoll_id = UDT::epoll_create(); + _epoll_loop = _epoll_thread.async( [=](){ poll_loop(); }, "udt_poll_loop" ); + } + + ~udt_epoll_service() + { + _epoll_loop.cancel("udt_epoll_service is destructing"); + _epoll_loop.wait(); + UDT::cleanup(); + } + + void poll_loop() + { + std::set read_ready; + std::set write_ready; + while( !_epoll_loop.canceled() ) + { + UDT::epoll_wait( _epoll_id, + &read_ready, + &write_ready, 100000000 ); + + { synchronized(_read_promises_mutex) + for( auto sock : read_ready ) + { + auto itr = _read_promises.find( sock ); + if( itr != _read_promises.end() ) + { + itr->second->set_value(); + _read_promises.erase(itr); + } + } + } // synchronized read promise mutex + + { synchronized(_write_promises_mutex) + for( auto sock : write_ready ) + { + auto itr = _write_promises.find( sock ); + if( itr != _write_promises.end() ) + { + itr->second->set_value(); + _write_promises.erase(itr); + } + } + } // synchronized write promise mutex + } // while not canceled + } // poll_loop + + + void notify_read( int udt_socket_id, + const promise::ptr& p ) + { + int events = UDT_EPOLL_IN | UDT_EPOLL_ERR; + if( 0 != UDT::epoll_add_usock( _epoll_id, + udt_socket_id, + &events ) ) + { + check_udt_errors(); + } + { synchronized(_read_promises_mutex) + + _read_promises[udt_socket_id] = p; + } + } + + void notify_write( int udt_socket_id, + const promise::ptr& p ) + { + int events = UDT_EPOLL_OUT | UDT_EPOLL_ERR; + if( 0 != UDT::epoll_add_usock( _epoll_id, + udt_socket_id, + &events ) ) + { + check_udt_errors(); + } + + { synchronized(_write_promises_mutex) + _write_promises[udt_socket_id] = p; + } + } + void remove( int udt_socket_id ) + { + { synchronized(_read_promises_mutex) + auto read_itr = _read_promises.find( udt_socket_id ); + if( read_itr != _read_promises.end() ) + { + read_itr->second->set_exception( fc::copy_exception( fc::exception() ) ); + _read_promises.erase(read_itr); + } + } + { synchronized(_write_promises_mutex) + auto write_itr = _write_promises.find( udt_socket_id ); + if( write_itr != _write_promises.end() ) + { + write_itr->second->set_exception( fc::copy_exception( fc::exception() ) ); + _write_promises.erase(write_itr); + } + } + UDT::epoll_remove_usock( _epoll_id, udt_socket_id ); + } + + private: + fc::mutex _read_promises_mutex; + fc::mutex _write_promises_mutex; + std::unordered_map::ptr > _read_promises; + std::unordered_map::ptr > _write_promises; + + fc::future _epoll_loop; + fc::thread _epoll_thread; + int _epoll_id; + }; + + + udt_epoll_service& default_epool_service() + { + static udt_epoll_service* default_service = new udt_epoll_service(); + return *default_service; + } + + + + udt_socket::udt_socket() + :_udt_socket_id( UDT::INVALID_SOCK ) + { + } + + udt_socket::~udt_socket() + { + try { + close(); + } catch ( const std::bad_alloc& ) { + throw; + } catch ( const boost::interprocess::bad_alloc& ) { + throw; + } catch ( const fc::exception& e ) { + wlog( "${e}", ("e", e.to_detail_string() ) ); + } catch ( const std::exception& e ) { + wlog( "${e}", ("e", e.what() ) ); + } + } + + void udt_socket::bind( const fc::ip::endpoint& local_endpoint ) + { try { + if( !is_open() ) + open(); + + sockaddr_in local_addr; + local_addr.sin_family = AF_INET; + local_addr.sin_port = htons(local_endpoint.port()); + local_addr.sin_addr.s_addr = htonl(local_endpoint.get_address()); + + if( UDT::ERROR == UDT::bind(_udt_socket_id, (sockaddr*)&local_addr, sizeof(local_addr)) ) + check_udt_errors(); + } FC_CAPTURE_AND_RETHROW() } + + void udt_socket::connect_to( const ip::endpoint& remote_endpoint ) + { try { + if( !is_open() ) + open(); + + sockaddr_in serv_addr; + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(remote_endpoint.port()); + serv_addr.sin_addr.s_addr = htonl(remote_endpoint.get_address()); + + // UDT doesn't allow now blocking connects... + fc::thread connect_thread("connect_thread"); + connect_thread.async( [&](){ + if( UDT::ERROR == UDT::connect(_udt_socket_id, (sockaddr*)&serv_addr, sizeof(serv_addr)) ) + check_udt_errors(); + }, "udt_socket::connect_to").wait(); + + bool block = false; + UDT::setsockopt(_udt_socket_id, 0, UDT_SNDSYN, &block, sizeof(bool)); + UDT::setsockopt(_udt_socket_id, 0, UDT_RCVSYN, &block, sizeof(bool)); + check_udt_errors(); + + } FC_CAPTURE_AND_RETHROW( (remote_endpoint) ) } + + ip::endpoint udt_socket::remote_endpoint() const + { try { + sockaddr_in peer_addr; + int peer_addr_size = sizeof(peer_addr); + int error_code = UDT::getpeername( _udt_socket_id, (struct sockaddr*)&peer_addr, &peer_addr_size ); + if( error_code == UDT::ERROR ) + check_udt_errors(); + return ip::endpoint( ip::address( htonl( peer_addr.sin_addr.s_addr ) ), htons(peer_addr.sin_port) ); + } FC_CAPTURE_AND_RETHROW() } + + ip::endpoint udt_socket::local_endpoint() const + { try { + sockaddr_in sock_addr; + int addr_size = sizeof(sock_addr); + int error_code = UDT::getsockname( _udt_socket_id, (struct sockaddr*)&sock_addr, &addr_size ); + if( error_code == UDT::ERROR ) + check_udt_errors(); + return ip::endpoint( ip::address( htonl( sock_addr.sin_addr.s_addr ) ), htons(sock_addr.sin_port) ); + } FC_CAPTURE_AND_RETHROW() } + + + /// @{ + size_t udt_socket::readsome( char* buffer, size_t max ) + { try { + auto bytes_read = UDT::recv( _udt_socket_id, buffer, max, 0 ); + while( bytes_read == UDT::ERROR ) + { + if( UDT::getlasterror().getErrorCode() == CUDTException::EASYNCRCV ) + { + UDT::getlasterror().clear(); + promise::ptr p(new promise("udt_socket::readsome")); + default_epool_service().notify_read( _udt_socket_id, p ); + p->wait(); + bytes_read = UDT::recv( _udt_socket_id, buffer, max, 0 ); + } + else + check_udt_errors(); + } + return bytes_read; + } FC_CAPTURE_AND_RETHROW( (max) ) } + + size_t udt_socket::readsome( const std::shared_ptr& buf, size_t len, size_t offset ) + { + return readsome(buf.get() + offset, len); + } + + bool udt_socket::eof()const + { + // TODO... + return false; + } + /// @} + + /// ostream interface + /// @{ + size_t udt_socket::writesome( const char* buffer, size_t len ) + { try { + auto bytes_sent = UDT::send(_udt_socket_id, buffer, len, 0); + + while( UDT::ERROR == bytes_sent ) + { + if( UDT::getlasterror().getErrorCode() == CUDTException::EASYNCSND ) + { + UDT::getlasterror().clear(); + promise::ptr p(new promise("udt_socket::writesome")); + default_epool_service().notify_write( _udt_socket_id, p ); + p->wait(); + bytes_sent = UDT::send(_udt_socket_id, buffer, len, 0); + continue; + } + else + check_udt_errors(); + } + return bytes_sent; + } FC_CAPTURE_AND_RETHROW( (len) ) } + + size_t udt_socket::writesome( const std::shared_ptr& buf, size_t len, size_t offset ) + { + return writesome(buf.get() + offset, len); + } + + void udt_socket::flush(){} + + void udt_socket::close() + { try { + if( is_open() ) + { + default_epool_service().remove( _udt_socket_id ); + UDT::close( _udt_socket_id ); + check_udt_errors(); + _udt_socket_id = UDT::INVALID_SOCK; + } + else + { + wlog( "already closed" ); + } + } FC_CAPTURE_AND_RETHROW() } + /// @} + + void udt_socket::open() + { + _udt_socket_id = UDT::socket(AF_INET, SOCK_STREAM, 0); + if( _udt_socket_id == UDT::INVALID_SOCK ) + check_udt_errors(); + } + + bool udt_socket::is_open()const + { + return _udt_socket_id != UDT::INVALID_SOCK; + } + + + + + + + udt_server::udt_server() + :_udt_socket_id( UDT::INVALID_SOCK ) + { + _udt_socket_id = UDT::socket(AF_INET, SOCK_STREAM, 0); + if( _udt_socket_id == UDT::INVALID_SOCK ) + check_udt_errors(); + + bool block = false; + UDT::setsockopt(_udt_socket_id, 0, UDT_SNDSYN, &block, sizeof(bool)); + check_udt_errors(); + UDT::setsockopt(_udt_socket_id, 0, UDT_RCVSYN, &block, sizeof(bool)); + check_udt_errors(); + } + + udt_server::~udt_server() + { + try { + close(); + } catch ( const std::bad_alloc& ) { + throw; + } catch ( const boost::interprocess::bad_alloc& ) { + throw; + } catch ( const fc::exception& e ) { + wlog( "${e}", ("e", e.to_detail_string() ) ); + } catch ( const std::exception& e ) { + wlog( "${e}", ("e", e.what() ) ); + } + } + + void udt_server::close() + { try { + if( _udt_socket_id != UDT::INVALID_SOCK ) + { + UDT::close( _udt_socket_id ); + check_udt_errors(); + default_epool_service().remove( _udt_socket_id ); + _udt_socket_id = UDT::INVALID_SOCK; + } + } FC_CAPTURE_AND_RETHROW() } + + void udt_server::accept( udt_socket& s ) + { try { + FC_ASSERT( !s.is_open() ); + + + while( s._udt_socket_id == UDT::INVALID_SOCK ) + { + s._udt_socket_id = UDT::accept( _udt_socket_id, (sockaddr*)&their_addr, &namelen ); + if( UDT::getlasterror().getErrorCode() == CUDTException::EASYNCRCV ) + { + UDT::getlasterror().clear(); + promise::ptr p(new promise("udt_server::accept")); + default_epool_service().notify_read( _udt_socket_id, p ); + p->wait(); + s._udt_socket_id = UDT::accept( _udt_socket_id, (sockaddr*)&their_addr, &namelen ); + } + else + check_udt_errors(); + } + } FC_CAPTURE_AND_RETHROW() } + + void udt_server::listen( const ip::endpoint& ep ) + { try { + sockaddr_in my_addr; + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(ep.port()); + my_addr.sin_addr.s_addr = INADDR_ANY; + memset(&(my_addr.sin_zero), '\0', 8); + + if( UDT::ERROR == UDT::bind(_udt_socket_id, (sockaddr*)&my_addr, sizeof(my_addr)) ) + check_udt_errors(); + + UDT::listen(_udt_socket_id, 10); + check_udt_errors(); + } FC_CAPTURE_AND_RETHROW( (ep) ) } + + fc::ip::endpoint udt_server::local_endpoint() const + { try { + sockaddr_in sock_addr; + int addr_size = sizeof(sock_addr); + int error_code = UDT::getsockname( _udt_socket_id, (struct sockaddr*)&sock_addr, &addr_size ); + if( error_code == UDT::ERROR ) + check_udt_errors(); + return ip::endpoint( ip::address( htonl( sock_addr.sin_addr.s_addr ) ), htons(sock_addr.sin_port) ); + } FC_CAPTURE_AND_RETHROW() } + +} diff --git a/libraries/libfc/src/network/url.cpp b/libraries/libfc/src/network/url.cpp new file mode 100644 index 0000000000..defc2ab7c2 --- /dev/null +++ b/libraries/libfc/src/network/url.cpp @@ -0,0 +1,202 @@ +#include +#include +#include +#include +#include +#include + +namespace fc +{ + namespace detail + { + class url_impl + { + public: + void parse( const fc::string& s ) + { + std::stringstream ss(s); + std::string skip,_lpath,_largs,luser,lpass; + std::getline( ss, _proto, ':' ); + std::getline( ss, skip, '/' ); + std::getline( ss, skip, '/' ); + + if( s.find('@') != size_t(fc::string::npos) ) { + fc::string user_pass; + std::getline( ss, user_pass, '@' ); + std::stringstream upss(user_pass); + if( user_pass.find( ':' ) != size_t(fc::string::npos) ) { + std::getline( upss, luser, ':' ); + std::getline( upss, lpass, ':' ); + _user = fc::move(luser); + _pass = fc::move(lpass); + } else { + _user = fc::move(user_pass); + } + } + fc::string host_port; + std::getline( ss, host_port, '/' ); + auto pos = host_port.find( ':' ); + if( pos != fc::string::npos ) { + try { + _port = static_cast(to_uint64( host_port.substr( pos+1 ) )); + } catch ( ... ) { + FC_THROW_EXCEPTION( parse_error_exception, "Unable to parse port field in url",( "url", s ) ); + } + _host = host_port.substr(0,pos); + } else { + _host = fc::move(host_port); + } + std::getline( ss, _lpath, '?' ); +#ifdef WIN32 + // On windows, a URL like file:///c:/autoexec.bat would result in _lpath = c:/autoexec.bat + // which is what we really want (it's already an absolute path) + if (!stricmp(_proto.c_str(), "file")) + _path = _lpath; + else + _path = fc::path( "/" ) / _lpath; // let other schemes behave like unix +#else + // On unix, a URL like file:///etc/rc.local would result in _lpath = etc/rc.local + // but we really want to make it the absolute path /etc/rc.local + _path = fc::path( "/" ) / _lpath; +#endif + std::getline( ss, _largs ); + if( _args && _args->size() ) + { + // TODO: args = fc::move(_args); + _query = fc::move(_largs); + } + } + + string _proto; + ostring _host; + ostring _user; + ostring _pass; + opath _path; + ostring _query; + ovariant_object _args; + std::optional _port; + }; + } + + void to_variant( const url& u, fc::variant& v ) + { + v = fc::string(u); + } + void from_variant( const fc::variant& v, url& u ) + { + u = url( v.as_string() ); + } + + url::operator string()const + { + std::stringstream ss; + ss<_proto<<"://"; + if( my->_user ) { + ss << *my->_user; + if( my->_pass ) { + ss<<":"<<*my->_pass; + } + ss<<"@"; + } + if( my->_host ) ss<<*my->_host; + if( my->_port ) ss<<":"<<*my->_port; + if( my->_path ) ss<_path->generic_string(); + if( my->_query ) ss<<"?"<<*my->_query; + // if( my->_args ) ss<<"?"<<*my->_args; + return ss.str(); + } + + url::url( const fc::string& u ) + :my( std::make_shared() ) + { + my->parse(u); + } + + std::shared_ptr get_null_url() + { + static auto u = std::make_shared(); + return u; + } + + url::url() + :my( get_null_url() ) + { } + + url::url( const url& u ) + :my(u.my){} + + url::url( url&& u ) + :my( fc::move(u.my) ) + { + u.my = get_null_url(); + } + + url::url( const string& proto, const ostring& host, const ostring& user, const ostring& pass, + const opath& path, const ostring& query, const ovariant_object& args, const std::optional& port) + :my( std::make_shared() ) + { + my->_proto = proto; + my->_host = host; + my->_user = user; + my->_pass = pass; + my->_path = path; + my->_query = query; + my->_args = args; + my->_port = port; + } + + url::~url(){} + + url& url::operator=(const url& u ) + { + my = u.my; + return *this; + } + + url& url::operator=(url&& u ) + { + if( this != &u ) + { + my = fc::move(u.my); + u.my= get_null_url(); + } + return *this; + } + + string url::proto()const + { + return my->_proto; + } + ostring url::host()const + { + return my->_host; + } + ostring url::user()const + { + return my->_user; + } + ostring url::pass()const + { + return my->_pass; + } + opath url::path()const + { + return my->_path; + } + ostring url::query()const + { + return my->_query; + } + ovariant_object url::args()const + { + return my->_args; + } + std::optional url::port()const + { + return my->_port; + } + + + +} + diff --git a/libraries/libfc/src/real128.cpp b/libraries/libfc/src/real128.cpp new file mode 100644 index 0000000000..c28a4f4e89 --- /dev/null +++ b/libraries/libfc/src/real128.cpp @@ -0,0 +1,130 @@ +#include +#include +#include +#include +#include + +namespace fc +{ + uint64_t real128::to_uint64()const + { + return (fixed/ FC_REAL128_PRECISION).to_uint64(); + } + + real128::real128( uint64_t integer ) + { + fixed = uint128(integer) * FC_REAL128_PRECISION; + } + real128& real128::operator += ( const real128& o ) + { + fixed += o.fixed; + return *this; + } + real128& real128::operator -= ( const real128& o ) + { + fixed -= o.fixed; + return *this; + } + + real128& real128::operator /= ( const real128& o ) + { try { + FC_ASSERT( o.fixed > uint128(0), "Divide by Zero" ); + + fc::bigint self(fixed); + fc::bigint other(o.fixed); + self *= FC_REAL128_PRECISION; + self /= other; + fixed = self; + + return *this; + } FC_CAPTURE_AND_RETHROW( (*this)(o) ) } + + real128& real128::operator *= ( const real128& o ) + { try { + fc::bigint self(fixed); + fc::bigint other(o.fixed); + self *= other; + self /= FC_REAL128_PRECISION; + fixed = self; + return *this; + } FC_CAPTURE_AND_RETHROW( (*this)(o) ) } + + + real128::real128( const std::string& ratio_str ) + { + const char* c = ratio_str.c_str(); + int digit = *c - '0'; + if (digit >= 0 && digit <= 9) + { + uint64_t int_part = digit; + ++c; + digit = *c - '0'; + while (digit >= 0 && digit <= 9) + { + int_part = int_part * 10 + digit; + ++c; + digit = *c - '0'; + } + *this = real128(int_part); + } + else + { + // if the string doesn't look like "123.45" or ".45", this code isn't designed to parse it correctly + // in particular, we don't try to handle leading whitespace or '+'/'-' indicators at the beginning + FC_ASSERT(*c == '.'); + fixed = fc::uint128(); + } + + + if (*c == '.') + { + c++; + digit = *c - '0'; + if (digit >= 0 && digit <= 9) + { + int64_t frac_part = digit; + int64_t frac_magnitude = 10; + ++c; + digit = *c - '0'; + while (digit >= 0 && digit <= 9) + { + frac_part = frac_part * 10 + digit; + frac_magnitude *= 10; + ++c; + digit = *c - '0'; + } + *this += real128( frac_part ) / real128( frac_magnitude ); + } + } + } + real128::operator std::string()const + { + std::stringstream ss; + ss << std::string(fixed / FC_REAL128_PRECISION); + ss << '.'; + auto frac = (fixed % FC_REAL128_PRECISION) + FC_REAL128_PRECISION; + ss << std::string( frac ).substr(1); + + auto number = ss.str(); + while( number.back() == '0' ) number.pop_back(); + + return number; + } + + real128 real128::from_fixed( const uint128& fixed ) + { + real128 result; + result.fixed = fixed; + return result; + } + + void to_variant( const real128& var, variant& vo ) + { + vo = std::string(var); + } + void from_variant( const variant& var, real128& vo ) + { + vo = real128(var.as_string()); + } + +} // namespace fc diff --git a/libraries/libfc/src/rpc/bstate.cpp b/libraries/libfc/src/rpc/bstate.cpp new file mode 100644 index 0000000000..4b15159442 --- /dev/null +++ b/libraries/libfc/src/rpc/bstate.cpp @@ -0,0 +1,69 @@ +#include +#include +#include + +namespace fc { namespace rpc { +bstate::~bstate() +{ + close(); +} + +void bstate::add_method( const fc::string& name, method m ) +{ + _methods.emplace(std::pair(name,fc::move(m))); +} + +void bstate::remove_method( const fc::string& name ) +{ + _methods.erase(name); +} + +result_type bstate::local_call( const string& method_name, const params_type& args ) +{ + auto method_itr = _methods.find(method_name); + if( method_itr == _methods.end() && _unhandled ) + return _unhandled( method_name, args ); + FC_ASSERT( method_itr != _methods.end(), "Unknown Method: ${name}", ("name",method_name) ); + return method_itr->second(args); +} + +void bstate::handle_reply( const bresponse& bresponse ) +{ + auto await = _awaiting.find( bresponse.id ); + FC_ASSERT( await != _awaiting.end(), "Unknown Response ID: ${id}", ("id",bresponse.id)("bresponse",bresponse) ); + if( bresponse.result ) + await->second->set_value( *bresponse.result ); + else if( bresponse.error ) + { + await->second->set_exception( std::make_exception_ptr( FC_EXCEPTION( exception, "${error}", ("error",bresponse.error->message)("data",bresponse) ) ) ); + } + else + await->second->set_value( params_type() ); + _awaiting.erase(await); +} + +brequest bstate::start_remote_call( const string& method_name, params_type args ) +{ + brequest brequest{ _next_id++, method_name, std::move(args) }; + _awaiting[*brequest.id].reset( new boost::fibers::promise() ); + return brequest; +} +result_type bstate::wait_for_response( uint64_t request_id ) +{ + auto itr = _awaiting.find(request_id); + FC_ASSERT( itr != _awaiting.end() ); + auto fut = itr->second->get_future(); + return fut.get(); +} +void bstate::close() +{ + for( auto& item : _awaiting ) + item.second->set_exception( std::make_exception_ptr( FC_EXCEPTION( eof_exception, "connection closed" )) ); + _awaiting.clear(); +} +void bstate::on_unhandled( const std::function& unhandled ) +{ + _unhandled = unhandled; +} + +} } // namespace fc::rpc diff --git a/libraries/libfc/src/rpc/cli.cpp b/libraries/libfc/src/rpc/cli.cpp new file mode 100644 index 0000000000..64fafbe185 --- /dev/null +++ b/libraries/libfc/src/rpc/cli.cpp @@ -0,0 +1,233 @@ +#include +#include + +#include + +#ifndef WIN32 +#include +#endif + +#ifdef HAVE_READLINE +# include +# include +// I don't know exactly what version of readline we need. I know the 4.2 version that ships on some macs is +// missing some functions we require. We're developing against 6.3, but probably anything in the 6.x +// series is fine +# if RL_VERSION_MAJOR < 6 +# ifdef _MSC_VER +# pragma message("You have an old version of readline installed that might not support some of the features we need") +# pragma message("Readline support will not be compiled in") +# else +# warning "You have an old version of readline installed that might not support some of the features we need" +# warning "Readline support will not be compiled in" +# endif +# undef HAVE_READLINE +# endif +# ifdef WIN32 +# include +# endif +#endif + +namespace fc { namespace rpc { + +static std::vector& cli_commands() +{ + static std::vector* cmds = new std::vector(); + return *cmds; +} + +cli::~cli() +{ + if( _run_complete.valid() ) + { + stop(); + } +} + +variant cli::send_call( api_id_type api_id, string method_name, variants args /* = variants() */ ) +{ + FC_ASSERT(false); +} + +variant cli::send_callback( uint64_t callback_id, variants args /* = variants() */ ) +{ + FC_ASSERT(false); +} + +void cli::send_notice( uint64_t callback_id, variants args /* = variants() */ ) +{ + FC_ASSERT(false); +} + +void cli::start() +{ + cli_commands() = get_method_names(0); + _run_complete = fc::async( [&](){ run(); } ); +} + +void cli::stop() +{ + _run_complete.cancel(); + _run_complete.wait(); +} + +void cli::wait() +{ + _run_complete.wait(); +} + +void cli::format_result( const string& method, std::function formatter) +{ + _result_formatters[method] = formatter; +} + +void cli::set_prompt( const string& prompt ) +{ + _prompt = prompt; +} + +void cli::run() +{ + while( !_run_complete.canceled() ) + { + try + { + std::string line; + try + { + getline( _prompt.c_str(), line ); + } + catch ( const fc::eof_exception& e ) + { + break; + } + std::cout << line << "\n"; + line += char(EOF); + fc::variants args = fc::json::variants_from_string(line);; + if( args.size() == 0 ) + continue; + + const string& method = args[0].get_string(); + + auto result = receive_call( 0, method, variants( args.begin()+1,args.end() ) ); + auto itr = _result_formatters.find( method ); + if( itr == _result_formatters.end() ) + { + std::cout << fc::json::to_pretty_string( result ) << "\n"; + } + else + std::cout << itr->second( result, args ) << "\n"; + } + catch ( const std::bad_alloc& ) + { + throw; + } + catch ( const boost::interprocess::bad_alloc& ) + { + throw; + } + catch ( const fc::exception& e ) + { + std::cout << e.to_detail_string() << "\n"; + } + catch ( const std::exception& e ) + { + std::cout << e.what() << "\n"; + } + } +} + + +char * dupstr (const char* s) { + char *r; + + r = (char*) malloc ((strlen (s) + 1)); + strcpy (r, s); + return (r); +} + +char* my_generator(const char* text, int state) +{ + static int list_index, len; + const char *name; + + if (!state) { + list_index = 0; + len = strlen (text); + } + + auto& cmd = cli_commands(); + + while( list_index < cmd.size() ) + { + name = cmd[list_index].c_str(); + list_index++; + + if (strncmp (name, text, len) == 0) + return (dupstr(name)); + } + + /* If no names matched, then return NULL. */ + return ((char *)NULL); +} + + +static char** cli_completion( const char * text , int start, int end) +{ + char **matches; + matches = (char **)NULL; + +#ifdef HAVE_READLINE + if (start == 0) + matches = rl_completion_matches ((char*)text, &my_generator); + else + rl_bind_key('\t',rl_abort); +#endif + + return (matches); +} + + +void cli::getline( const fc::string& prompt, fc::string& line) +{ + // getting file descriptor for C++ streams is near impossible + // so we just assume it's the same as the C stream... +#ifdef HAVE_READLINE +#ifndef WIN32 + if( isatty( fileno( stdin ) ) ) +#else + // it's implied by + // https://msdn.microsoft.com/en-us/library/f4s0ddew.aspx + // that this is the proper way to do this on Windows, but I have + // no access to a Windows compiler and thus, + // no idea if this actually works + if( _isatty( _fileno( stdin ) ) ) +#endif + { + rl_attempted_completion_function = cli_completion; + + static fc::thread getline_thread("getline"); + getline_thread.async( [&](){ + char* line_read = nullptr; + std::cout.flush(); //readline doesn't use cin, so we must manually flush _out + line_read = readline(prompt.c_str()); + if( line_read == nullptr ) + FC_THROW_EXCEPTION( fc::eof_exception, "" ); + rl_bind_key( '\t', rl_complete ); + if( *line_read ) + add_history(line_read); + line = line_read; + free(line_read); + }).wait(); + } + else +#endif + { + std::cout << prompt; + // sync_call( cin_thread, [&](){ std::getline( *input_stream, line ); }, "getline"); + fc::getline( fc::cin, line ); + return; + } +} + +} } // namespace fc::rpc diff --git a/libraries/libfc/src/rpc/http_api.cpp b/libraries/libfc/src/rpc/http_api.cpp new file mode 100644 index 0000000000..41b009f267 --- /dev/null +++ b/libraries/libfc/src/rpc/http_api.cpp @@ -0,0 +1,183 @@ + +#include + +namespace fc { namespace rpc { + +http_api_connection::~http_api_connection() +{ +} + +http_api_connection::http_api_connection() +{ + _rpc_state.add_method( "call", [this]( const variants& args ) -> variant + { + // TODO: This logic is duplicated between http_api_connection and websocket_api_connection + // it should be consolidated into one place instead of copy-pasted + FC_ASSERT( args.size() == 3 && args[2].is_array() ); + api_id_type api_id; + if( args[0].is_string() ) + { + variants subargs; + subargs.push_back( args[0] ); + variant subresult = this->receive_call( 1, "get_api_by_name", subargs ); + api_id = subresult.as_uint64(); + } + else + api_id = args[0].as_uint64(); + + return this->receive_call( + api_id, + args[1].as_string(), + args[2].get_array() ); + } ); + + _rpc_state.add_method( "notice", [this]( const variants& args ) -> variant + { + FC_ASSERT( args.size() == 2 && args[1].is_array() ); + this->receive_notice( + args[0].as_uint64(), + args[1].get_array() ); + return variant(); + } ); + + _rpc_state.add_method( "callback", [this]( const variants& args ) -> variant + { + FC_ASSERT( args.size() == 2 && args[1].is_array() ); + this->receive_callback( + args[0].as_uint64(), + args[1].get_array() ); + return variant(); + } ); + + _rpc_state.on_unhandled( [&]( const std::string& method_name, const variants& args ) + { + return this->receive_call( 0, method_name, args ); + } ); +} + +variant http_api_connection::send_call( + api_id_type api_id, + string method_name, + variants args /* = variants() */ ) +{ + // HTTP has no way to do this, so do nothing + return variant(); +} + +variant http_api_connection::send_callback( + uint64_t callback_id, + variants args /* = variants() */ ) +{ + // HTTP has no way to do this, so do nothing + return variant(); +} + +void http_api_connection::send_notice( + uint64_t callback_id, + variants args /* = variants() */ ) +{ + // HTTP has no way to do this, so do nothing + return; +} + +void http_api_connection::on_request( const fc::http::request& req, const fc::http::server::response& resp ) +{ + // this must be called by outside HTTP server's on_request method + std::string resp_body; + http::reply::status_code resp_status; + + auto handle_error = [&](const auto& e) + { + resp_status = http::reply::InternalServerError; + resp_body = ""; + wdump((e.to_detail_string())); + }; + + try + { + resp.add_header( "Content-Type", "application/json" ); + std::string req_body( req.body.begin(), req.body.end() ); + auto var = fc::json::from_string( req_body ); + const auto& var_obj = var.get_object(); + + if( var_obj.contains( "method" ) ) + { + auto call = var.as(); + auto handle_error_inner = [&](const auto& e) + { + resp_body = fc::json::to_string( fc::rpc::response( *call.id, error_object{ 1, e.to_detail_string(), fc::variant(e)} ) ); + resp_status = http::reply::InternalServerError;} + }; + + try + { + auto result = _rpc_state.local_call( call.method, call.params ); + resp_body = fc::json::to_string( fc::rpc::response( *call.id, result ) ); + resp_status = http::reply::OK; + } + catch ( const std::bad_alloc& ) + { + throw; + } + catch ( const boost::interprocess::bad_alloc& ) + { + throw; + } + catch ( const fc::exception& e ) + { + handle_error_inner(e); + } + catch ( const std::exception& e ) + { + handle_error_inner(fc::std_exception_wrapper::from_current_exception(e)); + } + } + else + { + resp_status = http::reply::BadRequest; + resp_body = ""; + } + } + catch ( const std::bad_alloc& ) + { + throw; + } + catch ( const boost::interprocess::bad_alloc& ) + { + throw; + } + catch ( const fc::exception& e ) + { + handle_error(e); + } + catch ( const std::exception& e ) + { + handle_error(fc::std_exception_wrapper::from_current_exception(e)); + } + + try + { + resp.set_status( resp_status ); + resp.set_length( resp_body.length() ); + resp.write( resp_body.c_str(), resp_body.length() ); + } + catch ( const std::bad_alloc& ) + { + throw; + } + catch ( const boost::interprocess::bad_alloc& ) + { + throw; + } + catch( const fc::exception& e ) + { + wdump((e.to_detail_string())); + } + catch ( const std::exception& e ) + { + wdump((fc::std_exception_wrapper::from_current_exception(e).to_detail_string())); + } + return; +} + +} } // namespace fc::rpc diff --git a/libraries/libfc/src/rpc/state.cpp b/libraries/libfc/src/rpc/state.cpp new file mode 100644 index 0000000000..0bad77f0a4 --- /dev/null +++ b/libraries/libfc/src/rpc/state.cpp @@ -0,0 +1,69 @@ +#include +#include +#include + +namespace fc { namespace rpc { +state::~state() +{ + close(); +} + +void state::add_method( const fc::string& name, method m ) +{ + _methods.emplace(std::pair(name,fc::move(m))); +} + +void state::remove_method( const fc::string& name ) +{ + _methods.erase(name); +} + +variant state::local_call( const string& method_name, const variants& args ) +{ + auto method_itr = _methods.find(method_name); + if( method_itr == _methods.end() && _unhandled ) + return _unhandled( method_name, args ); + FC_ASSERT( method_itr != _methods.end(), "Unknown Method: ${name}", ("name",method_name) ); + return method_itr->second(args); +} + +void state::handle_reply( const response& response ) +{ + auto await = _awaiting.find( response.id ); + FC_ASSERT( await != _awaiting.end(), "Unknown Response ID: ${id}", ("id",response.id)("response",response) ); + if( response.result ) + await->second->set_value( *response.result ); + else if( response.error ) + { + await->second->set_exception( std::make_exception_ptr( FC_EXCEPTION( exception, "${error}", ("error",response.error->message)("data",response) ) ) ); + } + else + await->second->set_value( fc::variant() ); + _awaiting.erase(await); +} + +request state::start_remote_call( const string& method_name, variants args ) +{ + request request{ _next_id++, method_name, std::move(args) }; + _awaiting[*request.id].reset( new boost::fibers::promise() ); + return request; +} +variant state::wait_for_response( uint64_t request_id ) +{ + auto itr = _awaiting.find(request_id); + FC_ASSERT( itr != _awaiting.end() ); + auto fut = itr->second->get_future(); + return fut.get(); +} +void state::close() +{ + for( auto& item : _awaiting ) + item.second->set_exception( std::make_exception_ptr( FC_EXCEPTION( eof_exception, "connection closed" )) ); + _awaiting.clear(); +} +void state::on_unhandled( const std::function& unhandled ) +{ + _unhandled = unhandled; +} + +} } // namespace fc::rpc diff --git a/libraries/libfc/src/rpc/websocket_api.cpp b/libraries/libfc/src/rpc/websocket_api.cpp new file mode 100644 index 0000000000..f89e2f0c03 --- /dev/null +++ b/libraries/libfc/src/rpc/websocket_api.cpp @@ -0,0 +1,177 @@ + +#include + +namespace fc { namespace rpc { + +websocket_api_connection::~websocket_api_connection() +{ +} + +websocket_api_connection::websocket_api_connection( fc::http::websocket_connection& c ) + : _connection(c) +{ + _rpc_state.add_method( "call", [this]( const variants& args ) -> variant + { + FC_ASSERT( args.size() == 3 && args[2].is_array() ); + api_id_type api_id; + if( args[0].is_string() ) + { + variants subargs; + subargs.push_back( args[0] ); + variant subresult = this->receive_call( 1, "get_api_by_name", subargs ); + api_id = subresult.as_uint64(); + } + else + api_id = args[0].as_uint64(); + + return this->receive_call( + api_id, + args[1].as_string(), + args[2].get_array() ); + } ); + + _rpc_state.add_method( "notice", [this]( const variants& args ) -> variant + { + FC_ASSERT( args.size() == 2 && args[1].is_array() ); + this->receive_notice( args[0].as_uint64(), args[1].get_array() ); + return variant(); + } ); + + _rpc_state.add_method( "callback", [this]( const variants& args ) -> variant + { + FC_ASSERT( args.size() == 2 && args[1].is_array() ); + this->receive_callback( args[0].as_uint64(), args[1].get_array() ); + return variant(); + } ); + + _rpc_state.on_unhandled( [&]( const std::string& method_name, const variants& args ) + { + return this->receive_call( 0, method_name, args ); + } ); + + _connection.on_message_handler( [&]( const std::string& msg ){ on_message(msg,true); } ); + _connection.on_http_handler( [&]( const std::string& msg ){ return on_message(msg,false); } ); + _connection.closed.connect( [this](){ closed(); } ); +} + +variant websocket_api_connection::send_call( + api_id_type api_id, + string method_name, + variants args /* = variants() */ ) +{ + auto request = _rpc_state.start_remote_call( "call", {api_id, std::move(method_name), std::move(args) } ); + _connection.send_message( fc::json::to_string(request) ); + return _rpc_state.wait_for_response( *request.id ); +} + +variant websocket_api_connection::send_callback( + uint64_t callback_id, + variants args /* = variants() */ ) +{ + auto request = _rpc_state.start_remote_call( "callback", {callback_id, std::move(args) } ); + _connection.send_message( fc::json::to_string(request) ); + return _rpc_state.wait_for_response( *request.id ); +} + +void websocket_api_connection::send_notice( + uint64_t callback_id, + variants args /* = variants() */ ) +{ + fc::rpc::request req{ std::optional(), "notice", {callback_id, std::move(args)}}; + _connection.send_message( fc::json::to_string(req) ); +} + +std::string websocket_api_connection::on_message( + const std::string& message, + bool send_message /* = true */ ) +{ + wdump((message)); + + auto handle_error = [&](const auto& e) + { + wdump((e.to_detail_string())); + return e.to_detail_string(); + }; + + try + { + auto var = fc::json::from_string(message); + const auto& var_obj = var.get_object(); + if( var_obj.contains( "method" ) ) + { + auto call = var.as(); + exception_ptr optexcept; + + auto handle_error_inner = [&](const auto& e) + { + if (!call.id) + { + return nullptr; + } + + return e.dynamic_copy_exception(); + }; + + try + { + auto result = _rpc_state.local_call( call.method, call.params ); + if( call.id ) + { + auto reply = fc::json::to_string( response( *call.id, result ) ); + if( send_message ) + _connection.send_message( reply ); + return reply; + } + } + catch ( const std::bad_alloc& ) + { + throw; + } + catch ( const boost::interprocess::bad_alloc& ) + { + throw; + } + catch ( const fc::exception& e ) + { + optexcept = handle_error_inner(e); + } + catch ( const std::exception& e ) + { + optexcept = handle_error_inner(fc::std_exception_wrapper::from_current_exception(e)); + } + + if( optexcept ) { + + auto reply = fc::json::to_string( response( *call.id, error_object{ 1, optexcept->to_detail_string(), fc::variant(*optexcept)} ) ); + if( send_message ) + _connection.send_message( reply ); + + return reply; + } + } + else + { + auto reply = var.as(); + _rpc_state.handle_reply( reply ); + } + } + catch ( const std::bad_alloc& ) + { + throw; + } + catch ( const boost::interprocess::bad_alloc& ) + { + throw; + } + catch ( const fc::exception& e ) + { + return handle_error(e); + } + catch ( const std::exception& e) + { + return handle_error(fc::std_exception_wrapper::from_current_exception(e)); + } + return string(); +} + +} } // namespace fc::rpc diff --git a/libraries/libfc/src/string.cpp b/libraries/libfc/src/string.cpp new file mode 100644 index 0000000000..f7fd1a554e --- /dev/null +++ b/libraries/libfc/src/string.cpp @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/** + * Implemented with std::string for now. + */ + +namespace fc { + class comma_numpunct : public std::numpunct + { + protected: + virtual char do_thousands_sep() const { return ','; } + virtual std::string do_grouping() const { return "\03"; } + }; + + std::string to_pretty_string( int64_t value ) + { + std::stringstream ss; + ss.imbue( {std::locale(), new comma_numpunct} ); + ss << std::fixed << value; + return ss.str(); + } + +#ifdef USE_FC_STRING + string::string(const char* s, int l) :my(s,l){ } + string::string(){} + string::string( const fc::string& c ):my(*c.my) { } + string::string( string&& m ):my(fc::move(*m.my)) {} + string::string( const char* c ):my(c){} + string::string( const_iterator b, const_iterator e ):my(b,e){} + string::string( const std::string& s ):my(s) {} + string::string( std::string&& s ):my(fc::move(s)) {} + string::~string() { } + string::operator std::string&() { return *my; } + string::operator const std::string&()const { return *my; } + char* string::data() { return &my->front(); } + + string::iterator string::begin() { return &(*this)[0]; } + string::iterator string::end() { return &(*this)[size()]; } + string::const_iterator string::begin()const { return my->c_str(); } + string::const_iterator string::end()const { return my->c_str() + my->size(); } + + char& string::operator[](size_t idx) { return (*my)[idx]; } + const char& string::operator[](size_t idx)const { return (*my)[idx]; } + + void string::reserve(size_t r) { my->reserve(r); } + size_t string::size()const { return my->size(); } + size_t string::find(char c, size_t p)const { return my->find(c,p); } + size_t string::find(const fc::string& str, size_t pos /* = 0 */) const { return my->find(str, pos); } + size_t string::find(const char* s, size_t pos /* = 0 */) const { return my->find(s,pos); } + size_t string::rfind(char c, size_t p)const { return my->rfind(c,p); } + size_t string::rfind(const char* c, size_t p)const { return my->rfind(c,p); } + size_t string::rfind(const fc::string& c, size_t p)const { return my->rfind(c,p); } + size_t string::find_first_of(const fc::string& str, size_t pos /* = 0 */) const { return my->find_first_of(str, pos); } + size_t string::find_first_of(const char* s, size_t pos /* = 0 */) const { return my->find_first_of(s, pos); } + + fc::string& string::replace(size_t pos, size_t len, const string& str) { my->replace(pos, len, str); return *this; } + fc::string& string::replace(size_t pos, size_t len, const char* s) { my->replace(pos, len, s); return *this; } + + void string::clear() { my->clear(); } + void string::resize( size_t s ) { my->resize(s); } + + fc::string string::substr( size_t start, size_t len )const { return my->substr(start,len); } + const char* string::c_str()const { return my->c_str(); } + + bool string::operator == ( const char* s )const { return *my == s; } + bool string::operator == ( const string& s )const { return *my == *s.my; } + bool string::operator != ( const string& s )const { return *my != *s.my; } + + string& string::operator =( const string& c ) { *my = *c.my; return *this; } + string& string::operator =( string&& c ) { *my = fc::move( *c.my ); return *this; } + + string& string::operator+=( const string& s ) { *my += *s.my; return *this; } + string& string::operator+=( char c ) { *my += c; return *this; } + + bool operator < ( const string& a, const string& b ) { return *a.my < *b.my; } + string operator + ( const string& s, const string& c ) { return string(s) += c; } + string operator + ( const string& s, char c ) { return string(s) += c; } +#endif // USE_FC_STRING + + + int64_t to_int64( const fc::string& i ) + { + try + { + return boost::lexical_cast(i.c_str(), i.size()); + } + catch( const boost::bad_lexical_cast& e ) + { + FC_THROW_EXCEPTION( parse_error_exception, "Couldn't parse int64_t" ); + } + FC_RETHROW_EXCEPTIONS( warn, "${i} => int64_t", ("i",i) ) + } + + uint64_t to_uint64( const fc::string& i ) + { try { + try + { + return boost::lexical_cast(i.c_str(), i.size()); + } + catch( const boost::bad_lexical_cast& e ) + { + FC_THROW_EXCEPTION( parse_error_exception, "Couldn't parse uint64_t" ); + } + FC_RETHROW_EXCEPTIONS( warn, "${i} => uint64_t", ("i",i) ) + } FC_CAPTURE_AND_RETHROW( (i) ) } + + double to_double( const fc::string& i) + { + try + { + return boost::lexical_cast(i.c_str(), i.size()); + } + catch( const boost::bad_lexical_cast& e ) + { + FC_THROW_EXCEPTION( parse_error_exception, "Couldn't parse double" ); + } + FC_RETHROW_EXCEPTIONS( warn, "${i} => double", ("i",i) ) + } + + fc::string to_string(double d) + { + // +2 is required to ensure that the double is rounded correctly when read back in. http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html + std::stringstream ss; + ss << std::setprecision(std::numeric_limits::digits10 + 2) << std::fixed << d; + return ss.str(); + } + + fc::string to_string( uint64_t d) + { + return boost::lexical_cast(d); + } + + fc::string to_string( int64_t d) + { + return boost::lexical_cast(d); + } + fc::string to_string( uint16_t d) + { + return boost::lexical_cast(d); + } + std::string trim( const std::string& s ) + { + return boost::algorithm::trim_copy(s); + /* + std::string cpy(s); + boost::algorithm::trim(cpy); + return cpy; + */ + } + std::string to_lower( const std::string& s ) + { + auto tmp = s; + boost::algorithm::to_lower(tmp); + return tmp; + } + string trim_and_normalize_spaces( const string& s ) + { + string result = boost::algorithm::trim_copy( s ); + while( result.find( " " ) != result.npos ) + boost::algorithm::replace_all( result, " ", " " ); + return result; + } + +} // namespace fc + + diff --git a/libraries/libfc/src/thread/asio/detail/yield.hpp b/libraries/libfc/src/thread/asio/detail/yield.hpp new file mode 100644 index 0000000000..39e5695aae --- /dev/null +++ b/libraries/libfc/src/thread/asio/detail/yield.hpp @@ -0,0 +1,302 @@ +// Copyright Oliver Kowalke, Nat Goodspeed 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP +#define BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include // std::unique_lock + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace fibers { +namespace asio { +namespace detail { + +//[fibers_asio_yield_completion +// Bundle a completion bool flag with a spinlock to protect it. +struct yield_completion { + typedef fibers::detail::spinlock mutex_t; + typedef std::unique_lock< mutex_t > lock_t; + + mutex_t mtx_{}; + bool completed_{ false }; + + void wait() { + // yield_handler_base::operator()() will set completed_ true and + // attempt to wake a suspended fiber. It would be Bad if that call + // happened between our detecting (! completed_) and suspending. + lock_t lk{ mtx_ }; + // If completed_ is already set, we're done here: don't suspend. + if ( ! completed_) { + // suspend(unique_lock) unlocks the lock in the act of + // resuming another fiber + fibers::context::active()->suspend( lk); + } + } +}; +//] + +//[fibers_asio_yield_handler_base +// This class encapsulates common elements between yield_handler (capturing +// a value to return from asio async function) and yield_handler (no +// such value). See yield_handler and its specialization below. Both +// yield_handler and yield_handler are passed by value through +// various layers of asio functions. In other words, they're potentially +// copied multiple times. So key data such as the yield_completion instance +// must be stored in our async_result> specialization, which +// should be instantiated only once. +class yield_handler_base { +public: + yield_handler_base( yield_t const& y) : + // capture the context* associated with the running fiber + ctx_{ boost::fibers::context::active() }, + // capture the passed yield_t + yt_( y ) { + } + + // completion callback passing only (error_code) + void operator()( boost::system::error_code const& ec) { + BOOST_ASSERT_MSG( ycomp_, + "Must inject yield_completion* " + "before calling yield_handler_base::operator()()"); + BOOST_ASSERT_MSG( yt_.ec_, + "Must inject boost::system::error_code* " + "before calling yield_handler_base::operator()()"); + // If originating fiber is busy testing completed_ flag, wait until it + // has observed (! completed_). + yield_completion::lock_t lk{ ycomp_->mtx_ }; + // Notify a subsequent yield_completion::wait() call that it need not + // suspend. + ycomp_->completed_ = true; + // set the error_code bound by yield_t + * yt_.ec_ = ec; + // If ctx_ is still active, e.g. because the async operation + // immediately called its callback (this method!) before the asio + // async function called async_result_base::get(), we must not set it + // ready. + if ( fibers::context::active() != ctx_ ) { + // wake the fiber + fibers::context::active()->set_ready( ctx_); + } + } + +//private: + boost::fibers::context * ctx_; + yield_t yt_; + // We depend on this pointer to yield_completion, which will be injected + // by async_result. + yield_completion * ycomp_{ nullptr }; +}; +//] + +//[fibers_asio_yield_handler_T +// asio uses handler_type::type to decide +// what to instantiate as the actual handler. Below, we specialize +// handler_type< yield_t, ... > to indicate yield_handler<>. So when you pass +// an instance of yield_t as an asio completion token, asio selects +// yield_handler<> as the actual handler class. +template< typename T > +class yield_handler: public yield_handler_base { +public: + // asio passes the completion token to the handler constructor + explicit yield_handler( yield_t const& y) : + yield_handler_base{ y } { + } + + // completion callback passing only value (T) + void operator()( T t) { + // just like callback passing success error_code + (*this)( boost::system::error_code(), std::move(t) ); + } + + // completion callback passing (error_code, T) + void operator()( boost::system::error_code const& ec, T t) { + BOOST_ASSERT_MSG( value_, + "Must inject value ptr " + "before caling yield_handler::operator()()"); + // move the value to async_result<> instance BEFORE waking up a + // suspended fiber + * value_ = std::move( t); + // forward the call to base-class completion handler + yield_handler_base::operator()( ec); + } + +//private: + // pointer to destination for eventual value + // this must be injected by async_result before operator()() is called + T * value_{ nullptr }; +}; +//] + +//[fibers_asio_yield_handler_void +// yield_handler is like yield_handler without value_. In fact it's +// just like yield_handler_base. +template<> +class yield_handler< void >: public yield_handler_base { +public: + explicit yield_handler( yield_t const& y) : + yield_handler_base{ y } { + } + + // nullary completion callback + void operator()() { + ( * this)( boost::system::error_code() ); + } + + // inherit operator()(error_code) overload from base class + using yield_handler_base::operator(); +}; +//] + +// Specialize asio_handler_invoke hook to ensure that any exceptions thrown +// from the handler are propagated back to the caller +template< typename Fn, typename T > +void asio_handler_invoke( Fn fn, yield_handler< T > * h) { + fn(); +} + +//[fibers_asio_async_result_base +// Factor out commonality between async_result> and +// async_result> +class async_result_base { +public: + explicit async_result_base( yield_handler_base & h) { + // Inject ptr to our yield_completion instance into this + // yield_handler<>. + h.ycomp_ = & this->ycomp_; + // if yield_t didn't bind an error_code, make yield_handler_base's + // error_code* point to an error_code local to this object so + // yield_handler_base::operator() can unconditionally store through + // its error_code* + if ( ! h.yt_.ec_) { + h.yt_.ec_ = & ec_; + } + } + + void get() { + // Unless yield_handler_base::operator() has already been called, + // suspend the calling fiber until that call. + ycomp_.wait(); + // The only way our own ec_ member could have a non-default value is + // if our yield_handler did not have a bound error_code AND the + // completion callback passed a non-default error_code. + if ( ec_) { + throw_exception( boost::system::system_error{ ec_ } ); + } + } + +private: + // If yield_t does not bind an error_code instance, store into here. + boost::system::error_code ec_{}; + // async_result_base owns the yield_completion because, unlike + // yield_handler<>, async_result<> is only instantiated once. + yield_completion ycomp_{}; +}; +//] + +}}}} + +namespace boost { +namespace asio { + +//[fibers_asio_async_result_T +// asio constructs an async_result<> instance from the yield_handler specified +// by handler_type<>::type. A particular asio async method constructs the +// yield_handler, constructs this async_result specialization from it, then +// returns the result of calling its get() method. +template< typename T > +class async_result< boost::fibers::asio::detail::yield_handler< T > > : + public boost::fibers::asio::detail::async_result_base { +public: + // type returned by get() + typedef T type; + + explicit async_result( boost::fibers::asio::detail::yield_handler< T > & h) : + boost::fibers::asio::detail::async_result_base{ h } { + // Inject ptr to our value_ member into yield_handler<>: result will + // be stored here. + h.value_ = & value_; + } + + // asio async method returns result of calling get() + type get() { + boost::fibers::asio::detail::async_result_base::get(); + return std::move( value_); + } + +private: + type value_{}; +}; +//] + +//[fibers_asio_async_result_void +// Without the need to handle a passed value, our yield_handler +// specialization is just like async_result_base. +template<> +class async_result< boost::fibers::asio::detail::yield_handler< void > > : + public boost::fibers::asio::detail::async_result_base { +public: + typedef void type; + + explicit async_result( boost::fibers::asio::detail::yield_handler< void > & h): + boost::fibers::asio::detail::async_result_base{ h } { + } +}; +//] + +// Handler type specialisation for fibers::asio::yield. +// When 'yield' is passed as a completion handler which accepts no parameters, +// use yield_handler. +template< typename ReturnType > +struct handler_type< fibers::asio::yield_t, ReturnType() > +{ typedef fibers::asio::detail::yield_handler< void > type; }; + +// Handler type specialisation for fibers::asio::yield. +// When 'yield' is passed as a completion handler which accepts a data +// parameter, use yield_handler to return that parameter to +// the caller. +template< typename ReturnType, typename Arg1 > +struct handler_type< fibers::asio::yield_t, ReturnType( Arg1) > +{ typedef fibers::asio::detail::yield_handler< Arg1 > type; }; + +//[asio_handler_type +// Handler type specialisation for fibers::asio::yield. +// When 'yield' is passed as a completion handler which accepts only +// error_code, use yield_handler. yield_handler will take care of the +// error_code one way or another. +template< typename ReturnType > +struct handler_type< fibers::asio::yield_t, ReturnType( boost::system::error_code) > +{ typedef fibers::asio::detail::yield_handler< void > type; }; +//] + +// Handler type specialisation for fibers::asio::yield. +// When 'yield' is passed as a completion handler which accepts a data +// parameter and an error_code, use yield_handler to return +// just the parameter to the caller. yield_handler will take care of the +// error_code one way or another. +template< typename ReturnType, typename Arg2 > +struct handler_type< fibers::asio::yield_t, ReturnType( boost::system::error_code, Arg2) > +{ typedef fibers::asio::detail::yield_handler< Arg2 > type; }; + +}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_FIBERS_ASIO_DETAIL_YIELD_HPP diff --git a/libraries/libfc/src/thread/asio/round_robin.hpp b/libraries/libfc/src/thread/asio/round_robin.hpp new file mode 100644 index 0000000000..0b208ab058 --- /dev/null +++ b/libraries/libfc/src/thread/asio/round_robin.hpp @@ -0,0 +1,185 @@ +// Copyright Oliver Kowalke 2013. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_FIBERS_ASIO_ROUND_ROBIN_H +#define BOOST_FIBERS_ASIO_ROUND_ROBIN_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "yield.hpp" + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace fibers { +namespace asio { + +class round_robin : public algo::algorithm { +private: + typedef scheduler::ready_queue_t rqueue_t; + +//[asio_rr_suspend_timer + boost::asio::io_service & io_svc_; + boost::asio::steady_timer suspend_timer_; +//] + rqueue_t rqueue_{}; + +public: +//[asio_rr_service_top + struct service : public boost::asio::io_service::service { + static boost::asio::io_service::id id; + + std::unique_ptr< boost::asio::io_service::work > work_; + + service( boost::asio::io_service & io_svc) : + boost::asio::io_service::service( io_svc), + work_{ new boost::asio::io_service::work( io_svc) } { + io_svc.post([&io_svc](){ +//] +//[asio_rr_service_lambda + while ( ! io_svc.stopped() ) { + if ( boost::fibers::has_ready_fibers() ) { + // run all pending handlers in round_robin + while ( io_svc.poll() ); + // run pending (ready) fibers + this_fiber::yield(); + } else { + // run one handler inside io_service + // if no handler available, block this thread + if ( ! io_svc.run_one() ) { + break; + } + } + } +//] +//[asio_rr_service_bottom + }); + } + + virtual ~service() {} + + service( service const&) = delete; + service & operator=( service const&) = delete; + + void shutdown_service() override final { + work_.reset(); + } + }; +//] + +//[asio_rr_ctor + round_robin( boost::asio::io_service & io_svc) : + io_svc_( io_svc), + suspend_timer_( io_svc_) { + // We use add_service() very deliberately. This will throw + // service_already_exists if you pass the same io_service instance to + // more than one round_robin instance. + boost::asio::add_service( io_svc_, new service( io_svc_)); + } +//] + + void awakened( context * ctx) noexcept { + BOOST_ASSERT( nullptr != ctx); + ctx->ready_link( rqueue_); /*< fiber, enqueue on ready queue >*/ + } + + context * pick_next() noexcept { + context * ctx( nullptr); + if ( ! rqueue_.empty() ) { /*< + pop an item from the ready queue + >*/ + ctx = & rqueue_.front(); + rqueue_.pop_front(); + BOOST_ASSERT( nullptr != ctx); + BOOST_ASSERT( context::active() != ctx); + } + return ctx; + } + + bool has_ready_fibers() const noexcept { + return ! rqueue_.empty(); + } + +//[asio_rr_suspend_until + void suspend_until( std::chrono::steady_clock::time_point const& abs_time) noexcept { + // Set a timer so at least one handler will eventually fire, causing + // run_one() to eventually return. Set a timer even if abs_time == + // time_point::max() so the timer can be canceled by our notify() + // method -- which calls the handler. + if ( suspend_timer_.expires_at() != abs_time) { + // Each expires_at(time_point) call cancels any previous pending + // call. We could inadvertently spin like this: + // dispatcher calls suspend_until() with earliest wake time + // suspend_until() sets suspend_timer_ + // lambda loop calls run_one() + // some other asio handler runs before timer expires + // run_one() returns to lambda loop + // lambda loop yields to dispatcher + // dispatcher finds no ready fibers + // dispatcher calls suspend_until() with SAME wake time + // suspend_until() sets suspend_timer_ to same time, canceling + // previous async_wait() + // lambda loop calls run_one() + // asio calls suspend_timer_ handler with operation_aborted + // run_one() returns to lambda loop... etc. etc. + // So only actually set the timer when we're passed a DIFFERENT + // abs_time value. + suspend_timer_.expires_at( abs_time); + // It really doesn't matter what the suspend_timer_ handler does, + // or even whether it's called because the timer ran out or was + // canceled. The whole point is to cause the run_one() call to + // return. So just pass a no-op lambda with proper signature. + suspend_timer_.async_wait([](boost::system::error_code const&){}); + } + } +//] + +//[asio_rr_notify + void notify() noexcept { + // Something has happened that should wake one or more fibers BEFORE + // suspend_timer_ expires. Reset the timer to cause it to fire + // immediately, causing the run_one() call to return. In theory we + // could use cancel() because we don't care whether suspend_timer_'s + // handler is called with operation_aborted or success. However -- + // cancel() doesn't change the expiration time, and we use + // suspend_timer_'s expiration time to decide whether it's already + // set. If suspend_until() set some specific wake time, then notify() + // canceled it, then suspend_until() was called again with the same + // wake time, it would match suspend_timer_'s expiration time and we'd + // refrain from setting the timer. So instead of simply calling + // cancel(), reset the timer, which cancels the pending sleep AND sets + // a new expiration time. This will cause us to spin the loop twice -- + // once for the operation_aborted handler, once for timer expiration + // -- but that shouldn't be a big problem. + suspend_timer_.expires_at( std::chrono::steady_clock::now() ); + } +//] +}; + +boost::asio::io_service::id round_robin::service::id; + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#endif // BOOST_FIBERS_ASIO_ROUND_ROBIN_H diff --git a/libraries/libfc/src/thread/asio/yield.hpp b/libraries/libfc/src/thread/asio/yield.hpp new file mode 100644 index 0000000000..d76467f9a0 --- /dev/null +++ b/libraries/libfc/src/thread/asio/yield.hpp @@ -0,0 +1,63 @@ +// Copyright 2003-2013 Christopher M. Kohlhoff +// Copyright Oliver Kowalke, Nat Goodspeed 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + + +#ifndef BOOST_FIBERS_ASIO_YIELD_HPP +#define BOOST_FIBERS_ASIO_YIELD_HPP + +#include + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +namespace boost { +namespace fibers { +namespace asio { + +//[fibers_asio_yield_t +class yield_t { +public: + yield_t() = default; + + /** + * @code + * static yield_t yield; + * boost::system::error_code myec; + * func(yield[myec]); + * @endcode + * @c yield[myec] returns an instance of @c yield_t whose @c ec_ points + * to @c myec. The expression @c yield[myec] "binds" @c myec to that + * (anonymous) @c yield_t instance, instructing @c func() to store any + * @c error_code it might produce into @c myec rather than throwing @c + * boost::system::system_error. + */ + yield_t operator[]( boost::system::error_code & ec) const { + yield_t tmp; + tmp.ec_ = & ec; + return tmp; + } + +//private: + // ptr to bound error_code instance if any + boost::system::error_code * ec_{ nullptr }; +}; +//] + +//[fibers_asio_yield +// canonical instance +thread_local yield_t yield{}; +//] + +}}} + +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#include "detail/yield.hpp" + +#endif // BOOST_FIBERS_ASIO_YIELD_HPP diff --git a/libraries/libfc/src/time.cpp b/libraries/libfc/src/time.cpp new file mode 100644 index 0000000000..8ad2a7f2ec --- /dev/null +++ b/libraries/libfc/src/time.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc { + + namespace bch = boost::chrono; + + time_point time_point::now() + { + if( UNLIKELY(mock_time_traits::is_set()) ) { + return mock_time_traits::fc_now(); + } + return time_point( microseconds( bch::duration_cast( bch::system_clock::now().time_since_epoch() ).count() ) ); + } + + fc::string time_point_sec::to_non_delimited_iso_string()const + { + const auto ptime = boost::posix_time::from_time_t( time_t( sec_since_epoch() ) ); + return boost::posix_time::to_iso_string( ptime ); + } + + fc::string time_point_sec::to_iso_string()const + { + const auto ptime = boost::posix_time::from_time_t( time_t( sec_since_epoch() ) ); + return boost::posix_time::to_iso_extended_string( ptime ); + } + + time_point_sec::operator fc::string()const + { + return this->to_iso_string(); + } + + time_point_sec time_point_sec::from_iso_string( const fc::string& s ) + { try { + static boost::posix_time::ptime epoch = boost::posix_time::from_time_t( 0 ); + boost::posix_time::ptime pt; + if( s.size() >= 5 && s.at( 4 ) == '-' ) // http://en.wikipedia.org/wiki/ISO_8601 + pt = boost::date_time::parse_delimited_time( s, 'T' ); + else + pt = boost::posix_time::from_iso_string( s ); + return fc::time_point_sec( (pt - epoch).total_seconds() ); + } FC_RETHROW_EXCEPTIONS( warn, "unable to convert ISO-formatted string to fc::time_point_sec" ) } + + time_point::operator fc::string()const + { + auto count = elapsed.count(); + if (count >= 0) { + uint64_t secs = (uint64_t)count / 1000000ULL; + uint64_t msec = ((uint64_t)count % 1000000ULL) / 1000ULL; + string padded_ms = to_string((uint64_t)(msec + 1000ULL)).substr(1); + const auto ptime = boost::posix_time::from_time_t(time_t(secs)); + return boost::posix_time::to_iso_extended_string(ptime) + "." + padded_ms; + } else { + // negative time_points serialized as "durations" in the ISO form with boost + // this is not very human readable but fits the precedent set by the above + auto as_duration = boost::posix_time::microseconds(count); + return boost::posix_time::to_iso_string(as_duration); + } + } + + time_point time_point::from_iso_string( const fc::string& s ) + { try { + auto dot = s.find( '.' ); + if( dot == std::string::npos ) + return time_point( time_point_sec::from_iso_string( s ) ); + else { + auto ms = s.substr( dot ); + ms[0] = '1'; + while( ms.size() < 4 ) ms.push_back('0'); + return time_point( time_point_sec::from_iso_string( s ) ) + milliseconds( to_int64(ms) - 1000 ); + } + } FC_RETHROW_EXCEPTIONS( warn, "unable to convert ISO-formatted string to fc::time_point" ) } + + void to_variant( const fc::time_point& t, variant& v ) { + v = fc::string( t ); + } + void from_variant( const fc::variant& v, fc::time_point& t ) { + t = fc::time_point::from_iso_string( v.as_string() ); + } + void to_variant( const fc::time_point_sec& t, variant& v ) { + v = fc::string( t ); + } + void from_variant( const fc::variant& v, fc::time_point_sec& t ) { + t = fc::time_point_sec::from_iso_string( v.as_string() ); + } + + // inspired by show_date_relative() in git's date.c + string get_approximate_relative_time_string(const time_point_sec& event_time, + const time_point_sec& relative_to_time /* = fc::time_point::now() */, + const std::string& default_ago /* = " ago" */) { + + + string ago = default_ago; + int32_t seconds_ago = relative_to_time.sec_since_epoch() - event_time.sec_since_epoch(); + if (seconds_ago < 0) + { + ago = " in the future"; + seconds_ago = -seconds_ago; + } + std::stringstream result; + if (seconds_ago < 90) + { + result << seconds_ago << " second" << (seconds_ago > 1 ? "s" : "") << ago; + return result.str(); + } + uint32_t minutes_ago = (seconds_ago + 30) / 60; + if (minutes_ago < 90) + { + result << minutes_ago << " minute" << (minutes_ago > 1 ? "s" : "") << ago; + return result.str(); + } + uint32_t hours_ago = (minutes_ago + 30) / 60; + if (hours_ago < 90) + { + result << hours_ago << " hour" << (hours_ago > 1 ? "s" : "") << ago; + return result.str(); + } + uint32_t days_ago = (hours_ago + 12) / 24; + if (days_ago < 90) + { + result << days_ago << " day" << (days_ago > 1 ? "s" : "") << ago; + return result.str(); + } + uint32_t weeks_ago = (days_ago + 3) / 7; + if (weeks_ago < 70) + { + result << weeks_ago << " week" << (weeks_ago > 1 ? "s" : "") << ago; + return result.str(); + } + uint32_t months_ago = (days_ago + 15) / 30; + if (months_ago < 12) + { + result << months_ago << " month" << (months_ago > 1 ? "s" : "") << ago; + return result.str(); + } + uint32_t years_ago = days_ago / 365; + result << years_ago << " year" << (months_ago > 1 ? "s" : ""); + if (months_ago < 12 * 5) + { + uint32_t leftover_days = days_ago - (years_ago * 365); + uint32_t leftover_months = (leftover_days + 15) / 30; + if (leftover_months) + result << leftover_months << " month" << (months_ago > 1 ? "s" : ""); + } + result << ago; + return result.str(); + } + string get_approximate_relative_time_string(const time_point& event_time, + const time_point& relative_to_time /* = fc::time_point::now() */, + const std::string& ago /* = " ago" */) { + return get_approximate_relative_time_string(time_point_sec(event_time), time_point_sec(relative_to_time), ago); + } + + void to_variant( const microseconds& input_microseconds, variant& output_variant ) + { + output_variant = input_microseconds.count(); + } + void from_variant( const variant& input_variant, microseconds& output_microseconds ) + { + output_microseconds = microseconds(input_variant.as_int64()); + } + +} //namespace fc diff --git a/libraries/libfc/src/uint128.cpp b/libraries/libfc/src/uint128.cpp new file mode 100644 index 0000000000..523569d78c --- /dev/null +++ b/libraries/libfc/src/uint128.cpp @@ -0,0 +1,405 @@ +#include +#include +#include +#include + +#include +#include "byteswap.hpp" + +namespace fc +{ + typedef boost::multiprecision::uint128_t m128; + + template + static void divide(const T &numerator, const T &denominator, T "ient, T &remainder) + { + static const int bits = sizeof(T) * 8;//CHAR_BIT; + + if(denominator == 0) { + throw std::domain_error("divide by zero"); + } else { + T n = numerator; + T d = denominator; + T x = 1; + T answer = 0; + + + while((n >= d) && (((d >> (bits - 1)) & 1) == 0)) { + x <<= 1; + d <<= 1; + } + + while(x != 0) { + if(n >= d) { + n -= d; + answer |= x; + } + + x >>= 1; + d >>= 1; + } + + quotient = answer; + remainder = n; + } + } + + uint128::uint128(const std::string &sz) + :hi(0), lo(0) + { + // do we have at least one character? + if(!sz.empty()) { + // make some reasonable assumptions + int radix = 10; + bool minus = false; + + std::string::const_iterator i = sz.begin(); + + // check for minus sign, i suppose technically this should only apply + // to base 10, but who says that -0x1 should be invalid? + if(*i == '-') { + ++i; + minus = true; + } + + // check if there is radix changing prefix (0 or 0x) + if(i != sz.end()) { + if(*i == '0') { + radix = 8; + ++i; + if(i != sz.end()) { + if(*i == 'x') { + radix = 16; + ++i; + } + } + } + + while(i != sz.end()) { + unsigned int n = 0; + const char ch = *i; + + if(ch >= 'A' && ch <= 'Z') { + if(((ch - 'A') + 10) < radix) { + n = (ch - 'A') + 10; + } else { + break; + } + } else if(ch >= 'a' && ch <= 'z') { + if(((ch - 'a') + 10) < radix) { + n = (ch - 'a') + 10; + } else { + break; + } + } else if(ch >= '0' && ch <= '9') { + if((ch - '0') < radix) { + n = (ch - '0'); + } else { + break; + } + } else { + /* completely invalid character */ + break; + } + + (*this) *= radix; + (*this) += n; + + ++i; + } + } + + // if this was a negative number, do that two's compliment madness :-P + if(minus) { + *this = -*this; + } + } + } + + + uint128::operator bigint()const + { + auto tmp = uint128( bswap_64( hi ), bswap_64( lo ) ); + bigint bi( (char*)&tmp, sizeof(tmp) ); + return bi; + } + uint128::uint128( const fc::bigint& bi ) + { + *this = uint128( std::string(bi) ); // TODO: optimize this... + } + + uint128::operator std::string ()const + { + if(*this == 0) { return "0"; } + + // at worst it will be size digits (base 2) so make our buffer + // that plus room for null terminator + static char sz [128 + 1]; + sz[sizeof(sz) - 1] = '\0'; + + uint128 ii(*this); + int i = 128 - 1; + + while (ii != 0 && i) { + + uint128 remainder; + divide(ii, uint128(10), ii, remainder); + sz [--i] = "0123456789abcdefghijklmnopqrstuvwxyz"[remainder.to_integer()]; + } + + return &sz[i]; + } + + + uint128& uint128::operator<<=(const uint128& rhs) + { + if(rhs >= 128) + { + hi = 0; + lo = 0; + } + else + { + unsigned int n = rhs.to_integer(); + const unsigned int halfsize = 128 / 2; + + if(n >= halfsize){ + n -= halfsize; + hi = lo; + lo = 0; + } + + if(n != 0) { + // shift high half + hi <<= n; + + const uint64_t mask(~(uint64_t(-1) >> n)); + + // and add them to high half + hi |= (lo & mask) >> (halfsize - n); + + // and finally shift also low half + lo <<= n; + } + } + + return *this; + } + + uint128 & uint128::operator>>=(const uint128& rhs) + { + if(rhs >= 128) + { + hi = 0; + lo = 0; + } + else + { + unsigned int n = rhs.to_integer(); + const unsigned int halfsize = 128 / 2; + + if(n >= halfsize) { + n -= halfsize; + lo = hi; + hi = 0; + } + + if(n != 0) { + // shift low half + lo >>= n; + + // get lower N bits of high half + const uint64_t mask(~(uint64_t(-1) << n)); + + // and add them to low qword + lo |= (hi & mask) << (halfsize - n); + + // and finally shift also high half + hi >>= n; + } + } + return *this; + } + + uint128& uint128::operator/=(const uint128 &b) + { + auto self = (m128(hi) << 64) + m128(lo); + auto other = (m128(b.hi) << 64) + m128(b.lo); + self /= other; + hi = static_cast(self >> 64); + lo = static_cast((self << 64 ) >> 64); + + /* + uint128 remainder; + divide(*this, b, *this, remainder ); //, *this); + if( tmp.hi != hi || tmp.lo != lo ) { + std::cerr << tmp.hi << " " << hi <<"\n"; + std::cerr << tmp.lo << " " << lo << "\n"; + exit(1); + } + */ + + /* + const auto& b128 = std::reinterpret_cast(b); + auto& this128 = std::reinterpret_cast(*this); + this128 /= b128; + */ + return *this; + } + + uint128& uint128::operator%=(const uint128 &b) + { + uint128 quotient; + divide(*this, b, quotient, *this); + return *this; + } + + uint128& uint128::operator*=(const uint128 &b) + { + uint64_t a0 = (uint32_t) (this->lo ); + uint64_t a1 = (uint32_t) (this->lo >> 0x20); + uint64_t a2 = (uint32_t) (this->hi ); + uint64_t a3 = (uint32_t) (this->hi >> 0x20); + + uint64_t b0 = (uint32_t) (b.lo ); + uint64_t b1 = (uint32_t) (b.lo >> 0x20); + uint64_t b2 = (uint32_t) (b.hi ); + uint64_t b3 = (uint32_t) (b.hi >> 0x20); + + // (a0 + (a1 << 0x20) + (a2 << 0x40) + (a3 << 0x60)) * + // (b0 + (b1 << 0x20) + (b2 << 0x40) + (b3 << 0x60)) = + // a0 * b0 + // + // (a1 * b0 + a0 * b1) << 0x20 + // (a2 * b0 + a1 * b1 + a0 * b2) << 0x40 + // (a3 * b0 + a2 * b1 + a1 * b2 + a0 * b3) << 0x60 + // + // all other cross terms are << 0x80 or higher, thus do not appear in result + + this->hi = 0; + this->lo = a3*b0; + (*this) += a2*b1; + (*this) += a1*b2; + (*this) += a0*b3; + (*this) <<= 0x20; + (*this) += a2*b0; + (*this) += a1*b1; + (*this) += a0*b2; + (*this) <<= 0x20; + (*this) += a1*b0; + (*this) += a0*b1; + (*this) <<= 0x20; + (*this) += a0*b0; + + return *this; + } + + void uint128::full_product( const uint128& a, const uint128& b, uint128& result_hi, uint128& result_lo ) + { + // (ah * 2**64 + al) * (bh * 2**64 + bl) + // = (ah * bh * 2**128 + al * bh * 2**64 + ah * bl * 2**64 + al * bl + // = P * 2**128 + (Q + R) * 2**64 + S + // = Ph * 2**192 + Pl * 2**128 + // + Qh * 2**128 + Ql * 2**64 + // + Rh * 2**128 + Rl * 2**64 + // + Sh * 2**64 + Sl + // + + uint64_t ah = a.hi; + uint64_t al = a.lo; + uint64_t bh = b.hi; + uint64_t bl = b.lo; + + uint128 s = al; + s *= bl; + uint128 r = ah; + r *= bl; + uint128 q = al; + q *= bh; + uint128 p = ah; + p *= bh; + + uint64_t sl = s.lo; + uint64_t sh = s.hi; + uint64_t rl = r.lo; + uint64_t rh = r.hi; + uint64_t ql = q.lo; + uint64_t qh = q.hi; + uint64_t pl = p.lo; + uint64_t ph = p.hi; + + uint64_t y[4]; // final result + y[0] = sl; + + uint128 acc = sh; + acc += ql; + acc += rl; + y[1] = acc.lo; + acc = acc.hi; + acc += qh; + acc += rh; + acc += pl; + y[2] = acc.lo; + y[3] = acc.hi + ph; + + result_hi = uint128( y[3], y[2] ); + result_lo = uint128( y[1], y[0] ); + + return; + } + + static uint8_t _popcount_64( uint64_t x ) + { + static const uint64_t m[] = { + 0x5555555555555555ULL, + 0x3333333333333333ULL, + 0x0F0F0F0F0F0F0F0FULL, + 0x00FF00FF00FF00FFULL, + 0x0000FFFF0000FFFFULL, + 0x00000000FFFFFFFFULL + }; + // TODO future optimization: replace slow, portable version + // with fast, non-portable __builtin_popcountll intrinsic + // (when available) + + for( int i=0, w=1; i<6; i++, w+=w ) + { + x = (x & m[i]) + ((x >> w) & m[i]); + } + return uint8_t(x); + } + + uint8_t uint128::popcount()const + { + return _popcount_64( lo ) + _popcount_64( hi ); + } + + void to_variant( const uint128& var, variant& vo ) { vo = std::string(var); } + void from_variant( const variant& var, uint128& vo ){ vo = uint128(var.as_string()); } +/* + void to_variant( const unsigned __int128& var, variant& vo ) { to_variant( uint128(var), vo); } + void from_variant( const variant& var, unsigned __int128& vo ) { + uint128 tmp; + from_variant( var, tmp ); + vo = (unsigned __int128)tmp; + } +*/ +} // namespace fc + + +/* + * Portions of the above code were adapted from the work of Evan Teran. + * + * Copyright (c) 2008 + * Evan Teran + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, provided + * that the above copyright notice appears in all copies and that both the + * copyright notice and this permission notice appear in supporting + * documentation, and that the same name not be used in advertising or + * publicity pertaining to distribution of the software without specific, + * written prior permission. We make no representations about the + * suitability this software for any purpose. It is provided "as is" + * without express or implied warranty. + */ diff --git a/libraries/libfc/src/utf8.cpp b/libraries/libfc/src/utf8.cpp new file mode 100644 index 0000000000..b274e3eaf7 --- /dev/null +++ b/libraries/libfc/src/utf8.cpp @@ -0,0 +1,85 @@ +#include "fc/utf8.hpp" + +#include "utf8/checked.h" +#include "utf8/core.h" + +#include +#include + +namespace fc { + + inline constexpr char hex_digits[] = "0123456789abcdef"; + + bool is_utf8( const std::string& str ) + { + return utf8::is_valid( str.begin(), str.end() ); + } + + // tweaked utf8::find_invalid that also considers provided range as invalid + // @param invalid_range, indicates additional invalid values + // @return [iterator to found invalid char, the value found if in range of provided pair invalid_range otherwise UINT32_MAX] + template + std::pair find_invalid(octet_iterator start, octet_iterator end, + const std::pair& invalid_range) + { + FC_ASSERT( invalid_range.first <= invalid_range.second ); + octet_iterator result = start; + uint32_t value = UINT32_MAX; + while( result != end ) { + octet_iterator itr = result; + utf8::internal::utf_error err_code = utf8::internal::validate_next( result, end, value ); + if( err_code != utf8::internal::UTF8_OK ) + return {result, UINT32_MAX}; + if( value >= invalid_range.first && value <= invalid_range.second ) + return {itr, value}; + } + return {result, UINT32_MAX}; + } + + + bool is_valid_utf8( const std::string_view& str ) { + const auto invalid_range = std::make_pair(0x80, 0x9F); + auto [itr, v] = find_invalid( str.begin(), str.end(), invalid_range ); + return itr == str.end(); + } + + // escape 0x80-0x9F C1 control characters + string prune_invalid_utf8( const std::string_view& str ) { + const auto invalid_range = std::make_pair(0x80, 0x9F); + auto [itr, v] = find_invalid( str.begin(), str.end(), invalid_range ); + if( itr == str.end() ) return std::string( str ); + + string result; + auto escape = [&result](uint32_t v) { // v is [0x80-0x9F] + result += "\\u00"; + result += hex_digits[v >> 4u]; + result += hex_digits[v & 15u]; + }; + + result = string( str.begin(), itr ); + if( v != UINT32_MAX ) escape(v); + while( itr != str.end() ) { + ++itr; + auto start = itr; + std::tie(itr, v) = find_invalid( start, str.end(), invalid_range ); + result += string( start, itr ); + if( v != UINT32_MAX ) escape(v); + } + return result; + } + + void decodeUtf8(const std::string& input, std::wstring* storage) + { + FC_ASSERT(storage != nullptr); + + utf8::utf8to32(input.begin(), input.end(), std::back_inserter(*storage)); + } + + void encodeUtf8(const std::wstring& input, std::string* storage) + { + FC_ASSERT(storage != nullptr); + + utf8::utf32to8(input.begin(), input.end(), std::back_inserter(*storage)); + } + +} ///namespace fc diff --git a/libraries/libfc/src/utf8/ReleaseNotes b/libraries/libfc/src/utf8/ReleaseNotes new file mode 100644 index 0000000000..364411a23d --- /dev/null +++ b/libraries/libfc/src/utf8/ReleaseNotes @@ -0,0 +1,12 @@ +utf8 cpp library +Release 2.3.4 + +A minor bug fix release. Thanks to all who reported bugs. + +Note: Version 2.3.3 contained a regression, and therefore was removed. + +Changes from version 2.3.2 +- Bug fix [39]: checked.h Line 273 and unchecked.h Line 182 have an extra ';' +- Bug fix [36]: replace_invalid() only works with back_inserter + +Files included in the release: utf8.h, core.h, checked.h, unchecked.h, utf8cpp.html, ReleaseNotes diff --git a/libraries/libfc/src/utf8/checked.h b/libraries/libfc/src/utf8/checked.h new file mode 100644 index 0000000000..08f3ac8179 --- /dev/null +++ b/libraries/libfc/src/utf8/checked.h @@ -0,0 +1,334 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" +#include + +namespace utf8 +{ + // Base for the exceptions that may be thrown from the library + class exception : public ::std::exception { + }; + + // Exceptions that may be thrown from the library functions. + class invalid_code_point : public exception { + uint32_t cp; + public: + invalid_code_point(uint32_t cp) : cp(cp) {} + virtual const char* what() const throw() { return "Invalid code point"; } + uint32_t code_point() const {return cp;} + }; + + class invalid_utf8 : public exception { + uint8_t u8; + public: + invalid_utf8 (uint8_t u) : u8(u) {} + virtual const char* what() const throw() { return "Invalid UTF-8"; } + uint8_t utf8_octet() const {return u8;} + }; + + class invalid_utf16 : public exception { + uint16_t u16; + public: + invalid_utf16 (uint16_t u) : u16(u) {} + virtual const char* what() const throw() { return "Invalid UTF-16"; } + uint16_t utf16_word() const {return u16;} + }; + + class not_enough_room : public exception { + public: + virtual const char* what() const throw() { return "Not enough space"; } + }; + + /// The library API - functions intended to be called by the users + + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (!utf8::internal::is_code_point_valid(cp)) + throw invalid_code_point(cp); + + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f) | 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement) + { + while (start != end) { + octet_iterator sequence_start = start; + internal::utf_error err_code = utf8::internal::validate_next(start, end); + switch (err_code) { + case internal::UTF8_OK : + for (octet_iterator it = sequence_start; it != start; ++it) + *out++ = *it; + break; + case internal::NOT_ENOUGH_ROOM: + throw not_enough_room(); + case internal::INVALID_LEAD: + out = utf8::append (replacement, out); + ++start; + break; + case internal::INCOMPLETE_SEQUENCE: + case internal::OVERLONG_SEQUENCE: + case internal::INVALID_CODE_POINT: + out = utf8::append (replacement, out); + ++start; + // just one replacement mark for the sequence + while (start != end && utf8::internal::is_trail(*start)) + ++start; + break; + } + } + return out; + } + + template + inline output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out) + { + static const uint32_t replacement_marker = utf8::internal::mask16(0xfffd); + return utf8::replace_invalid(start, end, out, replacement_marker); + } + + template + uint32_t next(octet_iterator& it, octet_iterator end) + { + uint32_t cp = 0; + internal::utf_error err_code = utf8::internal::validate_next(it, end, cp); + switch (err_code) { + case internal::UTF8_OK : + break; + case internal::NOT_ENOUGH_ROOM : + throw not_enough_room(); + case internal::INVALID_LEAD : + case internal::INCOMPLETE_SEQUENCE : + case internal::OVERLONG_SEQUENCE : + throw invalid_utf8(*it); + case internal::INVALID_CODE_POINT : + throw invalid_code_point(cp); + } + return cp; + } + + template + uint32_t peek_next(octet_iterator it, octet_iterator end) + { + return utf8::next(it, end); + } + + template + uint32_t prior(octet_iterator& it, octet_iterator start) + { + // can't do much if it == start + if (it == start) + throw not_enough_room(); + + octet_iterator end = it; + // Go back until we hit either a lead octet or start + while (utf8::internal::is_trail(*(--it))) + if (it == start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + return utf8::peek_next(it, end); + } + + /// Deprecated in versions that include "prior" + template + uint32_t previous(octet_iterator& it, octet_iterator pass_start) + { + octet_iterator end = it; + while (utf8::internal::is_trail(*(--it))) + if (it == pass_start) + throw invalid_utf8(*it); // error - no lead byte in the sequence + octet_iterator temp = it; + return utf8::next(temp, end); + } + + template + void advance (octet_iterator& it, distance_type n, octet_iterator end) + { + for (distance_type i = 0; i < n; ++i) + utf8::next(it, end); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::next(first, last); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + if (start != end) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + if (utf8::internal::is_trail_surrogate(trail_surrogate)) + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + else + throw invalid_utf16(static_cast(trail_surrogate)); + } + else + throw invalid_utf16(static_cast(cp)); + + } + // Lone trail surrogate + else if (utf8::internal::is_trail_surrogate(cp)) + throw invalid_utf16(static_cast(cp)); + + result = utf8::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start != end) { + uint32_t cp = utf8::next(start, end); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start != end) + (*result++) = utf8::next(start, end); + + return result; + } + + // The iterator class + template + class iterator { + octet_iterator it; + octet_iterator range_start; + octet_iterator range_end; + public: + + using iterator_category = std::bidirectional_iterator_tag; + using value_type = uint32_t; + using difference_type = std::ptrdiff_t; + using pointer = uint32_t*; + using reference = uint32_t&; + + iterator () {} + explicit iterator (const octet_iterator& octet_it, + const octet_iterator& range_start, + const octet_iterator& range_end) : + it(octet_it), range_start(range_start), range_end(range_end) + { + if (it < range_start || it > range_end) + throw std::out_of_range("Invalid utf-8 iterator position"); + } + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::next(temp, range_end); + } + bool operator == (const iterator& rhs) const + { + if (range_start != rhs.range_start || range_end != rhs.range_end) + throw std::logic_error("Comparing utf-8 iterators defined with different ranges"); + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + utf8::next(it, range_end); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + utf8::next(it, range_end); + return temp; + } + iterator& operator -- () + { + utf8::prior(it, range_start); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::prior(it, range_start); + return temp; + } + }; // class iterator + +} // namespace utf8 + +#endif //header guard + + diff --git a/libraries/libfc/src/utf8/core.h b/libraries/libfc/src/utf8/core.h new file mode 100644 index 0000000000..693d388c07 --- /dev/null +++ b/libraries/libfc/src/utf8/core.h @@ -0,0 +1,329 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_CORE_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include + +namespace utf8 +{ + // The typedefs for 8-bit, 16-bit and 32-bit unsigned integers + // You may need to change them to match your system. + // These typedefs have the same names as ones from cstdint, or boost/cstdint + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + +// Helper code - not intended to be directly called by the library users. May be changed at any time +namespace internal +{ + // Unicode constants + // Leading (high) surrogates: 0xd800 - 0xdbff + // Trailing (low) surrogates: 0xdc00 - 0xdfff + const uint16_t LEAD_SURROGATE_MIN = 0xd800u; + const uint16_t LEAD_SURROGATE_MAX = 0xdbffu; + const uint16_t TRAIL_SURROGATE_MIN = 0xdc00u; + const uint16_t TRAIL_SURROGATE_MAX = 0xdfffu; + const uint16_t LEAD_OFFSET = LEAD_SURROGATE_MIN - (0x10000 >> 10); + const uint32_t SURROGATE_OFFSET = 0x10000u - (LEAD_SURROGATE_MIN << 10) - TRAIL_SURROGATE_MIN; + + // Maximum valid value for a Unicode code point + const uint32_t CODE_POINT_MAX = 0x0010ffffu; + + template + inline uint8_t mask8(octet_type oc) + { + return static_cast(0xff & oc); + } + template + inline uint16_t mask16(u16_type oc) + { + return static_cast(0xffff & oc); + } + template + inline bool is_trail(octet_type oc) + { + return ((utf8::internal::mask8(oc) >> 6) == 0x2); + } + + template + inline bool is_lead_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= LEAD_SURROGATE_MAX); + } + + template + inline bool is_trail_surrogate(u16 cp) + { + return (cp >= TRAIL_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_surrogate(u16 cp) + { + return (cp >= LEAD_SURROGATE_MIN && cp <= TRAIL_SURROGATE_MAX); + } + + template + inline bool is_code_point_valid(u32 cp) + { + return (cp <= CODE_POINT_MAX && !utf8::internal::is_surrogate(cp)); + } + + template + inline typename std::iterator_traits::difference_type + sequence_length(octet_iterator lead_it) + { + uint8_t lead = utf8::internal::mask8(*lead_it); + if (lead < 0x80) + return 1; + else if ((lead >> 5) == 0x6) + return 2; + else if ((lead >> 4) == 0xe) + return 3; + else if ((lead >> 3) == 0x1e) + return 4; + else + return 0; + } + + template + inline bool is_overlong_sequence(uint32_t cp, octet_difference_type length) + { + if (cp < 0x80) { + if (length != 1) + return true; + } + else if (cp < 0x800) { + if (length != 2) + return true; + } + else if (cp < 0x10000) { + if (length != 3) + return true; + } + + return false; + } + + enum utf_error {UTF8_OK, NOT_ENOUGH_ROOM, INVALID_LEAD, INCOMPLETE_SEQUENCE, OVERLONG_SEQUENCE, INVALID_CODE_POINT}; + + /// Helper for get_sequence_x + template + utf_error increase_safely(octet_iterator& it, octet_iterator end) + { + if (++it == end) + return NOT_ENOUGH_ROOM; + + if (!utf8::internal::is_trail(*it)) + return INCOMPLETE_SEQUENCE; + + return UTF8_OK; + } + + #define UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(IT, END) {utf_error ret = increase_safely(IT, END); if (ret != UTF8_OK) return ret;} + + /// get_sequence_x functions decode utf-8 sequences of the length x + template + utf_error get_sequence_1(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + return UTF8_OK; + } + + template + utf_error get_sequence_2(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 6) & 0x7ff) + ((*it) & 0x3f); + + return UTF8_OK; + } + + template + utf_error get_sequence_3(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + template + utf_error get_sequence_4(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + if (it == end) + return NOT_ENOUGH_ROOM; + + code_point = utf8::internal::mask8(*it); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point = ((code_point << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (utf8::internal::mask8(*it) << 6) & 0xfff; + + UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR(it, end) + + code_point += (*it) & 0x3f; + + return UTF8_OK; + } + + #undef UTF8_CPP_INCREASE_AND_RETURN_ON_ERROR + + template + utf_error validate_next(octet_iterator& it, octet_iterator end, uint32_t& code_point) + { + // Save the original value of it so we can go back in case of failure + // Of course, it does not make much sense with i.e. stream iterators + octet_iterator original_it = it; + + uint32_t cp = 0; + // Determine the sequence length based on the lead octet + typedef typename std::iterator_traits::difference_type octet_difference_type; + const octet_difference_type length = utf8::internal::sequence_length(it); + + // Get trail octets and calculate the code point + utf_error err = UTF8_OK; + switch (length) { + case 0: + return INVALID_LEAD; + case 1: + err = utf8::internal::get_sequence_1(it, end, cp); + break; + case 2: + err = utf8::internal::get_sequence_2(it, end, cp); + break; + case 3: + err = utf8::internal::get_sequence_3(it, end, cp); + break; + case 4: + err = utf8::internal::get_sequence_4(it, end, cp); + break; + } + + if (err == UTF8_OK) { + // Decoding succeeded. Now, security checks... + if (utf8::internal::is_code_point_valid(cp)) { + if (!utf8::internal::is_overlong_sequence(cp, length)){ + // Passed! Return here. + code_point = cp; + ++it; + return UTF8_OK; + } + else + err = OVERLONG_SEQUENCE; + } + else + err = INVALID_CODE_POINT; + } + + // Failure branch - restore the original value of the iterator + it = original_it; + return err; + } + + template + inline utf_error validate_next(octet_iterator& it, octet_iterator end) { + uint32_t ignored; + return utf8::internal::validate_next(it, end, ignored); + } + +} // namespace internal + + /// The library API - functions intended to be called by the users + + // Byte order mark + const uint8_t bom[] = {0xef, 0xbb, 0xbf}; + + template + octet_iterator find_invalid(octet_iterator start, octet_iterator end) + { + octet_iterator result = start; + while (result != end) { + utf8::internal::utf_error err_code = utf8::internal::validate_next(result, end); + if (err_code != internal::UTF8_OK) + return result; + } + return result; + } + + template + inline bool is_valid(octet_iterator start, octet_iterator end) + { + return (utf8::find_invalid(start, end) == end); + } + + template + inline bool starts_with_bom (octet_iterator it, octet_iterator end) + { + return ( + ((it != end) && (utf8::internal::mask8(*it++)) == bom[0]) && + ((it != end) && (utf8::internal::mask8(*it++)) == bom[1]) && + ((it != end) && (utf8::internal::mask8(*it)) == bom[2]) + ); + } + + //Deprecated in release 2.3 + template + inline bool is_bom (octet_iterator it) + { + return ( + (utf8::internal::mask8(*it++)) == bom[0] && + (utf8::internal::mask8(*it++)) == bom[1] && + (utf8::internal::mask8(*it)) == bom[2] + ); + } +} // namespace utf8 + +#endif // header guard + + diff --git a/libraries/libfc/src/utf8/unchecked.h b/libraries/libfc/src/utf8/unchecked.h new file mode 100644 index 0000000000..6e7999d846 --- /dev/null +++ b/libraries/libfc/src/utf8/unchecked.h @@ -0,0 +1,234 @@ +// Copyright 2006 Nemanja Trifunovic + +/* +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +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, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + + +#ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 +#define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 + +#include "core.h" + +namespace utf8 +{ + namespace unchecked + { + template + octet_iterator append(uint32_t cp, octet_iterator result) + { + if (cp < 0x80) // one octet + *(result++) = static_cast(cp); + else if (cp < 0x800) { // two octets + *(result++) = static_cast((cp >> 6) | 0xc0); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) { // three octets + *(result++) = static_cast((cp >> 12) | 0xe0); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + else { // four octets + *(result++) = static_cast((cp >> 18) | 0xf0); + *(result++) = static_cast(((cp >> 12) & 0x3f)| 0x80); + *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); + *(result++) = static_cast((cp & 0x3f) | 0x80); + } + return result; + } + + template + uint32_t next(octet_iterator& it) + { + uint32_t cp = utf8::internal::mask8(*it); + typename std::iterator_traits::difference_type length = utf8::internal::sequence_length(it); + switch (length) { + case 1: + break; + case 2: + it++; + cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); + break; + case 3: + ++it; + cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); + ++it; + cp += (*it) & 0x3f; + break; + case 4: + ++it; + cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); + ++it; + cp += (utf8::internal::mask8(*it) << 6) & 0xfff; + ++it; + cp += (*it) & 0x3f; + break; + } + ++it; + return cp; + } + + template + uint32_t peek_next(octet_iterator it) + { + return utf8::unchecked::next(it); + } + + template + uint32_t prior(octet_iterator& it) + { + while (utf8::internal::is_trail(*(--it))) ; + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + + // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous) + template + inline uint32_t previous(octet_iterator& it) + { + return utf8::unchecked::prior(it); + } + + template + void advance (octet_iterator& it, distance_type n) + { + for (distance_type i = 0; i < n; ++i) + utf8::unchecked::next(it); + } + + template + typename std::iterator_traits::difference_type + distance (octet_iterator first, octet_iterator last) + { + typename std::iterator_traits::difference_type dist; + for (dist = 0; first < last; ++dist) + utf8::unchecked::next(first); + return dist; + } + + template + octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) + { + while (start != end) { + uint32_t cp = utf8::internal::mask16(*start++); + // Take care of surrogate pairs first + if (utf8::internal::is_lead_surrogate(cp)) { + uint32_t trail_surrogate = utf8::internal::mask16(*start++); + cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; + } + result = utf8::unchecked::append(cp, result); + } + return result; + } + + template + u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) + { + while (start < end) { + uint32_t cp = utf8::unchecked::next(start); + if (cp > 0xffff) { //make a surrogate pair + *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); + *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); + } + else + *result++ = static_cast(cp); + } + return result; + } + + template + octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) + { + while (start != end) + result = utf8::unchecked::append(*(start++), result); + + return result; + } + + template + u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) + { + while (start < end) + (*result++) = utf8::unchecked::next(start); + + return result; + } + + // The iterator class + template + class iterator { + octet_iterator it; + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = uint32_t; + using difference_type = std::ptrdiff_t; + using pointer = uint32_t*; + using reference = uint32_t&; + + iterator () {} + explicit iterator (const octet_iterator& octet_it): it(octet_it) {} + // the default "big three" are OK + octet_iterator base () const { return it; } + uint32_t operator * () const + { + octet_iterator temp = it; + return utf8::unchecked::next(temp); + } + bool operator == (const iterator& rhs) const + { + return (it == rhs.it); + } + bool operator != (const iterator& rhs) const + { + return !(operator == (rhs)); + } + iterator& operator ++ () + { + ::std::advance(it, utf8::internal::sequence_length(it)); + return *this; + } + iterator operator ++ (int) + { + iterator temp = *this; + ::std::advance(it, utf8::internal::sequence_length(it)); + return temp; + } + iterator& operator -- () + { + utf8::unchecked::prior(it); + return *this; + } + iterator operator -- (int) + { + iterator temp = *this; + utf8::unchecked::prior(it); + return temp; + } + }; // class iterator + + } // namespace utf8::unchecked +} // namespace utf8 + + +#endif // header guard + diff --git a/libraries/libfc/src/utf8/utf8cpp.html b/libraries/libfc/src/utf8/utf8cpp.html new file mode 100644 index 0000000000..6f2aacbe7b --- /dev/null +++ b/libraries/libfc/src/utf8/utf8cpp.html @@ -0,0 +1,1789 @@ + + + + + + + + + UTF8-CPP: UTF-8 with C++ in a Portable Way + + + + +

+ UTF8-CPP: UTF-8 with C++ in a Portable Way +

+
+ +

+ Introduction +

+

+ Many C++ developers miss an easy and portable way of handling Unicode encoded + strings. The original C++ Standard (known as C++98 or C++03) is Unicode agnostic. + C++11 provides some support for Unicode on core language and library level: + u8, u, and U character and string literals, char16_t and char32_t character types, + u16string and u32string library classes, and codecvt support for conversions + between Unicode encoding forms. + In the meantime, developers use third party libraries like ICU, OS specific capabilities, or simply + roll out their own solutions. +

+

+ In order to easily handle UTF-8 encoded Unicode strings, I came up with a small + generic library. For anybody used to work with STL algorithms and iterators, it should be + easy and natural to use. The code is freely available for any purpose - check out + the license at the beginning of the utf8.h file. If you run into + bugs or performance issues, please let me know and I'll do my best to address them. +

+

+ The purpose of this article is not to offer an introduction to Unicode in general, + and UTF-8 in particular. If you are not familiar with Unicode, be sure to check out + Unicode Home Page or some other source of + information for Unicode. Also, it is not my aim to advocate the use of UTF-8 + encoded strings in C++ programs; if you want to handle UTF-8 encoded strings from + C++, I am sure you have good reasons for it. +

+

+ Examples of use +

+

+ Introductionary Sample +

+

+ To illustrate the use of the library, let's start with a small but complete program + that opens a file containing UTF-8 encoded text, reads it line by line, checks each line + for invalid UTF-8 byte sequences, and converts it to UTF-16 encoding and back to UTF-8: +

+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+#include "utf8.h"
+using namespace std;
+int main(int argc, char** argv)
+{
+    if (argc != 2) {
+        cout << "\nUsage: docsample filename\n";
+        return 0;
+    }
+
+    const char* test_file_path = argv[1];
+    // Open the test file (contains UTF-8 encoded text)
+    ifstream fs8(test_file_path);
+    if (!fs8.is_open()) {
+    cout << "Could not open " << test_file_path << endl;
+    return 0;
+    }
+
+    unsigned line_count = 1;
+    string line;
+    // Play with all the lines in the file
+    while (getline(fs8, line)) {
+       // check for invalid utf-8 (for a simple yes/no check, there is also utf8::is_valid function)
+        string::iterator end_it = utf8::find_invalid(line.begin(), line.end());
+        if (end_it != line.end()) {
+            cout << "Invalid UTF-8 encoding detected at line " << line_count << "\n";
+            cout << "This part is fine: " << string(line.begin(), end_it) << "\n";
+        }
+
+        // Get the line length (at least for the valid part)
+        int length = utf8::distance(line.begin(), end_it);
+        cout << "Length of line " << line_count << " is " << length <<  "\n";
+
+        // Convert it to utf-16
+        vector<unsigned short> utf16line;
+        utf8::utf8to16(line.begin(), end_it, back_inserter(utf16line));
+
+        // And back to utf-8
+        string utf8line; 
+        utf8::utf16to8(utf16line.begin(), utf16line.end(), back_inserter(utf8line));
+
+        // Confirm that the conversion went OK:
+        if (utf8line != string(line.begin(), end_it))
+            cout << "Error in UTF-16 conversion at line: " << line_count << "\n";        
+
+        line_count++;
+    }
+    return 0;
+}
+
+

+ In the previous code sample, for each line we performed + a detection of invalid UTF-8 sequences with find_invalid; the number + of characters (more precisely - the number of Unicode code points, including the end + of line and even BOM if there is one) in each line was + determined with a use of utf8::distance; finally, we have converted + each line to UTF-16 encoding with utf8to16 and back to UTF-8 with + utf16to8. +

+

Checking if a file contains valid UTF-8 text

+

+Here is a function that checks whether the content of a file is valid UTF-8 encoded text without +reading the content into the memory: +

+
    
+bool valid_utf8_file(iconst char* file_name)
+{
+    ifstream ifs(file_name);
+    if (!ifs)
+        return false; // even better, throw here
+
+    istreambuf_iterator<char> it(ifs.rdbuf());
+    istreambuf_iterator<char> eos;
+
+    return utf8::is_valid(it, eos);
+}
+
+

+Because the function utf8::is_valid() works with input iterators, we were able +to pass an istreambuf_iterator to it and read the content of the file directly +without loading it to the memory first.

+

+Note that other functions that take input iterator arguments can be used in a similar way. For +instance, to read the content of a UTF-8 encoded text file and convert the text to UTF-16, just +do something like: +

+
+    utf8::utf8to16(it, eos, back_inserter(u16string));
+
+

Ensure that a string contains valid UTF-8 text

+

+If we have some text that "probably" contains UTF-8 encoded text and we want to +replace any invalid UTF-8 sequence with a replacement character, something like +the following function may be used: +

+
+void fix_utf8_string(std::string& str)
+{
+    std::string temp;
+    utf8::replace_invalid(str.begin(), str.end(), back_inserter(temp));
+    str = temp;
+}
+
+

The function will replace any invalid UTF-8 sequence with a Unicode replacement character. +There is an overloaded function that enables the caller to supply their own replacement character. +

+

+ Reference +

+

+ Functions From utf8 Namespace +

+

+ utf8::append +

+

+ Available in version 1.0 and later. +

+

+ Encodes a 32 bit code point as a UTF-8 sequence of octets and appends the sequence + to a UTF-8 string. +

+
+template <typename octet_iterator>
+octet_iterator append(uint32_t cp, octet_iterator result);
+   
+
+

+ octet_iterator: an output iterator.
+ cp: a 32 bit integer representing a code point to append to the + sequence.
+ result: an output iterator to the place in the sequence where to + append the code point.
+ Return value: an iterator pointing to the place + after the newly appended sequence. +

+

+ Example of use: +

+
+unsigned char u[5] = {0,0,0,0,0};
+unsigned char* end = append(0x0448, u);
+assert (u[0] == 0xd1 && u[1] == 0x88 && u[2] == 0 && u[3] == 0 && u[4] == 0);
+
+

+ Note that append does not allocate any memory - it is the burden of + the caller to make sure there is enough memory allocated for the operation. To make + things more interesting, append can add anywhere between 1 and 4 + octets to the sequence. In practice, you would most often want to use + std::back_inserter to ensure that the necessary memory is allocated. +

+

+ In case of an invalid code point, a utf8::invalid_code_point exception + is thrown. +

+

+ utf8::next +

+

+ Available in version 1.0 and later. +

+

+ Given the iterator to the beginning of the UTF-8 sequence, it returns the code + point and moves the iterator to the next position. +

+
+template <typename octet_iterator> 
+uint32_t next(octet_iterator& it, octet_iterator end);
+   
+
+

+ octet_iterator: an input iterator.
+ it: a reference to an iterator pointing to the beginning of an UTF-8 + encoded code point. After the function returns, it is incremented to point to the + beginning of the next code point.
+ end: end of the UTF-8 sequence to be processed. If it + gets equal to end during the extraction of a code point, an + utf8::not_enough_room exception is thrown.
+ Return value: the 32 bit representation of the + processed UTF-8 code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars;
+int cp = next(w, twochars + 6);
+assert (cp == 0x65e5);
+assert (w == twochars + 3);
+
+

+ This function is typically used to iterate through a UTF-8 encoded string. +

+

+ In case of an invalid UTF-8 seqence, a utf8::invalid_utf8 exception is + thrown. +

+

+ utf8::peek_next +

+

+ Available in version 2.1 and later. +

+

+ Given the iterator to the beginning of the UTF-8 sequence, it returns the code + point for the following sequence without changing the value of the iterator. +

+
+template <typename octet_iterator> 
+uint32_t peek_next(octet_iterator it, octet_iterator end);
+   
+
+

+ octet_iterator: an input iterator.
+ it: an iterator pointing to the beginning of an UTF-8 + encoded code point.
+ end: end of the UTF-8 sequence to be processed. If it + gets equal to end during the extraction of a code point, an + utf8::not_enough_room exception is thrown.
+ Return value: the 32 bit representation of the + processed UTF-8 code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars;
+int cp = peek_next(w, twochars + 6);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ In case of an invalid UTF-8 seqence, a utf8::invalid_utf8 exception is + thrown. +

+

+ utf8::prior +

+

+ Available in version 1.02 and later. +

+

+ Given a reference to an iterator pointing to an octet in a UTF-8 sequence, it + decreases the iterator until it hits the beginning of the previous UTF-8 encoded + code point and returns the 32 bits representation of the code point. +

+
+template <typename octet_iterator> 
+uint32_t prior(octet_iterator& it, octet_iterator start);
+   
+
+

+ octet_iterator: a bidirectional iterator.
+ it: a reference pointing to an octet within a UTF-8 encoded string. + After the function returns, it is decremented to point to the beginning of the + previous code point.
+ start: an iterator to the beginning of the sequence where the search + for the beginning of a code point is performed. It is a + safety measure to prevent passing the beginning of the string in the search for a + UTF-8 lead octet.
+ Return value: the 32 bit representation of the + previous code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+unsigned char* w = twochars + 3;
+int cp = prior (w, twochars);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ This function has two purposes: one is two iterate backwards through a UTF-8 + encoded string. Note that it is usually a better idea to iterate forward instead, + since utf8::next is faster. The second purpose is to find a beginning + of a UTF-8 sequence if we have a random position within a string. Note that in that + case utf8::prior may not detect an invalid UTF-8 sequence in some scenarios: + for instance if there are superfluous trail octets, it will just skip them. +

+

+ it will typically point to the beginning of + a code point, and start will point to the + beginning of the string to ensure we don't go backwards too far. it is + decreased until it points to a lead UTF-8 octet, and then the UTF-8 sequence + beginning with that octet is decoded to a 32 bit representation and returned. +

+

+ In case start is reached before a UTF-8 lead octet is hit, or if an + invalid UTF-8 sequence is started by the lead octet, an invalid_utf8 + exception is thrown. +

+

In case start equals it, a not_enough_room + exception is thrown. +

+ utf8::previous +

+

+ Deprecated in version 1.02 and later. +

+

+ Given a reference to an iterator pointing to an octet in a UTF-8 seqence, it + decreases the iterator until it hits the beginning of the previous UTF-8 encoded + code point and returns the 32 bits representation of the code point. +

+
+template <typename octet_iterator> 
+uint32_t previous(octet_iterator& it, octet_iterator pass_start);
+   
+
+

+ octet_iterator: a random access iterator.
+ it: a reference pointing to an octet within a UTF-8 encoded string. + After the function returns, it is decremented to point to the beginning of the + previous code point.
+ pass_start: an iterator to the point in the sequence where the search + for the beginning of a code point is aborted if no result was reached. It is a + safety measure to prevent passing the beginning of the string in the search for a + UTF-8 lead octet.
+ Return value: the 32 bit representation of the + previous code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+unsigned char* w = twochars + 3;
+int cp = previous (w, twochars - 1);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ utf8::previous is deprecated, and utf8::prior should + be used instead, although the existing code can continue using this function. + The problem is the parameter pass_start that points to the position + just before the beginning of the sequence. Standard containers don't have the + concept of "pass start" and the function can not be used with their iterators. +

+

+ it will typically point to the beginning of + a code point, and pass_start will point to the octet just before the + beginning of the string to ensure we don't go backwards too far. it is + decreased until it points to a lead UTF-8 octet, and then the UTF-8 sequence + beginning with that octet is decoded to a 32 bit representation and returned. +

+

+ In case pass_start is reached before a UTF-8 lead octet is hit, or if an + invalid UTF-8 sequence is started by the lead octet, an invalid_utf8 + exception is thrown +

+

+ utf8::advance +

+

+ Available in version 1.0 and later. +

+

+ Advances an iterator by the specified number of code points within an UTF-8 + sequence. +

+
+template <typename octet_iterator, typename distance_type> 
+void advance (octet_iterator& it, distance_type n, octet_iterator end);
+   
+
+

+ octet_iterator: an input iterator.
+ distance_type: an integral type convertible to octet_iterator's difference type.
+ it: a reference to an iterator pointing to the beginning of an UTF-8 + encoded code point. After the function returns, it is incremented to point to the + nth following code point.
+ n: a positive integer that shows how many code points we want to + advance.
+ end: end of the UTF-8 sequence to be processed. If it + gets equal to end during the extraction of a code point, an + utf8::not_enough_room exception is thrown.
+

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+unsigned char* w = twochars;
+advance (w, 2, twochars + 6);
+assert (w == twochars + 5);
+
+

+ This function works only "forward". In case of a negative n, there is + no effect. +

+

+ In case of an invalid code point, a utf8::invalid_code_point exception + is thrown. +

+

+ utf8::distance +

+

+ Available in version 1.0 and later. +

+

+ Given the iterators to two UTF-8 encoded code points in a seqence, returns the + number of code points between them. +

+
+template <typename octet_iterator> 
+typename std::iterator_traits<octet_iterator>::difference_type distance (octet_iterator first, octet_iterator last);
+   
+
+

+ octet_iterator: an input iterator.
+ first: an iterator to a beginning of a UTF-8 encoded code point.
+ last: an iterator to a "post-end" of the last UTF-8 encoded code + point in the sequence we are trying to determine the length. It can be the + beginning of a new code point, or not.
+ Return value the distance between the iterators, + in code points. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+size_t dist = utf8::distance(twochars, twochars + 5);
+assert (dist == 2);
+
+

+ This function is used to find the length (in code points) of a UTF-8 encoded + string. The reason it is called distance, rather than, say, + length is mainly because developers are used that length is an + O(1) function. Computing the length of an UTF-8 string is a linear operation, and + it looked better to model it after std::distance algorithm. +

+

+ In case of an invalid UTF-8 seqence, a utf8::invalid_utf8 exception is + thrown. If last does not point to the past-of-end of a UTF-8 seqence, + a utf8::not_enough_room exception is thrown. +

+

+ utf8::utf16to8 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-16 encoded string to UTF-8. +

+
+template <typename u16bit_iterator, typename octet_iterator>
+octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result);
+   
+
+

+ u16bit_iterator: an input iterator.
+ octet_iterator: an output iterator.
+ start: an iterator pointing to the beginning of the UTF-16 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-16 encoded + string to convert.
+ result: an output iterator to the place in the UTF-8 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-8 string. +

+

+ Example of use: +

+
+unsigned short utf16string[] = {0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e};
+vector<unsigned char> utf8result;
+utf16to8(utf16string, utf16string + 5, back_inserter(utf8result));
+assert (utf8result.size() == 10);    
+
+

+ In case of invalid UTF-16 sequence, a utf8::invalid_utf16 exception is + thrown. +

+

+ utf8::utf8to16 +

+

+ Available in version 1.0 and later. +

+

+ Converts an UTF-8 encoded string to UTF-16 +

+
+template <typename u16bit_iterator, typename octet_iterator>
+u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result);
+   
+
+

+ octet_iterator: an input iterator.
+ u16bit_iterator: an output iterator.
+ start: an iterator pointing to the beginning of the UTF-8 encoded + string to convert. < br /> end: an iterator pointing to + pass-the-end of the UTF-8 encoded string to convert.
+ result: an output iterator to the place in the UTF-16 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-16 string. +

+

+ Example of use: +

+
+char utf8_with_surrogates[] = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e";
+vector <unsigned short> utf16result;
+utf8to16(utf8_with_surrogates, utf8_with_surrogates + 9, back_inserter(utf16result));
+assert (utf16result.size() == 4);
+assert (utf16result[2] == 0xd834);
+assert (utf16result[3] == 0xdd1e);
+
+

+ In case of an invalid UTF-8 seqence, a utf8::invalid_utf8 exception is + thrown. If end does not point to the past-of-end of a UTF-8 seqence, a + utf8::not_enough_room exception is thrown. +

+

+ utf8::utf32to8 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-32 encoded string to UTF-8. +

+
+template <typename octet_iterator, typename u32bit_iterator>
+octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result);
+   
+
+

+ octet_iterator: an output iterator.
+ u32bit_iterator: an input iterator.
+ start: an iterator pointing to the beginning of the UTF-32 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-32 encoded + string to convert.
+ result: an output iterator to the place in the UTF-8 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-8 string. +

+

+ Example of use: +

+
+int utf32string[] = {0x448, 0x65E5, 0x10346, 0};
+vector<unsigned char> utf8result;
+utf32to8(utf32string, utf32string + 3, back_inserter(utf8result));
+assert (utf8result.size() == 9);
+
+

+ In case of invalid UTF-32 string, a utf8::invalid_code_point exception + is thrown. +

+

+ utf8::utf8to32 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-8 encoded string to UTF-32. +

+
+template <typename octet_iterator, typename u32bit_iterator>
+u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result);
+   
+
+

+ octet_iterator: an input iterator.
+ u32bit_iterator: an output iterator.
+ start: an iterator pointing to the beginning of the UTF-8 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-8 encoded string + to convert.
+ result: an output iterator to the place in the UTF-32 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-32 string. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+vector<int> utf32result;
+utf8to32(twochars, twochars + 5, back_inserter(utf32result));
+assert (utf32result.size() == 2);
+
+

+ In case of an invalid UTF-8 seqence, a utf8::invalid_utf8 exception is + thrown. If end does not point to the past-of-end of a UTF-8 seqence, a + utf8::not_enough_room exception is thrown. +

+

+ utf8::find_invalid +

+

+ Available in version 1.0 and later. +

+

+ Detects an invalid sequence within a UTF-8 string. +

+
+template <typename octet_iterator> 
+octet_iterator find_invalid(octet_iterator start, octet_iterator end);
+
+

+ octet_iterator: an input iterator.
+ start: an iterator pointing to the beginning of the UTF-8 string to + test for validity.
+ end: an iterator pointing to pass-the-end of the UTF-8 string to test + for validity.
+ Return value: an iterator pointing to the first + invalid octet in the UTF-8 string. In case none were found, equals + end. +

+

+ Example of use: +

+
+char utf_invalid[] = "\xe6\x97\xa5\xd1\x88\xfa";
+char* invalid = find_invalid(utf_invalid, utf_invalid + 6);
+assert (invalid == utf_invalid + 5);
+
+

+ This function is typically used to make sure a UTF-8 string is valid before + processing it with other functions. It is especially important to call it if before + doing any of the unchecked operations on it. +

+

+ utf8::is_valid +

+

+ Available in version 1.0 and later. +

+

+ Checks whether a sequence of octets is a valid UTF-8 string. +

+
+template <typename octet_iterator> 
+bool is_valid(octet_iterator start, octet_iterator end);
+   
+
+

+ octet_iterator: an input iterator.
+ start: an iterator pointing to the beginning of the UTF-8 string to + test for validity.
+ end: an iterator pointing to pass-the-end of the UTF-8 string to test + for validity.
+ Return value: true if the sequence + is a valid UTF-8 string; false if not. +

+ Example of use: +
+char utf_invalid[] = "\xe6\x97\xa5\xd1\x88\xfa";
+bool bvalid = is_valid(utf_invalid, utf_invalid + 6);
+assert (bvalid == false);
+
+

+ is_valid is a shorthand for find_invalid(start, end) == + end;. You may want to use it to make sure that a byte seqence is a valid + UTF-8 string without the need to know where it fails if it is not valid. +

+

+ utf8::replace_invalid +

+

+ Available in version 2.0 and later. +

+

+ Replaces all invalid UTF-8 sequences within a string with a replacement marker. +

+
+template <typename octet_iterator, typename output_iterator>
+output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out, uint32_t replacement);
+template <typename octet_iterator, typename output_iterator>
+output_iterator replace_invalid(octet_iterator start, octet_iterator end, output_iterator out);
+   
+
+

+ octet_iterator: an input iterator.
+ output_iterator: an output iterator.
+ start: an iterator pointing to the beginning of the UTF-8 string to + look for invalid UTF-8 sequences.
+ end: an iterator pointing to pass-the-end of the UTF-8 string to look + for invalid UTF-8 sequences.
+ out: An output iterator to the range where the result of replacement + is stored.
+ replacement: A Unicode code point for the replacement marker. The + version without this parameter assumes the value 0xfffd
+ Return value: An iterator pointing to the place + after the UTF-8 string with replaced invalid sequences. +

+

+ Example of use: +

+
+char invalid_sequence[] = "a\x80\xe0\xa0\xc0\xaf\xed\xa0\x80z";
+vector<char> replace_invalid_result;
+replace_invalid (invalid_sequence, invalid_sequence + sizeof(invalid_sequence), back_inserter(replace_invalid_result), '?');
+bvalid = is_valid(replace_invalid_result.begin(), replace_invalid_result.end());
+assert (bvalid);
+char* fixed_invalid_sequence = "a????z";
+assert (std::equal(replace_invalid_result.begin(), replace_invalid_result.end(), fixed_invalid_sequence));
+
+

+ replace_invalid does not perform in-place replacement of invalid + sequences. Rather, it produces a copy of the original string with the invalid + sequences replaced with a replacement marker. Therefore, out must not + be in the [start, end] range. +

+

+ If end does not point to the past-of-end of a UTF-8 sequence, a + utf8::not_enough_room exception is thrown. +

+

+ utf8::starts_with_bom +

+

+ Available in version 2.3 and later. Relaces deprecated is_bom() function. +

+

+ Checks whether an octet sequence starts with a UTF-8 byte order mark (BOM) +

+
+template <typename octet_iterator> 
+bool starts_with_bom (octet_iterator it, octet_iterator end);
+
+

+ octet_iterator: an input iterator.
+ it: beginning of the octet sequence to check
+ end: pass-end of the sequence to check
+ Return value: true if the sequence + starts with a UTF-8 byte order mark; false if not. +

+

+ Example of use: +

+
+unsigned char byte_order_mark[] = {0xef, 0xbb, 0xbf};
+bool bbom = starts_with_bom(byte_order_mark, byte_order_mark + sizeof(byte_order_mark));
+assert (bbom == true);
+
+

+ The typical use of this function is to check the first three bytes of a file. If + they form the UTF-8 BOM, we want to skip them before processing the actual UTF-8 + encoded text. +

+

+ utf8::is_bom +

+

+ Available in version 1.0 and later. Deprecated in version 2.3. starts_with_bom() should be used + instead. +

+

+ Checks whether a sequence of three octets is a UTF-8 byte order mark (BOM) +

+
+template <typename octet_iterator> 
+bool is_bom (octet_iterator it);  // Deprecated
+
+

+ octet_iterator: an input iterator.
+ it: beginning of the 3-octet sequence to check
+ Return value: true if the sequence + is UTF-8 byte order mark; false if not. +

+

+ Example of use: +

+
+unsigned char byte_order_mark[] = {0xef, 0xbb, 0xbf};
+bool bbom = is_bom(byte_order_mark);
+assert (bbom == true);
+
+

+ The typical use of this function is to check the first three bytes of a file. If + they form the UTF-8 BOM, we want to skip them before processing the actual UTF-8 + encoded text. +

+

+ If a sequence is + shorter than three bytes, an invalid iterator will be dereferenced. Therefore, this function is deprecated + in favor of starts_with_bom()that takes the end of sequence as an argument. +

+

+ Types From utf8 Namespace +

+

utf8::exception +

+

+ Available in version 2.3 and later. +

+

+ Base class for the exceptions thrown by UTF CPP library functions. +

+
+class exception : public std::exception {};
+
+

+ Example of use: +

+
+try {
+  code_that_uses_utf_cpp_library();
+}
+catch(const utf8::exception& utfcpp_ex) {
+  cerr << utfcpp_ex.what();
+}
+
+ +

utf8::invalid_code_point +

+

+ Available in version 1.0 and later. +

+

+ Thrown by UTF8 CPP functions such as advance and next if an UTF-8 sequence represents and invalid code point. +

+ +
+class invalid_code_point : public exception {
+public: 
+    uint32_t code_point() const;
+};
+
+
+

+ Member function code_point() can be used to determine the invalid code point that + caused the exception to be thrown. +

+

utf8::invalid_utf8 +

+

+ Available in version 1.0 and later. +

+

+ Thrown by UTF8 CPP functions such as next and prior if an invalid UTF-8 sequence + is detected during decoding. +

+ +
+class invalid_utf8 : public exception {
+public: 
+    uint8_t utf8_octet() const;
+};
+
+ +

+ Member function utf8_octet() can be used to determine the beginning of the byte + sequence that caused the exception to be thrown. +

+ +

utf8::invalid_utf16 +

+

+ Available in version 1.0 and later. +

+

+ Thrown by UTF8 CPP function utf16to8 if an invalid UTF-16 sequence + is detected during decoding. +

+ +
+class invalid_utf16 : public exception {
+public: 
+    uint16_t utf16_word() const;
+};
+
+ +

+ Member function utf16_word() can be used to determine the UTF-16 code unit + that caused the exception to be thrown. +

+

utf8::not_enough_room +

+

+ Available in version 1.0 and later. +

+

+ Thrown by UTF8 CPP functions such as next if the end of the decoded UTF-8 sequence + was reached before the code point was decoded. +

+ +
+class not_enough_room : public exception {};
+
+

+ utf8::iterator +

+

+ Available in version 2.0 and later. +

+

+ Adapts the underlying octet iterator to iterate over the sequence of code points, + rather than raw octets. +

+
+template <typename octet_iterator>
+class iterator;
+
+ +
Member functions
+
+
iterator();
the deafult constructor; the underlying octet_iterator is + constructed with its default constructor. +
explicit iterator (const octet_iterator& octet_it, + const octet_iterator& range_start, + const octet_iterator& range_end);
a constructor + that initializes the underlying octet_iterator with octet_it + and sets the range in which the iterator is considered valid. +
octet_iterator base () const;
returns the + underlying octet_iterator. +
uint32_t operator * () const;
decodes the utf-8 sequence + the underlying octet_iterator is pointing to and returns the code point. +
bool operator == (const iterator& rhs) + const;
returns true + if the two underlaying iterators are equal. +
bool operator != (const iterator& rhs) + const;
returns true + if the two underlaying iterators are not equal. +
iterator& operator ++ ();
the prefix increment - moves + the iterator to the next UTF-8 encoded code point. +
iterator operator ++ (int);
+ the postfix increment - moves the iterator to the next UTF-8 encoded code point and returns the current one. +
iterator& operator -- ();
the prefix decrement - moves + the iterator to the previous UTF-8 encoded code point. +
iterator operator -- (int);
+ the postfix decrement - moves the iterator to the previous UTF-8 encoded code point and returns the current one. +
+

+ Example of use: +

+
+char* threechars = "\xf0\x90\x8d\x86\xe6\x97\xa5\xd1\x88";
+utf8::iterator<char*> it(threechars, threechars, threechars + 9);
+utf8::iterator<char*> it2 = it;
+assert (it2 == it);
+assert (*it == 0x10346);
+assert (*(++it) == 0x65e5);
+assert ((*it++) == 0x65e5);
+assert (*it == 0x0448);
+assert (it != it2);
+utf8::iterator<char*> endit (threechars + 9, threechars, threechars + 9);  
+assert (++it == endit);
+assert (*(--it) == 0x0448);
+assert ((*it--) == 0x0448);
+assert (*it == 0x65e5);
+assert (--it == utf8::iterator<char*>(threechars, threechars, threechars + 9));
+assert (*it == 0x10346);
+
+

+ The purpose of utf8::iterator adapter is to enable easy iteration as well as the use of STL + algorithms with UTF-8 encoded strings. Increment and decrement operators are implemented in terms of + utf8::next() and utf8::prior() functions. +

+

+ Note that utf8::iterator adapter is a checked iterator. It operates on the range specified in + the constructor; any attempt to go out of that range will result in an exception. Even the comparison operators + require both iterator object to be constructed against the same range - otherwise an exception is thrown. Typically, + the range will be determined by sequence container functions begin and end, i.e.: +

+
+std::string s = "example";
+utf8::iterator i (s.begin(), s.begin(), s.end());
+
+

+ Functions From utf8::unchecked Namespace +

+

+ utf8::unchecked::append +

+

+ Available in version 1.0 and later. +

+

+ Encodes a 32 bit code point as a UTF-8 sequence of octets and appends the sequence + to a UTF-8 string. +

+
+template <typename octet_iterator>
+octet_iterator append(uint32_t cp, octet_iterator result);
+   
+
+

+ cp: A 32 bit integer representing a code point to append to the + sequence.
+ result: An output iterator to the place in the sequence where to + append the code point.
+ Return value: An iterator pointing to the place + after the newly appended sequence. +

+

+ Example of use: +

+
+unsigned char u[5] = {0,0,0,0,0};
+unsigned char* end = unchecked::append(0x0448, u);
+assert (u[0] == 0xd1 && u[1] == 0x88 && u[2] == 0 && u[3] == 0 && u[4] == 0);
+
+

+ This is a faster but less safe version of utf8::append. It does not + check for validity of the supplied code point, and may produce an invalid UTF-8 + sequence. +

+

+ utf8::unchecked::next +

+

+ Available in version 1.0 and later. +

+

+ Given the iterator to the beginning of a UTF-8 sequence, it returns the code point + and moves the iterator to the next position. +

+
+template <typename octet_iterator>
+uint32_t next(octet_iterator& it);
+   
+
+

+ it: a reference to an iterator pointing to the beginning of an UTF-8 + encoded code point. After the function returns, it is incremented to point to the + beginning of the next code point.
+ Return value: the 32 bit representation of the + processed UTF-8 code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars;
+int cp = unchecked::next(w);
+assert (cp == 0x65e5);
+assert (w == twochars + 3);
+
+

+ This is a faster but less safe version of utf8::next. It does not + check for validity of the supplied UTF-8 sequence. +

+

+ utf8::unchecked::peek_next +

+

+ Available in version 2.1 and later. +

+

+ Given the iterator to the beginning of a UTF-8 sequence, it returns the code point. +

+
+template <typename octet_iterator>
+uint32_t peek_next(octet_iterator it);
+   
+
+

+ it: an iterator pointing to the beginning of an UTF-8 + encoded code point.
+ Return value: the 32 bit representation of the + processed UTF-8 code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars;
+int cp = unchecked::peek_next(w);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ This is a faster but less safe version of utf8::peek_next. It does not + check for validity of the supplied UTF-8 sequence. +

+

+ utf8::unchecked::prior +

+

+ Available in version 1.02 and later. +

+

+ Given a reference to an iterator pointing to an octet in a UTF-8 seqence, it + decreases the iterator until it hits the beginning of the previous UTF-8 encoded + code point and returns the 32 bits representation of the code point. +

+
+template <typename octet_iterator>
+uint32_t prior(octet_iterator& it);
+   
+
+

+ it: a reference pointing to an octet within a UTF-8 encoded string. + After the function returns, it is decremented to point to the beginning of the + previous code point.
+ Return value: the 32 bit representation of the + previous code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars + 3;
+int cp = unchecked::prior (w);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ This is a faster but less safe version of utf8::prior. It does not + check for validity of the supplied UTF-8 sequence and offers no boundary checking. +

+

+ utf8::unchecked::previous (deprecated, see utf8::unchecked::prior) +

+

+ Deprecated in version 1.02 and later. +

+

+ Given a reference to an iterator pointing to an octet in a UTF-8 seqence, it + decreases the iterator until it hits the beginning of the previous UTF-8 encoded + code point and returns the 32 bits representation of the code point. +

+
+template <typename octet_iterator>
+uint32_t previous(octet_iterator& it);
+   
+
+

+ it: a reference pointing to an octet within a UTF-8 encoded string. + After the function returns, it is decremented to point to the beginning of the + previous code point.
+ Return value: the 32 bit representation of the + previous code point. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars + 3;
+int cp = unchecked::previous (w);
+assert (cp == 0x65e5);
+assert (w == twochars);
+
+

+ The reason this function is deprecated is just the consistency with the "checked" + versions, where prior should be used instead of previous. + In fact, unchecked::previous behaves exactly the same as + unchecked::prior +

+

+ This is a faster but less safe version of utf8::previous. It does not + check for validity of the supplied UTF-8 sequence and offers no boundary checking. +

+

+ utf8::unchecked::advance +

+

+ Available in version 1.0 and later. +

+

+ Advances an iterator by the specified number of code points within an UTF-8 + sequence. +

+
+template <typename octet_iterator, typename distance_type>
+void advance (octet_iterator& it, distance_type n);
+   
+
+

+ it: a reference to an iterator pointing to the beginning of an UTF-8 + encoded code point. After the function returns, it is incremented to point to the + nth following code point.
+ n: a positive integer that shows how many code points we want to + advance.
+

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+char* w = twochars;
+unchecked::advance (w, 2);
+assert (w == twochars + 5);
+
+

+ This function works only "forward". In case of a negative n, there is + no effect. +

+

+ This is a faster but less safe version of utf8::advance. It does not + check for validity of the supplied UTF-8 sequence and offers no boundary checking. +

+

+ utf8::unchecked::distance +

+

+ Available in version 1.0 and later. +

+

+ Given the iterators to two UTF-8 encoded code points in a seqence, returns the + number of code points between them. +

+
+template <typename octet_iterator>
+typename std::iterator_traits<octet_iterator>::difference_type distance (octet_iterator first, octet_iterator last);
+
+

+ first: an iterator to a beginning of a UTF-8 encoded code point.
+ last: an iterator to a "post-end" of the last UTF-8 encoded code + point in the sequence we are trying to determine the length. It can be the + beginning of a new code point, or not.
+ Return value the distance between the iterators, + in code points. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+size_t dist = utf8::unchecked::distance(twochars, twochars + 5);
+assert (dist == 2);
+
+

+ This is a faster but less safe version of utf8::distance. It does not + check for validity of the supplied UTF-8 sequence. +

+

+ utf8::unchecked::utf16to8 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-16 encoded string to UTF-8. +

+
+template <typename u16bit_iterator, typename octet_iterator>
+octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result);
+   
+
+

+ start: an iterator pointing to the beginning of the UTF-16 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-16 encoded + string to convert.
+ result: an output iterator to the place in the UTF-8 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-8 string. +

+

+ Example of use: +

+
+unsigned short utf16string[] = {0x41, 0x0448, 0x65e5, 0xd834, 0xdd1e};
+vector<unsigned char> utf8result;
+unchecked::utf16to8(utf16string, utf16string + 5, back_inserter(utf8result));
+assert (utf8result.size() == 10);    
+
+

+ This is a faster but less safe version of utf8::utf16to8. It does not + check for validity of the supplied UTF-16 sequence. +

+

+ utf8::unchecked::utf8to16 +

+

+ Available in version 1.0 and later. +

+

+ Converts an UTF-8 encoded string to UTF-16 +

+
+template <typename u16bit_iterator, typename octet_iterator>
+u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result);
+   
+
+

+ start: an iterator pointing to the beginning of the UTF-8 encoded + string to convert. < br /> end: an iterator pointing to + pass-the-end of the UTF-8 encoded string to convert.
+ result: an output iterator to the place in the UTF-16 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-16 string. +

+

+ Example of use: +

+
+char utf8_with_surrogates[] = "\xe6\x97\xa5\xd1\x88\xf0\x9d\x84\x9e";
+vector <unsigned short> utf16result;
+unchecked::utf8to16(utf8_with_surrogates, utf8_with_surrogates + 9, back_inserter(utf16result));
+assert (utf16result.size() == 4);
+assert (utf16result[2] == 0xd834);
+assert (utf16result[3] == 0xdd1e);
+
+

+ This is a faster but less safe version of utf8::utf8to16. It does not + check for validity of the supplied UTF-8 sequence. +

+

+ utf8::unchecked::utf32to8 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-32 encoded string to UTF-8. +

+
+template <typename octet_iterator, typename u32bit_iterator>
+octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result);
+   
+
+

+ start: an iterator pointing to the beginning of the UTF-32 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-32 encoded + string to convert.
+ result: an output iterator to the place in the UTF-8 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-8 string. +

+

+ Example of use: +

+
+int utf32string[] = {0x448, 0x65e5, 0x10346, 0};
+vector<unsigned char> utf8result;
+utf32to8(utf32string, utf32string + 3, back_inserter(utf8result));
+assert (utf8result.size() == 9);
+
+

+ This is a faster but less safe version of utf8::utf32to8. It does not + check for validity of the supplied UTF-32 sequence. +

+

+ utf8::unchecked::utf8to32 +

+

+ Available in version 1.0 and later. +

+

+ Converts a UTF-8 encoded string to UTF-32. +

+
+template <typename octet_iterator, typename u32bit_iterator>
+u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result);
+   
+
+

+ start: an iterator pointing to the beginning of the UTF-8 encoded + string to convert.
+ end: an iterator pointing to pass-the-end of the UTF-8 encoded string + to convert.
+ result: an output iterator to the place in the UTF-32 string where to + append the result of conversion.
+ Return value: An iterator pointing to the place + after the appended UTF-32 string. +

+

+ Example of use: +

+
+char* twochars = "\xe6\x97\xa5\xd1\x88";
+vector<int> utf32result;
+unchecked::utf8to32(twochars, twochars + 5, back_inserter(utf32result));
+assert (utf32result.size() == 2);
+
+

+ This is a faster but less safe version of utf8::utf8to32. It does not + check for validity of the supplied UTF-8 sequence. +

+

+ Types From utf8::unchecked Namespace +

+

+ utf8::iterator +

+

+ Available in version 2.0 and later. +

+

+ Adapts the underlying octet iterator to iterate over the sequence of code points, + rather than raw octets. +

+
+template <typename octet_iterator>
+class iterator;
+
+ +
Member functions
+
+
iterator();
the deafult constructor; the underlying octet_iterator is + constructed with its default constructor. +
explicit iterator (const octet_iterator& octet_it); +
a constructor + that initializes the underlying octet_iterator with octet_it +
octet_iterator base () const;
returns the + underlying octet_iterator. +
uint32_t operator * () const;
decodes the utf-8 sequence + the underlying octet_iterator is pointing to and returns the code point. +
bool operator == (const iterator& rhs) + const;
returns true + if the two underlaying iterators are equal. +
bool operator != (const iterator& rhs) + const;
returns true + if the two underlaying iterators are not equal. +
iterator& operator ++ ();
the prefix increment - moves + the iterator to the next UTF-8 encoded code point. +
iterator operator ++ (int);
+ the postfix increment - moves the iterator to the next UTF-8 encoded code point and returns the current one. +
iterator& operator -- ();
the prefix decrement - moves + the iterator to the previous UTF-8 encoded code point. +
iterator operator -- (int);
+ the postfix decrement - moves the iterator to the previous UTF-8 encoded code point and returns the current one. +
+

+ Example of use: +

+
+char* threechars = "\xf0\x90\x8d\x86\xe6\x97\xa5\xd1\x88";
+utf8::unchecked::iterator<char*> un_it(threechars);
+utf8::unchecked::iterator<char*> un_it2 = un_it;
+assert (un_it2 == un_it);
+assert (*un_it == 0x10346);
+assert (*(++un_it) == 0x65e5);
+assert ((*un_it++) == 0x65e5);
+assert (*un_it == 0x0448);
+assert (un_it != un_it2);
+utf8::::unchecked::iterator<char*> un_endit (threechars + 9);  
+assert (++un_it == un_endit);
+assert (*(--un_it) == 0x0448);
+assert ((*un_it--) == 0x0448);
+assert (*un_it == 0x65e5);
+assert (--un_it == utf8::unchecked::iterator<char*>(threechars));
+assert (*un_it == 0x10346);
+
+

+ This is an unchecked version of utf8::iterator. It is faster in many cases, but offers + no validity or range checks. +

+

+ Points of interest +

+

+ Design goals and decisions +

+

+ The library was designed to be: +

+
    +
  1. + Generic: for better or worse, there are many C++ string classes out there, and + the library should work with as many of them as possible. +
  2. +
  3. + Portable: the library should be portable both accross different platforms and + compilers. The only non-portable code is a small section that declares unsigned + integers of different sizes: three typedefs. They can be changed by the users of + the library if they don't match their platform. The default setting should work + for Windows (both 32 and 64 bit), and most 32 bit and 64 bit Unix derivatives. +
  4. +
  5. + Lightweight: follow the "pay only for what you use" guideline. +
  6. +
  7. + Unintrusive: avoid forcing any particular design or even programming style on the + user. This is a library, not a framework. +
  8. +
+

+ Alternatives +

+

+ In case you want to look into other means of working with UTF-8 strings from C++, + here is the list of solutions I am aware of: +

+
    +
  1. + ICU Library. It is very powerful, + complete, feature-rich, mature, and widely used. Also big, intrusive, + non-generic, and doesn't play well with the Standard Library. I definitelly + recommend looking at ICU even if you don't plan to use it. +
  2. +
  3. + C++11 language and library features. Still far from complete, and not widely + supported by compiler vendors. +
  4. +
  5. + Glib::ustring. + A class specifically made to work with UTF-8 strings, and also feel like + std::string. If you prefer to have yet another string class in your + code, it may be worth a look. Be aware of the licensing issues, though. +
  6. +
  7. + Platform dependent solutions: Windows and POSIX have functions to convert strings + from one encoding to another. That is only a subset of what my library offers, + but if that is all you need it may be good enough. +
  8. +
+ +
    +
  1. + The Unicode Consortium. +
  2. +
  3. + ICU Library. +
  4. +
  5. + UTF-8 at Wikipedia +
  6. +
  7. + UTF-8 and Unicode FAQ for + Unix/Linux +
  8. +
+ + diff --git a/libraries/libfc/src/variant.cpp b/libraries/libfc/src/variant.cpp new file mode 100644 index 0000000000..4b81c3c06c --- /dev/null +++ b/libraries/libfc/src/variant.cpp @@ -0,0 +1,1035 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fc +{ +/** + * The TypeID is stored in the 'last byte' of the variant. + */ +void set_variant_type( variant* v, variant::type_id t) +{ + char* data = reinterpret_cast(v); + data[ sizeof(variant) -1 ] = t; +} + +variant::variant() +{ + set_variant_type( this, null_type ); +} + +variant::variant( fc::nullptr_t ) +{ + set_variant_type( this, null_type ); +} + +variant::variant( uint8_t val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, uint64_type ); +} + +variant::variant( int8_t val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, int64_type ); +} + +variant::variant( uint16_t val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, uint64_type ); +} + +variant::variant( int16_t val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, int64_type ); +} + +variant::variant( uint32_t val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, uint64_type ); +} + +variant::variant( int32_t val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, int64_type ); +} + +variant::variant( uint64_t val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, uint64_type ); +} + +variant::variant( int64_t val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, int64_type ); +} + +variant::variant( float val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, double_type ); +} + +variant::variant( double val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, double_type ); +} + +variant::variant( bool val ) +{ + *reinterpret_cast(this) = val; + set_variant_type( this, bool_type ); +} + +variant::variant( char* str ) +{ + *reinterpret_cast(this) = new string( str ); + set_variant_type( this, string_type ); +} + +variant::variant( const char* str ) +{ + *reinterpret_cast(this) = new string( str ); + set_variant_type( this, string_type ); +} + +// TODO: do a proper conversion to utf8 +variant::variant( wchar_t* str ) +{ + size_t len = wcslen(str); + boost::scoped_array buffer(new char[len]); + for (unsigned i = 0; i < len; ++i) + buffer[i] = (char)str[i]; + *reinterpret_cast(this) = new string(buffer.get(), len); + set_variant_type( this, string_type ); +} + +// TODO: do a proper conversion to utf8 +variant::variant( const wchar_t* str ) +{ + size_t len = wcslen(str); + boost::scoped_array buffer(new char[len]); + for (unsigned i = 0; i < len; ++i) + buffer[i] = (char)str[i]; + *reinterpret_cast(this) = new string(buffer.get(), len); + set_variant_type( this, string_type ); +} + +variant::variant( fc::string val ) +{ + *reinterpret_cast(this) = new string( fc::move(val) ); + set_variant_type( this, string_type ); +} +variant::variant( blob val ) +{ + *reinterpret_cast(this) = new blob( fc::move(val) ); + set_variant_type( this, blob_type ); +} + +variant::variant( variant_object obj) +{ + *reinterpret_cast(this) = new variant_object(fc::move(obj)); + set_variant_type(this, object_type ); +} +variant::variant( mutable_variant_object obj) +{ + *reinterpret_cast(this) = new variant_object(fc::move(obj)); + set_variant_type(this, object_type ); +} + +variant::variant( variants arr ) +{ + *reinterpret_cast(this) = new variants(fc::move(arr)); + set_variant_type(this, array_type ); +} + + +typedef const variant_object* const_variant_object_ptr; +typedef const variants* const_variants_ptr; +typedef const blob* const_blob_ptr; +typedef const string* const_string_ptr; + +void variant::clear() +{ + switch( get_type() ) + { + case object_type: + delete *reinterpret_cast(this); + break; + case array_type: + delete *reinterpret_cast(this); + break; + case string_type: + delete *reinterpret_cast(this); + break; + case blob_type: + delete *reinterpret_cast(this); + break; + default: + break; + } + set_variant_type( this, null_type ); +} + +variant::variant( const variant& v ) +{ + switch( v.get_type() ) + { + case object_type: + *reinterpret_cast(this) = + new variant_object(**reinterpret_cast(&v)); + set_variant_type( this, object_type ); + return; + case array_type: + *reinterpret_cast(this) = + new variants(**reinterpret_cast(&v)); + set_variant_type( this, array_type ); + return; + case string_type: + *reinterpret_cast(this) = + new string(**reinterpret_cast(&v) ); + set_variant_type( this, string_type ); + return; + case blob_type: + *reinterpret_cast(this) = + new blob(**reinterpret_cast(&v) ); + set_variant_type( this, blob_type ); + return; + default: + memcpy( this, &v, sizeof(v) ); + } +} + +variant::variant( variant&& v ) +{ + memcpy( this, &v, sizeof(v) ); + set_variant_type( &v, null_type ); +} + +variant::~variant() +{ + clear(); +} + +variant& variant::operator=( variant&& v ) +{ + if( this == &v ) return *this; + clear(); + memcpy( (char*)this, (char*)&v, sizeof(v) ); + set_variant_type( &v, null_type ); + return *this; +} + +variant& variant::operator=( const variant& v ) +{ + if( this == &v ) + return *this; + + clear(); + switch( v.get_type() ) + { + case object_type: + *reinterpret_cast(this) = + new variant_object((**reinterpret_cast(&v))); + break; + case array_type: + *reinterpret_cast(this) = + new variants((**reinterpret_cast(&v))); + break; + case string_type: + *reinterpret_cast(this) = new string((**reinterpret_cast(&v)) ); + break; + case blob_type: + *reinterpret_cast(this) = new blob((**reinterpret_cast(&v)) ); + break; + default: + memcpy( this, &v, sizeof(v) ); + } + set_variant_type( this, v.get_type() ); + return *this; +} + +void variant::visit( const visitor& v )const +{ + switch( get_type() ) + { + case null_type: + v.handle(); + return; + case int64_type: + v.handle( *reinterpret_cast(this) ); + return; + case uint64_type: + v.handle( *reinterpret_cast(this) ); + return; + case double_type: + v.handle( *reinterpret_cast(this) ); + return; + case bool_type: + v.handle( *reinterpret_cast(this) ); + return; + case string_type: + v.handle( **reinterpret_cast(this) ); + return; + case array_type: + v.handle( **reinterpret_cast(this) ); + return; + case object_type: + v.handle( **reinterpret_cast(this) ); + return; + case blob_type: + v.handle( **reinterpret_cast(this) ); + return; + default: + FC_THROW_EXCEPTION( assert_exception, "Invalid Type / Corrupted Memory" ); + } +} + +variant::type_id variant::get_type()const +{ + return (type_id)reinterpret_cast(this)[sizeof(*this)-1]; +} + +bool variant::is_null()const +{ + return get_type() == null_type; +} + +bool variant::is_string()const +{ + return get_type() == string_type; +} +bool variant::is_bool()const +{ + return get_type() == bool_type; +} +bool variant::is_double()const +{ + return get_type() == double_type; +} +bool variant::is_uint64()const +{ + return get_type() == uint64_type; +} +bool variant::is_int64()const +{ + return get_type() == int64_type; +} + +bool variant::is_integer()const +{ + switch( get_type() ) + { + case int64_type: + case uint64_type: + case bool_type: + return true; + default: + return false; + } + return false; +} +bool variant::is_numeric()const +{ + switch( get_type() ) + { + case int64_type: + case uint64_type: + case double_type: + case bool_type: + return true; + default: + return false; + } + return false; +} + +bool variant::is_object()const +{ + return get_type() == object_type; +} + +bool variant::is_array()const +{ + return get_type() == array_type; +} +bool variant::is_blob()const +{ + return get_type() == blob_type; +} + +int64_t variant::as_int64()const +{ + switch( get_type() ) + { + case string_type: + return to_int64(**reinterpret_cast(this)); + case double_type: + return int64_t(*reinterpret_cast(this)); + case int64_type: + return *reinterpret_cast(this); + case uint64_type: + return int64_t(*reinterpret_cast(this)); + case bool_type: + return *reinterpret_cast(this); + case null_type: + return 0; + default: + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from ${type} to int64", ("type", get_type()) ); + } +} + +uint64_t variant::as_uint64()const +{ try { + switch( get_type() ) + { + case string_type: + return to_uint64(**reinterpret_cast(this)); + case double_type: + return static_cast(*reinterpret_cast(this)); + case int64_type: + return static_cast(*reinterpret_cast(this)); + case uint64_type: + return *reinterpret_cast(this); + case bool_type: + return static_cast(*reinterpret_cast(this)); + case null_type: + return 0; + default: + FC_THROW_EXCEPTION( bad_cast_exception,"Invalid cast from ${type} to uint64", ("type",get_type())); + } +} FC_CAPTURE_AND_RETHROW( (*this) ) } + + +double variant::as_double()const +{ + switch( get_type() ) + { + case string_type: + return to_double(**reinterpret_cast(this)); + case double_type: + return *reinterpret_cast(this); + case int64_type: + return static_cast(*reinterpret_cast(this)); + case uint64_type: + return static_cast(*reinterpret_cast(this)); + case bool_type: + return *reinterpret_cast(this); + case null_type: + return 0; + default: + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from ${type} to double", ("type",get_type()) ); + } +} + +bool variant::as_bool()const +{ + switch( get_type() ) + { + case string_type: + { + const string& s = **reinterpret_cast(this); + if( s == "true" ) + return true; + if( s == "false" ) + return false; + FC_THROW_EXCEPTION( bad_cast_exception, "Cannot convert string to bool (only \"true\" or \"false\" can be converted)" ); + } + case double_type: + return *reinterpret_cast(this) != 0.0; + case int64_type: + return *reinterpret_cast(this) != 0; + case uint64_type: + return *reinterpret_cast(this) != 0; + case bool_type: + return *reinterpret_cast(this); + case null_type: + return false; + default: + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from ${type} to bool" , ("type",get_type())); + } +} + +string variant::as_string()const +{ + switch( get_type() ) + { + case string_type: + return **reinterpret_cast(this); + case double_type: + return to_string(*reinterpret_cast(this)); + case int64_type: + return to_string(*reinterpret_cast(this)); + case uint64_type: + return to_string(*reinterpret_cast(this)); + case bool_type: + return *reinterpret_cast(this) ? "true" : "false"; + case blob_type: + if( get_blob().data.size() ) + return base64_encode( get_blob().data.data(), get_blob().data.size() ) + "="; + return string(); + case null_type: + return string(); + default: + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from ${type} to string", ("type", get_type() ) ); + } +} + + +/// @throw if get_type() != array_type | null_type +variants& variant::get_array() +{ + if( get_type() == array_type ) + return **reinterpret_cast(this); + + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from ${type} to Array", ("type",get_type()) ); +} +blob& variant::get_blob() +{ + if( get_type() == blob_type ) + return **reinterpret_cast(this); + + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from ${type} to Blob", ("type",get_type()) ); +} +const blob& variant::get_blob()const +{ + if( get_type() == blob_type ) + return **reinterpret_cast(this); + + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from ${type} to Blob", ("type",get_type()) ); +} + +blob variant::as_blob()const +{ + switch( get_type() ) + { + case null_type: return blob(); + case blob_type: return get_blob(); + case string_type: + { + const string& str = get_string(); + if( str.size() == 0 ) return blob(); + if( str.back() == '=' ) + { + std::string b64 = base64_decode( get_string() ); + return blob( { std::vector( b64.begin(), b64.end() ) } ); + } + return blob( { std::vector( str.begin(), str.end() ) } ); + } + case object_type: + case array_type: + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from ${type} to Blob", ("type",get_type()) ); + default: + return blob( { std::vector( (char*)&_data, (char*)&_data + sizeof(_data) ) } ); + } +} + + +/// @throw if get_type() != array_type +const variants& variant::get_array()const +{ + if( get_type() == array_type ) + return **reinterpret_cast(this); + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from ${type} to Array", ("type",get_type()) ); +} + + +/// @throw if get_type() != object_type | null_type +variant_object& variant::get_object() +{ + if( get_type() == object_type ) + return **reinterpret_cast(this); + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from ${type} to Object", ("type",get_type()) ); +} + +const variant& variant::operator[]( const char* key )const +{ + return get_object()[key]; +} +const variant& variant::operator[]( size_t pos )const +{ + return get_array()[pos]; +} + /// @pre is_array() +size_t variant::size()const +{ + return get_array().size(); +} + +size_t variant::estimated_size()const +{ + switch( get_type() ) + { + case null_type: + case int64_type: + case uint64_type: + case double_type: + case bool_type: + return sizeof(*this); + case string_type: + return as_string().length() + sizeof(string) + sizeof(*this); + case array_type: + { + const auto& arr = get_array(); + auto arr_size = arr.size(); + size_t sum = sizeof(*this) + sizeof(variants); + for (size_t iter = 0; iter < arr_size; ++iter) { + sum += arr[iter].estimated_size(); + } + return sum; + } + case object_type: + return get_object().estimated_size() + sizeof(*this); + case blob_type: + return sizeof(blob) + get_blob().data.size() + sizeof(*this); + default: + FC_THROW_EXCEPTION( assert_exception, "Invalid Type / Corrupted Memory" ); + } +} + +const string& variant::get_string()const +{ + if( get_type() == string_type ) + return **reinterpret_cast(this); + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from type '${type}' to string", ("type",get_type()) ); +} + +/// @throw if get_type() != object_type +const variant_object& variant::get_object()const +{ + if( get_type() == object_type ) + return **reinterpret_cast(this); + FC_THROW_EXCEPTION( bad_cast_exception, "Invalid cast from type '${type}' to Object", ("type",get_type()) ); +} + +void from_variant( const variant& var, variants& vo ) +{ + vo = var.get_array(); +} + +//void from_variant( const variant& var, variant_object& vo ) +//{ +// vo = var.get_object(); +//} + +void from_variant( const variant& var, variant& vo ) { vo = var; } + +void to_variant( const uint8_t& var, variant& vo ) { vo = uint64_t(var); } +// TODO: warn on overflow? +void from_variant( const variant& var, uint8_t& vo ){ vo = static_cast(var.as_uint64()); } + +void to_variant( const int8_t& var, variant& vo ) { vo = int64_t(var); } +// TODO: warn on overflow? +void from_variant( const variant& var, int8_t& vo ){ vo = static_cast(var.as_int64()); } + +void to_variant( const uint16_t& var, variant& vo ) { vo = uint64_t(var); } +// TODO: warn on overflow? +void from_variant( const variant& var, uint16_t& vo ){ vo = static_cast(var.as_uint64()); } + +void to_variant( const int16_t& var, variant& vo ) { vo = int64_t(var); } +// TODO: warn on overflow? +void from_variant( const variant& var, int16_t& vo ){ vo = static_cast(var.as_int64()); } + +void to_variant( const uint32_t& var, variant& vo ) { vo = uint64_t(var); } +void from_variant( const variant& var, uint32_t& vo ) +{ + vo = static_cast(var.as_uint64()); +} + +void to_variant( const int32_t& var, variant& vo ) { + vo = int64_t(var); +} + +void from_variant( const variant& var, int32_t& vo ) +{ + vo = static_cast(var.as_int64()); +} + +void to_variant( const unsigned __int128& var, variant& vo ) { + vo = boost::multiprecision::uint128_t( var ).str(); +} + +void from_variant( const variant& var, unsigned __int128& vo ) +{ + if( var.is_uint64() ) { + vo = var.as_uint64(); + } else if( var.is_string() ) { + vo = static_cast( boost::multiprecision::uint128_t(var.as_string()) ); + } else { + FC_THROW_EXCEPTION( bad_cast_exception, "Cannot convert variant of type '${type}' into a uint128_t", ("type", var.get_type()) ); + } +} + +void to_variant( const __int128& var, variant& vo ) { + vo = boost::multiprecision::int128_t( var ).str(); +} + +void from_variant( const variant& var, __int128& vo ) +{ + if( var.is_int64() ) { + vo = var.as_int64(); + } else if( var.is_string() ) { + vo = static_cast<__int128>( boost::multiprecision::int128_t(var.as_string()) ); + } else { + FC_THROW_EXCEPTION( bad_cast_exception, "Cannot convert variant of type '${type}' into a int128_t", ("type", var.get_type()) ); + } +} + +void from_variant( const variant& var, int64_t& vo ) +{ + vo = var.as_int64(); +} + +void from_variant( const variant& var, uint64_t& vo ) +{ + vo = var.as_uint64(); +} + +void from_variant( const variant& var, bool& vo ) +{ + vo = var.as_bool(); +} + +void from_variant( const variant& var, double& vo ) +{ + vo = var.as_double(); +} + +void from_variant( const variant& var, float& vo ) +{ + vo = static_cast(var.as_double()); +} + +void to_variant( const std::string& s, variant& v ) +{ + v = variant( fc::string(s) ); +} + +void from_variant( const variant& var, string& vo ) +{ + vo = var.as_string(); +} + +void to_variant( const std::vector& var, variant& vo ) +{ + FC_ASSERT( var.size() <= MAX_SIZE_OF_BYTE_ARRAYS ); + if( var.size() ) + vo = variant(to_hex(var.data(),var.size())); + else vo = ""; +} +void from_variant( const variant& var, std::vector& vo ) +{ + const auto& str = var.get_string(); + FC_ASSERT( str.size() <= 2*MAX_SIZE_OF_BYTE_ARRAYS ); // Doubled because hex strings needs two characters per byte + FC_ASSERT( str.size() % 2 == 0, "the length of hex string should be even number" ); + vo.resize( str.size() / 2 ); + if( vo.size() ) { + size_t r = from_hex( str, vo.data(), vo.size() ); + FC_ASSERT( r == vo.size() ); + } +} + +void to_variant( const blob& b, variant& v ) { + v = variant(base64_encode(b.data.data(), b.data.size())); +} + +void from_variant( const variant& v, blob& b ) { + string _s = base64_decode(v.as_string()); + b.data = std::vector(_s.begin(), _s.end()); +} + +void to_variant( const UInt<8>& n, variant& v ) { v = uint64_t(n); } +// TODO: warn on overflow? +void from_variant( const variant& v, UInt<8>& n ) { n = static_cast(v.as_uint64()); } + +void to_variant( const UInt<16>& n, variant& v ) { v = uint64_t(n); } +// TODO: warn on overflow? +void from_variant( const variant& v, UInt<16>& n ) { n = static_cast(v.as_uint64()); } + +void to_variant( const UInt<32>& n, variant& v ) { v = uint64_t(n); } +// TODO: warn on overflow? +void from_variant( const variant& v, UInt<32>& n ) { n = static_cast(v.as_uint64()); } + +void to_variant( const UInt<64>& n, variant& v ) { v = uint64_t(n); } +void from_variant( const variant& v, UInt<64>& n ) { n = v.as_uint64(); } + +constexpr size_t minimize_max_size = 1024; + +// same behavior as std::string::substr only removes invalid utf8, and lower ascii +void clean_append( string& app, const std::string_view& s, size_t pos = 0, size_t len = string::npos ) { + std::string_view sub = s.substr( pos, len ); + app.reserve( app.size() + sub.size() ); + const bool escape_control_chars = false; + app += escape_string( sub, nullptr, escape_control_chars ); +} + +string format_string( const string& frmt, const variant_object& args, bool minimize ) +{ + std::string result; + const string& format = ( minimize && frmt.size() > minimize_max_size ) ? + frmt.substr( 0, minimize_max_size ) + "..." : frmt; + + const auto arg_num = (args.size() == 0) ? 1 : args.size(); + const auto max_format_size = std::max(minimize_max_size, format.size()); + // limit each arg size when minimize is set + const auto minimize_sub_max_size = minimize ? ( max_format_size - format.size() ) / arg_num : minimize_max_size; + // reserve space for each argument replaced by ... + result.reserve( max_format_size + 3 * args.size()); + size_t prev = 0; + size_t next = format.find( '$' ); + while( prev != string::npos && prev < format.size() ) { + if( next != string::npos ) { + clean_append( result, format, prev, next - prev ); + } else { + clean_append( result, format, prev ); + } + + // if we got to the end, return it. + if( next == string::npos ) { + return result; + } else if( minimize && result.size() > minimize_max_size ) { + result += "..."; + return result; + } + + // if we are not at the end, then update the start + prev = next + 1; + + if( format[prev] == '{' ) { + // if the next char is a open, then find close + next = format.find( '}', prev ); + // if we found close... + if( next != string::npos ) { + // the key is between prev and next + string key = format.substr( prev + 1, (next - prev - 1) ); + + auto val = args.find( key ); + bool replaced = true; + if( val != args.end() ) { + if( val->value().is_object() || val->value().is_array() ) { + if( minimize && (result.size() >= minimize_max_size)) { + replaced = false; + } else { + const auto max_length = minimize ? minimize_sub_max_size : std::numeric_limits::max(); + try { + // clean_append not needed as to_string is valid utf8 + result += json::to_string( val->value(), fc::time_point::maximum(), + json::output_formatting::stringify_large_ints_and_doubles, max_length ); + } catch (...) { + replaced = false; + } + } + } else if( val->value().is_blob() ) { + if( minimize && val->value().get_blob().data.size() > minimize_sub_max_size ) { + replaced = false; + } else { + clean_append( result, val->value().as_string() ); + } + } else if( val->value().is_string() ) { + if( minimize && val->value().get_string().size() > minimize_sub_max_size ) { + auto sz = std::min( minimize_sub_max_size, minimize_max_size - result.size() ); + clean_append( result, val->value().get_string(), 0, sz ); + result += "..."; + } else { + clean_append( result, val->value().get_string() ); + } + } else { + clean_append( result, val->value().as_string() ); + } + } else { + replaced = false; + } + if( !replaced ) { + result += "${"; + clean_append( result, key ); + result += "}"; + } + prev = next + 1; + // find the next $ + next = format.find( '$', prev ); + } else { + // we didn't find it.. continue to while... + } + } else { + clean_append( result, format, prev, 1 ); + ++prev; + next = format.find( '$', prev ); + } + } + return result; +} + + #ifdef __APPLE__ + #elif !defined(_MSC_VER) + void to_variant( long long int s, variant& v ) { v = variant( int64_t(s) ); } + void to_variant( unsigned long long int s, variant& v ) { v = variant( uint64_t(s)); } + #endif + + bool operator == ( const variant& a, const variant& b ) + { + if( a.is_string() || b.is_string() ) return a.as_string() == b.as_string(); + if( a.is_double() || b.is_double() ) return a.as_double() == b.as_double(); + if( a.is_int64() || b.is_int64() ) return a.as_int64() == b.as_int64(); + if( a.is_uint64() || b.is_uint64() ) return a.as_uint64() == b.as_uint64(); + if( a.is_array() || b.is_array() ) return a.get_array() == b.get_array(); + return false; + } + + bool operator != ( const variant& a, const variant& b ) + { + return !( a == b ); + } + + bool operator ! ( const variant& a ) + { + return !a.as_bool(); + } + + bool operator < ( const variant& a, const variant& b ) + { + if( a.is_string() || b.is_string() ) return a.as_string() < b.as_string(); + if( a.is_double() || b.is_double() ) return a.as_double() < b.as_double(); + if( a.is_int64() || b.is_int64() ) return a.as_int64() < b.as_int64(); + if( a.is_uint64() || b.is_uint64() ) return a.as_uint64() < b.as_uint64(); + FC_ASSERT( false, "Invalid operation" ); + } + + bool operator > ( const variant& a, const variant& b ) + { + if( a.is_string() || b.is_string() ) return a.as_string() > b.as_string(); + if( a.is_double() || b.is_double() ) return a.as_double() > b.as_double(); + if( a.is_int64() || b.is_int64() ) return a.as_int64() > b.as_int64(); + if( a.is_uint64() || b.is_uint64() ) return a.as_uint64() > b.as_uint64(); + FC_ASSERT( false, "Invalid operation" ); + } + + bool operator <= ( const variant& a, const variant& b ) + { + if( a.is_string() || b.is_string() ) return a.as_string() <= b.as_string(); + if( a.is_double() || b.is_double() ) return a.as_double() <= b.as_double(); + if( a.is_int64() || b.is_int64() ) return a.as_int64() <= b.as_int64(); + if( a.is_uint64() || b.is_uint64() ) return a.as_uint64() <= b.as_uint64(); + FC_ASSERT( false, "Invalid operation" ); + } + + + variant operator + ( const variant& a, const variant& b ) + { + if( a.is_array() && b.is_array() ) + { + const variants& aa = a.get_array(); + const variants& ba = b.get_array(); + variants result; + result.reserve( std::max(aa.size(),ba.size()) ); + auto num = std::max(aa.size(),ba.size()); + for( unsigned i = 0; i < num; ++i ) + { + if( aa.size() > i && ba.size() > i ) + result[i] = aa[i] + ba[i]; + else if( aa.size() > i ) + result[i] = aa[i]; + else + result[i] = ba[i]; + } + return result; + } + if( a.is_string() || b.is_string() ) return a.as_string() + b.as_string(); + if( a.is_double() || b.is_double() ) return a.as_double() + b.as_double(); + if( a.is_int64() || b.is_int64() ) return a.as_int64() + b.as_int64(); + if( a.is_uint64() || b.is_uint64() ) return a.as_uint64() + b.as_uint64(); + FC_ASSERT( false, "invalid operation ${a} + ${b}", ("a",a)("b",b) ); + } + + variant operator - ( const variant& a, const variant& b ) + { + if( a.is_array() && b.is_array() ) + { + const variants& aa = a.get_array(); + const variants& ba = b.get_array(); + variants result; + result.reserve( std::max(aa.size(),ba.size()) ); + auto num = std::max(aa.size(),ba.size()); + for( unsigned i = 0; i < num; --i ) + { + if( aa.size() > i && ba.size() > i ) + result[i] = aa[i] - ba[i]; + else if( aa.size() > i ) + result[i] = aa[i]; + else + result[i] = ba[i]; + } + return result; + } + if( a.is_string() || b.is_string() ) return a.as_string() - b.as_string(); + if( a.is_double() || b.is_double() ) return a.as_double() - b.as_double(); + if( a.is_int64() || b.is_int64() ) return a.as_int64() - b.as_int64(); + if( a.is_uint64() || b.is_uint64() ) return a.as_uint64() - b.as_uint64(); + FC_ASSERT( false, "invalid operation ${a} + ${b}", ("a",a)("b",b) ); + } + variant operator * ( const variant& a, const variant& b ) + { + if( a.is_double() || b.is_double() ) return a.as_double() * b.as_double(); + if( a.is_int64() || b.is_int64() ) return a.as_int64() * b.as_int64(); + if( a.is_uint64() || b.is_uint64() ) return a.as_uint64() * b.as_uint64(); + if( a.is_array() && b.is_array() ) + { + const variants& aa = a.get_array(); + const variants& ba = b.get_array(); + variants result; + result.reserve( std::max(aa.size(),ba.size()) ); + auto num = std::max(aa.size(),ba.size()); + for( unsigned i = 0; i < num; ++i ) + { + if( aa.size() > i && ba.size() > i ) + result[i] = aa[i] * ba[i]; + else if( aa.size() > i ) + result[i] = aa[i]; + else + result[i] = ba[i]; + } + return result; + } + FC_ASSERT( false, "invalid operation ${a} * ${b}", ("a",a)("b",b) ); + } + variant operator / ( const variant& a, const variant& b ) + { + if( a.is_double() || b.is_double() ) return a.as_double() / b.as_double(); + if( a.is_int64() || b.is_int64() ) return a.as_int64() / b.as_int64(); + if( a.is_uint64() || b.is_uint64() ) return a.as_uint64() / b.as_uint64(); + if( a.is_array() && b.is_array() ) + { + const variants& aa = a.get_array(); + const variants& ba = b.get_array(); + variants result; + result.reserve( std::max(aa.size(),ba.size()) ); + auto num = std::max(aa.size(),ba.size()); + for( unsigned i = 0; i < num; ++i ) + { + if( aa.size() > i && ba.size() > i ) + result[i] = aa[i] / ba[i]; + else if( aa.size() > i ) + result[i] = aa[i]; + else + result[i] = ba[i]; + } + return result; + } + FC_ASSERT( false, "invalid operation ${a} / ${b}", ("a",a)("b",b) ); + } +} // namespace fc diff --git a/libraries/libfc/src/variant_object.cpp b/libraries/libfc/src/variant_object.cpp new file mode 100644 index 0000000000..3548d45a20 --- /dev/null +++ b/libraries/libfc/src/variant_object.cpp @@ -0,0 +1,424 @@ +#include +#include + + +namespace fc +{ + // --------------------------------------------------------------- + // entry + + variant_object::entry::entry() {} + variant_object::entry::entry( string k, variant v ) : _key(fc::move(k)),_value(fc::move(v)) {} + variant_object::entry::entry( entry&& e ) : _key(fc::move(e._key)),_value(fc::move(e._value)) {} + variant_object::entry::entry( const entry& e ) : _key(e._key),_value(e._value) {} + variant_object::entry& variant_object::entry::operator=( const variant_object::entry& e ) + { + if( this != &e ) + { + _key = e._key; + _value = e._value; + } + return *this; + } + variant_object::entry& variant_object::entry::operator=( variant_object::entry&& e ) + { + fc_swap( _key, e._key ); + fc_swap( _value, e._value ); + return *this; + } + + const string& variant_object::entry::key()const + { + return _key; + } + + const variant& variant_object::entry::value()const + { + return _value; + } + variant& variant_object::entry::value() + { + return _value; + } + + void variant_object::entry::set( variant v ) + { + fc_swap( _value, v ); + } + + // --------------------------------------------------------------- + // variant_object + + variant_object::iterator variant_object::begin() const + { + FC_ASSERT( _key_value != nullptr ); + return _key_value->begin(); + } + + variant_object::iterator variant_object::end() const + { + return _key_value->end(); + } + + variant_object::iterator variant_object::find( const string& key )const + { + return find( key.c_str() ); + } + + variant_object::iterator variant_object::find( const char* key )const + { + for( auto itr = begin(); itr != end(); ++itr ) + { + if( itr->key() == key ) + { + return itr; + } + } + return end(); + } + + const variant& variant_object::operator[]( const string& key )const + { + return (*this)[key.c_str()]; + } + + const variant& variant_object::operator[]( const char* key )const + { + auto itr = find( key ); + if( itr != end() ) return itr->value(); + FC_THROW_EXCEPTION( key_not_found_exception, "Key ${key}", ("key",key) ); + } + + size_t variant_object::size() const + { + return _key_value->size(); + } + + variant_object::variant_object() + :_key_value(std::make_shared>() ) + { + } + + variant_object::variant_object( string key, variant val ) + : _key_value(std::make_shared>()) + { + //_key_value->push_back(entry(fc::move(key), fc::move(val))); + _key_value->emplace_back(entry(fc::move(key), fc::move(val))); + } + + variant_object::variant_object( const variant_object& obj ) + :_key_value( obj._key_value ) + { + FC_ASSERT( _key_value != nullptr ); + } + + variant_object::variant_object( variant_object&& obj) + : _key_value( fc::move(obj._key_value) ) + { + obj._key_value = std::make_shared>(); + FC_ASSERT( _key_value != nullptr ); + } + + variant_object::variant_object( const mutable_variant_object& obj ) + : _key_value(std::make_shared>(*obj._key_value)) + { + } + + variant_object::variant_object( mutable_variant_object&& obj ) + : _key_value(fc::move(obj._key_value)) + { + FC_ASSERT( _key_value != nullptr ); + } + + variant_object& variant_object::operator=( variant_object&& obj ) + { + if (this != &obj) + { + fc_swap(_key_value, obj._key_value ); + FC_ASSERT( _key_value != nullptr ); + } + return *this; + } + + variant_object& variant_object::operator=( const variant_object& obj ) + { + if (this != &obj) + { + _key_value = obj._key_value; + } + return *this; + } + + variant_object& variant_object::operator=( mutable_variant_object&& obj ) + { + _key_value = fc::move(obj._key_value); + obj._key_value.reset( new std::vector() ); + return *this; + } + + variant_object& variant_object::operator=( const mutable_variant_object& obj ) + { + *_key_value = *obj._key_value; + return *this; + } + + size_t variant_object::estimated_size()const + { + auto kv_size = size(); + size_t sum = sizeof(*this) + sizeof(std::vector); + for (size_t iter = 0; iter < kv_size; ++iter) { + const auto& kv = _key_value->at(iter); + sum += kv.key().length() + sizeof(string); + sum += kv.value().estimated_size(); + } + return sum; + } + + void to_variant( const variant_object& var, variant& vo ) + { + vo = variant(var); + } + + void from_variant( const variant& var, variant_object& vo ) + { + vo = var.get_object(); + } + + // --------------------------------------------------------------- + // mutable_variant_object + + mutable_variant_object::iterator mutable_variant_object::begin() + { + return _key_value->begin(); + } + + mutable_variant_object::iterator mutable_variant_object::end() + { + return _key_value->end(); + } + + mutable_variant_object::iterator mutable_variant_object::begin() const + { + return _key_value->begin(); + } + + mutable_variant_object::iterator mutable_variant_object::end() const + { + return _key_value->end(); + } + + mutable_variant_object::iterator mutable_variant_object::find( const string& key )const + { + return find( key.c_str() ); + } + + mutable_variant_object::iterator mutable_variant_object::find( const char* key )const + { + for( auto itr = begin(); itr != end(); ++itr ) + { + if( itr->key() == key ) + { + return itr; + } + } + return end(); + } + + mutable_variant_object::iterator mutable_variant_object::find( const string& key ) + { + return find( key.c_str() ); + } + + mutable_variant_object::iterator mutable_variant_object::find( const char* key ) + { + for( auto itr = begin(); itr != end(); ++itr ) + { + if( itr->key() == key ) + { + return itr; + } + } + return end(); + } + + const variant& mutable_variant_object::operator[]( const string& key )const + { + return (*this)[key.c_str()]; + } + + const variant& mutable_variant_object::operator[]( const char* key )const + { + auto itr = find( key ); + if( itr != end() ) return itr->value(); + FC_THROW_EXCEPTION( key_not_found_exception, "Key ${key}", ("key",key) ); + } + variant& mutable_variant_object::operator[]( const string& key ) + { + return (*this)[key.c_str()]; + } + + variant& mutable_variant_object::operator[]( const char* key ) + { + auto itr = find( key ); + if( itr != end() ) return itr->value(); + _key_value->emplace_back(entry(key, variant())); + return _key_value->back().value(); + } + + size_t mutable_variant_object::size() const + { + return _key_value->size(); + } + + mutable_variant_object::mutable_variant_object() + :_key_value(new std::vector) + { + } + + mutable_variant_object::mutable_variant_object( string key, variant val ) + : _key_value(new std::vector()) + { + _key_value->push_back(entry(fc::move(key), fc::move(val))); + } + + mutable_variant_object::mutable_variant_object( const variant_object& obj ) + : _key_value( new std::vector(*obj._key_value) ) + { + } + + mutable_variant_object::mutable_variant_object( const mutable_variant_object& obj ) + : _key_value( new std::vector(*obj._key_value) ) + { + } + + mutable_variant_object::mutable_variant_object( mutable_variant_object&& obj ) + : _key_value(fc::move(obj._key_value)) + { + } + + mutable_variant_object& mutable_variant_object::operator=( const variant_object& obj ) + { + *_key_value = *obj._key_value; + return *this; + } + + mutable_variant_object& mutable_variant_object::operator=( mutable_variant_object&& obj ) + { + if (this != &obj) + { + _key_value = fc::move(obj._key_value); + } + return *this; + } + + mutable_variant_object& mutable_variant_object::operator=( const mutable_variant_object& obj ) + { + if (this != &obj) + { + *_key_value = *obj._key_value; + } + return *this; + } + + void mutable_variant_object::reserve( size_t s ) + { + _key_value->reserve(s); + } + + void mutable_variant_object::erase( const string& key ) + { + for( auto itr = begin(); itr != end(); ++itr ) + { + if( itr->key() == key ) + { + _key_value->erase(itr); + return; + } + } + } + + /** replaces the value at \a key with \a var or insert's \a key if not found */ + mutable_variant_object& mutable_variant_object::set( string key, variant var ) & + { + auto itr = find( key.c_str() ); + if( itr != end() ) + { + itr->set( fc::move(var) ); + } + else + { + _key_value->push_back( entry( fc::move(key), fc::move(var) ) ); + } + return *this; + } + + mutable_variant_object mutable_variant_object::set( string key, variant var ) && + { + auto itr = find( key.c_str() ); + if( itr != end() ) + { + itr->set( fc::move(var) ); + } + else + { + _key_value->push_back( entry( fc::move(key), fc::move(var) ) ); + } + return std::move(*this); + } + + /** Appends \a key and \a var without checking for duplicates, designed to + * simplify construction of dictionaries using (key,val)(key2,val2) syntax + */ + mutable_variant_object& mutable_variant_object::operator()( string key, variant var ) & + { + _key_value->push_back( entry( fc::move(key), fc::move(var) ) ); + return *this; + } + + mutable_variant_object mutable_variant_object::operator()( string key, variant var ) && + { + _key_value->push_back( entry( fc::move(key), fc::move(var) ) ); + return std::move(*this); + } + + mutable_variant_object& mutable_variant_object::operator()( const variant_object& vo ) & + { + for( const variant_object::entry& e : vo ) + set( e.key(), e.value() ); + return *this; + } + + mutable_variant_object mutable_variant_object::operator()( const variant_object& vo ) && + { + for( const variant_object::entry& e : vo ) + set( e.key(), e.value() ); + return std::move(*this); + } + + mutable_variant_object& mutable_variant_object::operator()( const mutable_variant_object& mvo ) & + { + if( &mvo == this ) // mvo(mvo) is no-op + return *this; + for( const mutable_variant_object::entry& e : mvo ) + set( e.key(), e.value() ); + return *this; + } + + mutable_variant_object mutable_variant_object::operator()( const mutable_variant_object& mvo ) && + { + for( const mutable_variant_object::entry& e : mvo ) + set( e.key(), e.value() ); + return std::move(*this); + } + + void to_variant( const mutable_variant_object& var, variant& vo ) + { + vo = variant(var); + } + + void from_variant( const variant& var, mutable_variant_object& vo ) + { + vo = var.get_object(); + } + +} // namesapce fc diff --git a/libraries/libfc/test/CMakeLists.txt b/libraries/libfc/test/CMakeLists.txt new file mode 100644 index 0000000000..4788c05814 --- /dev/null +++ b/libraries/libfc/test/CMakeLists.txt @@ -0,0 +1,17 @@ +add_subdirectory( crypto ) +add_subdirectory( io ) +add_subdirectory( network ) +add_subdirectory( scoped_exit ) +add_subdirectory( static_variant ) +add_subdirectory( variant ) +add_subdirectory( variant_estimated_size ) + +add_executable( test_base64 test_base64.cpp ) +target_link_libraries( test_base64 fc ) + +add_test(NAME test_base64 COMMAND libraries/fc/test/test_base64 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + +add_executable( test_filesystem test_filesystem.cpp ) +target_link_libraries( test_filesystem fc ) + +add_test(NAME test_filesystem COMMAND libraries/fc/test/test_filesystem WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/libraries/libfc/test/crypto/CMakeLists.txt b/libraries/libfc/test/crypto/CMakeLists.txt new file mode 100644 index 0000000000..216eda7e50 --- /dev/null +++ b/libraries/libfc/test/crypto/CMakeLists.txt @@ -0,0 +1,28 @@ +add_executable( test_cypher_suites test_cypher_suites.cpp ) +target_link_libraries( test_cypher_suites fc ) + +add_executable( test_webauthn test_webauthn.cpp ) +target_link_libraries( test_webauthn fc ) + +add_executable( test_hash_functions test_hash_functions.cpp ) +target_link_libraries( test_hash_functions fc ) + +add_executable( test_alt_bn128 test_alt_bn128.cpp ) +target_link_libraries( test_alt_bn128 fc ) + +add_executable( test_blake2 test_blake2.cpp ) +target_link_libraries( test_blake2 fc ) + +add_executable( test_modular_arithmetic test_modular_arithmetic.cpp ) +target_link_libraries( test_modular_arithmetic fc ) + +add_executable( test_k1_recover test_k1_recover.cpp ) +target_link_libraries( test_k1_recover fc ) + +add_test(NAME test_cypher_suites COMMAND libraries/fc/test/crypto/test_cypher_suites WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_webauthn COMMAND libraries/fc/test/crypto/test_webauthn WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_hash_functions COMMAND libraries/fc/test/crypto/test_hash_functions WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_alt_bn128 COMMAND libraries/fc/test/crypto/test_alt_bn128 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_blake2 COMMAND libraries/fc/test/crypto/test_blake2 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_modular_arithmetic COMMAND libraries/fc/test/crypto/test_modular_arithmetic WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_k1_recover COMMAND libraries/fc/test/crypto/test_k1_recover WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) \ No newline at end of file diff --git a/libraries/libfc/test/crypto/test_alt_bn128.cpp b/libraries/libfc/test/crypto/test_alt_bn128.cpp new file mode 100644 index 0000000000..bc81fdabeb --- /dev/null +++ b/libraries/libfc/test/crypto/test_alt_bn128.cpp @@ -0,0 +1,270 @@ +#define BOOST_TEST_MODULE altbn_128_tests +#include +#include +#include +#include +#include + +using namespace fc; +#include "test_utils.hpp" + +namespace std { +std::ostream& operator<<(std::ostream& st, const std::variant& err) +{ + if( std::holds_alternative(err) ) + st << static_cast(std::get(err)); + else + st << fc::to_hex(std::get(err)); + return st; +} + +std::ostream& operator<<(std::ostream& st, const std::variant& err) +{ + if( std::holds_alternative(err) ) + st << static_cast(std::get(err)); + else + st << std::get(err); + return st; +} +} + +BOOST_AUTO_TEST_SUITE(altbn_128_tests) +BOOST_AUTO_TEST_CASE(add) try { + + using test_add = std::tuple>; + const std::vector tests = { + //test (2 valid points, both on curve) + { + "222480c9f95409bfa4ac6ae890b9c150bc88542b87b352e92950c340458b0c092976efd698cf23b414ea622b3f720dd9080d679042482ff3668cb2e32cad8ae2", + "1bd20beca3d8d28e536d2b5bd3bf36d76af68af5e6c96ca6e5519ba9ff8f53322a53edf6b48bcf5cb1c0b4ad1d36dfce06a79dcd6526f1c386a14d8ce4649844", + to_bytes("16c7c4042e3a725ddbacf197c519c3dcad2bc87dfd9ac7e1e1631154ee0b7d9c19cd640dd28c9811ebaaa095a16b16190d08d6906c4f926fce581985fe35be0e") + }, + + //test (2 valid points, P1 not on curve) + { + "222480c9f95409bfa4ac6ae890b9c150bc88542b87b352e92950c340458b0c092976efd698cf23b414ea622b3f720dd9080d679042482ff3668cb2e32cad8ae2", + "2a53edf6b48bcf5cb1c0b4ad1d36dfce06a79dcd6526f1c386a14d8ce46498441bd20beca3d8d28e536d2b5bd3bf36d76af68af5e6c96ca6e5519ba9ff8f5332", + alt_bn128_error::operand_not_in_curve + }, + + //test (invalid P1 length) + { + "2a", + "222480c9f95409bfa4ac6ae890b9c150bc88542b87b352e92950c340458b0c092976efd698cf23b414ea622b3f720dd9080d679042482ff3668cb2e32cad8ae2", + alt_bn128_error::input_len_error + }, + + //|Fp| = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + //test (P1.x=|Fp|) + { + "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd472976efd698cf23b414ea622b3f720dd9080d679042482ff3668cb2e32cad8ae2", + "1bd20beca3d8d28e536d2b5bd3bf36d76af68af5e6c96ca6e5519ba9ff8f53322a53edf6b48bcf5cb1c0b4ad1d36dfce06a79dcd6526f1c386a14d8ce4649844", + alt_bn128_error::operand_component_invalid + }, + + //test (P1=(0,0)) + { + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "1bd20beca3d8d28e536d2b5bd3bf36d76af68af5e6c96ca6e5519ba9ff8f53322a53edf6b48bcf5cb1c0b4ad1d36dfce06a79dcd6526f1c386a14d8ce4649844", + to_bytes("1bd20beca3d8d28e536d2b5bd3bf36d76af68af5e6c96ca6e5519ba9ff8f53322a53edf6b48bcf5cb1c0b4ad1d36dfce06a79dcd6526f1c386a14d8ce4649844") + }, + }; + + for(const auto& test : tests) { + auto op1 = to_bytes(std::get<0>(test)); + auto op2 = to_bytes(std::get<1>(test)); + auto expected_result = std::get<2>(test); + + auto res = alt_bn128_add(op1, op2); + BOOST_CHECK_EQUAL(res, expected_result); + } + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(mul) try { + + using test_mul = std::tuple>; + const std::vector tests = { + //test (valid point on curve, scalar size = 256 bits) + { + "007c43fcd125b2b13e2521e395a81727710a46b34fe279adbf1b94c72f7f91360db2f980370fb8962751c6ff064f4516a6a93d563388518bb77ab9a6b30755be", + "0312ed43559cf8ecbab5221256a56e567aac5035308e3f1d54954d8b97cd1c9b", + to_bytes("2d66cdeca5e1715896a5a924c50a149be87ddd2347b862150fbb0fd7d0b1833c11c76319ebefc5379f7aa6d85d40169a612597637242a4bbb39e5cd3b844becd") + }, + + //test (scalar size < 256 bits) + { + "007c43fcd125b2b13e2521e395a81727710a46b34fe279adbf1b94c72f7f91360db2f980370fb8962751c6ff064f4516a6a93d563388518bb77ab9a6b30755be", + "01", + alt_bn128_error::invalid_scalar_size, + }, + + //test (P1 not on curve) + { + "0db2f980370fb8962751c6ff064f4516a6a93d563388518bb77ab9a6b30755be007c43fcd125b2b13e2521e395a81727710a46b34fe279adbf1b94c72f7f9136", + "0312ed43559cf8ecbab5221256a56e567aac5035308e3f1d54954d8b97cd1c9b", + alt_bn128_error::operand_not_in_curve, + }, + + //test (invalid P1 length) + { + "222480c9f95409bfa4ac6ae890b9c150bc88542b87b352e92950c340458b0c092976efd698cf23b414ea622b3f720dd9080d679042482ff3668cb2e32cad8a", + "0312ed43559cf8ecbab5221256a56e567aac5035308e3f1d54954d8b97cd1c9b", + alt_bn128_error::input_len_error, + }, + + //|Fp| = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + //test (P1.y=|Fp|) + { + "2976efd698cf23b414ea622b3f720dd9080d679042482ff3668cb2e32cad8ae230644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", + "0100010001000100010001000100010001000100010001000100010001000100", + alt_bn128_error::operand_component_invalid, + }, + + //test (P1=(0,0)) + { + "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0312ed43559cf8ecbab5221256a56e567aac5035308e3f1d54954d8b97cd1c9b", + to_bytes("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") + }, + + }; + + for(const auto& test : tests) { + auto point = to_bytes(std::get<0>(test)); + auto scalar = to_bytes(std::get<1>(test)); + auto expected_result = std::get<2>(test); + + auto res = alt_bn128_mul(point, scalar); + BOOST_CHECK_EQUAL(res, expected_result); + } + +} FC_LOG_AND_RETHROW(); + + +BOOST_AUTO_TEST_CASE(pair) try { + + using g1g2_pair = std::vector; + using pair_test = std::tuple, std::variant>; + + const std::vector tests = + { + //test1: 2 pairs => (G1_a,G2_a),(G1_b,G2_b) ; alt_bn128_error::none ; true + { + { + { //G1_a G2_a + "0f25929bcb43d5a57391564615c9e70a992b10eafa4db109709649cf48c50dd2", //G1_a.x + "16da2f5cb6be7a0aa72c440c53c9bbdfec6c36c7d515536431b3a865468acbba", //G1_a.y + "2e89718ad33c8bed92e210e81d1853435399a271913a6520736a4729cf0d51eb", //G2_a.x + "01a9e2ffa2e92599b68e44de5bcf354fa2642bd4f26b259daa6f7ce3ed57aeb3", + "14a9a87b789a58af499b314e13c3d65bede56c07ea2d418d6874857b70763713", //G2_a.y + "178fb49a2d6cd347dc58973ff49613a20757d0fcc22079f9abd10c3baee24590", + }, + + { //G1_b G2_b + "1b9e027bd5cfc2cb5db82d4dc9677ac795ec500ecd47deee3b5da006d6d049b8", //G1_b.x + "11d7511c78158de484232fc68daf8a45cf217d1c2fae693ff5871e8752d73b21", //G1_b.y + "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", //G2_b.x + "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", + "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", //G2_b.y + "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa", + } + }, + true + }, + + //test2: 1 pair => (G1_a,G2_a) ; alt_bn128_error::none; false + { + { + { //G1_a G2_a + "0000000000000000000000000000000000000000000000000000000000000001", //G1_a.x + "0000000000000000000000000000000000000000000000000000000000000002", //G1_a.y + "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", //G2_b.x + "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", + "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", //G2_b.y + "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa" + }, + + }, + false + }, + + //test3: 1 pair => (G1_a,G2_a) ; alt_bn128_error::pairing_list_size_error; false + { + { + { //G1_a G2_a + "00000000000000000000000000000000000000000000000000000000000001", //G1_a.x + "0000000000000000000000000000000000000000000000000000000000000002", //G1_a.y + "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", //G2_b.x + "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", + "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", //G2_b.y + "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa" + }, + + }, + alt_bn128_error::pairing_list_size_error + }, + + //test5: 1 pair => (G1_a,G2_a) ; alt_bn128_error::operand_not_in_curve; false + { + { + { //G1_a G2_a + "0000000000000000000000000000000000000000000000000000000000000000", //G1_a.x + "0000000000000000000000000000000100000000000000000000000000000000", //G1_a.y + "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", //G2_b.x + "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", + "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", //G2_b.y + "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa" + }, + + }, + alt_bn128_error::operand_not_in_curve + }, + + //test6: 1 pair => (G1_a,G2_a) ; alt_bn128_error::operand_component_invalid; false + { + { + { //G1_a G2_a + "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47", //G1_a.x == |Fp| + "0000000000000000000000000000000100000000000000000000000000000000", //G1_a.y + "198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2", //G2_b.x + "1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed", + "090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b", //G2_b.y + "12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa" + }, + + }, + alt_bn128_error::operand_component_invalid + } + }; + + auto concat = [&](const std::string& s, bytes& buffer) { + auto res = to_bytes(s); + buffer.insert( buffer.end(), res.begin(), res.end()); + }; + + yield_function_t yield = [](){}; + + for(const auto& test : tests) { + const auto& pairs = std::get<0>(test); + const auto& expected_result = std::get<1>(test); + + bytes g1_g2_pairs; + for(const auto& pair : pairs) { + BOOST_REQUIRE(pair.size() == 6); + concat(pair[0], g1_g2_pairs); + concat(pair[1], g1_g2_pairs); + concat(pair[2], g1_g2_pairs); + concat(pair[3], g1_g2_pairs); + concat(pair[4], g1_g2_pairs); + concat(pair[5], g1_g2_pairs); + } + + auto res = alt_bn128_pair(g1_g2_pairs, yield); + BOOST_CHECK_EQUAL(res, expected_result); + } + +} FC_LOG_AND_RETHROW(); + + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/crypto/test_blake2.cpp b/libraries/libfc/test/crypto/test_blake2.cpp new file mode 100644 index 0000000000..d7e71f112c --- /dev/null +++ b/libraries/libfc/test/crypto/test_blake2.cpp @@ -0,0 +1,119 @@ +#define BOOST_TEST_MODULE blake2 +#include + +#include +#include +#include +#include + +using namespace fc; + +#include "test_utils.hpp" + +namespace std { +std::ostream& operator<<(std::ostream& st, const std::variant& err) +{ + if(std::holds_alternative(err)) + st << static_cast(std::get(err)); + else + st << fc::to_hex(std::get(err)); + return st; +} +} + +BOOST_AUTO_TEST_SUITE(blake2) +BOOST_AUTO_TEST_CASE(compress) try { + + using compress_test = std::tuple, std::variant>; + + const std::vector tests { + //test1 + { + { + "00000000", + "48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b", + "6162630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0300000000000000", + "0000000000000000", + "01", + }, + to_bytes("08c9bcf367e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d282e6ad7f520e511f6c3e2b8c68059b9442be0454267ce079217e1319cde05b") + }, + + //test2 + { + { + "0000000c", + "48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b", + "6162630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0300000000000000", + "0000000000000000", + "01", + }, + to_bytes("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923") + }, + + //test3 + { + { + "0000000c", + "48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b", + "6162630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0300000000000000", + "0000000000000000", + "00", + }, + to_bytes("75ab69d3190a562c51aef8d88f1c2775876944407270c42c9844252c26d2875298743e7f6d5ea2f2d3e8d226039cd31b4e426ac4f2d3d666a610c2116fde4735") + }, + + //test4 + { + { + "00000001", + "48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b", + "6162630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0300000000000000", + "0000000000000000", + "01", + }, + to_bytes("b63a380cb2897d521994a85234ee2c181b5f844d2c624c002677e9703449d2fba551b3a8333bcdf5f2f7e08993d53923de3d64fcc68c034e717b9293fed7a421") + }, + + //test5 + { + { + "00000000", + "c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b", + "6162630000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0300000000000000", + "0000000000000000", + "01", + }, + blake2b_error::input_len_error + }, + }; + + yield_function_t yield = [](){}; + + for(const auto& test : tests) { + + const auto& params = std::get<0>(test); + const auto& expected_result = std::get<1>(test); + + BOOST_REQUIRE(params.size() == 6); + + uint32_t _rounds = to_uint32(params[0] ); + bytes _h = to_bytes( params[1] ); + bytes _m = to_bytes( params[2] ); + bytes _t0_offset = to_bytes( params[3] ); + bytes _t1_offset = to_bytes( params[4] ); + bool _f = params[5] == "00" ? false : true; + + auto res = blake2b(_rounds, _h, _m, _t0_offset, _t1_offset, _f, yield); + + BOOST_CHECK_EQUAL(res, expected_result); + } + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/libraries/libfc/test/crypto/test_cypher_suites.cpp b/libraries/libfc/test/crypto/test_cypher_suites.cpp new file mode 100644 index 0000000000..2a421a822b --- /dev/null +++ b/libraries/libfc/test/crypto/test_cypher_suites.cpp @@ -0,0 +1,82 @@ +#define BOOST_TEST_MODULE cypher_suites +#include + +#include +#include +#include +#include + +using namespace fc::crypto; +using namespace fc; + +BOOST_AUTO_TEST_SUITE(cypher_suites) +BOOST_AUTO_TEST_CASE(test_k1) try { + auto private_key_string = std::string("5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"); + auto expected_public_key = std::string("EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV"); + auto test_private_key = private_key(private_key_string); + auto test_public_key = test_private_key.get_public_key(); + + BOOST_CHECK_EQUAL(private_key_string, test_private_key.to_string()); + BOOST_CHECK_EQUAL(expected_public_key, test_public_key.to_string()); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(test_r1) try { + auto private_key_string = std::string("PVT_R1_iyQmnyPEGvFd8uffnk152WC2WryBjgTrg22fXQryuGL9mU6qW"); + auto expected_public_key = std::string("PUB_R1_6EPHFSKVYHBjQgxVGQPrwCxTg7BbZ69H9i4gztN9deKTEXYne4"); + auto test_private_key = private_key(private_key_string); + auto test_public_key = test_private_key.get_public_key(); + + BOOST_CHECK_EQUAL(private_key_string, test_private_key.to_string()); + BOOST_CHECK_EQUAL(expected_public_key, test_public_key.to_string()); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(test_k1_recovery) try { + auto payload = "Test Cases"; + auto digest = sha256::hash(payload, const_strlen(payload)); + auto key = private_key::generate(); + auto pub = key.get_public_key(); + auto sig = key.sign(digest); + + auto recovered_pub = public_key(sig, digest); + std::cout << recovered_pub << std::endl; + + BOOST_CHECK_EQUAL(recovered_pub.to_string(), pub.to_string()); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(test_r1_recovery) try { + auto payload = "Test Cases"; + auto digest = sha256::hash(payload, const_strlen(payload)); + auto key = private_key::generate(); + auto pub = key.get_public_key(); + auto sig = key.sign(digest); + + auto recovered_pub = public_key(sig, digest); + std::cout << recovered_pub << std::endl; + + BOOST_CHECK_EQUAL(recovered_pub.to_string(), pub.to_string()); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(test_k1_recyle) try { + auto key = private_key::generate(); + auto pub = key.get_public_key(); + auto pub_str = pub.to_string(); + auto recycled_pub = public_key(pub_str); + + std::cout << pub << " -> " << recycled_pub << std::endl; + + BOOST_CHECK_EQUAL(pub.to_string(), recycled_pub.to_string()); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(test_r1_recyle) try { + auto key = private_key::generate(); + auto pub = key.get_public_key(); + auto pub_str = pub.to_string(); + auto recycled_pub = public_key(pub_str); + + std::cout << pub << " -> " << recycled_pub << std::endl; + + BOOST_CHECK_EQUAL(pub.to_string(), recycled_pub.to_string()); +} FC_LOG_AND_RETHROW(); + + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/libraries/libfc/test/crypto/test_hash_functions.cpp b/libraries/libfc/test/crypto/test_hash_functions.cpp new file mode 100644 index 0000000000..7612e497d0 --- /dev/null +++ b/libraries/libfc/test/crypto/test_hash_functions.cpp @@ -0,0 +1,70 @@ +#define BOOST_TEST_MODULE hash_functions +#include + +#include +#include +#include + +using namespace fc; + +BOOST_AUTO_TEST_SUITE(hash_functions) +BOOST_AUTO_TEST_CASE(sha3) try { + + using test_sha3 = std::tuple; + const std::vector tests { + //test + { + "", + "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a", + }, + + //test + { + "abc", + "3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532", + }, + + //test + { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376", + } + }; + + for(const auto& test : tests) { + BOOST_CHECK_EQUAL(fc::sha3::hash(std::get<0>(test), true).str(), std::get<1>(test)); + } + +} FC_LOG_AND_RETHROW(); + + +BOOST_AUTO_TEST_CASE(keccak256) try { + + using test_keccak256 = std::tuple; + const std::vector tests { + //test + { + "", + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + }, + + //test + { + "abc", + "4e03657aea45a94fc7d47ba826c8d667c0d1e6e33a64a036ec44f58fa12d6c45", + }, + + //test + { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "45d3b367a6904e6e8d502ee04999a7c27647f91fa845d456525fd352ae3d7371", + } + }; + + for(const auto& test : tests) { + BOOST_CHECK_EQUAL(fc::sha3::hash(std::get<0>(test), false).str(), std::get<1>(test)); + } + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/libraries/libfc/test/crypto/test_k1_recover.cpp b/libraries/libfc/test/crypto/test_k1_recover.cpp new file mode 100644 index 0000000000..a93311ef49 --- /dev/null +++ b/libraries/libfc/test/crypto/test_k1_recover.cpp @@ -0,0 +1,71 @@ +#define BOOST_TEST_MODULE blake2 +#include + +#include +#include +#include +#include + +using namespace fc; + +#include "test_utils.hpp" + +namespace std { +std::ostream& operator<<(std::ostream& st, const std::variant& err) +{ + if(std::holds_alternative(err)) + st << static_cast(std::get(err)); + else + st << fc::to_hex(std::get(err)); + return st; +} +} + +BOOST_AUTO_TEST_SUITE(k1_recover) +BOOST_AUTO_TEST_CASE(recover) try { + + using test_k1_recover = std::tuple>; + const std::vector tests { + //test + { + "1b323dd47a1dd5592c296ee2ee12e0af38974087a475e99098a440284f19c1f7642fa0baa10a8a3ab800dfdbe987dee68a09b6fa3db45a5cc4f3a5835a1671d4dd", + "92390316873c5a9d520b28aba61e7a8f00025ac069acd9c4d2a71d775a55fa5f", + to_bytes("044424982f5c4044aaf27444965d15b53f219c8ad332bf98a98a902ebfb05d46cb86ea6fe663aa83fd4ce0a383855dfae9bf7a07b779d34c84c347fec79d04c51e") + }, + + //test (invalid signature v) + { + "01174de755b55bd29026d626f7313a5560353dc5175f29c78d79d961b81a0c04360d833ca789bc16d4ee714a6d1a19461d890966e0ec5c074f67be67e631d33aa7", + "45fd65f6dd062fe7020f11d19fe5c35dc4d425e1479c0968c8e932c208f25399", + fc::k1_recover_error::invalid_signature + }, + + //test (invalid signature len) + { + "174de755b55bd29026d626f7313a5560353dc5175f29c78d79d961b81a0c04360d833ca789bc16d4ee714a6d1a19461d890966e0ec5c074f67be67e631d33aa7", + "45fd65f6dd062fe7020f11d19fe5c35dc4d425e1479c0968c8e932c208f25399", + fc::k1_recover_error::input_error + }, + + //test (invalid digest len) + { + "00174de755b55bd29026d626f7313a5560353dc5175f29c78d79d961b81a0c04360d833ca789bc16d4ee714a6d1a19461d890966e0ec5c074f67be67e631d33aa7", + "fd65f6dd062fe7020f11d19fe5c35dc4d425e1479c0968c8e932c208f25399", + fc::k1_recover_error::input_error + }, + + }; + + for(const auto& test : tests) { + + const auto& signature = to_bytes(std::get<0>(test)); + const auto& digest = to_bytes(std::get<1>(test)); + const auto& expected_result = std::get<2>(test); + + auto res = fc::k1_recover(signature, digest); + BOOST_CHECK_EQUAL(res, expected_result); + } + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/libraries/libfc/test/crypto/test_modular_arithmetic.cpp b/libraries/libfc/test/crypto/test_modular_arithmetic.cpp new file mode 100644 index 0000000000..23f0b0db01 --- /dev/null +++ b/libraries/libfc/test/crypto/test_modular_arithmetic.cpp @@ -0,0 +1,302 @@ +#define BOOST_TEST_MODULE modular_arithmetic +#include + +#include +#include +#include +#include + +#include +#include +#include + +using namespace fc; +#include "test_utils.hpp" + +namespace std { +std::ostream& operator<<(std::ostream& st, const std::variant& err) +{ + if(std::holds_alternative(err)) + st << static_cast(std::get(err)); + else + st << fc::to_hex(std::get(err)); + return st; +} +} + + +BOOST_AUTO_TEST_SUITE(modular_arithmetic) + +BOOST_AUTO_TEST_CASE(modexp) try { + + + using modexp_test = std::tuple, std::variant>; + + const std::vector tests { + //test1 + { + { + "03", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + }, + to_bytes("0000000000000000000000000000000000000000000000000000000000000001"), + }, + + //test2 + { + { + "", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f", + }, + to_bytes("0000000000000000000000000000000000000000000000000000000000000000") + }, + + //test3 + { + { + "01", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + "", + }, + modular_arithmetic_error::modulus_len_zero + }, + + //test4 + { + { + "01", + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e", + "0000", + }, + to_bytes("0000") + }, + + //test5 + { + { + "00", + "00", + "0F", + }, + to_bytes("01"), + }, + + //test6 + { + { + "00", + "01", + "0F", + }, + to_bytes("00"), + }, + + //test7 + { + { + "01", + "00", + "0F", + }, + to_bytes("01"), + }, + + }; + + for(const auto& test : tests) { + const auto& parts = std::get<0>(test); + const auto& expected_result = std::get<1>(test); + + auto base = to_bytes(parts[0]); + auto exponent = to_bytes(parts[1]); + auto modulus = to_bytes(parts[2]); + + auto res = fc::modexp(base, exponent, modulus); + BOOST_CHECK_EQUAL(res, expected_result); + } + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(modexp_benchmarking) try { + + std::mt19937 r(0x11223344); + + auto generate_random_bytes = [](std::mt19937& rand_eng, unsigned int num_bytes) { + std::vector result(num_bytes); + + uint_fast32_t v = 0; + for(int byte_pos = 0, end = result.size(); byte_pos < end; ++byte_pos) { + if ((byte_pos & 0x03) == 0) { // if divisible by 4 + v = rand_eng(); + } + result[byte_pos] = v & 0xFF; + v >>= 8; + } + + return result; + }; + + static constexpr unsigned int num_trials = 10; // 10000 + + static_assert(num_trials > 0); + + static constexpr unsigned int bit_calc_limit = 101; // 120 + + static constexpr unsigned int start_num_bytes = 1; + static constexpr unsigned int end_num_bytes = 1 << ((bit_calc_limit + 7)/8); + + static_assert(start_num_bytes <= end_num_bytes); + + struct statistics { + unsigned int modulus_bit_size; // bit size of modulus and base + unsigned int exponent_bit_size; // bit size of exponent + int64_t min_time_ns; + int64_t max_time_ns; + int64_t avg_time_ns; + }; + + std::vector stats; + + auto ceil_log2 = [](uint32_t n) -> uint32_t { + if (n <= 1) { + return 0; + } + return 32 - __builtin_clz(n - 1); + }; + + BOOST_CHECK(ceil_log2(0) == 0); + BOOST_CHECK(ceil_log2(1) == 0); + BOOST_CHECK(ceil_log2(2) == 1); + BOOST_CHECK(ceil_log2(3) == 2); + BOOST_CHECK(ceil_log2(4) == 2); + BOOST_CHECK(ceil_log2(5) == 3); + BOOST_CHECK(ceil_log2(15) == 4); + BOOST_CHECK(ceil_log2(16) == 4); + BOOST_CHECK(ceil_log2(17) == 5); + + for (unsigned int n = start_num_bytes; n <= end_num_bytes; n *= 2) { + unsigned int bit_calc = 8 * ceil_log2(n); + for (unsigned int exponent_num_bytes = 1; + exponent_num_bytes <= 2*n && bit_calc <= bit_calc_limit; + exponent_num_bytes *= 2, bit_calc += 5) + { + int64_t min_duration_ns = std::numeric_limits::max(); + int64_t max_duration_ns = 0; + int64_t total_duration_ns = 0; + + for (unsigned int trial = 0; trial < num_trials; ++trial) { + auto base = generate_random_bytes(r, n); + auto exponent = generate_random_bytes(r, exponent_num_bytes); + auto modulus = generate_random_bytes(r, n); + + auto start_time = std::chrono::steady_clock::now(); + + auto res = fc::modexp(base, exponent, modulus); + + auto end_time = std::chrono::steady_clock::now(); + + int64_t duration_ns = std::chrono::duration_cast(end_time - start_time).count(); + + //ilog("(${base})^(${exp}) % ${mod} = ${result} [took ${duration} ns]", + // ("base", base)("exp", exponent)("mod", modulus)("result", std::get(res))("duration", duration_ns) + // ); + + min_duration_ns = std::min(min_duration_ns, duration_ns); + max_duration_ns = std::max(max_duration_ns, duration_ns); + total_duration_ns += duration_ns; + } + + stats.push_back(statistics{ + .modulus_bit_size = n * 8, + .exponent_bit_size = exponent_num_bytes * 8, + .min_time_ns = min_duration_ns, + .max_time_ns = max_duration_ns, + .avg_time_ns = (total_duration_ns / num_trials), + }); + + const auto& stat = stats.back(); + + ilog("Completed random runs of mod_exp with ${bit_width}-bit width base and modulus values and " + "${exp_bit_width}-bit width exponent values. " + "Min time: ${min} ns; Average time: ${avg} ns; Max time: ${max} ns.", + ("bit_width", stat.modulus_bit_size)("exp_bit_width", stat.exponent_bit_size) + ("min", stat.min_time_ns)("avg", stat.avg_time_ns)("max", stat.max_time_ns) + ); + + } + } + + std::string stats_output = "Table (in csv format) summarizing statistics from runs:\n"; + stats_output += "Modulus/Base Bit Size,Exponent Bit Size,Average Time (ns)\n"; + for (const auto& stat : stats) { + stats_output += std::to_string(stat.modulus_bit_size); + stats_output += ','; + stats_output += std::to_string(stat.exponent_bit_size); + stats_output += ','; + stats_output += std::to_string(stat.avg_time_ns); + stats_output += '\n'; + } + + ilog(stats_output); + + // Running the above benchmark (using commented values for num_trials and bit_calc_limit) with a release build on + // an AMD 3.4 GHz CPU provides average durations for executing mod_exp for varying bit sizes for the values + // (but with base and modulus bit sizes kept equal to one another). + + // Holding the base/modulus bit size constant and increasing the exponent bit size shows a linear relationship with increasing bit + // size on the average time to execute the modular exponentiation. The slope of the best fit line to the empirical data appears + // to scale super-linearly with base/modulus size. A quadratic (degree 2) fit works okay, but it appears that a better fit is to + // model the slope of the linear relationship between average time and exponent bit size as a the base/modulus bit size taken to + // the 1.6 power and then scaled by some constant. + + // Holding the exponent bit size constant and increasing the base/modulus bit size shows a super-linear relationship with + // increasing bit size on the average time to execute the modular exponentiation. A quadratic relationship works pretty well + // but perhaps a fractional exponent between 1 and 2 (e.g. 1.6) would work well as well. + + // What is particularly revealing is plotting the average time with respect to some combination of the bit sizes of base/modulus and + // exponent. If the independent variable is the product of the exponent bit size and the base/modulus bit size, the correlation is + // not great. Even if the independent variable is the product of the exponent bit size and the base/modulus bit size taken to some power, + // the correlation is still not great. + // It seems that trying to capture all the data using a model like that breaks down when the exponent bit size is greater than the + // base/modulus bit size. + // If we filter out all the data points where the exponent bit size is greater than the base/modulus bit size, and then choose as + // then independent variable the product of the exponent bit size and the base/modulus bit size taken to some power, then we get + // a pretty good linear correlation when a power of 1.6 is chosen. + + // TODO: See if theoretical analysis of the modular exponentiation algorithm also justifies these scaling properties. + + // Example results for average time: + // | Modulus/Base Bit Size | Exponent Bit Size | Average Time (ns) | + // | --------------------- | ----------------- | ----------------- | + // | 2048 | 32 | 33826 | + // | 2048 | 256 | 250067 | + // | 2048 | 2048 | 1891095 | + // | 4096 | 32 | 129181 | + // | 4096 | 256 | 954024 | + // | 4096 | 2048 | 7205115 | + // | 8192 | 32 | 347938 | + // | 8192 | 256 | 2503652 | + // | 8192 | 2048 | 19199775 | + + // The empirical results show that the average time stays well below 5 ms if the exponent bit size does not exceed the + // modulus/base bit size and the product of the exponent bit size and the + // (modulus/base bit size)^1.6 does not exceed 550,000,000. + // Another way of satisfying that constraint is to require that the 5*ceil(log2(exponent bit size)) + 8*ceil(log2(modulus bit size)) + // be less than or equal to 5*floor(log2(500000000)) = 145. + // Or equivalently, assuming the bit sizes are multiples of 8: + // 5*ceil(log2(exponent bit size/8)) + 8*ceil(log2(modulus bit size/8)) <= 106. + + // Take, as an example, a 8192-bit modulus/base and a 128-bit exponent (which on average took 1.29 ms). + // 5*ceil(log2(128)) + 8*ceil(log2(8192)) = 5*7 + 8*13 = 139 which is less than the limit of 145. + // + // Or, as an other example, a 2048-bit modulus/base and a 2048-bit exponent (which on average took 1.89 ms). + // 5*ceil(log2(2048)) + 8*ceil(log2(2048)) = 5*11 + 8*11 = 143 which is less than the limit of 145. + // + // On the other hand, consider a 4096-bit modulus/base and a 1024-bit exponent (which on average took 3.69 ms). + // 5*ceil(log2(1024)) + 8*ceil(log2(4096)) = 5*10 + 8*12 = 146 which is greater than the limit of 145. + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/libraries/libfc/test/crypto/test_utils.hpp b/libraries/libfc/test/crypto/test_utils.hpp new file mode 100644 index 0000000000..f70dbc84c7 --- /dev/null +++ b/libraries/libfc/test/crypto/test_utils.hpp @@ -0,0 +1,11 @@ +uint32_t to_uint32(const std::string& s) { + size_t l = s.size(); + return (uint32_t)std::stoul(s.c_str(), &l, 16); +} + +bytes to_bytes(const std::string& source) { + BOOST_REQUIRE(!(source.length() % 2)); + bytes output(source.length()/2); + fc::from_hex(source, output.data(), output.size()); + return output; +} diff --git a/libraries/libfc/test/crypto/test_webauthn.cpp b/libraries/libfc/test/crypto/test_webauthn.cpp new file mode 100644 index 0000000000..ba5a5bd0ff --- /dev/null +++ b/libraries/libfc/test/crypto/test_webauthn.cpp @@ -0,0 +1,420 @@ +#define BOOST_TEST_MODULE webauthn_test_mod +#include +#include + +#include +#include +#include +#include + +using namespace fc::crypto; +using namespace fc; +using namespace std::literals; + +static fc::crypto::webauthn::signature make_webauthn_sig(const fc::crypto::r1::private_key& priv_key, + std::vector& auth_data, + const std::string& json) { + + //webauthn signature is sha256(auth_data || client_data_hash) + fc::sha256 client_data_hash = fc::sha256::hash(json); + fc::sha256::encoder e; + e.write((char*)auth_data.data(), auth_data.size()); + e.write(client_data_hash.data(), client_data_hash.data_size()); + + r1::compact_signature sig = priv_key.sign_compact(e.result()); + + char buff[8192]; + datastream ds(buff, sizeof(buff)); + fc::raw::pack(ds, sig); + fc::raw::pack(ds, auth_data); + fc::raw::pack(ds, json); + ds.seekp(0); + + fc::crypto::webauthn::signature ret; + fc::raw::unpack(ds, ret); + + return ret; +} + +//used for many below +static const r1::private_key priv = fc::crypto::r1::private_key::generate(); +static const r1::public_key pub = priv.get_public_key(); +static const fc::sha256 d = fc::sha256::hash("sup"s); +static const fc::sha256 origin_hash = fc::sha256::hash("fctesting.invalid"s); + +BOOST_AUTO_TEST_SUITE(webauthn_suite) + +//Good signature +BOOST_AUTO_TEST_CASE(good) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EQUAL(wa_pub, make_webauthn_sig(priv, auth_data, json).recover(d, true)); +} FC_LOG_AND_RETHROW(); + +//A valid signature but shouldn't match public key due to presence difference +BOOST_AUTO_TEST_CASE(mismatch_presence) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_PRESENT, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + auth_data[32] |= 0x04; //User presence verified + + BOOST_CHECK_NE(wa_pub, make_webauthn_sig(priv, auth_data, json).recover(d, true)); +} FC_LOG_AND_RETHROW(); + +//A valid signature but shouldn't match public key due to origin difference +BOOST_AUTO_TEST_CASE(mismatch_origin) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_PRESENT, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://mallory.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + fc::sha256 mallory_origin_hash = fc::sha256::hash("mallory.invalid"s); + memcpy(auth_data.data(), mallory_origin_hash.data(), sizeof(mallory_origin_hash)); + + BOOST_CHECK_NE(wa_pub, make_webauthn_sig(priv, auth_data, json).recover(d, true)); +} FC_LOG_AND_RETHROW(); + +//A valid signature but shouldn't recover because http was in use instead of https +BOOST_AUTO_TEST_CASE(non_https) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"http://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("webauthn origin must begin with https://") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//A valid signature but shouldn't recover because there is no origin scheme +BOOST_AUTO_TEST_CASE(lacking_scheme) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("webauthn origin must begin with https://") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//A valid signature but shouldn't recover because empty origin +BOOST_AUTO_TEST_CASE(empty_origin) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("webauthn origin must begin with https://") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//Good signature with a port +BOOST_AUTO_TEST_CASE(good_port) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid:123456\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EQUAL(wa_pub, make_webauthn_sig(priv, auth_data, json).recover(d, true)); +} FC_LOG_AND_RETHROW(); + +//Good signature with an empty port +BOOST_AUTO_TEST_CASE(empty_port) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid:\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EQUAL(wa_pub, make_webauthn_sig(priv, auth_data, json).recover(d, true)); +} FC_LOG_AND_RETHROW(); + +//valid signature but with misc junk in challenge +BOOST_AUTO_TEST_CASE(challenge_junk) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + "blahBLAH"s + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::exception, [](const fc::exception& e) { + return e.to_detail_string().find("sha256: size mismatch") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//valid signature but with non base64 in challenge +BOOST_AUTO_TEST_CASE(challenge_non_base64) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + "hello@world$"s + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::exception, [](const fc::exception& e) { + return e.to_detail_string().find("encountered non-base64 character") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//valid signature but with some other digest in the challenge that is not the one we are recovering from +BOOST_AUTO_TEST_CASE(challenge_wrong) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + fc::sha256 other_digest = fc::sha256::hash("yo"s); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(other_digest.data(), other_digest.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("Wrong webauthn challenge") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//valid signature but wrong webauthn type +BOOST_AUTO_TEST_CASE(wrong_type) try { + + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.meh\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("webauthn signature type not an assertion") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//signature if good but the auth_data rpid hash is not what is expected for origin +BOOST_AUTO_TEST_CASE(auth_data_rpid_hash_bad) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + fc::sha256 origin_hash_corrupt = fc::sha256::hash("fctesting.invalid"s); + memcpy(auth_data.data(), origin_hash_corrupt.data(), sizeof(origin_hash_corrupt)); + auth_data[4]++; + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("webauthn rpid hash doesn't match origin") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//signature if good but auth_data too short to store what needs to be stored +BOOST_AUTO_TEST_CASE(auth_data_too_short) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(1); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("auth_data not as large as required") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//Good signature but missing origin completely +BOOST_AUTO_TEST_CASE(missing_origin) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("webauthn origin must begin with https://") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//Good signature but missing type completely +BOOST_AUTO_TEST_CASE(missing_type) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("webauthn signature type not an assertion") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//Good signature but missing challenge completely +BOOST_AUTO_TEST_CASE(missing_challenge) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::exception, [](const fc::exception& e) { + return e.to_detail_string().find("sha256: size mismatch") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//Good signature with some extra stuff sprinkled in the json +BOOST_AUTO_TEST_CASE(good_extrajunk) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"cool\":\"beans\",\"obj\":{\"array\":[4, 5, 6]}," + "\"type\":\"webauthn.get\",\"answer\": 42 ,\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EQUAL(wa_pub, make_webauthn_sig(priv, auth_data, json).recover(d, true)); +} FC_LOG_AND_RETHROW(); + +//Good signature but it's not a JSON object! +BOOST_AUTO_TEST_CASE(not_json_object) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "hey man"s; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("Failed to parse client data JSON") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//damage the r/s portion of the signature +BOOST_AUTO_TEST_CASE(damage_sig) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + webauthn::signature sig = make_webauthn_sig(priv, auth_data, json); + char buff[8192]; + datastream ds(buff, sizeof(buff)); + fc::raw::pack(ds, sig); + buff[4]++; + ds.seekp(0); + fc::raw::unpack(ds, sig); + + bool failed_recovery = false; + bool failed_compare = false; + webauthn::public_key recovered_pub; + + try { + recovered_pub = sig.recover(d, true); + failed_compare = !(wa_pub == recovered_pub); + } + catch(fc::exception& e) { + failed_recovery = e.to_detail_string().find("unable to reconstruct public key from signature") != std::string::npos; + } + + //We can fail either by failing to recover any key, or recovering the wrong key. Check that at least one of these failed + BOOST_CHECK_EQUAL(failed_recovery || failed_compare, true); +} FC_LOG_AND_RETHROW(); + +//damage the recovery index portion of the sig +BOOST_AUTO_TEST_CASE(damage_sig_idx) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + webauthn::signature sig = make_webauthn_sig(priv, auth_data, json); + char buff[8192]; + datastream ds(buff, sizeof(buff)); + fc::raw::pack(ds, sig); + buff[0]++; + ds.seekp(0); + fc::raw::unpack(ds, sig); + + bool failed_recovery = false; + bool failed_compare = false; + webauthn::public_key recovered_pub; + + try { + recovered_pub = sig.recover(d, true); + failed_compare = !(wa_pub == recovered_pub); + } + catch(fc::exception& e) { + failed_recovery = e.to_detail_string().find("unable to reconstruct public key from signature") != std::string::npos; + } + + //We can fail either by failing to recover any key, or recovering the wrong key. Check that at least one of these failed + BOOST_CHECK_EQUAL(failed_recovery || failed_compare, true); +} FC_LOG_AND_RETHROW(); + +//Good signature but with a different private key than was expecting +BOOST_AUTO_TEST_CASE(different_priv_key) try { + r1::private_key other_priv = fc::crypto::r1::private_key::generate(); + + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_NE(wa_pub, make_webauthn_sig(other_priv, auth_data, json).recover(d, true)); +} FC_LOG_AND_RETHROW(); + +//Good signature but has empty json +BOOST_AUTO_TEST_CASE(empty_json) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + std::string json; + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("Failed to parse client data JSON") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//empty rpid not allowed for pub key +BOOST_AUTO_TEST_CASE(empty_rpid) try { + char data[67] = {}; + datastream ds(data, sizeof(data)); + webauthn::public_key pubkey; + + BOOST_CHECK_EXCEPTION(fc::raw::unpack(ds, pubkey), fc::assert_exception, [](const fc::assert_exception& e) { + return e.to_detail_string().find("webauthn pubkey must have non empty rpid") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +//good sig but remove the trailing =, should still be accepted +BOOST_AUTO_TEST_CASE(good_no_trailing_equal) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "\"}"; + boost::erase_all(json, "="); + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EQUAL(wa_pub, make_webauthn_sig(priv, auth_data, json).recover(d, true)); +} FC_LOG_AND_RETHROW(); + +//Before the base64 decoder was adjusted to throw in error (pre-webauthn-merge), it would simply stop when encountering +//a non-base64 char. This means it was possible for the following JSON & challenge to validate completely correctly. Now it +//should not pass. +BOOST_AUTO_TEST_CASE(base64_wonky) try { + webauthn::public_key wa_pub(pub.serialize(), webauthn::public_key::user_presence_t::USER_PRESENCE_NONE, "fctesting.invalid"); + + std::string json = "{\"origin\":\"https://fctesting.invalid\",\"type\":\"webauthn.get\",\"challenge\":\"" + fc::base64url_encode(d.data(), d.data_size()) + "!@#$\"}"; + boost::erase_all(json, "="); + + std::vector auth_data(37); + memcpy(auth_data.data(), origin_hash.data(), sizeof(origin_hash)); + + BOOST_CHECK_EXCEPTION(make_webauthn_sig(priv, auth_data, json).recover(d, true), fc::exception, [](const fc::exception& e) { + return e.to_detail_string().find("encountered non-base64 character") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/io/CMakeLists.txt b/libraries/libfc/test/io/CMakeLists.txt new file mode 100644 index 0000000000..1bcbaaf0f1 --- /dev/null +++ b/libraries/libfc/test/io/CMakeLists.txt @@ -0,0 +1,14 @@ +add_executable( test_cfile test_cfile.cpp ) +target_link_libraries( test_cfile fc ) + +add_executable( test_json test_json.cpp ) +target_link_libraries( test_json fc ) + +add_executable( test_tracked_storage test_tracked_storage.cpp ) +target_link_libraries( test_tracked_storage fc ) + +add_test(NAME test_cfile COMMAND libraries/fc/test/io/test_cfile WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_json COMMAND libraries/fc/test/io/test_json WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_tracked_storage COMMAND libraries/fc/test/io/test_tracked_storage WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + + diff --git a/libraries/libfc/test/io/test_cfile.cpp b/libraries/libfc/test/io/test_cfile.cpp new file mode 100644 index 0000000000..e4697378a1 --- /dev/null +++ b/libraries/libfc/test/io/test_cfile.cpp @@ -0,0 +1,160 @@ +#define BOOST_TEST_MODULE io +#include + +#include + +using namespace fc; + +BOOST_AUTO_TEST_SUITE(cfile_test_suite) + BOOST_AUTO_TEST_CASE(test_simple) + { + fc::temp_directory tempdir; + + cfile t; + t.set_file_path( tempdir.path() / "test" ); + t.open( "ab+" ); + BOOST_CHECK( t.is_open() ); + BOOST_CHECK( fc::exists( tempdir.path() / "test") ); + + t.open( "rb+" ); + BOOST_CHECK( t.is_open() ); + t.write( "abc", 3 ); + BOOST_CHECK_EQUAL( t.tellp(), 3 ); + std::vector v(3); + t.seek( 0 ); + BOOST_CHECK_EQUAL( t.tellp(), 0 ); + t.read( &v[0], 3 ); + + BOOST_CHECK_EQUAL( v[0], 'a' ); + BOOST_CHECK_EQUAL( v[1], 'b' ); + BOOST_CHECK_EQUAL( v[2], 'c' ); + + t.seek_end( -2 ); + BOOST_CHECK_EQUAL( t.tellp(), 1 ); + t.read( &v[0], 1 ); + BOOST_CHECK_EQUAL( v[0], 'b' ); + + int x = 42, y = 0; + t.seek( 1 ); + t.write( reinterpret_cast( &x ), sizeof( x ) ); + t.seek( 1 ); + t.read( reinterpret_cast( &y ), sizeof( y ) ); + BOOST_CHECK_EQUAL( x, y ); + + t.close(); + BOOST_CHECK( !t.is_open() ); + + // re-open and read again + t.open( "rb+" ); + BOOST_CHECK( t.is_open() ); + + y = 0; + t.seek( 1 ); + t.read( reinterpret_cast( &y ), sizeof( y ) ); + BOOST_CHECK_EQUAL( x, y ); + + t.close(); + fc::remove_all( t.get_file_path() ); + BOOST_CHECK( !fc::exists( tempdir.path() / "test") ); + } + + BOOST_AUTO_TEST_CASE(test_hole_punching) + { + if(!cfile::supports_hole_punching()) + return; + + fc::temp_file tmpfile; + cfile file; + file.set_file_path(tmpfile.path()); + file.open("a+b"); + file.close(); + file.open("w+b"); + + std::vector a, b, c, d, e, f, g, h, i, j; + a.assign(file.filesystem_block_size(), 'A'); //0 + b.assign(file.filesystem_block_size(), 'B'); //1 + c.assign(file.filesystem_block_size()/4, 'C'); //2 + d.assign(file.filesystem_block_size()/4, 'D'); + e.assign(file.filesystem_block_size()/4, 'E'); + f.assign(file.filesystem_block_size()/4, 'F'); + g.assign(file.filesystem_block_size()/2, 'G'); //3 + h.assign(file.filesystem_block_size()/2, 'H'); + i.assign(file.filesystem_block_size(), 'I'); //4 + j.assign(file.filesystem_block_size(), 'J'); //5 + + std::vector nom, nom2, nom4; + nom.resize(file.filesystem_block_size()); + nom2.resize(file.filesystem_block_size()/2); + nom4.resize(file.filesystem_block_size()/4); + + file.write(a.data(), a.size()); + file.write(b.data(), b.size()); + file.write(c.data(), c.size()); + file.write(d.data(), d.size()); + file.write(e.data(), e.size()); + file.write(f.data(), f.size()); + file.write(g.data(), g.size()); + file.write(h.data(), h.size()); + + //should do nothing + file.punch_hole(4, 8); + file.seek(0); + file.read(nom.data(), nom.size()); + BOOST_TEST_REQUIRE(nom == a); + + //should also do nothing + file.punch_hole(file.filesystem_block_size(), file.filesystem_block_size()+file.filesystem_block_size()/2); + file.seek(file.filesystem_block_size()); + file.read(nom.data(), nom.size()); + BOOST_TEST_REQUIRE(nom == b); + + //should only wipe out B + file.punch_hole(file.filesystem_block_size(), file.filesystem_block_size()*2+file.filesystem_block_size()/2); + file.seek(0); + file.read(nom.data(), nom.size()); + BOOST_TEST_REQUIRE(nom == a); + file.read(nom.data(), nom.size()); + BOOST_TEST_REQUIRE(nom != b); + file.read(nom4.data(), nom4.size()); + BOOST_TEST_REQUIRE(nom4 == c); + + //write some stuff at the end after we had punched + file.seek_end(0); + file.write(i.data(), i.size()); + file.write(j.data(), j.size()); + + //check C is intact + file.seek(file.filesystem_block_size()*2); + file.read(nom4.data(), nom4.size()); + BOOST_TEST_REQUIRE(nom4 == c); + + //should wipe out C,D,E,F + file.punch_hole(file.filesystem_block_size()*2, file.filesystem_block_size()*3+file.filesystem_block_size()/2); + file.seek(file.filesystem_block_size()*2); + file.read(nom4.data(), nom4.size()); + BOOST_TEST_REQUIRE(nom4 != c); + + //so check that G,H,I are still intact + file.seek(file.filesystem_block_size()*3); + file.read(nom2.data(), nom2.size()); + BOOST_TEST_REQUIRE(nom2 == g); + file.read(nom2.data(), nom2.size()); + BOOST_TEST_REQUIRE(nom2 == h); + file.read(nom.data(), nom.size()); + BOOST_TEST_REQUIRE(nom == i); + + //check I is intact + file.seek(file.filesystem_block_size()*4); + file.read(nom.data(), nom.size()); + BOOST_TEST_REQUIRE(nom == i); + + //should only wipe out I + file.punch_hole(file.filesystem_block_size()*4, file.filesystem_block_size()*5); + file.seek(file.filesystem_block_size()*4); + file.read(nom.data(), nom.size()); + BOOST_TEST_REQUIRE(nom != i); + file.read(nom.data(), nom.size()); + BOOST_TEST_REQUIRE(nom == j); + } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/io/test_json.cpp b/libraries/libfc/test/io/test_json.cpp new file mode 100644 index 0000000000..a07fbefad0 --- /dev/null +++ b/libraries/libfc/test/io/test_json.cpp @@ -0,0 +1,225 @@ +#define BOOST_TEST_MODULE io_json +#include + +#include +#include + +using namespace fc; + +BOOST_AUTO_TEST_SUITE(json_test_suite) + +namespace json_test_util { + constexpr size_t exception_limit_size = 250; + + const json::yield_function_t yield_deadline_exception_at_start = [](size_t s) { + FC_CHECK_DEADLINE(fc::time_point::now() - fc::milliseconds(1)); + }; + + const json::yield_function_t yield_deadline_exception_in_mid = [](size_t s) { + if (s >= exception_limit_size) { + throw fc::timeout_exception(fc::exception_code::timeout_exception_code, "timeout_exception", "execution timed out" ); + } + }; + + const json::yield_function_t yield_length_exception = [](size_t s) { + FC_ASSERT( s <= exception_limit_size ); + }; + + const json::yield_function_t yield_no_limitation = [](size_t s) { + // no limitation + }; + + const auto time_except_verf_func = [](const fc::timeout_exception& ex) -> bool { + return (static_cast(ex.code_value) == static_cast(fc::exception_code::timeout_exception_code)); + }; + + const auto length_limit_except_verf_func = [](const fc::assert_exception& ex) -> bool { + return (static_cast(ex.code_value) == static_cast(fc::exception_code::assert_exception_code)); + }; + + constexpr size_t repeat_char_num = 512; + const std::string repeat_chars(repeat_char_num, 'a'); + const string escape_input_str = "\\b\\f\\n\\r\\t-\\-\\\\-\\x0\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f" \ + "\\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f-" + repeat_chars; + +} // namespace json_test_util + +BOOST_AUTO_TEST_CASE(to_string_test) +{ + { // to_string( const variant& v, const fc::time_point& deadline, const uint64_t max_len = max_length_limit, output_formatting format = stringify_large_ints_and_doubles); + { + variant v(json_test_util::escape_input_str); + std::string deadline_exception_at_start_str; + BOOST_CHECK_EXCEPTION(deadline_exception_at_start_str = json::to_string( v, fc::time_point::min(), json::output_formatting::stringify_large_ints_and_doubles, json::max_length_limit), + fc::timeout_exception, + json_test_util::time_except_verf_func); + BOOST_CHECK_EQUAL(deadline_exception_at_start_str.empty(), true); + } + { + constexpr size_t max_len = 10; + variant v(json_test_util::repeat_chars); + std::string deadline_exception_at_start_str; + BOOST_CHECK_EQUAL(deadline_exception_at_start_str.empty(), true); + BOOST_CHECK_EXCEPTION(deadline_exception_at_start_str = json::to_string( v, fc::time_point::maximum(), json::output_formatting::stringify_large_ints_and_doubles, max_len), + fc::assert_exception, + json_test_util::length_limit_except_verf_func); + BOOST_CHECK_EQUAL(deadline_exception_at_start_str.empty(), true); + } + { + variant v(json_test_util::repeat_chars); + std::string length_exception_in_mid_str; + BOOST_CHECK_NO_THROW(length_exception_in_mid_str = json::to_string( v, fc::time_point::maximum(), json::output_formatting::stringify_large_ints_and_doubles, json::max_length_limit)); + BOOST_CHECK_EQUAL(length_exception_in_mid_str, "\"" + json_test_util::repeat_chars + "\""); + } + } + { // to_string( const variant& v, const yield_function_t& yield, output_formatting format = stringify_large_ints_and_doubles); + const variant v(json_test_util::repeat_chars); + BOOST_CHECK_LT(json_test_util::exception_limit_size, json_test_util::repeat_chars.size()); + { + std::string deadline_exception_at_start_str; + BOOST_CHECK_EXCEPTION(deadline_exception_at_start_str = json::to_string( v, json_test_util::yield_deadline_exception_at_start), + fc::timeout_exception, + json_test_util::time_except_verf_func); + BOOST_CHECK_EQUAL(deadline_exception_at_start_str.empty(), true); + } + { + std::string deadline_exception_in_mid_str; + BOOST_CHECK_EXCEPTION(deadline_exception_in_mid_str = json::to_string( v, json_test_util::yield_deadline_exception_in_mid), + fc::timeout_exception, + json_test_util::time_except_verf_func); + BOOST_CHECK_EQUAL(deadline_exception_in_mid_str.empty(), true); + } + { + std::string length_exception_in_mid_str; + BOOST_CHECK_EXCEPTION(length_exception_in_mid_str = json::to_string( v, json_test_util::yield_length_exception), + fc::assert_exception, + json_test_util::length_limit_except_verf_func); + BOOST_CHECK_EQUAL(length_exception_in_mid_str.empty(), true); + } + { + std::string no_exception_str; + BOOST_CHECK_NO_THROW(no_exception_str = json::to_string( v, json_test_util::yield_no_limitation)); + BOOST_CHECK_EQUAL(no_exception_str, "\"" + json_test_util::repeat_chars + "\""); + } + } + { // to_string template call + const uint16_t id = 1000; + const uint64_t len = 3; + const std::string id_ret_1 = json::to_string( id, fc::time_point::maximum()); + BOOST_CHECK_EQUAL(std::to_string(id), id_ret_1); + + // exceed length + std::string id_ret_2; + BOOST_REQUIRE_THROW(id_ret_2 = json::to_string( id, fc::time_point::maximum(), json::output_formatting::stringify_large_ints_and_doubles, len), fc::assert_exception); + BOOST_CHECK_EQUAL(id_ret_2.empty(), true); + + // time_out + std::string id_ret_3; + BOOST_REQUIRE_THROW(id_ret_3 = json::to_string( id, fc::time_point::now() - fc::milliseconds(1) ), fc::timeout_exception); + BOOST_CHECK_EQUAL(id_ret_3.empty(), true); + } +} + +BOOST_AUTO_TEST_CASE(to_pretty_string_test) +{ + { // to_pretty_string( const variant& v, const fc::time_point& deadline, const uint64_t max_len = max_length_limit, output_formatting format = stringify_large_ints_and_doubles ); + { + variant v(json_test_util::escape_input_str); + std::string deadline_exception_at_start_str; + BOOST_CHECK_EXCEPTION(deadline_exception_at_start_str = json::to_pretty_string( v, fc::time_point::min(), json::output_formatting::stringify_large_ints_and_doubles, json::max_length_limit), + fc::timeout_exception, + json_test_util::time_except_verf_func); + BOOST_CHECK_EQUAL(deadline_exception_at_start_str.empty(), true); + } + { + constexpr size_t max_len = 10; + variant v(json_test_util::repeat_chars); + std::string deadline_exception_at_start_str; + BOOST_CHECK_EQUAL(deadline_exception_at_start_str.empty(), true); + BOOST_CHECK_EXCEPTION(deadline_exception_at_start_str = json::to_pretty_string( v, fc::time_point::maximum(), json::output_formatting::stringify_large_ints_and_doubles, max_len), + fc::assert_exception, + json_test_util::length_limit_except_verf_func); + BOOST_CHECK_EQUAL(deadline_exception_at_start_str.empty(), true); + } + { + variant v(json_test_util::repeat_chars); + std::string length_exception_in_mid_str; + BOOST_CHECK_NO_THROW(length_exception_in_mid_str = json::to_pretty_string( v, fc::time_point::maximum(), json::output_formatting::stringify_large_ints_and_doubles, json::max_length_limit)); + BOOST_CHECK_EQUAL(length_exception_in_mid_str, "\"" + json_test_util::repeat_chars + "\""); + } + } + { // to_pretty_string( const variant& v, const yield_function_t& yield, output_formatting format = stringify_large_ints_and_doubles ); + const variant v(json_test_util::repeat_chars); + BOOST_CHECK_LT(json_test_util::exception_limit_size, json_test_util::repeat_chars.size()); + { + std::string deadline_exception_at_start_str; + BOOST_CHECK_EXCEPTION(deadline_exception_at_start_str = json::to_pretty_string( v, json_test_util::yield_deadline_exception_at_start), + fc::timeout_exception, + json_test_util::time_except_verf_func); + BOOST_CHECK_EQUAL(deadline_exception_at_start_str.empty(), true); + } + { + std::string deadline_exception_in_mid_str; + BOOST_CHECK_EXCEPTION(deadline_exception_in_mid_str = json::to_pretty_string( v, json_test_util::yield_deadline_exception_in_mid), + fc::timeout_exception, + json_test_util::time_except_verf_func); + BOOST_CHECK_EQUAL(deadline_exception_in_mid_str.empty(), true); + } + { + std::string length_exception_in_mid_str; + BOOST_CHECK_EXCEPTION(length_exception_in_mid_str = json::to_pretty_string( v, json_test_util::yield_length_exception), + fc::assert_exception, + json_test_util::length_limit_except_verf_func); + BOOST_CHECK_EQUAL(length_exception_in_mid_str.empty(), true); + } + { + std::string no_exception_str; + BOOST_CHECK_NO_THROW(no_exception_str = json::to_pretty_string( v, json_test_util::yield_no_limitation)); + BOOST_CHECK_EQUAL(no_exception_str, "\"" + json_test_util::repeat_chars + "\""); + } + } + { // to_pretty_string template call + const uint16_t id = 1000; + const uint64_t len = 3; + const std::string id_ret_1 = json::to_pretty_string( id, fc::time_point::maximum()); + BOOST_CHECK_EQUAL(std::to_string(id), id_ret_1); + + // exceed length + std::string id_ret_2; + BOOST_REQUIRE_THROW(id_ret_2 = json::to_pretty_string( id, fc::time_point::maximum(), json::output_formatting::stringify_large_ints_and_doubles, len), fc::assert_exception); + BOOST_CHECK_EQUAL(id_ret_2.empty(), true); + + // time_out + std::string id_ret_3; + BOOST_REQUIRE_THROW(id_ret_3 = json::to_pretty_string( id, fc::time_point::now() - fc::milliseconds(1) ), fc::timeout_exception); + BOOST_CHECK_EQUAL(id_ret_3.empty(), true); + } +} + +BOOST_AUTO_TEST_CASE(escape_string_test) +{ + std::string escape_out_str; + escape_out_str = fc::escape_string(json_test_util::escape_input_str, json_test_util::yield_no_limitation); + BOOST_CHECK_LT(json_test_util::repeat_char_num, json_test_util::escape_input_str.size()); + BOOST_CHECK_LT(json_test_util::escape_input_str.size() - json_test_util::repeat_char_num, json_test_util::exception_limit_size); // by using size_different to calculate expected string + { + // simulate exceed time exception at the beginning of processing + BOOST_CHECK_EXCEPTION(escape_string(json_test_util::escape_input_str, json_test_util::yield_deadline_exception_at_start), + fc::timeout_exception, + json_test_util::time_except_verf_func); + } + { + // simulate exceed time exception in the middle of processing + BOOST_CHECK_EXCEPTION(escape_string(json_test_util::escape_input_str, json_test_util::yield_deadline_exception_in_mid), + fc::timeout_exception, + json_test_util::time_except_verf_func); + } + { + // length limitation exception in the middle of processing + BOOST_CHECK_EXCEPTION(escape_string(json_test_util::escape_input_str, json_test_util::yield_length_exception), + fc::assert_exception, + json_test_util::length_limit_except_verf_func); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/io/test_tracked_storage.cpp b/libraries/libfc/test/io/test_tracked_storage.cpp new file mode 100644 index 0000000000..2c478d687f --- /dev/null +++ b/libraries/libfc/test/io/test_tracked_storage.cpp @@ -0,0 +1,222 @@ +#define BOOST_TEST_MODULE tracked_storage +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using boost::multi_index_container; +using namespace boost::multi_index; + +struct test_size { + uint64_t key = 0; + uint64_t s = 0; +}; + +FC_REFLECT( test_size, (key)(s) ) + +struct by_key; +typedef multi_index_container< + test_size, + indexed_by< + hashed_unique< tag, member, std::hash> + > +> test_size_container; + +struct test_size2 { + uint64_t key = 0; + fc::time_point time; + uint64_t s = 0; +}; + +FC_REFLECT( test_size2, (key)(time)(s) ) + +namespace fc::tracked { + template<> + size_t memory_size(const test_size& t) { + return t.s; + } + + template<> + size_t memory_size(const test_size2& t) { + return t.s; + } +} + +struct by_time; +typedef multi_index_container< + test_size2, + indexed_by< + hashed_unique< tag, member, std::hash>, + ordered_non_unique< tag, member > + > +> test_size2_container; + + +BOOST_AUTO_TEST_SUITE(tracked_storage_tests) + +BOOST_AUTO_TEST_CASE(track_storage_test) { + fc::tracked_storage storage; + BOOST_CHECK(storage.insert(test_size{ 0, 5 }).second); + BOOST_CHECK_EQUAL( storage.memory_size(), 5); + BOOST_CHECK_EQUAL( storage.index().size(), 1); + BOOST_CHECK(storage.insert(test_size{ 1, 4 }).second); + BOOST_CHECK_EQUAL( storage.memory_size(), 9); + BOOST_CHECK_EQUAL( storage.index().size(), 2); + BOOST_CHECK(storage.insert(test_size{ 2, 15 }).second); + BOOST_CHECK_EQUAL( storage.memory_size(), 24); + BOOST_CHECK_EQUAL( storage.index().size(), 3); + auto to_mod = storage.find(1); + storage.modify(to_mod, [](test_size& ts) { ts.s = 14; }); + BOOST_CHECK_EQUAL( storage.memory_size(), 34); + BOOST_CHECK_EQUAL( storage.index().size(), 3); + storage.modify(to_mod, [](test_size& ts) { ts.s = 0; }); + BOOST_CHECK_EQUAL( storage.memory_size(), 20); + BOOST_CHECK(!storage.insert(test_size{ 1, 100 }).second); + BOOST_CHECK_EQUAL( storage.memory_size(), 20); + BOOST_CHECK_EQUAL( storage.index().size(), 3); + storage.erase(2); + BOOST_CHECK_EQUAL( storage.memory_size(), 5); + BOOST_CHECK_NO_THROW(storage.erase(2)); + BOOST_CHECK_EQUAL( storage.memory_size(), 5); + storage.erase( storage.index().find(0) ); + BOOST_CHECK_EQUAL( storage.memory_size(), 0); +} + +BOOST_AUTO_TEST_CASE(simple_write_read_file_storage_test) { + using tracked_storage1 = fc::tracked_storage; + tracked_storage1 storage1_1; + BOOST_CHECK_EQUAL( storage1_1.memory_size(), 0); + BOOST_CHECK_EQUAL( storage1_1.index().size(), 0); + + fc::temp_directory td; + auto out = fc::persistence_util::open_cfile_for_write(td.path(), "temp.dat"); + fc::persistence_util::write_persistence_header(out, 0x12345678, 5); + storage1_1.write(out); + out.flush(); + out.close(); + + auto content = fc::persistence_util::open_cfile_for_read(td.path(), "temp.dat"); + auto version = fc::persistence_util::read_persistence_header(content, 0x12345678, 5, 5); + BOOST_CHECK_EQUAL( version, 5 ); + auto ds = content.create_datastream(); + tracked_storage1 storage1_2; + BOOST_CHECK(storage1_2.read(ds, 500)); + BOOST_CHECK_EQUAL( storage1_2.index().size(), 0); + BOOST_CHECK_EQUAL( storage1_2.memory_size(), 0); + + const auto tellp = content.tellp(); + content.seek_end(0); + BOOST_CHECK_EQUAL( content.tellp(), tellp ); +} + +BOOST_AUTO_TEST_CASE(single_write_read_file_storage_test) { try { + using tracked_storage1 = fc::tracked_storage; + tracked_storage1 storage1_1; + storage1_1.insert(test_size{ 0, 6 }); + BOOST_CHECK_EQUAL( storage1_1.memory_size(), 6); + BOOST_CHECK_EQUAL( storage1_1.index().size(), 1); + fc::temp_directory td; + auto out = fc::persistence_util::open_cfile_for_write(td.path(), "temp.dat"); + fc::persistence_util::write_persistence_header(out, 0x12345678, 5); + storage1_1.write(out); + out.flush(); + out.close(); + + auto content = fc::persistence_util::open_cfile_for_read(td.path(), "temp.dat"); + auto version = fc::persistence_util::read_persistence_header(content, 0x12345678, 5, 5); + BOOST_CHECK_EQUAL( version, 5 ); + auto ds = content.create_datastream(); + tracked_storage1 storage1_2; + BOOST_CHECK(storage1_2.read(ds, 500)); + BOOST_CHECK_EQUAL( storage1_2.index().size(), 1); + const auto& primary_idx2 = storage1_2.index().get(); + auto itr2 = primary_idx2.cbegin(); + BOOST_CHECK_EQUAL( itr2->key, 0); + BOOST_CHECK_EQUAL( itr2->s, 6); + BOOST_CHECK_EQUAL( storage1_2.memory_size(), 6); + + const auto tellp = content.tellp(); + content.seek_end(0); + BOOST_CHECK_EQUAL( content.tellp(), tellp ); +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(write_read_file_storage_test) { + using tracked_storage1 = fc::tracked_storage; + tracked_storage1 storage1_1; + storage1_1.insert(test_size{ 0, 6 }); + storage1_1.insert(test_size{ 3, 7 }); + storage1_1.insert(test_size{ 5, 3 }); + storage1_1.insert(test_size{ 9, 4 }); + storage1_1.insert(test_size{ 15, 6 }); + storage1_1.insert(test_size{ 16, 4 }); + storage1_1.insert(test_size{ 19, 3 }); + storage1_1.insert(test_size{ 25, 7 }); + BOOST_CHECK_EQUAL( storage1_1.memory_size(), 40); + BOOST_CHECK_EQUAL( storage1_1.index().size(), 8); + + fc::temp_directory td; + auto out = fc::persistence_util::open_cfile_for_write(td.path(), "temp.dat"); + fc::persistence_util::write_persistence_header(out, 0x12345678, 5); + storage1_1.write(out); + + using tracked_storage2 = fc::tracked_storage; + tracked_storage2 storage2_1; + const auto now = fc::time_point::now(); + storage2_1.insert(test_size2{ 3, now, 7 }); + BOOST_CHECK_EQUAL( storage2_1.memory_size(), 7); + BOOST_CHECK_EQUAL( storage2_1.index().size(), 1); + + storage2_1.write(out); + + out.flush(); + out.close(); + + auto content = fc::persistence_util::open_cfile_for_read(td.path(), "temp.dat"); + auto version = fc::persistence_util::read_persistence_header(content, 0x12345678, 5, 5); + BOOST_CHECK_EQUAL( version, 5 ); + auto ds = content.create_datastream(); + tracked_storage1 storage1_2; + BOOST_CHECK(storage1_2.read(ds, 500)); + BOOST_CHECK_EQUAL( storage1_2.index().size(), 8); + const auto& primary_idx1_2 = storage1_2.index().get(); + auto itr2 = primary_idx1_2.cbegin(); + BOOST_CHECK_EQUAL( itr2->key, 0); + BOOST_CHECK_EQUAL( itr2->s, 6); + BOOST_CHECK_EQUAL( (++itr2)->key, 3); + BOOST_CHECK_EQUAL( itr2->s, 7); + BOOST_CHECK_EQUAL( (++itr2)->key, 5); + BOOST_CHECK_EQUAL( itr2->s, 3); + BOOST_CHECK_EQUAL( (++itr2)->key, 9); + BOOST_CHECK_EQUAL( itr2->s, 4); + BOOST_CHECK_EQUAL( (++itr2)->key, 15); + BOOST_CHECK_EQUAL( itr2->s, 6); + BOOST_CHECK_EQUAL( (++itr2)->key, 16); + BOOST_CHECK_EQUAL( itr2->s, 4); + BOOST_CHECK_EQUAL( (++itr2)->key, 19); + BOOST_CHECK_EQUAL( itr2->s, 3); + BOOST_CHECK_EQUAL( (++itr2)->key, 25); + BOOST_CHECK_EQUAL( itr2->s, 7); + BOOST_CHECK_EQUAL( storage1_2.memory_size(), 40); + + tracked_storage2 storage2_2; + BOOST_CHECK(storage2_2.read(ds, 500)); + BOOST_CHECK_EQUAL( storage2_2.index().size(), 1); + const auto& primary_idx2_2 = storage2_2.index().get(); + auto itr3 = primary_idx2_2.cbegin(); + BOOST_CHECK_EQUAL( itr3->key, 3); + BOOST_CHECK( itr3->time == now); + BOOST_CHECK_EQUAL( itr3->s, 7); + + const auto tellp = content.tellp(); + content.seek_end(0); + BOOST_CHECK_EQUAL( content.tellp(), tellp ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/network/CMakeLists.txt b/libraries/libfc/test/network/CMakeLists.txt new file mode 100644 index 0000000000..a514cdd6dc --- /dev/null +++ b/libraries/libfc/test/network/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable( test_message_buffer test_message_buffer.cpp) +target_link_libraries( test_message_buffer fc ) + +add_test(NAME test_message_buffer COMMAND libraries/fc/test/network/test_message_buffer WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/libraries/libfc/test/network/test_message_buffer.cpp b/libraries/libfc/test/network/test_message_buffer.cpp new file mode 100644 index 0000000000..2f44b5a0b9 --- /dev/null +++ b/libraries/libfc/test/network/test_message_buffer.cpp @@ -0,0 +1,380 @@ +#include + +#include + +#define BOOST_TEST_MODULE message_buffer +#include + +namespace { +size_t mb_size(boost::asio::mutable_buffer& mb) { +#if BOOST_VERSION >= 106600 + return mb.size(); +#else + return boost::asio::detail::buffer_size_helper(mb); +#endif +} + +void* mb_data(boost::asio::mutable_buffer& mb) { +#if BOOST_VERSION >= 106600 + return mb.data(); +#else + return boost::asio::detail::buffer_cast_helper(mb); +#endif +} +} + + +BOOST_AUTO_TEST_SUITE(message_buffer_tests) + +constexpr size_t def_buffer_size_mb = 4; +constexpr size_t def_buffer_size = 1024*1024*def_buffer_size_mb; + +/// Test default construction and buffer sequence generation +BOOST_AUTO_TEST_CASE(message_buffer_construction) +{ + try { + fc::message_buffer mb; + BOOST_CHECK_EQUAL(mb.total_bytes(), def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0u); + BOOST_CHECK_EQUAL(mb.read_ptr(), mb.write_ptr()); + + auto mbs = mb.get_buffer_sequence_for_boost_async_read(); + auto mbsi = mbs.begin(); + BOOST_CHECK_EQUAL(mb_size(*mbsi), def_buffer_size); + BOOST_CHECK_EQUAL(mb_data(*mbsi), mb.write_ptr()); + mbsi++; + BOOST_CHECK(mbsi == mbs.end()); + } + FC_LOG_AND_RETHROW() +} + +/// Test buffer growth and shrinking +BOOST_AUTO_TEST_CASE(message_buffer_growth) +{ + try { + fc::message_buffer mb; + mb.add_buffer_to_chain(); + BOOST_CHECK_EQUAL(mb.total_bytes(), 2 * def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), 2 * def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0u); + BOOST_CHECK_EQUAL(mb.read_ptr(), mb.write_ptr()); + + { + auto mbs = mb.get_buffer_sequence_for_boost_async_read(); + auto mbsi = mbs.begin(); + BOOST_CHECK_EQUAL(mb_size(*mbsi), def_buffer_size); + BOOST_CHECK_EQUAL(mb_data(*mbsi), mb.write_ptr()); + mbsi++; + BOOST_CHECK(mbsi != mbs.end()); + BOOST_CHECK_EQUAL(mb_size(*mbsi), def_buffer_size); + BOOST_CHECK_NE(mb_data(*mbsi), nullptr); + mbsi++; + BOOST_CHECK(mbsi == mbs.end()); + } + + mb.advance_write_ptr(100); + BOOST_CHECK_EQUAL(mb.total_bytes(), 2 * def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), 2 * def_buffer_size - 100); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 100u); + BOOST_CHECK_NE(mb.read_ptr(), nullptr); + BOOST_CHECK_NE(mb.write_ptr(), nullptr); + BOOST_CHECK_EQUAL((mb.read_ptr() + 100), mb.write_ptr()); + + { + auto mbs = mb.get_buffer_sequence_for_boost_async_read(); + auto mbsi = mbs.begin(); + BOOST_CHECK_EQUAL(mb_size(*mbsi), def_buffer_size - 100); + BOOST_CHECK_EQUAL(mb_data(*mbsi), mb.write_ptr()); + mbsi++; + BOOST_CHECK(mbsi != mbs.end()); + BOOST_CHECK_EQUAL(mb_size(*mbsi), def_buffer_size); + BOOST_CHECK_NE(mb_data(*mbsi), nullptr); + mbsi++; + BOOST_CHECK(mbsi == mbs.end()); + } + + mb.advance_read_ptr(50); + BOOST_CHECK_EQUAL(mb.total_bytes(), 2 * def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), 2 * def_buffer_size - 100); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 50u); + + mb.advance_write_ptr(def_buffer_size); + BOOST_CHECK_EQUAL(mb.total_bytes(), 2 * def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), def_buffer_size - 100); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 50 + def_buffer_size); + + // Moving read_ptr into second block should reset second block to first + mb.advance_read_ptr(def_buffer_size); + BOOST_CHECK_EQUAL(mb.total_bytes(), def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), def_buffer_size - 100); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 50u); + + // Moving read_ptr to write_ptr should shrink chain and reset ptrs + mb.advance_read_ptr(50); + BOOST_CHECK_EQUAL(mb.total_bytes(), def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0u); + + mb.add_buffer_to_chain(); + BOOST_CHECK_EQUAL(mb.total_bytes(), 2 * def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), 2 * def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0u); + + mb.advance_write_ptr(50); + BOOST_CHECK_EQUAL(mb.total_bytes(), 2 * def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), 2 * def_buffer_size - 50); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 50u); + + // Moving read_ptr to write_ptr should shrink chain and reset ptrs + mb.advance_read_ptr(50); + BOOST_CHECK_EQUAL(mb.total_bytes(), def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), def_buffer_size); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0u); + } + FC_LOG_AND_RETHROW() +} + +/// Test peek and read across multiple buffers +BOOST_AUTO_TEST_CASE(message_buffer_peek_read) +{ + try { + { + const uint32_t small = 32; + fc::message_buffer mb; + BOOST_CHECK_EQUAL(mb.total_bytes(), small); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), small); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0); + BOOST_CHECK_EQUAL(mb.read_ptr(), mb.write_ptr()); + BOOST_CHECK_EQUAL(mb.read_index().first, 0); + BOOST_CHECK_EQUAL(mb.read_index().second, 0); + BOOST_CHECK_EQUAL(mb.write_index().first, 0); + BOOST_CHECK_EQUAL(mb.write_index().second, 0); + + mb.add_space(100 - small); + BOOST_CHECK_EQUAL(mb.total_bytes(), 4 * small); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), 4 * small); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0); + BOOST_CHECK_EQUAL(mb.read_ptr(), mb.write_ptr()); + + char* write_ptr = mb.write_ptr(); + for (char ind = 0; ind < 100; ) { + *write_ptr = ind; + ind++; + if (ind % small == 0) { + mb.advance_write_ptr(small); + write_ptr = mb.write_ptr(); + } else { + write_ptr++; + } + } + mb.advance_write_ptr(100 % small); + + BOOST_CHECK_EQUAL(mb.total_bytes(), 4 * small); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), 4 * small - 100); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 100); + BOOST_CHECK_NE((void*) mb.read_ptr(), (void*) mb.write_ptr()); + BOOST_CHECK_EQUAL(mb.read_index().first, 0); + BOOST_CHECK_EQUAL(mb.read_index().second, 0); + BOOST_CHECK_EQUAL(mb.write_index().first, 3); + BOOST_CHECK_EQUAL(mb.write_index().second, 4); + + char buffer[100]; + auto index = mb.read_index(); + mb.peek(buffer, 50, index); + mb.peek(buffer+50, 50, index); + for (int i=0; i < 100; i++) { + BOOST_CHECK_EQUAL(i, buffer[i]); + } + + BOOST_CHECK_EQUAL(mb.total_bytes(), 4 * small); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), 4 * small - 100); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 100); + BOOST_CHECK_NE((void*) mb.read_ptr(), (void*) mb.write_ptr()); + + char buffer2[100]; + mb.read(buffer2, 100); + for (int i=0; i < 100; i++) { + BOOST_CHECK_EQUAL(i, buffer2[i]); + } + + BOOST_CHECK_EQUAL(mb.total_bytes(), small); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), small); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0); + BOOST_CHECK_EQUAL(mb.read_ptr(), mb.write_ptr()); + } + } + FC_LOG_AND_RETHROW() +} + +/// Test automatic allocation when advancing the read_ptr to the end. +BOOST_AUTO_TEST_CASE(message_buffer_write_ptr_to_end) +{ + try { + { + const uint32_t small = 32; + fc::message_buffer mb; + BOOST_CHECK_EQUAL(mb.total_bytes(), small); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), small); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), 0); + BOOST_CHECK_EQUAL(mb.read_ptr(), mb.write_ptr()); + BOOST_CHECK_EQUAL(mb.read_index().first, 0); + BOOST_CHECK_EQUAL(mb.read_index().second, 0); + BOOST_CHECK_EQUAL(mb.write_index().first, 0); + BOOST_CHECK_EQUAL(mb.write_index().second, 0); + + char* write_ptr = mb.write_ptr(); + for (uint32_t ind = 0; ind < small; ind++) { + *write_ptr = ind; + write_ptr++; + } + mb.advance_write_ptr(small); + + BOOST_CHECK_EQUAL(mb.total_bytes(), 2 * small); + BOOST_CHECK_EQUAL(mb.bytes_to_write(), small); + BOOST_CHECK_EQUAL(mb.bytes_to_read(), small); + BOOST_CHECK_NE((void*) mb.read_ptr(), (void*) mb.write_ptr()); + BOOST_CHECK_EQUAL(mb.read_index().first, 0); + BOOST_CHECK_EQUAL(mb.read_index().second, 0); + BOOST_CHECK_EQUAL(mb.write_index().first, 1); + BOOST_CHECK_EQUAL(mb.write_index().second, 0); + + auto mbs = mb.get_buffer_sequence_for_boost_async_read(); + auto mbsi = mbs.begin(); + BOOST_CHECK_EQUAL(mb_size(*mbsi), small); + BOOST_CHECK_EQUAL(mb_data(*mbsi), mb.write_ptr()); + mbsi++; + BOOST_CHECK(mbsi == mbs.end()); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(message_buffer_read_peek_bounds) { + using my_message_buffer_t = fc::message_buffer<1024>; + my_message_buffer_t mbuff; + unsigned char stuff[] = { + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + memcpy(mbuff.write_ptr(), stuff, sizeof(stuff)); + mbuff.advance_write_ptr(sizeof(stuff)); + + my_message_buffer_t::index_t index = mbuff.read_index(); + uint8_t throw_away_buffer[4]; + mbuff.peek(&throw_away_buffer, 4, index); //8 bytes left to peek afterwards + mbuff.peek(&throw_away_buffer, 4, index); //4 bytes left to peek afterwards + mbuff.peek(&throw_away_buffer, 2, index); //2 bytes left to peek afterwards + BOOST_CHECK_THROW(mbuff.peek(&throw_away_buffer, 3, index), fc::out_of_range_exception); + mbuff.peek(&throw_away_buffer, 1, index); //1 byte left to peek afterwards + mbuff.peek(&throw_away_buffer, 0, index); //1 byte left to peek afterwards + mbuff.peek(&throw_away_buffer, 1, index); //no bytes left to peek afterwards + BOOST_CHECK_THROW(mbuff.peek(&throw_away_buffer, 1, index), fc::out_of_range_exception); + + mbuff.read(&throw_away_buffer, 4); //8 bytes left to read afterwards + mbuff.read(&throw_away_buffer, 4); //4 bytes left to read afterwards + mbuff.read(&throw_away_buffer, 2); //2 bytes left to read afterwards + BOOST_CHECK_THROW(mbuff.read(&throw_away_buffer, 4), fc::out_of_range_exception); + mbuff.read(&throw_away_buffer, 1); //1 byte left to read afterwards + mbuff.read(&throw_away_buffer, 0); //1 byte left to read afterwards + mbuff.read(&throw_away_buffer, 1); //no bytes left to read afterwards + BOOST_CHECK_THROW(mbuff.read(&throw_away_buffer, 1), fc::out_of_range_exception); +} + +BOOST_AUTO_TEST_CASE(message_buffer_read_peek_bounds_multi) { + using my_message_buffer_t = fc::message_buffer<5>; + my_message_buffer_t mbuff; + unsigned char stuff[] = { + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + memcpy(mbuff.write_ptr(), stuff, 5); + mbuff.advance_write_ptr(5); + memcpy(mbuff.write_ptr(), stuff+5, 5); + mbuff.advance_write_ptr(5); + memcpy(mbuff.write_ptr(), stuff+10, 2); + mbuff.advance_write_ptr(2); + + my_message_buffer_t::index_t index = mbuff.read_index(); + uint8_t throw_away_buffer[4]; + mbuff.peek(&throw_away_buffer, 4, index); //8 bytes left to peek afterwards + mbuff.peek(&throw_away_buffer, 4, index); //4 bytes left to peek afterwards + mbuff.peek(&throw_away_buffer, 2, index); //2 bytes left to peek afterwards + BOOST_CHECK_THROW(mbuff.peek(&throw_away_buffer, 3, index), fc::out_of_range_exception); + mbuff.peek(&throw_away_buffer, 1, index); //1 bytes left to peek afterwards + mbuff.peek(&throw_away_buffer, 0, index); //1 bytes left to peek afterwards + mbuff.peek(&throw_away_buffer, 1, index); //no bytes left to peek afterwards + BOOST_CHECK_THROW(mbuff.peek(&throw_away_buffer, 1, index), fc::out_of_range_exception); + + mbuff.read(&throw_away_buffer, 4); //8 bytes left to read afterwards + mbuff.read(&throw_away_buffer, 4); //4 bytes left to read afterwards + mbuff.read(&throw_away_buffer, 2); //2 bytes left to read afterwards + BOOST_CHECK_THROW(mbuff.read(&throw_away_buffer, 4), fc::out_of_range_exception); + mbuff.read(&throw_away_buffer, 1); //1 bytes left to read afterwards + mbuff.read(&throw_away_buffer, 0); //1 bytes left to read afterwards + mbuff.read(&throw_away_buffer, 1); //no bytes left to read afterwards + BOOST_CHECK_THROW(mbuff.read(&throw_away_buffer, 1), fc::out_of_range_exception); +} + +BOOST_AUTO_TEST_CASE(message_buffer_datastream) { + using my_message_buffer_t = fc::message_buffer<1024>; + my_message_buffer_t mbuff; + + char buf[1024]; + fc::datastream ds( buf, 1024 ); + + int v = 13; + fc::raw::pack( ds, v ); + v = 42; + fc::raw::pack( ds, 42 ); + fc::raw::pack( ds, std::string( "hello" ) ); + + memcpy(mbuff.write_ptr(), buf, 1024); + mbuff.advance_write_ptr(1024); + + for( int i = 0; i < 3; ++i ) { + auto ds2 = mbuff.create_peek_datastream(); + fc::raw::unpack( ds2, v ); + BOOST_CHECK_EQUAL( 13, v ); + fc::raw::unpack( ds2, v ); + BOOST_CHECK_EQUAL( 42, v ); + std::string s; + fc::raw::unpack( ds2, s ); + BOOST_CHECK_EQUAL( s, std::string( "hello" ) ); + } + + { + auto ds2 = mbuff.create_datastream(); + fc::raw::unpack( ds2, v ); + BOOST_CHECK_EQUAL( 13, v ); + fc::raw::unpack( ds2, v ); + BOOST_CHECK_EQUAL( 42, v ); + std::string s; + fc::raw::unpack( ds2, s ); + BOOST_CHECK_EQUAL( s, std::string( "hello" ) ); + } +} + +// Make sure that the memory allocation is thread-safe. +// A previous version used boost::object_pool without synchronization. +BOOST_AUTO_TEST_CASE(test_message_buffer) { + std::vector threads; + constexpr int num_threads = 4; + constexpr int iterations = 10000; + for(int i = 0; i < num_threads; ++i) { + threads.emplace_back([]{ + for(int i = 0; i < iterations; ++i) { + // Use all functions that allocate or free buffers + fc::message_buffer mb; + mb.add_buffer_to_chain(); + mb.add_space(def_buffer_size); + mb.reset(); + mb.advance_write_ptr(def_buffer_size); + mb.advance_read_ptr(def_buffer_size); + } + }); + } + for(std::thread& t : threads) { + t.join(); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/scoped_exit/CMakeLists.txt b/libraries/libfc/test/scoped_exit/CMakeLists.txt new file mode 100644 index 0000000000..4e620e2ae8 --- /dev/null +++ b/libraries/libfc/test/scoped_exit/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable( test_scoped_exit test_scoped_exit.cpp ) +target_link_libraries( test_scoped_exit fc ) + +add_test(NAME test_scope_exit COMMAND libraries/fc/test/scoped_exit/test_scoped_exit WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/libraries/libfc/test/scoped_exit/test_scoped_exit.cpp b/libraries/libfc/test/scoped_exit/test_scoped_exit.cpp new file mode 100644 index 0000000000..d143cae4cf --- /dev/null +++ b/libraries/libfc/test/scoped_exit/test_scoped_exit.cpp @@ -0,0 +1,56 @@ +#include + +#define BOOST_TEST_MODULE scoped_exit +#include + +using namespace fc; + +BOOST_AUTO_TEST_SUITE(scoped_exit_test_suite) + +BOOST_AUTO_TEST_CASE(scoped_exit_test) +{ + bool result = false; + { + auto g1 = make_scoped_exit([&]{ result = true; }); + BOOST_TEST(result == false); + } + BOOST_TEST(result == true); +} + +BOOST_AUTO_TEST_CASE(cancel) { + bool result = false; + { + auto g1 = make_scoped_exit([&]{ result = true; }); + BOOST_TEST(result == false); + g1.cancel(); + } + BOOST_TEST(result == false); +} + +BOOST_AUTO_TEST_CASE(test_move) { + bool result = false; + { + auto g1 = make_scoped_exit([&]{ result = true; }); + BOOST_TEST(result == false); + { + auto g2 = std::move(g1); + BOOST_TEST(result == false); + } + BOOST_TEST(result == true); + result = false; + } + BOOST_TEST(result == false); +} + +struct move_only { + move_only() = default; + move_only(move_only&&) = default; + void operator()() {} +}; + +BOOST_AUTO_TEST_CASE(test_forward) { + auto g = make_scoped_exit(move_only{}); + auto g2 = std::move(g); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/static_variant/CMakeLists.txt b/libraries/libfc/test/static_variant/CMakeLists.txt new file mode 100644 index 0000000000..4dca5720c9 --- /dev/null +++ b/libraries/libfc/test/static_variant/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable( test_static_variant test_static_variant.cpp) +target_link_libraries( test_static_variant fc ) + +add_test(NAME test_static_variant COMMAND libraries/fc/test/static_variant/test_static_variant WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/libraries/libfc/test/static_variant/test_static_variant.cpp b/libraries/libfc/test/static_variant/test_static_variant.cpp new file mode 100644 index 0000000000..77cbaa7dd9 --- /dev/null +++ b/libraries/libfc/test/static_variant/test_static_variant.cpp @@ -0,0 +1,56 @@ +#define BOOST_TEST_MODULE static_variant +#include +#include +#include +#include + +BOOST_AUTO_TEST_SUITE(static_variant_test_suite) + BOOST_AUTO_TEST_CASE(to_from_fc_variant) + { + using variant_type = std::variant; + auto std_variant_1 = variant_type{false}; + auto fc_variant = fc::variant{}; + + fc::to_variant(std_variant_1, fc_variant); + + auto std_variant_2 = variant_type{}; + fc::from_variant(fc_variant, std_variant_2); + + BOOST_REQUIRE(std_variant_1 == std_variant_2); + } + + BOOST_AUTO_TEST_CASE(get) + { + using variant_type = std::variant; + + auto v1 = variant_type{std::string{"hello world"}}; + BOOST_CHECK_EXCEPTION(std::get(v1), std::bad_variant_access, [](const auto& e) { return true; }); + auto result1 = std::get(v1); + BOOST_REQUIRE(result1 == std::string{"hello world"}); + + const auto v2 = variant_type{std::string{"hello world"}}; + BOOST_CHECK_EXCEPTION(std::get(v2), std::bad_variant_access, [](const auto& e) { return true; }); + const auto result2 = std::get(v2); + BOOST_REQUIRE(result2 == std::string{"hello world"}); + } + + BOOST_AUTO_TEST_CASE(static_variant_from_index) + { + using variant_type = std::variant; + auto v = variant_type{}; + + BOOST_CHECK_EXCEPTION(fc::from_index(v, 3), fc::assert_exception, [](const auto& e) { return e.code() == fc::assert_exception_code; }); + + fc::from_index(v, 2); + BOOST_REQUIRE(std::string{} == std::get(v)); + } + + BOOST_AUTO_TEST_CASE(static_variant_get_index) + { + using variant_type = std::variant; + BOOST_REQUIRE((fc::get_index() == 0)); + BOOST_REQUIRE((fc::get_index() == 1)); + BOOST_REQUIRE((fc::get_index() == 2)); + BOOST_REQUIRE((fc::get_index() == std::variant_size_v)); // Isn't a type contained in variant. + } +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/test_base64.cpp b/libraries/libfc/test/test_base64.cpp new file mode 100644 index 0000000000..f1a7cc2803 --- /dev/null +++ b/libraries/libfc/test/test_base64.cpp @@ -0,0 +1,55 @@ +#define BOOST_TEST_MODULE base64 +#include + +#include +#include + +using namespace fc; +using namespace std::literals; + +BOOST_AUTO_TEST_SUITE(base64) + +BOOST_AUTO_TEST_CASE(base64enc) try { + auto input = "abc123$&()'?\xb4\xf5\x01\xfa~a"s; + auto expected_output = "YWJjMTIzJCYoKSc/tPUB+n5h"s; + + BOOST_CHECK_EQUAL(expected_output, base64_encode(input)); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(base64urlenc) try { + auto input = "abc123$&()'?\xb4\xf5\x01\xfa~a"s; + auto expected_output = "YWJjMTIzJCYoKSc_tPUB-n5h"s; + + BOOST_CHECK_EQUAL(expected_output, base64url_encode(input)); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(base64dec) try { + auto input = "YWJjMTIzJCYoKSc/tPUB+n5h"s; + auto expected_output = "abc123$&()'?\xb4\xf5\x01\xfa~a"s; + + BOOST_CHECK_EQUAL(expected_output, base64_decode(input)); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(base64urldec) try { + auto input = "YWJjMTIzJCYoKSc_tPUB-n5h"s; + auto expected_output = "abc123$&()'?\xb4\xf5\x01\xfa~a"s; + + BOOST_CHECK_EQUAL(expected_output, base64url_decode(input)); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(base64dec_extraequals) try { + auto input = "YWJjMTIzJCYoKSc/tPUB+n5h========="s; + auto expected_output = "abc123$&()'?\xb4\xf5\x01\xfa~a"s; + + BOOST_CHECK_EQUAL(expected_output, base64_decode(input)); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(base64dec_bad_stuff) try { + auto input = "YWJjMTIzJCYoKSc/tPU$B+n5h="s; + + BOOST_CHECK_EXCEPTION(base64_decode(input), fc::exception, [](const fc::exception& e) { + return e.to_detail_string().find("encountered non-base64 character") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/libraries/libfc/test/test_filesystem.cpp b/libraries/libfc/test/test_filesystem.cpp new file mode 100644 index 0000000000..53020171a4 --- /dev/null +++ b/libraries/libfc/test/test_filesystem.cpp @@ -0,0 +1,95 @@ +#define BOOST_TEST_MODULE fc_filesystem +#include +#include +#include +#include + +using namespace fc; + +std::string getFileContent(const string& filename) { + string ret; + boost::filesystem::ifstream ifs { filename }; + ifs >> ret; + ifs.close(); + return ret; +} + +BOOST_AUTO_TEST_SUITE(fc_filesystem) + +BOOST_AUTO_TEST_CASE(dir_copy) try { + // 1. check whether dir can be copied when target dir does not exist, + // but not recursively (compatible with 1.73) + + const string src_dir {"/tmp/fc_copy_test_src"}; + if (!fc::exists(src_dir)) { + create_directories(src_dir); + } + + BOOST_CHECK_EQUAL(fc::exists(src_dir), true); + const string test_file_name = "fc_copy_test_file"; + if (!fc::exists(src_dir + "/" + test_file_name)) { + boost::filesystem::ofstream ofs { src_dir + "/" + test_file_name}; + ofs << "This the test of fc system copy \n"; + ofs.close(); + } + BOOST_CHECK_EQUAL(fc::exists(src_dir + "/" + test_file_name), true); + + const string tgt_dir {"/tmp/fc_copy_test_tgt"}; + if (fc::exists(tgt_dir)) { + remove_all(tgt_dir); + } + BOOST_CHECK_EQUAL(fc::exists(tgt_dir), false); + + fc::copy(src_dir, tgt_dir); + BOOST_CHECK_EQUAL(fc::exists(tgt_dir), true); + // check not copied recursively + BOOST_CHECK_EQUAL(fc::exists(tgt_dir + "/" + test_file_name), false); + + // 2. check whether exception be thrown (no overwritten) when target dir does exist, + BOOST_CHECK_EQUAL(fc::exists(tgt_dir), true); + BOOST_CHECK_EXCEPTION(fc::copy(src_dir, tgt_dir), fc::exception, [](const fc::exception& e) { + return e.code() == fc::unspecified_exception_code; + }); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_CASE(file_copy) try { + // 1. check whether file can be copied when target file does not exist, + const string src_dir {"/tmp/fc_copy_test_src"}; + if (!fc::exists(src_dir)) { + create_directories(src_dir); + } + + BOOST_CHECK_EQUAL(fc::exists(src_dir), true); + const string test_file_name = "fc_copy_test_file"; + if (!fc::exists(src_dir + "/" + test_file_name)) { + boost::filesystem::ofstream ofs { src_dir + "/" + test_file_name}; + ofs << "This the test of fc system copy \n"; + ofs.close(); + } + BOOST_CHECK_EQUAL(fc::exists(src_dir + "/" + test_file_name), true); + + const string tgt_dir {"/tmp/fc_copy_test_tgt"}; + if (!fc::exists(tgt_dir)) { + fc::copy(src_dir, tgt_dir); + } + BOOST_CHECK_EQUAL(fc::exists(tgt_dir), true); + BOOST_CHECK_EQUAL(fc::exists(tgt_dir + "/" + test_file_name), false); + fc::copy(src_dir + "/" + test_file_name, tgt_dir + "/" + test_file_name); + BOOST_CHECK_EQUAL(fc::exists(tgt_dir + "/" + test_file_name), true); + const string src_file_content = getFileContent(src_dir + "/" + test_file_name); + BOOST_CHECK_EQUAL(src_file_content.empty(), false); + const string tgt_file_content = getFileContent(tgt_dir + "/" + test_file_name); + BOOST_CHECK_EQUAL(src_file_content, tgt_file_content); + + // 2. check whether exception be thrown (no overwritten) when target file does exist, + BOOST_CHECK_EQUAL(fc::exists(tgt_dir + "/" + test_file_name), true); + BOOST_CHECK_EXCEPTION(fc::copy(src_dir + "/" + test_file_name, tgt_dir + "/" + test_file_name), + fc::exception, + [](const fc::exception& e) { + return e.code() == fc::unspecified_exception_code; + }); + +} FC_LOG_AND_RETHROW(); + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/libraries/libfc/test/variant/CMakeLists.txt b/libraries/libfc/test/variant/CMakeLists.txt new file mode 100644 index 0000000000..5da74417e1 --- /dev/null +++ b/libraries/libfc/test/variant/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable( test_variant test_variant.cpp ) +target_link_libraries( test_variant fc ) + +add_test(NAME test_variant COMMAND libraries/fc/test/variant/test_variant WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/libraries/libfc/test/variant/test_variant.cpp b/libraries/libfc/test/variant/test_variant.cpp new file mode 100644 index 0000000000..591a4b231d --- /dev/null +++ b/libraries/libfc/test/variant/test_variant.cpp @@ -0,0 +1,98 @@ +#define BOOST_TEST_MODULE variant +#include + +#include +#include +#include +#include + +using namespace fc; + +BOOST_AUTO_TEST_SUITE(variant_test_suite) +BOOST_AUTO_TEST_CASE(mutable_variant_object_test) +{ + // no BOOST_CHECK / BOOST_REQUIRE, just see that this compiles on all supported platforms + try { + variant v(42); + variant_object vo; + mutable_variant_object mvo; + variants vs; + vs.push_back(mutable_variant_object("level", "debug")("color", v)); + vs.push_back(mutable_variant_object()("level", "debug")("color", v)); + vs.push_back(mutable_variant_object("level", "debug")("color", "green")); + vs.push_back(mutable_variant_object()("level", "debug")("color", "green")); + vs.push_back(mutable_variant_object("level", "debug")(vo)); + vs.push_back(mutable_variant_object()("level", "debug")(mvo)); + vs.push_back(mutable_variant_object("level", "debug").set("color", v)); + vs.push_back(mutable_variant_object()("level", "debug").set("color", v)); + vs.push_back(mutable_variant_object("level", "debug").set("color", "green")); + vs.push_back(mutable_variant_object()("level", "debug").set("color", "green")); + } + FC_LOG_AND_RETHROW(); +} + +BOOST_AUTO_TEST_CASE(variant_format_string_limited) +{ + constexpr size_t long_rep_char_num = 1024; + const std::string a_long_list = std::string(long_rep_char_num, 'a'); + const std::string b_long_list = std::string(long_rep_char_num, 'b'); + { + const string format = "${a} ${b} ${c}"; + fc::mutable_variant_object mu; + mu( "a", string( long_rep_char_num, 'a' ) ); + mu( "b", string( long_rep_char_num, 'b' ) ); + mu( "c", string( long_rep_char_num, 'c' ) ); + const string result = fc::format_string( format, mu, true ); + BOOST_CHECK_LT(0, mu.size()); + const auto arg_limit_size = (1024 - format.size()) / mu.size(); + BOOST_CHECK_EQUAL( result, string(arg_limit_size, 'a' ) + "... " + string(arg_limit_size, 'b' ) + "... " + string(arg_limit_size, 'c' ) + "..." ); + BOOST_CHECK_LT(result.size(), 1024 + 3 * mu.size()); + } + { // verify object, array, blob, and string, all exceeds limits, fold display for each + fc::mutable_variant_object mu; + mu( "str", a_long_list ); + mu( "obj", variant_object(mutable_variant_object()("a", a_long_list)("b", b_long_list)) ); + mu( "arr", variants{variant(a_long_list), variant(b_long_list)} ); + mu( "blob", blob({std::vector(a_long_list.begin(), a_long_list.end())}) ); + const string format_prefix = "Format string test: "; + const string format_str = format_prefix + "${str} ${obj} ${arr} {blob}"; + const string result = fc::format_string( format_str, mu, true ); + BOOST_CHECK_LT(0, mu.size()); + const auto arg_limit_size = (1024 - format_str.size()) / mu.size(); + BOOST_CHECK_EQUAL( result, format_prefix + a_long_list.substr(0, arg_limit_size) + "..." + " ${obj} ${arr} {blob}"); + BOOST_CHECK_LT(result.size(), 1024 + 3 * mu.size()); + } + { // verify object, array can be displayed properly + const string format_prefix = "Format string test: "; + const string format_str = format_prefix + "${str} ${obj} ${arr} ${blob} ${var}"; + BOOST_CHECK_LT(format_str.size(), 1024); + const size_t short_rep_char_num = (1024 - format_str.size()) / 5 - 1; + const std::string a_short_list = std::string(short_rep_char_num, 'a'); + const std::string b_short_list = std::string(short_rep_char_num / 3, 'b'); + const std::string c_short_list = std::string(short_rep_char_num / 3, 'c'); + const std::string d_short_list = std::string(short_rep_char_num / 3, 'd'); + const std::string e_short_list = std::string(short_rep_char_num / 3, 'e'); + const std::string f_short_list = std::string(short_rep_char_num, 'f'); + const std::string g_short_list = std::string(short_rep_char_num, 'g'); + fc::mutable_variant_object mu; + const variant_object vo(mutable_variant_object()("b", b_short_list)("c", c_short_list)); + const variants variant_list{variant(d_short_list), variant(e_short_list)}; + const blob a_blob({std::vector(f_short_list.begin(), f_short_list.end())}); + const variant a_variant(g_short_list); + mu( "str", a_short_list ); + mu( "obj", vo); + mu( "arr", variant_list); + mu( "blob", a_blob); + mu( "var", a_variant); + const string result = fc::format_string( format_str, mu, true ); + const string target_result = format_prefix + a_short_list + " " + + "{" + "\"b\":\"" + b_short_list + "\",\"c\":\"" + c_short_list + "\"}" + " " + + "[\"" + d_short_list + "\",\"" + e_short_list + "\"]" + " " + + base64_encode( a_blob.data.data(), a_blob.data.size() ) + "=" + " " + + g_short_list; + + BOOST_CHECK_EQUAL( result, target_result); + BOOST_CHECK_LT(result.size(), 1024 + 3 * mu.size()); + } +} +BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/libfc/test/variant_estimated_size/CMakeLists.txt b/libraries/libfc/test/variant_estimated_size/CMakeLists.txt new file mode 100644 index 0000000000..f7c46f4b1e --- /dev/null +++ b/libraries/libfc/test/variant_estimated_size/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable( variant_estimated_size test_variant_estimated_size.cpp ) +target_link_libraries( variant_estimated_size fc ) + +add_test(NAME test_variant_estimated_size COMMAND variant_estimated_size) diff --git a/libraries/libfc/test/variant_estimated_size/test_variant_estimated_size.cpp b/libraries/libfc/test/variant_estimated_size/test_variant_estimated_size.cpp new file mode 100644 index 0000000000..8a3b0baa6a --- /dev/null +++ b/libraries/libfc/test/variant_estimated_size/test_variant_estimated_size.cpp @@ -0,0 +1,157 @@ +#define BOOST_TEST_MODULE variant +#include + +#include +#include +#include +#include + +using namespace fc; + +BOOST_AUTO_TEST_SUITE(variant_estimated_size_suite) +BOOST_AUTO_TEST_CASE(null_variant_estimated_size_test) +{ + nullptr_t np; + + variant v; + variant v_nullptr(np); + + BOOST_CHECK_EQUAL(v.estimated_size(), sizeof(variant)); + BOOST_CHECK_EQUAL(v_nullptr.estimated_size(), sizeof(variant)); +} + +BOOST_AUTO_TEST_CASE(int64_variant_estimated_size_test) +{ + int64_t i = 1; + int32_t j = 2; + int16_t k = 3; + int8_t l = 4; + + variant v_int_64(i); + variant v_int_32(j); + variant v_int_16(k); + variant v_int_8(l); + + BOOST_CHECK_EQUAL(v_int_64.estimated_size(), sizeof(variant)); + BOOST_CHECK_EQUAL(v_int_32.estimated_size(), sizeof(variant)); + BOOST_CHECK_EQUAL(v_int_16.estimated_size(), sizeof(variant)); + BOOST_CHECK_EQUAL(v_int_8.estimated_size(), sizeof(variant)); +} + +BOOST_AUTO_TEST_CASE(uint64_variant_estimated_size_test) +{ + uint64_t i = 1; + uint32_t j = 2; + uint16_t k = 3; + uint8_t l = 4; + + variant v_uint_64(i); + variant v_uint_32(j); + variant v_uint_16(k); + variant v_uint_8(l); + + BOOST_CHECK_EQUAL(v_uint_64.estimated_size(), sizeof(variant)); + BOOST_CHECK_EQUAL(v_uint_32.estimated_size(), sizeof(variant)); + BOOST_CHECK_EQUAL(v_uint_16.estimated_size(), sizeof(variant)); + BOOST_CHECK_EQUAL(v_uint_8.estimated_size(), sizeof(variant)); +} + +BOOST_AUTO_TEST_CASE(double_variant_estimated_size_test) +{ + float f = 3.14; + double d = 12.345; + + variant v_float(f); + variant v_double(d); + + BOOST_CHECK_EQUAL(v_float.estimated_size(), sizeof(variant)); + BOOST_CHECK_EQUAL(v_double.estimated_size(), sizeof(variant)); +} + +BOOST_AUTO_TEST_CASE(string_variant_estimated_size_test) +{ + char c[] = "Hello World"; + const char* cc = "Goodbye"; + wchar_t wc[] = L"0123456789"; + const wchar_t* cwc = L"foo"; + string s = "abcdefghijklmnopqrstuvwxyz"; + + variant v_char(c); + variant v_const_char(cc); + variant v_wchar(wc); + variant v_const_wchar(cwc); + variant v_string(s); + + BOOST_CHECK_EQUAL(v_char.estimated_size(), 11 + sizeof(variant) + sizeof(string)); + BOOST_CHECK_EQUAL(v_const_char.estimated_size(), 7 + sizeof(variant) + sizeof(string)); + BOOST_CHECK_EQUAL(v_wchar.estimated_size(), 10 + sizeof(variant) + sizeof(string)); + BOOST_CHECK_EQUAL(v_const_wchar.estimated_size(), 3 + sizeof(variant) + sizeof(string)); + BOOST_CHECK_EQUAL(v_string.estimated_size(), 26 + sizeof(variant) + sizeof(string)); +} + +BOOST_AUTO_TEST_CASE(blob_variant_estimated_size_test) +{ + blob bl; + bl.data.push_back('f'); + bl.data.push_back('o'); + bl.data.push_back('o'); + + variant v_blob(bl); + + BOOST_CHECK_EQUAL(v_blob.estimated_size(), 3 + sizeof(variant) + sizeof(blob)); +} + +BOOST_AUTO_TEST_CASE(variant_object_variant_estimated_size_test) +{ + string k1 = "key_bool"; + string k2 = "key_string"; + string k3 = "key_int16"; + string k4 = "key_blob"; // 35 + 4 * sizeof(string) + + bool b = false; + string s = "HelloWorld"; // 10 + sizeof(string) + int16_t i = 123; + blob bl; + bl.data.push_back('b'); + bl.data.push_back('a'); + bl.data.push_back('r'); // 3 + sizeof(blob) + + variant v_bool(b); + variant v_string(s); + variant v_int16(i); + variant v_blob(bl); // + 4 * sizeof(variant) + + mutable_variant_object mu; + mu(k1, b); + mu(k2, v_string); + mu(k3, v_int16); + mu(k4, bl); + variant_object vo(mu); // + sizeof(variant_object) + sizeof(std::vector) + variant v_vo(vo); // + sizeof(variant) + + BOOST_CHECK_EQUAL(vo.estimated_size(), 48 + 5 * sizeof(string) + sizeof(blob) + 4 * sizeof(variant) + + sizeof(variant_object) + sizeof(std::vector)); + BOOST_CHECK_EQUAL(v_vo.estimated_size(), 48 + 5 * sizeof(string) + sizeof(blob) + 5 * sizeof(variant) + + sizeof(variant_object) + sizeof(std::vector)); +} + +BOOST_AUTO_TEST_CASE(array_variant_estimated_size_test) +{ + bool b = true; + wchar_t wc[] = L"Goodbye"; // 7 + sizeof(string) + uint32_t i = 54321; + + variant v_bool(b); + variant v_wchar(wc); + variant v_uint32(i); // + 3 * sizeof(variant) + + variants vs; // + sizeof(variants) + vs.push_back(v_bool); + vs.push_back(v_wchar); + vs.push_back(v_uint32); + + variant v_variants(vs); // + sizeof(variant) + BOOST_CHECK_EQUAL(v_variants.estimated_size(), 7 + sizeof(string) + 4 * sizeof(variant) + sizeof(variants)); +} + +BOOST_AUTO_TEST_SUITE_END() From 92b790b8e4cb7640bf00fe3740d56b22ecc5d6ed Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Sun, 25 Sep 2022 21:48:20 -0400 Subject: [PATCH 2/3] addtional minimal changes to get fc fully built & test after desubmod --- CMakeModules/EosioTesterBuild.cmake.in | 10 +++++----- libraries/libfc/test/CMakeLists.txt | 4 ++-- libraries/libfc/test/crypto/CMakeLists.txt | 14 +++++++------- libraries/libfc/test/io/CMakeLists.txt | 6 +++--- libraries/libfc/test/network/CMakeLists.txt | 2 +- libraries/libfc/test/scoped_exit/CMakeLists.txt | 2 +- libraries/libfc/test/static_variant/CMakeLists.txt | 2 +- libraries/libfc/test/variant/CMakeLists.txt | 2 +- unittests/CMakeLists.txt | 2 +- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/CMakeModules/EosioTesterBuild.cmake.in b/CMakeModules/EosioTesterBuild.cmake.in index f00f1020cb..48d12ea76d 100644 --- a/CMakeModules/EosioTesterBuild.cmake.in +++ b/CMakeModules/EosioTesterBuild.cmake.in @@ -53,14 +53,14 @@ find_library(libtester eosio_testing @CMAKE_BINARY_DIR@/libraries/testing NO_DEF find_library(libchain eosio_chain @CMAKE_BINARY_DIR@/libraries/chain NO_DEFAULT_PATH) if ( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" ) find_library(libfc fc_debug @CMAKE_BINARY_DIR@/libraries/fc NO_DEFAULT_PATH) - find_library(libsecp256k1 secp256k1_debug @CMAKE_BINARY_DIR@/libraries/fc/secp256k1 NO_DEFAULT_PATH) + find_library(libsecp256k1 secp256k1_debug @CMAKE_BINARY_DIR@/libraries/libfc/secp256k1 NO_DEFAULT_PATH) else() find_library(libfc fc @CMAKE_BINARY_DIR@/libraries/fc NO_DEFAULT_PATH) - find_library(libsecp256k1 secp256k1 @CMAKE_BINARY_DIR@/libraries/fc/secp256k1 NO_DEFAULT_PATH) + find_library(libsecp256k1 secp256k1 @CMAKE_BINARY_DIR@/libraries/libfc/secp256k1 NO_DEFAULT_PATH) endif() -find_library(libff ff @CMAKE_BINARY_DIR@/libraries/fc/libraries/ff/libff NO_DEFAULT_PATH) +find_library(libff ff @CMAKE_BINARY_DIR@/libraries/libfc/libraries/ff/libff NO_DEFAULT_PATH) find_library(libwasm WASM @CMAKE_BINARY_DIR@/libraries/wasm-jit/Source/WASM NO_DEFAULT_PATH) find_library(libwast WAST @CMAKE_BINARY_DIR@/libraries/wasm-jit/Source/WAST NO_DEFAULT_PATH) find_library(libir IR @CMAKE_BINARY_DIR@/libraries/wasm-jit/Source/IR NO_DEFAULT_PATH) @@ -129,7 +129,7 @@ macro(add_eosio_test_executable test_name) @OPENSSL_INCLUDE_DIR@ @CMAKE_SOURCE_DIR@/libraries/chain/include @CMAKE_BINARY_DIR@/libraries/chain/include - @CMAKE_SOURCE_DIR@/libraries/fc/include + @CMAKE_SOURCE_DIR@/libraries/libfc/include @CMAKE_SOURCE_DIR@/libraries/softfloat/source/include @CMAKE_SOURCE_DIR@/libraries/appbase/include @CMAKE_SOURCE_DIR@/libraries/chainbase/include @@ -176,7 +176,7 @@ if(ENABLE_COVERAGE_TESTING) COMMAND ${LCOV_PATH} --directory . --capture --gcov-tool ${CMAKE_SOURCE_DIR}/tools/llvm-gcov.sh --output-file ${Coverage_NAME}.info - COMMAND ${LCOV_PATH} -remove ${Coverage_NAME}.info '*/boost/*' '/usr/lib/*' '/usr/include/*' '*/externals/*' '*/fc/*' '*/wasm-jit/*' --output-file ${Coverage_NAME}_filtered.info + COMMAND ${LCOV_PATH} -remove ${Coverage_NAME}.info '*/boost/*' '/usr/lib/*' '/usr/include/*' '*/externals/*' '*/libfc/*' '*/wasm-jit/*' --output-file ${Coverage_NAME}_filtered.info COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}_filtered.info diff --git a/libraries/libfc/test/CMakeLists.txt b/libraries/libfc/test/CMakeLists.txt index 4788c05814..14c1cbf59b 100644 --- a/libraries/libfc/test/CMakeLists.txt +++ b/libraries/libfc/test/CMakeLists.txt @@ -9,9 +9,9 @@ add_subdirectory( variant_estimated_size ) add_executable( test_base64 test_base64.cpp ) target_link_libraries( test_base64 fc ) -add_test(NAME test_base64 COMMAND libraries/fc/test/test_base64 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_base64 COMMAND test_base64 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) add_executable( test_filesystem test_filesystem.cpp ) target_link_libraries( test_filesystem fc ) -add_test(NAME test_filesystem COMMAND libraries/fc/test/test_filesystem WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_filesystem COMMAND test_filesystem WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/libraries/libfc/test/crypto/CMakeLists.txt b/libraries/libfc/test/crypto/CMakeLists.txt index 216eda7e50..dea8a2bc0a 100644 --- a/libraries/libfc/test/crypto/CMakeLists.txt +++ b/libraries/libfc/test/crypto/CMakeLists.txt @@ -19,10 +19,10 @@ target_link_libraries( test_modular_arithmetic fc ) add_executable( test_k1_recover test_k1_recover.cpp ) target_link_libraries( test_k1_recover fc ) -add_test(NAME test_cypher_suites COMMAND libraries/fc/test/crypto/test_cypher_suites WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME test_webauthn COMMAND libraries/fc/test/crypto/test_webauthn WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME test_hash_functions COMMAND libraries/fc/test/crypto/test_hash_functions WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME test_alt_bn128 COMMAND libraries/fc/test/crypto/test_alt_bn128 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME test_blake2 COMMAND libraries/fc/test/crypto/test_blake2 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME test_modular_arithmetic COMMAND libraries/fc/test/crypto/test_modular_arithmetic WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME test_k1_recover COMMAND libraries/fc/test/crypto/test_k1_recover WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) \ No newline at end of file +add_test(NAME test_cypher_suites COMMAND test_cypher_suites WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_webauthn COMMAND test_webauthn WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_hash_functions COMMAND test_hash_functions WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_alt_bn128 COMMAND test_alt_bn128 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_blake2 COMMAND test_blake2 WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_modular_arithmetic COMMAND test_modular_arithmetic WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_k1_recover COMMAND test_k1_recover WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) \ No newline at end of file diff --git a/libraries/libfc/test/io/CMakeLists.txt b/libraries/libfc/test/io/CMakeLists.txt index 1bcbaaf0f1..6041a6686c 100644 --- a/libraries/libfc/test/io/CMakeLists.txt +++ b/libraries/libfc/test/io/CMakeLists.txt @@ -7,8 +7,8 @@ target_link_libraries( test_json fc ) add_executable( test_tracked_storage test_tracked_storage.cpp ) target_link_libraries( test_tracked_storage fc ) -add_test(NAME test_cfile COMMAND libraries/fc/test/io/test_cfile WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME test_json COMMAND libraries/fc/test/io/test_json WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) -add_test(NAME test_tracked_storage COMMAND libraries/fc/test/io/test_tracked_storage WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_cfile COMMAND test_cfile WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_json COMMAND test_json WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_tracked_storage COMMAND test_tracked_storage WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/libraries/libfc/test/network/CMakeLists.txt b/libraries/libfc/test/network/CMakeLists.txt index a514cdd6dc..31c2d229db 100644 --- a/libraries/libfc/test/network/CMakeLists.txt +++ b/libraries/libfc/test/network/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable( test_message_buffer test_message_buffer.cpp) target_link_libraries( test_message_buffer fc ) -add_test(NAME test_message_buffer COMMAND libraries/fc/test/network/test_message_buffer WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_message_buffer COMMAND test_message_buffer WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/libraries/libfc/test/scoped_exit/CMakeLists.txt b/libraries/libfc/test/scoped_exit/CMakeLists.txt index 4e620e2ae8..bd627de3fa 100644 --- a/libraries/libfc/test/scoped_exit/CMakeLists.txt +++ b/libraries/libfc/test/scoped_exit/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable( test_scoped_exit test_scoped_exit.cpp ) target_link_libraries( test_scoped_exit fc ) -add_test(NAME test_scope_exit COMMAND libraries/fc/test/scoped_exit/test_scoped_exit WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_scope_exit COMMAND test_scoped_exit WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/libraries/libfc/test/static_variant/CMakeLists.txt b/libraries/libfc/test/static_variant/CMakeLists.txt index 4dca5720c9..f07097c6c9 100644 --- a/libraries/libfc/test/static_variant/CMakeLists.txt +++ b/libraries/libfc/test/static_variant/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable( test_static_variant test_static_variant.cpp) target_link_libraries( test_static_variant fc ) -add_test(NAME test_static_variant COMMAND libraries/fc/test/static_variant/test_static_variant WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_static_variant COMMAND test_static_variant WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/libraries/libfc/test/variant/CMakeLists.txt b/libraries/libfc/test/variant/CMakeLists.txt index 5da74417e1..4114d1a4c1 100644 --- a/libraries/libfc/test/variant/CMakeLists.txt +++ b/libraries/libfc/test/variant/CMakeLists.txt @@ -1,4 +1,4 @@ add_executable( test_variant test_variant.cpp ) target_link_libraries( test_variant fc ) -add_test(NAME test_variant COMMAND libraries/fc/test/variant/test_variant WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +add_test(NAME test_variant COMMAND test_variant WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 98ef546086..ee54dfbfdd 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -117,7 +117,7 @@ if(ENABLE_COVERAGE_TESTING) # run tests COMMAND ./tools/ctestwrapper.sh -R ${ctest_tests} -E ${ctest_exclude_tests} COMMAND ${LCOV_PATH} --directory . --capture --gcov-tool ${CMAKE_SOURCE_DIR}/tools/llvm-gcov.sh --output-file ${Coverage_NAME}.info - COMMAND ${LCOV_PATH} -remove ${Coverage_NAME}.info '*/boost/*' '/usr/lib/*' '/usr/include/*' '*/externals/*' '*/fc/*' '*/wasm-jit/*' --output-file ${Coverage_NAME}_filtered.info + COMMAND ${LCOV_PATH} -remove ${Coverage_NAME}.info '*/boost/*' '/usr/lib/*' '/usr/include/*' '*/externals/*' '*/libfc/*' '*/wasm-jit/*' --output-file ${Coverage_NAME}_filtered.info COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}_filtered.info COMMAND if [ "$CI" != "true" ]\; then ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.info ${Coverage_NAME}_filtered.info ${Coverage_NAME}.total ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned ${PROJECT_BINARY_DIR}/${Coverage_NAME}_filtered.info.cleaned\; fi WORKING_DIRECTORY ${PROJECT_BINARY_DIR} From d5af1838fbc0339e289f776ed5dd261a27083bc2 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Sun, 25 Sep 2022 22:54:39 -0400 Subject: [PATCH 3/3] misc cmake cleanup & discrete license removal to fc desubmod --- CMakeModules/EosioTester.cmake.in | 9 +- CMakeModules/EosioTesterBuild.cmake.in | 10 +- libraries/libfc/CMakeLists.txt | 137 ++----- .../libfc/CMakeModules/ArgumentParser.cmake | 74 ---- .../libfc/CMakeModules/ParseLibraryList.cmake | 79 ---- .../CMakeModules/SetupTargetMacros.cmake | 369 ------------------ .../libfc/CMakeModules/UseLibraryMacros.cmake | 72 ---- .../libfc/CMakeModules/VersionMacros.cmake | 244 ------------ .../GetGitRevisionDescription.cmake.in | 38 -- libraries/libfc/LICENSE.txt | 27 -- 10 files changed, 36 insertions(+), 1023 deletions(-) delete mode 100644 libraries/libfc/CMakeModules/ArgumentParser.cmake delete mode 100644 libraries/libfc/CMakeModules/ParseLibraryList.cmake delete mode 100644 libraries/libfc/CMakeModules/SetupTargetMacros.cmake delete mode 100644 libraries/libfc/CMakeModules/UseLibraryMacros.cmake delete mode 100644 libraries/libfc/CMakeModules/VersionMacros.cmake delete mode 100644 libraries/libfc/GitVersionGen/GetGitRevisionDescription.cmake.in delete mode 100644 libraries/libfc/LICENSE.txt diff --git a/CMakeModules/EosioTester.cmake.in b/CMakeModules/EosioTester.cmake.in index 297f0f0b72..ec0199cf79 100644 --- a/CMakeModules/EosioTester.cmake.in +++ b/CMakeModules/EosioTester.cmake.in @@ -54,13 +54,8 @@ find_package(Boost @Boost_MAJOR_VERSION@.@Boost_MINOR_VERSION@ EXACT REQUIRED CO find_library(libtester eosio_testing @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) find_library(libchain eosio_chain @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) -if ( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" ) - find_library(libfc fc_debug @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) - find_library(libsecp256k1 secp256k1_debug @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) -else() - find_library(libfc fc @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) - find_library(libsecp256k1 secp256k1 @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) -endif() +find_library(libfc fc @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) +find_library(libsecp256k1 secp256k1 @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) find_library(libff ff @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) find_library(libwasm WASM @CMAKE_INSTALL_FULL_LIBDIR@ NO_DEFAULT_PATH) diff --git a/CMakeModules/EosioTesterBuild.cmake.in b/CMakeModules/EosioTesterBuild.cmake.in index 48d12ea76d..ac1b5763b2 100644 --- a/CMakeModules/EosioTesterBuild.cmake.in +++ b/CMakeModules/EosioTesterBuild.cmake.in @@ -51,14 +51,8 @@ find_package(Boost @Boost_MAJOR_VERSION@.@Boost_MINOR_VERSION@ EXACT REQUIRED CO find_library(libtester eosio_testing @CMAKE_BINARY_DIR@/libraries/testing NO_DEFAULT_PATH) find_library(libchain eosio_chain @CMAKE_BINARY_DIR@/libraries/chain NO_DEFAULT_PATH) -if ( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" ) - find_library(libfc fc_debug @CMAKE_BINARY_DIR@/libraries/fc NO_DEFAULT_PATH) - find_library(libsecp256k1 secp256k1_debug @CMAKE_BINARY_DIR@/libraries/libfc/secp256k1 NO_DEFAULT_PATH) - -else() - find_library(libfc fc @CMAKE_BINARY_DIR@/libraries/fc NO_DEFAULT_PATH) - find_library(libsecp256k1 secp256k1 @CMAKE_BINARY_DIR@/libraries/libfc/secp256k1 NO_DEFAULT_PATH) -endif() +find_library(libfc fc @CMAKE_BINARY_DIR@/libraries/fc NO_DEFAULT_PATH) +find_library(libsecp256k1 secp256k1 @CMAKE_BINARY_DIR@/libraries/libfc/secp256k1 NO_DEFAULT_PATH) find_library(libff ff @CMAKE_BINARY_DIR@/libraries/libfc/libraries/ff/libff NO_DEFAULT_PATH) find_library(libwasm WASM @CMAKE_BINARY_DIR@/libraries/wasm-jit/Source/WASM NO_DEFAULT_PATH) diff --git a/libraries/libfc/CMakeLists.txt b/libraries/libfc/CMakeLists.txt index fed4ab9ea5..04cd0efeee 100644 --- a/libraries/libfc/CMakeLists.txt +++ b/libraries/libfc/CMakeLists.txt @@ -1,66 +1,29 @@ -project( fc ) -cmake_minimum_required( VERSION 3.8 ) - -SET( DEFAULT_HEADER_INSTALL_DIR usr/include/${target} ) -SET( DEFAULT_LIBRARY_INSTALL_DIR usr/lib ) -SET( DEFAULT_EXECUTABLE_INSTALL_DIR usr/bin ) -SET( CMAKE_DEBUG_POSTFIX _debug ) -SET( BUILD_SHARED_LIBS NO ) -SET( ECC_IMPL secp256k1 CACHE STRING "secp256k1 or openssl or mixed" ) - -set(platformBitness 32) -if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(platformBitness 64) -endif() - -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") - -include( GNUInstallDirs ) -include( SetupTargetMacros ) - -if(CMAKE_CXX_STANDARD EQUAL 98 OR CMAKE_CXX_STANDARD LESS 17) - message(FATAL_ERROR "fc requires c++17 or newer") -elseif(NOT CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 17) - set(CMAKE_CXX_STANDARD_REQUIRED ON) -endif() - -if(FC_INSTALL_COMPONENT) - set(INSTALL_COMPONENT_ARGS COMPONENT ${FC_INSTALL_COMPONENT} EXCLUDE_FROM_ALL) -endif() +set( ECC_IMPL secp256k1 CACHE STRING "secp256k1 or openssl or mixed" ) add_subdirectory( secp256k1 ) -SET( WITH_PROCPS OFF CACHE BOOL "" FORCE) -SET( CURVE "ALT_BN128" CACHE STRING "" FORCE) -SET( USE_ASM OFF CACHE BOOL "" FORCE) -SET( IS_LIBFF_PARENT OFF CACHE BOOL "" FORCE) -SET( FF_INSTALL_COMPONENT "${FC_INSTALL_COMPONENT}") +set( WITH_PROCPS OFF CACHE BOOL "" FORCE) +set( CURVE "ALT_BN128" CACHE STRING "" FORCE) +set( USE_ASM OFF CACHE BOOL "" FORCE) +set( IS_LIBFF_PARENT OFF CACHE BOOL "" FORCE) +set( FF_INSTALL_COMPONENT "${FC_INSTALL_COMPONENT}") add_subdirectory( libraries/ff ) -IF( ECC_IMPL STREQUAL openssl ) - SET( ECC_REST src/crypto/elliptic_impl_pub.cpp ) -ELSE( ECC_IMPL STREQUAL openssl ) - SET( ECC_LIB secp256k1 ) - IF( ECC_IMPL STREQUAL mixed ) - SET( ECC_REST src/crypto/elliptic_impl_priv.cpp src/crypto/elliptic_impl_pub.cpp ) - ELSE( ECC_IMPL STREQUAL mixed ) - SET( ECC_REST src/crypto/elliptic_impl_priv.cpp ) - ENDIF( ECC_IMPL STREQUAL mixed ) -ENDIF( ECC_IMPL STREQUAL openssl ) - -MESSAGE(STATUS "Configuring fc to build on Unix/Apple") +if( ECC_IMPL STREQUAL openssl ) + set( ECC_REST src/crypto/elliptic_impl_pub.cpp ) +else( ECC_IMPL STREQUAL openssl ) + set( ECC_LIB secp256k1 ) + if( ECC_IMPL STREQUAL mixed ) + set( ECC_REST src/crypto/elliptic_impl_priv.cpp src/crypto/elliptic_impl_pub.cpp ) + else( ECC_IMPL STREQUAL mixed ) + set( ECC_REST src/crypto/elliptic_impl_priv.cpp ) + endif( ECC_IMPL STREQUAL mixed ) +endif( ECC_IMPL STREQUAL openssl ) set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads) -IF(NOT "$ENV{OPENSSL_ROOT_DIR}" STREQUAL "") - set(OPENSSL_ROOT_DIR $ENV{OPENSSL_ROOT_DIR} ) - set(OPENSSL_INCLUDE_DIR ${OPENSSL_ROOT_DIR}/include) - message(STATUS "Setting up OpenSSL root and include vars to ${OPENSSL_ROOT_DIR}, ${OPENSSL_INCLUDE_DIR}") -ENDIF() - find_package(OpenSSL REQUIRED) set( fc_sources @@ -132,13 +95,7 @@ set( fc_sources file( GLOB_RECURSE fc_headers ${CMAKE_CURRENT_SOURCE_DIR} *.hpp *.h ) -set( sources - ${fc_sources} -) - -list(APPEND sources ${fc_headers}) - -setup_library( fc SOURCES ${sources} LIBRARY_TYPE STATIC DONT_INSTALL_LIBRARY ) +add_library(fc ${fc_sources} ${fc_headers}) function(detect_thread_name) include(CheckSymbolExists) @@ -160,69 +117,39 @@ find_package(Boost 1.66 REQUIRED COMPONENTS unit_test_framework iostreams) -IF(APPLE) - # As of 10.10 yosemite, the OpenSSL static libraries shipped with os x have a dependency - # on zlib, so any time you link in openssl you also need to link zlib. . We really want to detect whether openssl was configured with the --no-zlib - # option or not when it was built, but that's difficult to do in practice, so we - # just always try to link it in on mac. - find_package( ZLIB REQUIRED ) -ELSE(APPLE) - find_package( ZLIB ) -ENDIF(APPLE) - -if( ZLIB_FOUND ) - MESSAGE( STATUS "zlib found" ) - add_definitions( -DHAS_ZLIB ) -else() - MESSAGE( STATUS "zlib not found" ) - set( ZLIB_LIBRARIES "" ) -endif( ZLIB_FOUND ) +# fc picks up a dependency on zlib via Boost::iostreams, however in some versions of cmake/boost (depending on if CMake config +# files are used) the Boost::iostreams target does not have a dependency on zlib itself so add it explicitly +find_package(ZLIB REQUIRED) -target_include_directories(fc - PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include - ${OPENSSL_INCLUDE_DIR} - ) +target_include_directories(fc PUBLIC include) # try and make this very clear that this json parser is intended only for webauthn parsing.. if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.11.0) - set_source_files_properties(src/crypto/elliptic_webauthn.cpp PROPERTIES INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/include/fc/crypto/webauthn_json/include) + set_source_files_properties(src/crypto/elliptic_webauthn.cpp PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/include/fc/crypto/webauthn_json/include") else() set_source_files_properties(src/crypto/elliptic_webauthn.cpp PROPERTIES COMPILE_FLAGS "-I${CMAKE_CURRENT_SOURCE_DIR}/include/fc/crypto/webauthn_json/include") endif() -IF(NOT WIN32) - set(LINK_USR_LOCAL_LIB -L/usr/local/lib) -ENDIF() - -IF(WIN32) +if(WIN32) target_link_libraries( fc PUBLIC ws2_32 mswsock userenv ) -ENDIF() +endif() -IF(APPLE) +if(APPLE) find_library(security_framework Security) find_library(corefoundation_framework CoreFoundation) -ENDIF() -target_link_libraries( fc PUBLIC ff ${LINK_USR_LOCAL_LIB} +endif() +target_link_libraries( fc PUBLIC ff Boost::date_time Boost::filesystem Boost::chrono Boost::iostreams Threads::Threads - ${OPENSSL_LIBRARIES} ${ZLIB_LIBRARIES} ${PLATFORM_SPECIFIC_LIBS} ${RPCRT4} ${CMAKE_DL_LIBS} ${readline_libraries} ${ECC_LIB} ${security_framework} ${corefoundation_framework} ) + OpenSSL::Crypto OpenSSL::SSL ZLIB::ZLIB ${PLATFORM_SPECIFIC_LIBS} ${CMAKE_DL_LIBS} ${ECC_LIB} ${security_framework} ${corefoundation_framework} ) # Critically, this ensures that OpenSSL 1.1 & 3.0 both have a variant of BN_zero() with void return value. But it also allows access # to some obsoleted AES functions in 3.0 too, since 3.0's API_COMPAT is effectively 3.0 by default target_compile_definitions(fc PUBLIC "OPENSSL_API_COMPAT=0x10100000L" "OPENSSL_NO_DEPRECATED") -SET(OPENSSL_CONF_TARGET ) -IF(DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY) - SET (OPENSSL_CONF_TARGET ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) -ELSE() - SET (OPENSSL_CONF_TARGET ${CMAKE_CURRENT_BINARY_DIR}) -ENDIF() - -IF(NOT DEFINED SKIP_FC_TESTS) - add_subdirectory( test ) -ENDIF() +add_subdirectory( test ) install(TARGETS fc - LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} ${INSTALL_COMPONENT_ARGS} - ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} ${INSTALL_COMPONENT_ARGS} ) -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/fc DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR} ${INSTALL_COMPONENT_ARGS} ) + LIBRARY DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} COMPONENT dev EXCLUDE_FROM_ALL + ARCHIVE DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} COMPONENT dev EXCLUDE_FROM_ALL) +install(DIRECTORY include/fc DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR} COMPONENT dev EXCLUDE_FROM_ALL) diff --git a/libraries/libfc/CMakeModules/ArgumentParser.cmake b/libraries/libfc/CMakeModules/ArgumentParser.cmake deleted file mode 100644 index a6eb49b1f1..0000000000 --- a/libraries/libfc/CMakeModules/ArgumentParser.cmake +++ /dev/null @@ -1,74 +0,0 @@ -# This module defines the ARGUMENT_PARSER macro for parsing macro arguments. - -# ARGUMENT_PARSER Macro -# This macro parses a mixed list of arguments and headers into lists and boolean -# variables. The output lists and boolean variables are stored using -# tolower( header ) variable names. All non-header arguments will be added to -# the output list that corresponds to the header that they follow (or to the -# default list if no header has been parsed yet). If a boolean header is passed, -# then its corresponding output variable is set to YES. -# -# Usage: -# ARGUMENT_PARSER( default_list lists bools ARGN ) -# -# Parameters: -# default_list The name of the variable that list values should be added -# to before any list headers have been reached. You may -# pass "" to disregard premature list values. -# lists The list headers (semicolon-separated string). -# bools The boolean headers (semicolon-separated string). -# ARGN The arguments to parse. -MACRO( ARGUMENT_PARSER default_list lists bools ) - - # Start using the default list. - SET( dest "${default_list}" ) - IF( NOT dest ) - SET( dest tmp ) - ENDIF( NOT dest ) - - # Clear all of the lists. - FOREACH( list_itr ${lists} ) - STRING( TOLOWER ${list_itr} lower ) - SET( ${lower} "" ) - ENDFOREACH( list_itr ) - - # Set all boolean variables to NO. - FOREACH( bool_itr ${bools} ) - STRING( TOLOWER ${bool_itr} lower ) - SET( ${lower} NO ) - ENDFOREACH( bool_itr ) - - # For all arguments. - FOREACH( arg_itr ${ARGN} ) - - SET( done NO ) - - # For each of the list headers, if the current argument matches a list - # header, then set the destination to the header. - FOREACH( list_itr ${lists} ) - IF( ${arg_itr} STREQUAL ${list_itr} ) - STRING( TOLOWER ${arg_itr} lower ) - SET( dest ${lower} ) - SET( done YES ) - ENDIF( ${arg_itr} STREQUAL ${list_itr} ) - ENDFOREACH( list_itr ) - - # For each of the boolean headers, if the current argument matches a - # boolean header, then set the boolean variable to true. - FOREACH( bool_itr ${bools} ) - IF( ${arg_itr} STREQUAL ${bool_itr} ) - STRING( TOLOWER ${arg_itr} lower ) - SET( ${lower} YES ) - SET( done YES ) - ENDIF( ${arg_itr} STREQUAL ${bool_itr} ) - ENDFOREACH( bool_itr ) - - # If the current argument is not a header, then add it to the current - # destination list. - IF( NOT done ) - SET( ${dest} ${${dest}} ${arg_itr} ) - ENDIF( NOT done ) - - ENDFOREACH( arg_itr ) - -ENDMACRO( ARGUMENT_PARSER ) diff --git a/libraries/libfc/CMakeModules/ParseLibraryList.cmake b/libraries/libfc/CMakeModules/ParseLibraryList.cmake deleted file mode 100644 index e559b9d539..0000000000 --- a/libraries/libfc/CMakeModules/ParseLibraryList.cmake +++ /dev/null @@ -1,79 +0,0 @@ -# -*- mode: cmake -*- - -# -# Shamelessly stolen from MSTK who shamelessly stole from Amanzi open source code https://software.lanl.gov/ascem/trac) -# -# PARSE_LIBRARY_LIST( -# DEBUG -# OPT -# GENERAL ) - -# CMake module -include(CMakeParseArguments) - -function(PARSE_LIBRARY_LIST) - - # Macro: _print_usage - macro(_print_usage) - message("PARSE_LIBRARY_LIST \n" - " FOUND \n" - " DEBUG \n" - " OPT \n" - " GENERAL \n" - "lib_list string to parse\n" - "FOUND flag to indicate if keywords were found\n" - "DEBUG variable containing debug libraries\n" - "OPT variable containing optimized libraries\n" - "GENERAL variable containing debug libraries\n") - - endmacro() - - # Read in args - cmake_parse_arguments(PARSE_ARGS "" "FOUND;DEBUG;OPT;GENERAL" "" ${ARGN}) - set(_parse_list "${PARSE_ARGS_UNPARSED_ARGUMENTS}") - if ( (NOT PARSE_ARGS_FOUND) OR - (NOT PARSE_ARGS_DEBUG) OR - (NOT PARSE_ARGS_OPT) OR - (NOT PARSE_ARGS_GENERAL) OR - (NOT _parse_list ) - ) - _print_usage() - message(FATAL_ERROR "Invalid arguments") - endif() - - # Now split the list - set(_debug_libs "") - set(_opt_libs "") - set(_gen_libs "") - foreach( item ${_parse_list} ) - if( ${item} MATCHES debug OR - ${item} MATCHES optimized OR - ${item} MATCHES general ) - - if( ${item} STREQUAL "debug" ) - set( mylist "_debug_libs" ) - elseif( ${item} STREQUAL "optimized" ) - set( mylist "_opt_libs" ) - elseif( ${item} STREQUAL "general" ) - set( mylist "_gen_libs" ) - endif() - else() - list( APPEND ${mylist} ${item} ) - endif() - endforeach() - - - # Now set output vairables - set(${PARSE_ARGS_DEBUG} "${_debug_libs}" PARENT_SCOPE) - set(${PARSE_ARGS_OPT} "${_opt_libs}" PARENT_SCOPE) - set(${PARSE_ARGS_GENERAL} "${_gen_libs}" PARENT_SCOPE) - - # If any of the lib lists are defined set flag to TRUE - if ( (_debug_libs) OR (_opt_libs) OR (_gen_libs) ) - set(${PARSE_ARGS_FOUND} TRUE PARENT_SCOPE) - else() - set(${PARSE_ARGS_FOUND} FALSE PARENT_SCOPE) - endif() - -endfunction(PARSE_LIBRARY_LIST) - diff --git a/libraries/libfc/CMakeModules/SetupTargetMacros.cmake b/libraries/libfc/CMakeModules/SetupTargetMacros.cmake deleted file mode 100644 index 776ed7d7a1..0000000000 --- a/libraries/libfc/CMakeModules/SetupTargetMacros.cmake +++ /dev/null @@ -1,369 +0,0 @@ -# This module defines several macros that are useful for setting up library, -# plugin, and executable targets. - - -INCLUDE( ArgumentParser ) - -function(enable_unity_build UB_SUFFIX SOURCE_VARIABLE_NAME) - set(files ${${SOURCE_VARIABLE_NAME}}) - # Generate a unique filename for the unity build translation unit - set(unit_build_file ${CMAKE_CURRENT_BINARY_DIR}/ub_${UB_SUFFIX}.cpp) - # Exclude all translation units from compilation - set_source_files_properties(${files} PROPERTIES HEADER_FILE_ONLY true) - # Open the ub file - FILE(WRITE ${unit_build_file} "// Unity Build generated by CMake\n") - # Add include statement for each translation unit - foreach(source_file ${files} ) - FILE( APPEND ${unit_build_file} "#include <${CMAKE_CURRENT_SOURCE_DIR}/${source_file}>\n") - endforeach(source_file) - # Complement list of translation units with the name of ub - set(${SOURCE_VARIABLE_NAME} ${${SOURCE_VARIABLE_NAME}} ${unit_build_file} PARENT_SCOPE) -endfunction(enable_unity_build) - -# SETUP_LIBRARY Macro -# Sets up to build a library target. The macro uses the following global -# variables to define default values (you may change these variables to change -# the defaults: -# DEFAULT_HEADER_INSTALL_DIR -# DEFAULT_LIBRARY_INSTALL_DIR -# -# Usage: -# SETUP_LIBRARY( target -# SOURCES source1 [source2...] -# MOC_HEADERS header1 [header2...] -# LIBRARIES library1 [library2...] -# INSTALL_HEADERS header1 [header2...] -# HEADER_INSTALL_DIR dir -# LIBRARY_INSTALL_DIR dir -# DEBUG_POSTFIX string -# LIBRARY_TYPE string -# AUTO_INSTALL_HEADERS -# DONT_INSTALL_LIBRARY ) -# -# Parameters: -# target The target library. -# SOURCES Follow with the sources to compile. -# MOC_HEADERS Follow with the headers to moc (Requires Qt). -# LIBRARIES Follow with the libraries to link. -# INSTALL_HEADERS Follow with the headers to install. -# HEADER_INSTALL_DIR Follow with the directory to install the headers -# in (${DEFAULT_HEADER_INSTALL_DIR} by default). -# LIBRARY_INSTALL_DIR Follow with the directory to install the library -# in (${DEFAULT_LIBRARY_INSTALL_DIR} by default). -# DEBUG_POSTFIX Follow with the postfix to use when building in -# debug mode (${CMAKE_DEBUG_POSTFIX} by default). -# LIBRARY_TYPE Follow with the type of library to build: SHARED, -# STATIC, or MODULE (if not passed, then the -# behavior is defined by BUILD_SHARED_LIBS). -# AUTO_INSTALL_HEADERS If passed, all *.h files in the current directory -# will be installed. -# DONT_INSTALL_LIBRARY If passed, the library will not be installed. -MACRO( SETUP_LIBRARY target ) - - # Setup the list headers. - SET( list_headers - SOURCES - MOC_HEADERS - LIBRARIES - INSTALL_HEADERS - HEADER_INSTALL_DIR - LIBRARY_INSTALL_DIR - DEBUG_POSTFIX - LIBRARY_TYPE - ) - - # Setup the boolean headers. - SET( bool_headers - AUTO_INSTALL_HEADERS - DONT_INSTALL_LIBRARY - ) - - # Parse the arguments into variables. - ARGUMENT_PARSER( "" "${list_headers}" "${bool_headers}" ${ARGN} ) - - # Set the default values for the header_install_dir, library_install_dir, - # and debug_postfix. - IF( NOT "${ARGN}" MATCHES "(^|;)HEADER_INSTALL_DIR($|;)" ) - SET( header_install_dir ${DEFAULT_HEADER_INSTALL_DIR} ) - ENDIF( NOT "${ARGN}" MATCHES "(^|;)HEADER_INSTALL_DIR($|;)" ) - - IF( NOT "${ARGN}" MATCHES "(^|;)LIBRARY_INSTALL_DIR($|;)" ) - SET( library_install_dir ${DEFAULT_LIBRARY_INSTALL_DIR} ) - ENDIF( NOT "${ARGN}" MATCHES "(^|;)LIBRARY_INSTALL_DIR($|;)" ) - - IF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) - SET( debug_postfix ${CMAKE_DEBUG_POSTFIX} ) - ENDIF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) - - # Configure the header_install_dir and library_install_dir so that ${target} - # may be used in them. Setting target to itself is REQUIRED for the - # configuration to work. - SET( target "${target}" ) - STRING( CONFIGURE "${header_install_dir}" header_install_dir ) - STRING( CONFIGURE "${library_install_dir}" library_install_dir ) - - # Setup the library_type. - IF( NOT library_type ) - SET( library_type STATIC ) - IF( BUILD_SHARED_LIBS ) - SET( library_type SHARED ) - ENDIF( BUILD_SHARED_LIBS ) - ENDIF( NOT library_type ) - - # Clear the moc_sources. - SET( moc_sources "" ) - - # If Qt is being used... - IF( QT_FOUND AND QT_LIBRARIES ) - # Setup QT to build a shared library. - IF( library_type MATCHES SHARED ) - ADD_DEFINITIONS( -DQT_SHARED ) - ENDIF( library_type MATCHES SHARED ) - - # Setup the moc sources. - IF( moc_headers ) - QT4_WRAP_CPP( moc_sources ${moc_headers} ) - ENDIF( moc_headers ) - ENDIF( QT_FOUND AND QT_LIBRARIES ) - - # Fatal error if moc_headers given but moc_sources not created. - IF( moc_headers AND NOT moc_sources ) - MESSAGE( FATAL_ERROR "Calling SETUP_LIBRARY() with MOC_HEADERS failed. " - "Make sure that you included \${QT_USE_FILE} prior to calling " - "SETUP_LIBRARY()." ) - ENDIF( moc_headers AND NOT moc_sources ) - - - IF( UNITY_BUILD ) - enable_unity_build( ${target} sources ) - ENDIF( UNITY_BUILD ) - - # Add the library. - ADD_LIBRARY( "${target}" ${library_type} ${sources} ${moc_sources} ) - - # Setup the debug_postfix. - SET_TARGET_PROPERTIES ( "${target}" PROPERTIES - DEBUG_POSTFIX "${debug_postfix}" ) - - # Link in the dependency libraries. - TARGET_LINK_LIBRARIES( "${target}" ${libraries} ) - - # If auto_install_headers, then set the headers to all .h files in the - # directory. - IF( auto_install_headers ) - FILE( GLOB install_headers *.h ) - ENDIF( auto_install_headers ) - - # Install the headers. - IF( install_headers ) - INSTALL( FILES ${install_headers} DESTINATION "${header_install_dir}" ) - ENDIF( install_headers ) - - # Install the library. - IF( NOT dont_install_library ) - INSTALL( TARGETS "${target}" - LIBRARY DESTINATION "${library_install_dir}" - ARCHIVE DESTINATION "${library_install_dir}" ) - ENDIF( NOT dont_install_library ) - -ENDMACRO( SETUP_LIBRARY ) - - -# SETUP_MODULE Macro -# Sets up to build a module (also setup as a Qt plugin if using Qt). A module is -# built as a shared library; however, modules are typically loaded dynamically -# rather than linked against. Therefore, this macro does not install header -# files and uses its own default install directory. The macro uses the following -# global variables to define default values (you may change these variables to -# change the defaults: -# DEFAULT_MODULE_INSTALL_DIR -# -# Usage: -# SETUP_MODULE( target -# SOURCES source1 [source2...] -# MOC_HEADERS header1 [header2...] -# LIBRARIES library1 [library2...] -# MODULE_INSTALL_DIR dir -# DEBUG_POSTFIX string -# DONT_INSTALL_MODULE ) -# -# Parameters: -# target The target module (built as a shared library). -# SOURCES Follow with the sources to compile. -# MOC_HEADERS Follow with the headers to moc (Requires Qt). -# LIBRARIES Follow with the libraries to link. -# MODULE_INSTALL_DIR Follow with the directory to install the module in -# (${DEFAULT_MODULE_INSTALL_DIR} by default). -# DEBUG_POSTFIX Follow with the postfix to use when building in -# debug mode (${CMAKE_DEBUG_POSTFIX} by default). -# DONT_INSTALL_MODULE If passed, the module will not be installed. -MACRO( SETUP_MODULE target ) - - # Setup the list headers. - SET( list_headers - SOURCES - MOC_HEADERS - LIBRARIES - MODULE_INSTALL_DIR - DEBUG_POSTFIX - ) - - # Setup the boolean headers. - SET( bool_headers - DONT_INSTALL_MODULE - ) - - # Parse the arguments into variables. - ARGUMENT_PARSER( "" "${list_headers}" "${bool_headers}" ${ARGN} ) - - # Set the default values for the module_install_dir and debug postfix. - IF( NOT "${ARGN}" MATCHES "(^|;)MODULE_INSTALL_DIR($|;)" ) - SET( module_install_dir ${DEFAULT_MODULE_INSTALL_DIR} ) - ENDIF( NOT "${ARGN}" MATCHES "(^|;)MODULE_INSTALL_DIR($|;)" ) - - IF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) - SET( debug_postfix ${CMAKE_DEBUG_POSTFIX} ) - ENDIF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) - - # Configure the module_install_dir so that ${target} may be used in it. - # Setting target to itself is REQUIRED for the configuration to work. - SET( target "${target}" ) - STRING( CONFIGURE "${module_install_dir}" module_install_dir ) - - # Clear the moc_sources. - SET( moc_sources "" ) - - # If Qt is being used... - IF( QT_FOUND AND QT_LIBRARIES ) - ADD_DEFINITIONS( -DQT_PLUGIN ) - - # Setup the moc sources. - IF( moc_headers ) - QT4_WRAP_CPP( moc_sources ${moc_headers} ) - ENDIF( moc_headers ) - ENDIF( QT_FOUND AND QT_LIBRARIES ) - - # Fatal error if moc_headers given but moc_sources not created. - IF( moc_headers AND NOT moc_sources ) - MESSAGE( FATAL_ERROR "Calling SETUP_MODULE() with MOC_HEADERS failed. " - "Make sure that you included \${QT_USE_FILE} prior to calling " - "SETUP_MODULE()." ) - ENDIF( moc_headers AND NOT moc_sources ) - - # Add the module (built as a shared library). - ADD_LIBRARY( "${target}" SHARED ${sources} ${moc_sources} ) - - # Setup the debug postfix. - SET_TARGET_PROPERTIES ( "${target}" PROPERTIES - DEBUG_POSTFIX "${debug_postfix}" ) - - # Link in the dependency libraries. - TARGET_LINK_LIBRARIES( "${target}" ${libraries} ) - - # Install the module. - IF( NOT dont_install_module ) - INSTALL( TARGETS "${target}" - LIBRARY DESTINATION "${module_install_dir}" ) - ENDIF( NOT dont_install_module ) - -ENDMACRO( SETUP_MODULE ) - - -# SETUP_EXECUTABLE Macro -# Sets up to build an executable target. The macro uses the following global -# variables to define default values (you may change these variables to change -# the defaults: -# DEFAULT_EXECUTABLE_INSTALL_DIR -# -# Usage: -# SETUP_EXECUTABLE( target -# SOURCES source1 [source2...] -# MOC_HEADERS header1 [header2...] -# LIBRARIES library1 [library2...] -# EXECUTABLE_INSTALL_DIR dir -# DEBUG_POSTFIX string -# DONT_INSTALL_EXECUTABLE ) -# -# Parameters: -# target The target executable. -# SOURCES Follow with the sources to compile. -# MOC_HEADERS Follow with the headers to moc (Requires Qt). -# LIBRARIES Follow with the libraries to link. -# EXECUTABLE_INSTALL_DIR Follow with the directory to install the -# executable in -# (${DEFAULT_EXECUTABLE_INSTALL_DIR} by default). -# DEBUG_POSTFIX Follow with the postfix to use when building in -# debug mode (${CMAKE_DEBUG_POSTFIX} by -# default). -# DONT_INSTALL_EXECUTABLE If passed, the executable will not be -# installed. -MACRO( SETUP_EXECUTABLE target ) - - # Setup the list headers. - SET( list_headers - SOURCES - MOC_HEADERS - LIBRARIES - EXECUTABLE_INSTALL_DIR - DEBUG_POSTFIX - ) - - # Setup the boolean headers. - SET( bool_headers - DONT_INSTALL_EXECUTABLE - ) - - # Parse the arguments into variables. - ARGUMENT_PARSER( "" "${list_headers}" "${bool_headers}" ${ARGN} ) - - # Set the default values for the executable_install_dir and debug postfix. - IF( NOT "${ARGN}" MATCHES "(^|;)EXECUTABLE_INSTALL_DIR($|;)" ) - SET( executable_install_dir ${DEFAULT_EXECUTABLE_INSTALL_DIR} ) - ENDIF( NOT "${ARGN}" MATCHES "(^|;)EXECUTABLE_INSTALL_DIR($|;)" ) - - IF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) - SET( debug_postfix ${CMAKE_DEBUG_POSTFIX} ) - ENDIF( NOT "${ARGN}" MATCHES "(^|;)DEBUG_POSTFIX($|;)" ) - - # Configure the executable_install_dir so that ${target} may be used in it. - # Setting target to itself is REQUIRED for the configuration to work. - SET( target "${target}" ) - STRING( CONFIGURE "${executable_install_dir}" executable_install_dir ) - - # Clear the moc_sources. - SET( moc_sources "" ) - - # If Qt is being used... - IF( QT_FOUND AND QT_LIBRARIES ) - ADD_DEFINITIONS( -DQT_SHARED ) - - # Setup the moc sources. - IF( moc_headers ) - QT4_WRAP_CPP( moc_sources ${moc_headers} ) - ENDIF( moc_headers ) - ENDIF( QT_FOUND AND QT_LIBRARIES ) - - # Fatal error if moc_headers given but moc_sources not created. - IF( moc_headers AND NOT moc_sources ) - MESSAGE( FATAL_ERROR "Calling SETUP_EXECUTABLE() with MOC_HEADERS failed. " - "Make sure that you included \${QT_USE_FILE} prior to calling " - "SETUP_EXECUTABLE()." ) - ENDIF( moc_headers AND NOT moc_sources ) - - # Add the executable. - ADD_EXECUTABLE( "${target}" ${sources} ${moc_sources} ) - - # Setup the debug postfix. - SET_TARGET_PROPERTIES ( "${target}" PROPERTIES - DEBUG_POSTFIX "${debug_postfix}" ) - - # Link in the dependency libraries. - TARGET_LINK_LIBRARIES( "${target}" ${libraries} ) - - # Install the executable. - IF( NOT dont_install_executable ) - INSTALL( TARGETS "${target}" RUNTIME DESTINATION - "${executable_install_dir}" ) - ENDIF( NOT dont_install_executable ) - -ENDMACRO( SETUP_EXECUTABLE ) diff --git a/libraries/libfc/CMakeModules/UseLibraryMacros.cmake b/libraries/libfc/CMakeModules/UseLibraryMacros.cmake deleted file mode 100644 index ed4ccd7908..0000000000 --- a/libraries/libfc/CMakeModules/UseLibraryMacros.cmake +++ /dev/null @@ -1,72 +0,0 @@ -# This module defines macros that are useful for using libraries in a build. The -# macros in this module are typically used along with the FindDependencyMacros. - -# ADD_LIBRARY_TO_LIST Macro -# Adds a library to a list of libraries if it is found. Otherwise, reports an -# error. -# -# Usage: -# ADD_LIBRARY_TO_LIST( libraries found lib lib_name ) -# -# Parameters: -# libraries The list of libraries to add the library to. -# found Whether or not the library to add was found. -# lib The library to add to the list. -# lib_name The name of the library to add to the list. -MACRO( ADD_LIBRARY_TO_LIST libraries found lib lib_name ) - - # Setting found to itself is necessary for the conditional to work. - SET( found ${found} ) - - # IF found, then add the library to the list, else report an error. - IF( found ) - LIST( REMOVE_ITEM ${libraries} ${lib} ) - SET( ${libraries} ${${libraries}} ${lib} ) - ENDIF( found ) - IF( NOT found ) - MESSAGE( "Using ${lib_name} failed." ) - ENDIF( NOT found ) - -ENDMACRO( ADD_LIBRARY_TO_LIST ) - - -# USE_LIBRARY_GLOBALS Macro -# If ${prefix}_USE_${LIB} is true, then ${prefix}_${LIB}_LIBRARY will be added -# to ${prefix}_LIBRARIES (assuming the library was correctly found). All of the -# dependencies will also be added to ${prefix}_LIBRARIES. -# -# Usage: -# USE_LIBRARY_GLOBALS( prefix lib -# DEPS dependency1 [dependency2...] ) -# -# Parameters: -# prefix The prefix for the global variables. -# lib The library to try to use. -# DEPS Follow with the list of dependencies that should be added with -# the given library. -MACRO( USE_LIBRARY_GLOBALS prefix lib ) - - STRING( TOUPPER ${lib} upper ) - - # If the library should be used... - IF( ${prefix}_USE_${upper} ) - - # Parse the arguments into variables. - ARGUMENT_PARSER( "" "DEPS" "" ${ARGN} ) - - # Add the library to the list. - ADD_LIBRARY_TO_LIST( ${prefix}_LIBRARIES "${${prefix}_${upper}_FOUND}" - "${${prefix}_${upper}_LIBRARY}" ${lib} ) - - # For each of the library's dependencies. - FOREACH( dep_itr ${deps} ) - STRING( TOUPPER ${dep_itr} upper ) - - # Add the dependency to the list. - ADD_LIBRARY_TO_LIST( ${prefix}_LIBRARIES - "${${prefix}_${upper}_FOUND}" - "${${prefix}_${upper}_LIBRARY}" ${dep_itr} ) - ENDFOREACH( dep_itr ) - ENDIF( ${prefix}_USE_${upper} ) - -ENDMACRO( USE_LIBRARY_GLOBALS ) diff --git a/libraries/libfc/CMakeModules/VersionMacros.cmake b/libraries/libfc/CMakeModules/VersionMacros.cmake deleted file mode 100644 index 6dd32c26a9..0000000000 --- a/libraries/libfc/CMakeModules/VersionMacros.cmake +++ /dev/null @@ -1,244 +0,0 @@ -# This module defines several macros that are useful for handling version -# information. These macros work for version strings of format "#.#.#" -# representing major, minor, and patch integer components. - - -INCLUDE( ArgumentParser ) - - -# PARSE_VERSION_STR Macro -# This macro parses the version string information from a string. The macro -# parses the string for the given definitions followed by whitespace (or by ':' -# or '"' characters) and then version information. For example, passing -# "MyVersion" as a definition would properly retrieve the version from a string -# "containing the line "def MyVersion: 1.2.3". -# -# Usage: -# PARSE_VERSION_STR( version string definition [definition2...] ) -# -# Parameters: -# version The variable to store the version string in. -# string The string to parse. -# definition The definition(s) that may preceed the version string -# information. -MACRO( PARSE_VERSION_STR version string ) - - # Parse the arguments into variables. - ARGUMENT_PARSER( definitions "" "" ${ARGN} ) - - # For each of the given definitions... - FOREACH( def_itr ${definitions} ) - # If the version has not been found, then attempt to parse it. - IF( NOT ${version} ) - # Parse the version string. - STRING( REGEX MATCH "${def_itr}[ \t\":]+[0-9]+(.[0-9]+)?(.[0-9]+)?" - ${version} ${string} ) - - STRING( REGEX MATCH "[0-9]+(.[0-9]+)?(.[0-9]+)?" ${version} - "${${version}}" ) - - CORRECT_VERSION_STR( ${version} "${${version}}" ) - ENDIF( NOT ${version} ) - ENDFOREACH( def_itr ) - -ENDMACRO( PARSE_VERSION_STR ) - - -# PARSE_VERSION_INT Macro -# This macro parses the version integer component information from a string. The -# macro parses the string for the given definitions followed by whitespace (or -# by ':' or '"' characters) and then version information. For example, passing -# "MyVersionMajor" as a definition would properly retrieve the version from a -# string "containing the line "def MyVersionMajor: 1". -# -# Usage: -# PARSE_VERSION_INT( version string definition [definition2...] ) -# -# Parameters: -# version The variable to store the version integer component in. -# string The string to parse. -# definition The definition(s) that may preceed the version integer -# component information. -MACRO( PARSE_VERSION_INT version string ) - - # Parse the arguments into variables. - ARGUMENT_PARSER( definitions "" "" ${ARGN} ) - - # For each of the given definitions... - FOREACH( def_itr ${definitions} ) - # If the version has not been found, then attempt to parse it. - IF( NOT ${version} ) - # Parse the version string. - STRING( REGEX MATCH "${def_itr}[ \t\":]+[0-9]+" ${version} - ${string} ) - - STRING( REGEX MATCH "[0-9]+" ${version} "${${version}}" ) - ENDIF( NOT ${version} ) - ENDFOREACH( def_itr ) - -ENDMACRO( PARSE_VERSION_INT ) - - -# VERSION_STR_TO_INTS Macro -# This macro converts a version string into its three integer components. -# -# Usage: -# VERSION_STR_TO_INTS( major minor patch version ) -# -# Parameters: -# major The variable to store the major integer component in. -# minor The variable to store the minor integer component in. -# patch The variable to store the patch integer component in. -# version The version string to convert ("#.#.#" format). -MACRO( VERSION_STR_TO_INTS major minor patch version ) - - STRING( REGEX REPLACE "([0-9]+).[0-9]+.[0-9]+" "\\1" ${major} ${version} ) - STRING( REGEX REPLACE "[0-9]+.([0-9]+).[0-9]+" "\\1" ${minor} ${version} ) - STRING( REGEX REPLACE "[0-9]+.[0-9]+.([0-9]+)" "\\1" ${patch} ${version} ) - -ENDMACRO( VERSION_STR_TO_INTS ) - - -# VERSION_INTS_TO_STR Macro -# This macro converts three version integer components into a version string. -# -# Usage: -# VERSION_INTS_TO_STR( version major minor patch ) -# -# Parameters: -# version The variable to store the version string in. -# major The major version integer. -# minor The minor version integer. -# patch The patch version integer. -MACRO( VERSION_INTS_TO_STR version major minor patch ) - - SET( ${version} "${major}.${minor}.${patch}" ) - CORRECT_VERSION_STR( ${version} ${${version}} ) - -ENDMACRO( VERSION_INTS_TO_STR version major minor patch ) - - -# COMPARE_VERSION_STR Macro -# This macro compares two version strings to each other. The macro sets the -# result variable to -1 if lhs < rhs, 0 if lhs == rhs, and 1 if lhs > rhs. -# -# Usage: -# COMPARE_VERSION_STR( result lhs rhs ) -# -# Parameters: -# result The variable to store the result of the comparison in. -# lhs The version of the left hand side ("#.#.#" format). -# rhs The version of the right hand side ("#.#.#" format). -MACRO( COMPARE_VERSION_STR result lhs rhs ) - - VERSION_STR_TO_INTS( lhs_major lhs_minor lhs_patch ${lhs} ) - VERSION_STR_TO_INTS( rhs_major rhs_minor rhs_patch ${rhs} ) - - COMPARE_VERSION_INTS( ${result} - ${lhs_major} ${lhs_minor} ${lhs_patch} - ${rhs_major} ${rhs_minor} ${rhs_patch} ) - -ENDMACRO( COMPARE_VERSION_STR result lhs rhs ) - - -# COMPARE_VERSION_INTS Macro -# This macro compares two versions to each other using their integer components. -# The macro sets the result variable to -1 if lhs < rhs, 0 if lhs == rhs, and 1 -# if lhs > rhs. -# -# Usage: -# COMPARE_VERSION_INTS( result -# lhs_major lhs_minor lhs_patch -# rhs_major rhs_minor rhs_patch ) -# -# Parameters: -# result The variable to store the result of the comparison in. -# lhs_major The major integer component for the left hand side. -# lhs_minor The minor integer component for the left hand side. -# lhs_patch The patch integer component for the left hand side. -# rhs_major The major integer component for the right hand side. -# rhs_minor The minor integer component for the right hand side. -# rhs_patch The patch integer component for the right hand side. -MACRO( COMPARE_VERSION_INTS result lhs_major lhs_minor lhs_patch - rhs_major rhs_minor rhs_patch ) - - SET( ${result} 0 ) - IF( NOT ${result} AND ${lhs_major} LESS ${rhs_major} ) - SET( ${result} -1 ) - ENDIF( NOT ${result} AND ${lhs_major} LESS ${rhs_major} ) - IF( NOT ${result} AND ${lhs_major} GREATER ${rhs_major} ) - SET( ${result} 1 ) - ENDIF( NOT ${result} AND ${lhs_major} GREATER ${rhs_major} ) - - IF( NOT ${result} AND ${lhs_minor} LESS ${rhs_minor} ) - SET( ${result} -1 ) - ENDIF( NOT ${result} AND ${lhs_minor} LESS ${rhs_minor} ) - IF( NOT ${result} AND ${lhs_minor} GREATER ${rhs_minor} ) - SET( ${result} 1 ) - ENDIF( NOT ${result} AND ${lhs_minor} GREATER ${rhs_minor} ) - - IF( NOT ${result} AND ${lhs_patch} LESS ${rhs_patch} ) - SET( ${result} -1 ) - ENDIF( NOT ${result} AND ${lhs_patch} LESS ${rhs_patch} ) - IF( NOT ${result} AND ${lhs_patch} GREATER ${rhs_patch} ) - SET( ${result} 1 ) - ENDIF( NOT ${result} AND ${lhs_patch} GREATER ${rhs_patch} ) - -ENDMACRO( COMPARE_VERSION_INTS result lhs_major lhs_minor lhs_patch - rhs_major rhs_minor rhs_patch ) - - -# CORRECT_VERSION_STR Macro -# This macro corrects the version_str and stores the result in the version -# variable. If the version_str contains a version string in "#" or "#.#" format, -# then ".0" will be appended to the string to convert it to "#.#.#" format. If -# the version_str is invalid, then version will be set to "". -# -# Usage: -# CORRECT_VERSION_STR( version version_str ) -# -# Parameters: -# version The variable to store the corrected version string in. -# version_str The version string to correct. -MACRO( CORRECT_VERSION_STR version version_str ) - - SET( ${version} "${version_str}" ) - - # Add ".0" to the end of the version string in case a full "#.#.#" string - # was not given. - FOREACH( itr RANGE 2 ) - IF( NOT ${version} MATCHES "[0-9]+.[0-9]+.[0-9]+" ) - SET( ${version} "${${version}}.0" ) - ENDIF( NOT ${version} MATCHES "[0-9]+.[0-9]+.[0-9]+" ) - ENDFOREACH( itr ) - - # If the version string is not correct, then set it to "". - IF( NOT ${version} MATCHES "^[0-9]+.[0-9]+.[0-9]+$" ) - SET( ${version} "" ) - ENDIF( NOT ${version} MATCHES "^[0-9]+.[0-9]+.[0-9]+$" ) - -ENDMACRO( CORRECT_VERSION_STR ) - - - -# CORRECT_VERSION_Int Macro -# This macro corrects the version_int and stores the result in the version -# variable. If the version_int is invalid, then version will be set to "". -# -# Usage: -# CORRECT_VERSION_Int( version version_int ) -# -# Parameters: -# version The variable to store the corrected version integer -# component in. -# version_INT The version integer component to correct. -MACRO( CORRECT_VERSION_INT version version_int ) - - SET( ${version} "${version_int}" ) - - # If the version is not an integer, then set it to "". - IF( NOT ${version} MATCHES "^[0-9]+$" ) - SET( ${version} "" ) - ENDIF( NOT ${version} MATCHES "^[0-9]+$" ) - -ENDMACRO( CORRECT_VERSION_INT ) diff --git a/libraries/libfc/GitVersionGen/GetGitRevisionDescription.cmake.in b/libraries/libfc/GitVersionGen/GetGitRevisionDescription.cmake.in deleted file mode 100644 index 888ce13aab..0000000000 --- a/libraries/libfc/GitVersionGen/GetGitRevisionDescription.cmake.in +++ /dev/null @@ -1,38 +0,0 @@ -# -# Internal file for GetGitRevisionDescription.cmake -# -# Requires CMake 2.6 or newer (uses the 'function' command) -# -# Original Author: -# 2009-2010 Ryan Pavlik -# http://academic.cleardefinition.com -# Iowa State University HCI Graduate Program/VRAC -# -# Copyright Iowa State University 2009-2010. -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -set(HEAD_HASH) - -file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) - -string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) -if(HEAD_CONTENTS MATCHES "ref") - # named branch - string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") - if(EXISTS "@GIT_DIR@/${HEAD_REF}") - configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) - elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") - configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) - set(HEAD_HASH "${HEAD_REF}") - endif() -else() - # detached HEAD - configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) -endif() - -if(NOT HEAD_HASH) - file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) - string(STRIP "${HEAD_HASH}" HEAD_HASH) -endif() diff --git a/libraries/libfc/LICENSE.txt b/libraries/libfc/LICENSE.txt deleted file mode 100644 index 3689c742a7..0000000000 --- a/libraries/libfc/LICENSE.txt +++ /dev/null @@ -1,27 +0,0 @@ -AntelopeIO/fc - -Copyright (c) 2021-2022 EOS Network Foundation (ENF) and its contributors. All rights reserved. -This ENF software is based upon: - -EOSIO/fc -Copyright (c) 2018, Respective Authors all rights reserved. - -The MIT License - -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.

+ The Sourceforge project page +