diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5f0fc23ad5a10368a6ba4e705cdc027800a63ac6..398bf209e70b7c130c1eba6e71e5b38e8867eb66 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -188,7 +188,7 @@ tests nvidia: - docker build -t "recalbox-${ARCH}" . - WORKDIR="$(pwd)" CCACHE="true" OUTPUTDIR="$(pwd)/output" - mkdir -p ${OUTPUTDIR} - - docker run --rm --tmpfs /tmp:exec --security-opt seccomp=unconfined -v "${WORKDIR}:/work" -v "${OUTPUTDIR}:/work/output" -v "/recalbox-builds/dl:/share/dl" -v "/recalbox-builds/ccaches/ccache-${ARCH}:/share/ccache" -e "ARCH=${ARCH}" -e "GITLAB_TOKEN_RB5000=${GITLAB_TOKEN_RB5000}" -e "GITLAB_TOKEN_BEEBEM=${GITLAB_TOKEN_BEEBEM}" -e "GITLAB_TOKEN_THEMES=${GITLAB_TOKEN_THEMES}" -e "RECALBOX_VERSION=${RECALBOX_VERSION}" -e "RECALBOX_CCACHE_ENABLED=${CCACHE}" "recalbox-${ARCH}" 2>&1 | tee build.log | grep '>>>' || tac build.log | grep '>>>' -m 1 -B 9999 | tac + - docker run --rm --tmpfs /tmp:exec --security-opt seccomp=unconfined -v "${WORKDIR}:/work" -v "${OUTPUTDIR}:/work/output" -v "/recalbox-builds/dl:/share/dl" -v "/recalbox-builds/ccaches/ccache-${ARCH}:/share/ccache" -e "ARCH=${ARCH}" -e "GITLAB_TOKEN_WEBMANAGER3=${GITLAB_TOKEN_WEBMANAGER3}" -e "GITLAB_TOKEN_RB5000=${GITLAB_TOKEN_RB5000}" -e "GITLAB_TOKEN_BEEBEM=${GITLAB_TOKEN_BEEBEM}" -e "GITLAB_TOKEN_THEMES=${GITLAB_TOKEN_THEMES}" -e "RECALBOX_VERSION=${RECALBOX_VERSION}" -e "RECALBOX_CCACHE_ENABLED=${CCACHE}" "recalbox-${ARCH}" 2>&1 | tee build.log | grep '>>>' || tac build.log | grep '>>>' -m 1 -B 9999 | tac - echo $? # Dist - export DIST_DIR="dist/${ARCH}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 88c44a0c6fc253a657b982221cde228853b6bd86..7f7f6a37678bfd716e794932071d49891a1d67f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -646,6 +646,8 @@ All notable changes to this project have been documented in this file since 8.1. - Fix recalbox stable version generation - Change board build order for gitlab ci - Remove Donkey Kong Classic +- Implement Pulseaudio +- Add WebManager v3 ## [7.1.1-Reloaded] - Fix abnormally long boots diff --git a/Config.in b/Config.in index fff55c6fb21a261c4b767d08fb55d256fb808327..104da20928686ed60420c5b8256ac059fc8175fa 100644 --- a/Config.in +++ b/Config.in @@ -31,6 +31,7 @@ menu "Recalbox" source "$BR2_EXTERNAL_RECALBOX_PATH/package/recalbox-configgen/Config.in" source "$BR2_EXTERNAL_RECALBOX_PATH/package/recalbox-initramfs/Config.in" source "$BR2_EXTERNAL_RECALBOX_PATH/package/recalbox-manager2/Config.in" + source "$BR2_EXTERNAL_RECALBOX_PATH/package/recalbox-manager3/Config.in" source "$BR2_EXTERNAL_RECALBOX_PATH/package/recalbox-mplayer/Config.in" source "$BR2_EXTERNAL_RECALBOX_PATH/package/recalbox-piboy/Config.in" source "$BR2_EXTERNAL_RECALBOX_PATH/package/recalbox-romfs2/Config.in" diff --git a/Dockerfile b/Dockerfile index 7ad1a6b2edd92af39bce5028b011348ef222f39f..a70e79144c4c87a6753f672a9e8daaea58460bbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,15 +10,18 @@ ENV PACKAGE '' # Install dependencies # needed ? xterm RUN apt-get update -y && \ -apt-get install -y tzdata && \ +apt-get install -y tzdata apt-utils && \ ln -fs /usr/share/zoneinfo/Europe/Paris /etc/localtime && \ dpkg-reconfigure --frontend noninteractive tzdata && \ apt-get -y install build-essential git libncurses5-dev qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools \ mercurial libdbus-glib-1-dev texinfo zip openssh-client libxml2-utils libpng-dev \ software-properties-common wget cpio bc locales rsync imagemagick bison flex bsdmainutils \ nano vim automake autopoint mtools dosfstools subversion openjdk-8-jdk libssl-dev libelf-dev \ -graphviz python3 python3-matplotlib python3-numpy python3-six re2c libc6-dev-i386 libtool cabextract && \ -rm -rf /var/lib/apt/lists/* +graphviz python3 python3-matplotlib python3-numpy python3-six re2c libc6-dev-i386 libtool cabextract +RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && apt-get -y install nodejs yarn +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && apt update && apt install -y yarn +RUN yarn global add @quasar/cli +RUN rm -rf /var/lib/apt/lists/* # Set the locale needed by toolchain RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 724a573300ae478105a5e810381ba2018dbefe01..c6e74bd939af63631e56e7f571c8b51e15e160fc 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -380,6 +380,7 @@ as release notes for end user on a Recalbox upgrade. - Bump Dolphin Emulator to Version 5.0-13178 - Bump Buildroot to version 2020.02 - Bump Kernel to 5.8 (except on Odroid Go Advance) +- Add WebManager v3 - New multi-source rom management: - Pre-installed games are no longer copied into SHARE partition (except ports) - New option in game menu to show/hide pre-installed games diff --git a/TESTING.md b/TESTING.md index 52bbe067508c581d92626369c033edfb848c9f75..33cefc5aa9bc367fa0e0ef579feaf9ed270e7b0b 100644 --- a/TESTING.md +++ b/TESTING.md @@ -476,6 +476,8 @@ Pour les pending features, elles ne seront activées que si les beta testeurs le - [ ] Check if WPA-PSK-SHA256 key mgmt works (`wpa_cli status |grep -q key_mgmt=WPA2-PSK-SHA256 && echo "OK"`) - [ ] Check that there are no regression with kernel 5.10 on rpi boards - [ ] Tester que le GPi ou NesPi4 case s'installent correctement (via installation fraiche) +- [ ] Pulseaudio - Check audio works everywhere +- [ ] WebMAnager v3 => test all ## [7.1-Reloaded] diff --git a/board/recalbox/fsoverlay/etc/init.d/S93webmanager b/board/recalbox/fsoverlay/etc/init.d/S93webmanager new file mode 100755 index 0000000000000000000000000000000000000000..e9b6667ce47017985f7ad30e0e74a56fb8fbd6c7 --- /dev/null +++ b/board/recalbox/fsoverlay/etc/init.d/S93webmanager @@ -0,0 +1,45 @@ +#!/bin/sh +# + +systemsetting="recalbox_settings" + +WBPIDFILE=/var/run/webapi-server.pid +WBBIN=/usr/bin/webapi-server + +case "$1" in + start) + enabled="`$systemsetting -command load -key system.manager.enabled`" + if [ "$enabled" != "0" ];then + recallog "starting webapi-server" + HOME=/recalbox/share/system start-stop-daemon -S -q -m -p "$WBPIDFILE" --exec "$WBBIN" & + fi + ;; + stop) + recallog "Stopping webapi-server" + start-stop-daemon -K -q -p "${WBPIDFILE}" + ;; + restart|reload) + "$0" stop + if [ -f "${PIDFILE}" ] ; then + while `"$0" status > /dev/null` ; do + sleep 0.1 + done + fi + "$0" start + ;; + status) + WBPID=`cat ${WBPIDFILE} 2>/dev/null` + if [ -f "${WBPIDFILE}" ] && `ps | grep -qE "^[[:space:]]*${WBPID}"` ; then + echo "webapi-server is running (pid `cat ${WBPIDFILE}`)" + exit 0 + else + echo "webapi-server is stopped" + exit 1 + fi + ;; + *) + echo "Usage: $0 {start|stop|restart}" + exit 1 +esac + +exit $? diff --git a/configs/recalbox-odroidgo2_defconfig b/configs/recalbox-odroidgo2_defconfig index 0d40ddc4d04e5ba06d5f61e146f987a1e15a910d..86e1669ef28ca537f73fa3574df9feef056daf1a 100644 --- a/configs/recalbox-odroidgo2_defconfig +++ b/configs/recalbox-odroidgo2_defconfig @@ -160,6 +160,7 @@ BR2_PACKAGE_RECALBOX_CONFIGGEN=y BR2_PACKAGE_RECALBOX_INITRAMFS=y BR2_PACKAGE_RECALBOX_MANAGER2=y BR2_PACKAGE_RECALBOX_ROMFS2=y +BR2_PACKAGE_RECALBOX_MANAGER3=y BR2_PACKAGE_RECALBOX_SYSTEM=y BR2_PACKAGE_RECALBOX_TARGET_ODROIDGO2=y BR2_PACKAGE_RECALBOX_THEMES=y diff --git a/configs/recalbox-odroidxu4_defconfig b/configs/recalbox-odroidxu4_defconfig index 6e5a75eea570b6fbefde2c71b19ac4e2d48deb72..b17873e649fad97c5f1c57128dfb65ba59d35d73 100644 --- a/configs/recalbox-odroidxu4_defconfig +++ b/configs/recalbox-odroidxu4_defconfig @@ -175,6 +175,7 @@ BR2_PACKAGE_MOONLIGHT_EMBEDDED=y BR2_PACKAGE_RECALBOX_CONFIGGEN=y BR2_PACKAGE_RECALBOX_MANAGER2=y BR2_PACKAGE_RECALBOX_ROMFS2=y +BR2_PACKAGE_RECALBOX_MANAGER3=y BR2_PACKAGE_RECALBOX_SYSTEM=y BR2_PACKAGE_RECALBOX_TFT_ASSETS=y BR2_PACKAGE_RECALBOX_TARGET_ODROIDXU4=y diff --git a/configs/recalbox-rpi1_defconfig b/configs/recalbox-rpi1_defconfig index 4ad8b65fc6835fa3390675f33eda0f7f06377376..165ea8d814c965b43c01ece6e1965e07feece7d1 100644 --- a/configs/recalbox-rpi1_defconfig +++ b/configs/recalbox-rpi1_defconfig @@ -146,6 +146,7 @@ BR2_PACKAGE_RECALBOX_CONFIGGEN=y BR2_PACKAGE_RECALBOX_INITRAMFS=y BR2_PACKAGE_RECALBOX_MANAGER2=y BR2_PACKAGE_RECALBOX_ROMFS2=y +BR2_PACKAGE_RECALBOX_MANAGER3=y BR2_PACKAGE_RECALBOX_SYSTEM=y BR2_PACKAGE_RECALBOX_TARGET_RPI1=y BR2_PACKAGE_RECALBOX_TFT_ASSETS=y diff --git a/configs/recalbox-rpi3_defconfig b/configs/recalbox-rpi3_defconfig index ff91805ecf42acbf2bb0154b2d5465a37173f502..35774e1c36db97b24825bffbc679ef1c808cd12a 100644 --- a/configs/recalbox-rpi3_defconfig +++ b/configs/recalbox-rpi3_defconfig @@ -184,6 +184,7 @@ BR2_PACKAGE_RASPI_GPIO=y BR2_PACKAGE_RECALBOX_CONFIGGEN=y BR2_PACKAGE_RECALBOX_MANAGER2=y BR2_PACKAGE_RECALBOX_ROMFS2=y +BR2_PACKAGE_RECALBOX_MANAGER3=y BR2_PACKAGE_RECALBOX_SYSTEM=y BR2_PACKAGE_RECALBOX_TFT_ASSETS=y BR2_PACKAGE_RECALBOX_TARGET_RPI3=y diff --git a/configs/recalbox-rpi4_defconfig b/configs/recalbox-rpi4_defconfig index 65036bdfc302be8b01cb5f4002c12f09039c0c3c..f5ad1d6e532e2c9ebcad3fc02a5fc55b5bfc0c61 100644 --- a/configs/recalbox-rpi4_defconfig +++ b/configs/recalbox-rpi4_defconfig @@ -174,6 +174,7 @@ BR2_PACKAGE_RASPI_GPIO=y BR2_PACKAGE_RECALBOX_CONFIGGEN=y BR2_PACKAGE_RECALBOX_MANAGER2=y BR2_PACKAGE_RECALBOX_ROMFS2=y +BR2_PACKAGE_RECALBOX_MANAGER3=y BR2_PACKAGE_RECALBOX_SYSTEM=y BR2_PACKAGE_RECALBOX_TFT_ASSETS=y BR2_PACKAGE_RECALBOX_TARGET_RPI4=y diff --git a/configs/recalbox-rpizero2_defconfig b/configs/recalbox-rpizero2_defconfig index d17ffa2aecc2e3d8e1e5bb51708e7025d7c4c207..41ab88e829f34124421b0a2a4cfd1a84d3f89325 100644 --- a/configs/recalbox-rpizero2_defconfig +++ b/configs/recalbox-rpizero2_defconfig @@ -184,6 +184,7 @@ BR2_PACKAGE_RASPI_GPIO=y BR2_PACKAGE_RECALBOX_CONFIGGEN=y BR2_PACKAGE_RECALBOX_MANAGER2=y BR2_PACKAGE_RECALBOX_ROMFS2=y +BR2_PACKAGE_RECALBOX_MANAGER3=y BR2_PACKAGE_RECALBOX_SYSTEM=y BR2_PACKAGE_RECALBOX_TFT_ASSETS=y BR2_PACKAGE_RECALBOX_TARGET_RPIZERO2=y diff --git a/configs/recalbox-x86_64_defconfig b/configs/recalbox-x86_64_defconfig index 5c94883b4d1562d50075772391acf59179e3d6af..c87ae7b5a14b6a39b2495eb5aac958aeb45fd7d4 100644 --- a/configs/recalbox-x86_64_defconfig +++ b/configs/recalbox-x86_64_defconfig @@ -224,6 +224,7 @@ BR2_PACKAGE_MOONLIGHT_EMBEDDED=y BR2_PACKAGE_RECALBOX_CONFIGGEN=y BR2_PACKAGE_RECALBOX_INITRAMFS=y BR2_PACKAGE_RECALBOX_MANAGER2=y +BR2_PACKAGE_RECALBOX_MANAGER3=y BR2_PACKAGE_RECALBOX_MPLAYER=y BR2_PACKAGE_NVIDIA_DRIVER_LEGACY_RECALBOX=y BR2_PACKAGE_NVIDIA_DRIVER_LEGACY_RECALBOX_XORG=y diff --git a/package/recalbox-manager3/Config.in b/package/recalbox-manager3/Config.in new file mode 100644 index 0000000000000000000000000000000000000000..ae5f42aa28f07de686a05627c9928f4ff47edd02 --- /dev/null +++ b/package/recalbox-manager3/Config.in @@ -0,0 +1,9 @@ +config BR2_PACKAGE_RECALBOX_MANAGER3 + bool "recalbox webmanager v3" + depends on BR2_INSTALL_LIBSTDCPP + help + Recalbox WebManager v3 + https://gitlab.com/recalbox/recalbox-manager-v3 + +comment "RECALBOX_MANAGER3 needs a toolchain w/ C++" + depends on !BR2_INSTALL_LIBSTDCPP diff --git a/package/recalbox-manager3/recalbox-manager3.mk b/package/recalbox-manager3/recalbox-manager3.mk new file mode 100644 index 0000000000000000000000000000000000000000..f98a562f80261745b41014dc9274e8e6f0bd999c --- /dev/null +++ b/package/recalbox-manager3/recalbox-manager3.mk @@ -0,0 +1,29 @@ +################################################################################ +# +# RECALBOX_MANAGER3 +# +################################################################################ + +ifneq ($(GITLAB_TOKEN_WEBMANAGER3),) +RECALBOX_MANAGER3_VERSION = 53a14a166c6261fec8134186fccf15c8fa7099a2 +RECALBOX_MANAGER3_SITE = https://gitlab-ci-token:$(GITLAB_TOKEN_WEBMANAGER3)@gitlab.com/recalbox/recalbox-manager-v3.git +else +RECALBOX_MANAGER3_VERSION = b1f66e32d41a84a9e05786b4c0b5ee4bc9783922 +RECALBOX_MANAGER3_SITE = https://gitlab.com/Bkg2k/recalbox-manager-v3-empty.git +endif +RECALBOX_MANAGER3_SITE_METHOD = git + +define RECALBOX_MANAGER3_BUILD_CMDS + if [ -f $(@D)/package-lock.json ] ; then rm $(@D)/package-lock.json ; fi + cd $(@D) && ls && \ + HOME=/tmp yarn install && \ + HOME=/tmp quasar build +endef + +define RECALBOX_MANAGER3_INSTALL_TARGET_CMDS + mkdir -p $(TARGET_DIR)/recalbox/web/manager-v3 + cp -r $(@D)/dist/spa/* \ + $(TARGET_DIR)/recalbox/web/manager-v3 +endef + +$(eval $(generic-package)) diff --git a/projects/frontend/CMakeLists.txt b/projects/frontend/CMakeLists.txt index 45f947f5f5dfd29c5adfa7010b5bb240cf83873f..457e051b1f82852c926de229552906700a1f2ec4 100644 --- a/projects/frontend/CMakeLists.txt +++ b/projects/frontend/CMakeLists.txt @@ -192,15 +192,15 @@ if(MSGFMT_EXECUTABLE AND MSGMERGE_EXECUTABLE AND XGETTEXT_EXECUTABLE) add_subdirectory (locale) endif() -# pad to keyb -#add_subdirectory(tool-pad2keyb) - # Tests add_subdirectory(tests) # md5zip add_subdirectory(tool-md5zip) +# WebServer +add_subdirectory(server-webapi) + message( "=========================================================================================================\n" "\n" diff --git a/projects/frontend/README.md b/projects/frontend/README.md index 5e4a9b1c3b29b6da37214687315febc8872ba7fd..1081625ab4a77b72b79dad62a47c5949fd9c88f2 100644 --- a/projects/frontend/README.md +++ b/projects/frontend/README.md @@ -1,5 +1,10 @@ Recalbox Emulationstation ================ + +Moved to the monorepo : https://gitlab.com/recalbox/recalbox/-/tree/master/projects/frontend/ + + + Your are on the Emulationstation github for the recalbox. The frontend has been modified for the need of the recalbox. diff --git a/projects/frontend/es-app/src/MainRunner.cpp b/projects/frontend/es-app/src/MainRunner.cpp index 2955a3e8bc9bc12afe078bd0face7d98b601b35f..0c8a043a733d2549d2f6a51368020e770897fad8 100644 --- a/projects/frontend/es-app/src/MainRunner.cpp +++ b/projects/frontend/es-app/src/MainRunner.cpp @@ -204,24 +204,28 @@ MainRunner::ExitState MainRunner::Run() switch(exitState) { case ExitState::Quit: - case ExitState::FatalError: NotificationManager::Instance().Notify(Notification::Quit, exitState == ExitState::FatalError ? "fatalerror" : "quitrequested"); break; + case ExitState::FatalError: mNotificationManager.Notify(Notification::Quit, exitState == ExitState::FatalError ? "fatalerror" : "quitrequested"); break; case ExitState::Relaunch: - case ExitState::RelaunchNoUpdate: NotificationManager::Instance().Notify(Notification::Relaunch); break; + case ExitState::RelaunchNoUpdate: mNotificationManager.Notify(Notification::Relaunch); break; case ExitState::NormalReboot: case ExitState::FastReboot: { - NotificationManager::Instance().Notify(Notification::Reboot, exitState == ExitState::FastReboot ? "fast" : "normal"); + mNotificationManager.Notify(Notification::Reboot, exitState == ExitState::FastReboot ? "fast" : "normal"); board.OnRebootOrShutdown(); break; } case ExitState::Shutdown: case ExitState::FastShutdown: { - NotificationManager::Instance().Notify(Notification::Shutdown, exitState == ExitState::FastShutdown ? "fast" : "normal"); + mNotificationManager.Notify(Notification::Shutdown, exitState == ExitState::FastShutdown ? "fast" : "normal"); board.OnRebootOrShutdown(); break; } } + // Wait for all notifications to be processed before + // main objects are destroyed + mNotificationManager.WaitCompletion(); + return exitState; } catch(std::exception& ex) diff --git a/projects/frontend/es-app/src/bios/Bios.cpp b/projects/frontend/es-app/src/bios/Bios.cpp index d7dfc7225f13b2f07da4b4d6afd57383815af2b0..0bf5820aad6592b182bf9112144798cb3c81aed7 100644 --- a/projects/frontend/es-app/src/bios/Bios.cpp +++ b/projects/frontend/es-app/src/bios/Bios.cpp @@ -188,10 +188,21 @@ std::string Bios::Filename(bool shorten) const return result; } +bool Bios::IsMD5Known(const std::string& newmd5) const +{ + Md5Hash md5(newmd5); + if (md5.IsValid()) + for(const Md5Hash& hash : mHashes) + if (hash.IsValid()) + if (hash.IsMatching(md5)) + return true; + + return false; +} + std::string Bios::GenerateReport() const { std::string report; - switch(mStatus) { case Status::FileNotFound: diff --git a/projects/frontend/es-app/src/bios/Bios.h b/projects/frontend/es-app/src/bios/Bios.h index 4a3530b53d0a6b0865d4cb89778fe000c4bd2f3c..89007a400cfd1ec04e3efa626aa66c69ede7724a 100644 --- a/projects/frontend/es-app/src/bios/Bios.h +++ b/projects/frontend/es-app/src/bios/Bios.h @@ -102,7 +102,7 @@ class Bios * @param other Md5 to compare to * @return True if both hashes match */ - bool IsMatching(const Md5Hash& other) + bool IsMatching(const Md5Hash& other) const { return memcmp(mBytes, other.mBytes, sizeof(mBytes)) == 0; } @@ -218,6 +218,14 @@ class Bios return "unknown"; } + /*! + * @brief Check the given md5 against the known md5 list + * and return true if a matching is found + * @param md5 MD5 to check + * @return True if the given md5 match one of the known MD5 + */ + bool IsMD5Known(const std::string& md5) const; + /*! * @brief Get bios name * @param shorten Get a short name instead of the long path diff --git a/projects/frontend/es-app/src/bios/BiosManager.cpp b/projects/frontend/es-app/src/bios/BiosManager.cpp index 84eec5f31b0592b9e4e0bc5d4fde157fe137fce6..bc5a1a76f75768e30f9ec5d44752e36c7ce83ef1 100644 --- a/projects/frontend/es-app/src/bios/BiosManager.cpp +++ b/projects/frontend/es-app/src/bios/BiosManager.cpp @@ -112,6 +112,37 @@ const BiosList& BiosManager::SystemBios(const std::string& name) return sEmptyBiosList; } +BiosManager::LookupResult BiosManager::Lookup(const std::string& name, const std::string& md5, const Bios*& outputBios) +{ + outputBios = nullptr; + + // MD5 first + for(const BiosList& biosList : mSystemBiosList) + for(int i = biosList.BiosCount(); --i >= 0; ) + { + const Bios& bios = biosList.BiosAt(i); + if (bios.IsMD5Known(md5)) + { + outputBios = &bios; + return (bios.BiosStatus() == Bios::Status::HashMatching) ? LookupResult::AlreadyExists : LookupResult::Found; + } + } + + // Name & no matching mandatory + for(const BiosList& biosList : mSystemBiosList) + for(int i = biosList.BiosCount(); --i >= 0; ) + { + const Bios& bios = biosList.BiosAt(i); + if (name == bios.Filepath().Filename() && !bios.IsHashMatchingMandatory()) + { + outputBios = &bios; + return LookupResult::Found; + } + } + + return LookupResult::NotFound; +} + void BiosManager::GenerateReport() const { std::string report = "==============================================================\r\n" @@ -121,7 +152,6 @@ void BiosManager::GenerateReport() const "==============================================================\r\n\r\n"; Strings::ReplaceAllIn(report, "#ARCH#", Files::LoadFile(Path("/recalbox/recalbox.arch"))); Strings::ReplaceAllIn(report, "#DATE#", DateTime().ToLongFormat()); - for(const BiosList& biosList : mSystemBiosList) { std::string subReport = biosList.GenerateReport(); @@ -131,4 +161,3 @@ void BiosManager::GenerateReport() const Files::SaveFile(RootFolders::DataRootFolder / sReportPath, report); } - diff --git a/projects/frontend/es-app/src/bios/BiosManager.h b/projects/frontend/es-app/src/bios/BiosManager.h index f0f71a3c69431383ded5e93a545df3bfae63e537..4c1602f2da80a67ce385929ed22360905e3def98 100644 --- a/projects/frontend/es-app/src/bios/BiosManager.h +++ b/projects/frontend/es-app/src/bios/BiosManager.h @@ -84,6 +84,13 @@ class BiosManager : public StaticLifeCycleControler #endif public: + enum LookupResult + { + NotFound, //!< Bios name not found or MD5 does not match any known md5 + Found, //!< Bios has been found either by name or MD5 + AlreadyExists, //!< The given bios already exists with the same + }; + /*! * @brief Default constructor */ @@ -122,6 +129,14 @@ class BiosManager : public StaticLifeCycleControler */ void Scan(IBiosScanReporting* reporting, bool sync = false); + /*! + * @brief Try to lookup the given bios in all systems + * @param name Bios name + * @param md5 Bios md5 + * @return LookupResult and outputPath set to bios path or empty path + */ + LookupResult Lookup(const std::string& name, const std::string& md5, const Bios*& outputBios); + /*! * @brief Generate missing bios report in bios root folder */ diff --git a/projects/frontend/es-app/src/emulators/EmulatorDescriptor.h b/projects/frontend/es-app/src/emulators/EmulatorDescriptor.h index 2a798ec4f7ca3b0e882aaacc8a9e15dcf726777b..5538df643550e94b84520b410c4d293acbaaeeb0 100644 --- a/projects/frontend/es-app/src/emulators/EmulatorDescriptor.h +++ b/projects/frontend/es-app/src/emulators/EmulatorDescriptor.h @@ -166,6 +166,10 @@ class EmulatorDescriptor //! Core count int mCoreCount; + //! Give access to private part from the webmanager process class + friend class RequestHandlerTools; + + //! Get core at index const Core& CoreAt(int index) const { return (unsigned int)index < (unsigned int)mCoreCount ? mCores[index] : mCores[0]; } /*! diff --git a/projects/frontend/es-app/src/mqtt/IMqttReceiver.h b/projects/frontend/es-app/src/mqtt/IMqttReceiver.h new file mode 100644 index 0000000000000000000000000000000000000000..5fb66affc6b861a9a25c37f1f12e22db0b17711f --- /dev/null +++ b/projects/frontend/es-app/src/mqtt/IMqttReceiver.h @@ -0,0 +1,10 @@ +// +// Created by bkg2k on 30/04/2020. +// +#pragma once + +class IMqttReceiver +{ + public: + virtual void MqttReceive(const std::string& topic, const std::string& payload) = 0; +}; diff --git a/projects/frontend/es-app/src/mqtt/MqttClient.cpp b/projects/frontend/es-app/src/mqtt/MqttClient.cpp index b922e87cc5efa21e890b81cd5017aade6635e861..01241220c14c522d270189ae506297a300ea98f6 100644 --- a/projects/frontend/es-app/src/mqtt/MqttClient.cpp +++ b/projects/frontend/es-app/src/mqtt/MqttClient.cpp @@ -5,8 +5,9 @@ #include "MqttClient.h" #include -MqttClient::MqttClient(const char* clientId) - : mMqtt("tcp://127.0.0.1:1883", clientId, 0, nullptr) +MqttClient::MqttClient(const char* clientId, IMqttReceiver* receiver) + : mMqtt("tcp://127.0.0.1:1883", clientId, 0, nullptr), + mReceiver(receiver) { // Set options mqtt::connect_options connectOptions; @@ -17,6 +18,7 @@ MqttClient::MqttClient(const char* clientId) try { mOriginalTocken = mMqtt.connect(connectOptions, nullptr, *this); + mMqtt.start_consuming(); } catch(std::exception& e) { @@ -24,6 +26,11 @@ MqttClient::MqttClient(const char* clientId) } } +MqttClient::~MqttClient() +{ + mMqtt.stop_consuming(); +} + bool MqttClient::Send(const std::string& topic, const std::string& message) { try @@ -96,3 +103,17 @@ void MqttClient::on_success(const mqtt::token& asyncActionToken) } } +bool MqttClient::Subscribe(const char* topic) +{ + try + { + mMqtt.subscribe(topic, 0, nullptr, *this); + mMqtt.set_message_callback([this](mqtt::const_message_ptr msg) { if (mReceiver != nullptr) mReceiver->MqttReceive(msg->get_topic(), msg->get_payload_str()); }); + return true; + } + catch(std::exception& e) + { + LOG(LogError) << "[MQTT] Sending messageConnexion to " << mMqtt.get_server_uri() << " from " << mMqtt.get_client_id() << " failed (send) !"; + } + return false; +} diff --git a/projects/frontend/es-app/src/mqtt/MqttClient.h b/projects/frontend/es-app/src/mqtt/MqttClient.h index 27e3d1b0b30a6d556363fe52604aef54e3bd7177..3343907bdbc3751a1d85264a0381438f1b60ac32 100644 --- a/projects/frontend/es-app/src/mqtt/MqttClient.h +++ b/projects/frontend/es-app/src/mqtt/MqttClient.h @@ -5,6 +5,7 @@ #include #include "TcpNetwork.h" +#include "IMqttReceiver.h" #include #include #include @@ -23,13 +24,21 @@ class MqttClient : mqtt::iaction_listener //! Initial connection token mqtt::token_ptr mOriginalTocken; + //! Receiver + IMqttReceiver* mReceiver; + public: /*! * @brief Default constructor + * @param clientid Client ID + * @param receiver Receiver interface */ - explicit MqttClient(const char* clientid); + explicit MqttClient(const char* clientid, IMqttReceiver* receiver = nullptr); - ~MqttClient() override = default; + /*! + * @brief Default destructor + */ + ~MqttClient() override; /*! * @brief Send a string to the specified topic @@ -39,33 +48,21 @@ class MqttClient : mqtt::iaction_listener */ bool Send(const std::string& topic, const std::string& data); + /*! + * @brief Subscribe to a topic + * @param topic Topic to subscibe to + * @return True if the subscribing is successful, false otherwise + */ + bool Subscribe(const char* topic); + private: /* * mqtt::iaction_listener implementation */ - // Connection failure + //! Connection failure void on_failure(const mqtt::token& asyncActionToken) override; - // Connection success + //! Connection success void on_success(const mqtt::token& asyncActionToken) override; - - /*! - * @brief Subscribe and set callback to the given method - * @tparam T Callback's class - * @param topic Topic to subscibe to - * @param item - * @param method - */ - /*template void Subscribe(const char* topic, T* item, void (T::*method)(MQTT::MessageData&)) - { - EnsureConnection(); - if (subscribe(topic, MQTT::QOS0, item, method) != 0) - { - { LOG(LogError) << "Error subscribing to: " << topic; } - return; - } - { LOG(LogInfo) << "Subscribed to: " << topic; } - - }*/ }; diff --git a/projects/frontend/es-app/src/usernotifications/NotificationManager.cpp b/projects/frontend/es-app/src/usernotifications/NotificationManager.cpp index 3edbc056d13b12666b0c616364f3c2c0845ed921..756d38adc85240383a986a075206018d01ff0f71 100644 --- a/projects/frontend/es-app/src/usernotifications/NotificationManager.cpp +++ b/projects/frontend/es-app/src/usernotifications/NotificationManager.cpp @@ -19,11 +19,18 @@ const Path NotificationManager::sStatusFilePath("/tmp/es_state.inf"); HashMap NotificationManager::sPermanentScriptsPID; NotificationManager::NotificationManager(char** environment) - : StaticLifeCycleControler("NotificationManager"), - mMQTTClient("recalbox-emulationstation"), - mEnvironment(environment) + : StaticLifeCycleControler("NotificationManager") + , mMQTTClient("recalbox-emulationstation") + , mEnvironment(environment) + , mProcessing(false) { LoadScriptList(); + Thread::Start("EventNotifier"); +} + +NotificationManager::~NotificationManager() +{ + Thread::Stop(); } const char* NotificationManager::ActionToString(Notification action) @@ -179,6 +186,63 @@ void NotificationManager::RunScripts(Notification action, const std::string& par } } +JSONBuilder NotificationManager::BuildJsonPacket(const NotificationManager::NotificationRequest& request) +{ + std::string emulator; + std::string core; + JSONBuilder builder; + + builder.Open(); + + // Action + builder.Field("Action", ActionToString(request.mAction)) + .Field("Parameter", request.mActionParameters) + .Field("Version", LEGACY_STRING("2.0")); + // System + if (request.mSystemData != nullptr) + { + builder.OpenObject("System") + .Field("System", request.mAction == Notification::RunKodi ? "kodi" : request.mSystemData->FullName()) + .Field("SystemId", request.mAction == Notification::RunKodi ? "kodi" : request.mSystemData->Name()); + if (!request.mSystemData->IsVirtual()) + if (request.mSystemData->Manager().Emulators().GetDefaultEmulator(*request.mSystemData, emulator, core)) + builder.OpenObject("DefaultEmulator") + .Field("Emulator", emulator) + .Field("Core", core) + .CloseObject(); + builder.CloseObject(); + } + // Game + if (request.mFileData != nullptr) + { + builder.OpenObject("Game") + .Field("Game", request.mFileData->Name()) + .Field("GamePath", request.mFileData->RomPath().ToString()) + .Field("IsFolder", request.mFileData->IsFolder()) + .Field("ImagePath", request.mFileData->Metadata().Image().ToString()) + .Field("ThumbnailPath", request.mFileData->Metadata().Thumbnail().ToString()) + .Field("VideoPath", request.mFileData->Metadata().Video().ToString()) + .Field("Developer", request.mFileData->Metadata().Developer()) + .Field("Publisher", request.mFileData->Metadata().Publisher()) + .Field("Players", request.mFileData->Metadata().PlayersAsString()) + .Field("Region", request.mFileData->Metadata().RegionAsString()) + .Field("Genre", request.mFileData->Metadata().Genre()) + .Field("GenreId", request.mFileData->Metadata().GenreIdAsString()) + .Field("Favorite", request.mFileData->Metadata().Favorite()) + .Field("Hidden", request.mFileData->Metadata().Hidden()) + .Field("Adult", request.mFileData->Metadata().Adult()); + if (request.mFileData->System().Manager().Emulators().GetGameEmulator(*request.mFileData, emulator, core)) + builder.OpenObject("SelectedEmulator") + .Field("Emulator", emulator) + .Field("Core", core) + .CloseObject(); + builder.CloseObject(); + } + builder.Close(); + + return builder; +} + void NotificationManager::BuildStateCommons(std::string& output, const SystemData* system, const FileData* game, Notification action, const std::string& actionParameters) { // Build status file @@ -278,36 +342,74 @@ void NotificationManager::BuildStateCompatibility(std::string& output, Notificat } } -void NotificationManager::Notify(const SystemData* system, const FileData* game, Notification action, const std::string& actionParameters) +void NotificationManager::Break() { -// why ? -// if (VideoEngine::IsInstantiated()) -// VideoEngine::Instance().StopVideo(); - - const std::string& notificationParameter = (game != nullptr) ? game->RomPath().ToString() : - ((system != nullptr) ? system->Name() : actionParameters); + mSignal.Fire(); +} - // Check if it is the same event than in previous call - ParamBag newBag(system, game, action, actionParameters); - if (newBag != mPreviousParamBag) +void NotificationManager::Run() +{ + mSignal.WaitSignal(); + if (IsRunning()) { - // Build all - std::string output("Version=2.0"); output.append(eol); - BuildStateCommons(output, system, game, action, actionParameters); - BuildStateGame(output, game, action); - BuildStateSystem(output, system, action); - BuildStateCompatibility(output, action); + // Get request + mSyncer.Lock(); + mProcessing = true; + NotificationRequest* request = !mRequestQueue.Empty() ? mRequestQueue.Pop() : nullptr; + mSyncer.UnLock(); + + // Process + if (request != nullptr) + { + if (*request != mPreviousRequest) + { + // Build all + std::string output("Version=2.0"); output.append(eol); + BuildStateCommons(output, request->mSystemData, request->mFileData, request->mAction, request->mActionParameters); + BuildStateGame(output, request->mFileData, request->mAction); + BuildStateSystem(output, request->mSystemData, request->mAction); + BuildStateCompatibility(output, request->mAction); + // Save + Files::SaveFile(Path(sStatusFilePath), output); + // MQTT notification + mMQTTClient.Send(sEventTopic, ActionToString(request->mAction)); + + // Build json event + JSONBuilder json = BuildJsonPacket(*request); + // MQTT notification + mMQTTClient.Send(sEventJsonTopic, json); + + // Run scripts + const std::string& notificationParameter = (request->mFileData != nullptr) ? request->mFileData->RomPath().ToString() : + ((request->mSystemData != nullptr) ? request->mSystemData->Name() : request->mActionParameters); + RunScripts(request->mAction, notificationParameter); + + mPreviousRequest = *request; + } - // Save - Files::SaveFile(Path(sStatusFilePath), output); + // Recycle + mRequestProvider.Recycle(request); + } - // MQTT notification - mMQTTClient.Send(sEventTopic, ActionToString(action)); + // En processing + mSyncer.Lock(); + mProcessing = true; + mSyncer.UnLock(); + } +} - // Run scripts - RunScripts(action, notificationParameter); +void NotificationManager::Notify(const SystemData* system, const FileData* game, Notification action, const std::string& actionParameters) +{ + // Build new parameter bag + NotificationRequest* request = mRequestProvider.Obtain(); + request->Set(system, game, action, actionParameters); + + // Push new param bag + { + Mutex::AutoLock locker(mSyncer); + mRequestQueue.Push(request); + mSignal.Fire(); } - mPreviousParamBag = newBag; } void NotificationManager::RunProcess(const Path& target, const Strings::Vector& arguments, bool synchronous, bool permanent) @@ -378,4 +480,14 @@ bool NotificationManager::HasValidExtension(const Path& path) (ext == ".py3"); } - +void NotificationManager::WaitCompletion() +{ + for(;;) + { + mSyncer.Lock(); + bool havePendings = !mRequestQueue.Empty(); + mSyncer.UnLock(); + if (!havePendings && !mProcessing) break; + Thread::Sleep(100); + } +} diff --git a/projects/frontend/es-app/src/usernotifications/NotificationManager.h b/projects/frontend/es-app/src/usernotifications/NotificationManager.h index 0894c4b1f9bfc8bda4074747553b145a9372185c..ef8c118e84ea7bd8f7b3afe3fafaf1f3251f5f70 100644 --- a/projects/frontend/es-app/src/usernotifications/NotificationManager.h +++ b/projects/frontend/es-app/src/usernotifications/NotificationManager.h @@ -4,8 +4,12 @@ #pragma once #include +#include +#include #include #include +#include +#include enum class Notification { @@ -35,7 +39,7 @@ enum class Notification DEFINE_BITFLAG_ENUM(Notification, int) -class NotificationManager : public StaticLifeCycleControler +class NotificationManager : public StaticLifeCycleControler, private Thread { private: /*! @@ -54,9 +58,9 @@ class NotificationManager : public StaticLifeCycleControler typedef std::vector RefScriptList; /*! - * @brief Struture to hold a bag of parameters + * @brief Hold a notification request */ - struct ParamBag + struct NotificationRequest { //! Action parameters std::string mActionParameters; @@ -70,7 +74,7 @@ class NotificationManager : public StaticLifeCycleControler /*! * @brief Default constructor */ - ParamBag() + NotificationRequest() : mActionParameters(), mSystemData(nullptr), mFileData(nullptr), @@ -79,18 +83,30 @@ class NotificationManager : public StaticLifeCycleControler } /*! - * @brief Full constructor + * @brief Sata data * @param systemData System * @param fileData Game * @param action Action * @param actionParameters Optional action parameters */ - ParamBag(const SystemData* systemData, const FileData* fileData, Notification action, const std::string& actionParameters) - : mActionParameters(actionParameters), - mSystemData(systemData), - mFileData(fileData), - mAction(action) + void Set(const SystemData* systemData, const FileData* fileData, Notification action, const std::string& actionParameters) { + mActionParameters = actionParameters; + mSystemData = systemData; + mFileData = fileData; + mAction = action; + } + + NotificationRequest& operator =(const NotificationRequest& source) + { + if (&source != this) + { + mActionParameters = source.mActionParameters; + mSystemData = source.mSystemData; + mFileData = source.mFileData; + mAction = source.mAction; + } + return *this; } /*! @@ -98,7 +114,7 @@ class NotificationManager : public StaticLifeCycleControler * @param compareTo Structure to compare against * @return True if at least one field is not equal */ - bool operator != (const ParamBag& compareTo) const + bool operator !=(const NotificationRequest& compareTo) const { return ((mAction != compareTo.mAction) || (mSystemData != compareTo.mSystemData) || @@ -110,8 +126,10 @@ class NotificationManager : public StaticLifeCycleControler //! Script folder static constexpr const char* sScriptPath = "/recalbox/share/userscripts"; - //! MQTT Topic - static constexpr const char* sEventTopic = "Recalbox/EmulationStation/Event"; + //! MQTT Topic - event only + static constexpr const char* sEventTopic = "Recalbox/EmulationStation/Event"; + //! MQTT Topic - complete event w/ data in JSON form + static constexpr const char* sEventJsonTopic = "Recalbox/EmulationStation/EventJson"; // MQTT client MqttClient mMQTTClient; @@ -125,12 +143,23 @@ class NotificationManager : public StaticLifeCycleControler //! All available scripts ScriptList mScriptList; + //! Request provider + MessageFactory mRequestProvider; + //! Request synchronized queue + Queue mRequestQueue; + //! Queue syncer' + Mutex mSyncer; + //! Thread signal + Signal mSignal; //! Previous data - ParamBag mPreviousParamBag; + NotificationRequest mPreviousRequest; //! Environement char** mEnvironment; + //! In-process flag + volatile bool mProcessing; + /*! * @brief Convert an Action into a string * @param action Action to convert @@ -244,6 +273,24 @@ class NotificationManager : public StaticLifeCycleControler * @return True if the path has a valid extension */ static bool HasValidExtension(const Path& path); + + /*! + * @brief Build a JSON packet from the given request + * @param request Request to process + * @return JSon packet + */ + static JSONBuilder BuildJsonPacket(const NotificationRequest& request); + + /* + * Thread implementation + */ + + //! Stop the thread + void Break() override; + + //! Main runner + void Run() override; + public: /*! @@ -251,6 +298,12 @@ class NotificationManager : public StaticLifeCycleControler */ explicit NotificationManager(char* environement[]); + //! Destructor + ~NotificationManager() override; + + //! Wait for the queue to be empty + void WaitCompletion(); + /*! * @brief Start Kodi notification */ diff --git a/projects/frontend/es-core/src/CrtConf.cpp b/projects/frontend/es-core/src/CrtConf.cpp index f35f9cf73a9195aac8146f302d03cdfbd3818e75..89a85744e5fbfc0d87a7132d874d65f0a9c08e77 100644 --- a/projects/frontend/es-core/src/CrtConf.cpp +++ b/projects/frontend/es-core/src/CrtConf.cpp @@ -16,7 +16,7 @@ CrtConf::CrtConf() { } -void CrtConf::OnSave() +void CrtConf::OnSave() const { NotificationManager::Instance().Notify(Notification::ConfigurationChanged, crtConfFile.ToString()); } diff --git a/projects/frontend/es-core/src/CrtConf.h b/projects/frontend/es-core/src/CrtConf.h index 4f79849eaef52e7a279b98fe6bec2d966b044789..19db43bf75b4455539ff73c677f5f0fa5b09e95a 100644 --- a/projects/frontend/es-core/src/CrtConf.h +++ b/projects/frontend/es-core/src/CrtConf.h @@ -35,7 +35,7 @@ class CrtConf: public IniFile, public StaticLifeCycleControler /*! * @brief Called when file has been saved */ - void OnSave() override; + void OnSave() const override; DefineGetterSetterEnumGeneric(CrtConf, SystemCRT, CrtAdapterType, sSystemCRT, CrtAdapter) DefineGetterSetterGeneric(CrtConf, SystemCRTResolution, std::string, String, sSystemCRTResolution, "240") diff --git a/projects/frontend/es-core/src/RecalboxConf.cpp b/projects/frontend/es-core/src/RecalboxConf.cpp index 9049f9355830af8413411cbe1459b0b94b2a9c12..627460a8b8bee2e58c7fab1afeebdcd1fe7d9336 100644 --- a/projects/frontend/es-core/src/RecalboxConf.cpp +++ b/projects/frontend/es-core/src/RecalboxConf.cpp @@ -12,7 +12,7 @@ RecalboxConf::RecalboxConf() { } -void RecalboxConf::OnSave() +void RecalboxConf::OnSave() const { NotificationManager::Instance().Notify(Notification::ConfigurationChanged, recalboxConfFile.ToString()); } diff --git a/projects/frontend/es-core/src/RecalboxConf.h b/projects/frontend/es-core/src/RecalboxConf.h index 3acd58d8b06c112a15062a35024afccc50c8ab66..2551e7d5d9713c5b048127c7fdb760a907d2639f 100644 --- a/projects/frontend/es-core/src/RecalboxConf.h +++ b/projects/frontend/es-core/src/RecalboxConf.h @@ -28,7 +28,7 @@ class RecalboxConf: public IniFile, public StaticLifeCycleControler #include #include #include "utils/Log.h" @@ -103,6 +103,7 @@ bool IniFile::Save() // Write new kay/value std::string key = it.first; std::string val = it.second; + bool deleted = (it.second == sDeleteTag); bool lineFound = false; bool commented = false; for (auto& line : lines) @@ -154,12 +155,30 @@ std::string IniFile::AsString(const std::string& name, const std::string& defaul return (!item.empty()) ? item : defaultValue; } +std::string IniFile::AsString(const char* name) const +{ + std::string* item = mConfiguration.try_get(name); + return (item != nullptr) ? *item : std::string(); +} + +std::string IniFile::AsString(const char* name, const char* defaultValue) const +{ + std::string* item = mConfiguration.try_get(name); + return (item != nullptr) ? *item : defaultValue; +} + bool IniFile::AsBool(const std::string& name, bool defaultValue) const { std::string item = ExtractValue(name); return (!item.empty()) ? (item.size() == 1 && item[0] == '1') : defaultValue; } +bool IniFile::AsBool(const char* name, bool defaultValue) const +{ + std::string* item = mConfiguration.try_get(name); + return (item != nullptr) ? (item->size() == 1 && (*item)[0] == '1') : defaultValue; +} + unsigned int IniFile::AsUInt(const std::string& name, unsigned int defaultValue) const { std::string item = ExtractValue(name); @@ -186,6 +205,19 @@ int IniFile::AsInt(const std::string& name, int defaultValue) const return defaultValue; } +int IniFile::AsInt(const char* name, int defaultValue) const +{ + std::string* item = mConfiguration.try_get(name); + if (item != nullptr) + { + int value = 0; + if (Strings::ToInt(*item, value)) + return value; + } + + return defaultValue; +} + void IniFile::Delete(const std::string& name) { mPendingDelete.insert(name); diff --git a/projects/frontend/es-core/src/utils/IniFile.h b/projects/frontend/es-core/src/utils/IniFile.h index bd2ffbf04d2060b6d2e9e41c29c6b3282e78d6c8..58bd0acc4643417572d7cb3fa60c2a0d0cb52c6b 100644 --- a/projects/frontend/es-core/src/utils/IniFile.h +++ b/projects/frontend/es-core/src/utils/IniFile.h @@ -76,6 +76,21 @@ class IniFile */ std::string AsString(const std::string &name, const std::string &defaultValue) const; + /*! + * @brief Get string value from the given key + * @param name Key + * @return Value or empty string if the key does not exist + */ + std::string AsString(const char* name) const; + + /*! + * @brief Get string value from the given key or return the default value + * @param name Key + * @param defaultValue Default value + * @return Value or default value if the key does not exist + */ + std::string AsString(const char* name, const char* defaultValue) const; + /*! * @brief Get boolean value from the given key or return the default value * @param name Key @@ -84,6 +99,14 @@ class IniFile */ bool AsBool(const std::string& name, bool defaultValue = false) const; + /*! + * @brief Get boolean value from the given key or return the default value + * @param name Key + * @param defaultValue Default value (optional, false by default) + * @return Value or default value if the key does not exist + */ + bool AsBool(const char* name, bool defaultValue = false) const; + /*! * @brief Get value as unsigned int from the given key or return the default value * @param name Key @@ -100,6 +123,14 @@ class IniFile */ int AsInt(const std::string& name, int defaultValue = 0) const; + /*! + * @brief Get value as signed int from the given key or return the default value + * @param name Key + * @param defaultValue Default value (optional, 0 by default) + * @return Value or default value if the key does not exist + */ + int AsInt(const char* name, int defaultValue = 0) const; + /*! * @brief Set the value as string of the given key * @param name Key @@ -135,6 +166,13 @@ class IniFile */ void SetList(const std::string &name, const std::vector &values); + /*! + * @brief Check if the given key exists in the configuration file + * @param name Key to check + * @return True if the key exists, false otherwise + */ + bool Exists(const std::string& name) const { return mConfiguration.contains(name); } + /*! * @brief Check if a value is in the given named list * @param name Key from which to obtain the list @@ -188,9 +226,12 @@ class IniFile /*! * @brief Called after saving the file */ - virtual void OnSave() {} + virtual void OnSave() const {} private: + //! Delete tag + static constexpr const char* sDeleteTag = "\x01\x07\x03\x09"; + //! Configuration map: key, value - Read from file HashMap mConfiguration; //! Configuration map: key, value - Pending writes diff --git a/projects/frontend/es-core/src/utils/Strings.cpp b/projects/frontend/es-core/src/utils/Strings.cpp index b0aca70628e8b602530c016992daa99c7b9acaf4..9f2832faf89251573e92270ce316d83939a65180 100644 --- a/projects/frontend/es-core/src/utils/Strings.cpp +++ b/projects/frontend/es-core/src/utils/Strings.cpp @@ -951,3 +951,50 @@ std::string Strings::ToHumanSize(long long int size) size >>= 10; return ToString((float)size / 1024.f, 2).append("TB", 2); } + +static const char Base64Values[] = +{ + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, + 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 62, 00, 00, 00, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 00, 00, 00, 00, 00, 00, + 00, 00, 01, 02, 03, 04, 05, 06, 07, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 00, 00, 00, 00, 00, + 00, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 +}; + +std::string Strings::Decode64(const std::string& base64) +{ + std::string result; + + // Align on 3 char + int len = (int)base64.size() & -4; + int off7 = 0; + + // Padding? + bool padded = (base64[off7 + len - 1] == '='); + if (padded) len -= 4; + + // 3 char loop + for (int L = len >> 2; --L >= 0; off7 += 4) + { + int V = (Base64Values[(unsigned int)(unsigned char)base64[off7]] << 18) + + (Base64Values[(unsigned int)(unsigned char)base64[off7 + 1]] << 12) + + (Base64Values[(unsigned int)(unsigned char)base64[off7 + 2]] << 6) + + (Base64Values[(unsigned int)(unsigned char)base64[off7 + 3]]); + result.append(1, (char)(V >> 16)); + result.append(1, (char)(V >> 8)); + result.append(1, (char)V); + } + // remaining + if (padded) + { + int V = (Base64Values[(unsigned int)(unsigned char)base64[off7]] << 10) + + (Base64Values[(unsigned int)(unsigned char)base64[off7 + 1]] << 4) + + (Base64Values[(unsigned int)(unsigned char)base64[off7 + 2]] >> 2); + result.append(1, (char)(V >> 8)); + if (base64[off7 + 2] != '=') + result.append(1, (char)V); + } +} \ No newline at end of file diff --git a/projects/frontend/es-core/src/utils/Strings.h b/projects/frontend/es-core/src/utils/Strings.h index 480c9d42a940d19cf00d735a43f9b14416d8fb0d..ecfb7905d1a0c743db31130f7ebb41838a266b01 100644 --- a/projects/frontend/es-core/src/utils/Strings.h +++ b/projects/frontend/es-core/src/utils/Strings.h @@ -204,6 +204,8 @@ class Strings * @return String representation */ /**/ static std::string ToHumanSize(long long size); + + static std::string Decode64(const std::string& base64); }; #pragma clang diagnostic pop \ No newline at end of file diff --git a/projects/frontend/es-core/src/utils/hash/Md5.cpp b/projects/frontend/es-core/src/utils/hash/Md5.cpp index d31c90444cd2c5e8ba9e162b540d91c9bfef5e3c..29100b6c37185927f421b91a1f40a641ee455354 100644 --- a/projects/frontend/es-core/src/utils/hash/Md5.cpp +++ b/projects/frontend/es-core/src/utils/hash/Md5.cpp @@ -115,6 +115,13 @@ MD5::MD5(const std::string &text) finalize(); } +MD5::MD5(const char* text, size_type size) +{ + init(); + update(text, size); + finalize(); +} + ////////////////////////////// void MD5::init() @@ -358,5 +365,12 @@ std::string md5(const std::string& str) { MD5 md5 = MD5(str); + return md5.hexdigest(); +} + +std::string md5(const char* text, unsigned int length) +{ + MD5 md5 = MD5(text, length); + return md5.hexdigest(); } \ No newline at end of file diff --git a/projects/frontend/es-core/src/utils/hash/Md5.h b/projects/frontend/es-core/src/utils/hash/Md5.h index 96d244d1c777bfa904ebcc4c9455327d6de359c5..f26b86b14021a03d9de9dc5f753fe5a255e7dacd 100644 --- a/projects/frontend/es-core/src/utils/hash/Md5.h +++ b/projects/frontend/es-core/src/utils/hash/Md5.h @@ -81,6 +81,7 @@ class MD5 public: MD5(); MD5(const std::string& text); + MD5(const char* text, size_type length); void reset() { init(); } void update(const unsigned char *buf, size_type length); void update(const char *buf, size_type length); @@ -93,5 +94,6 @@ class MD5 }; std::string md5(const std::string& str); +std::string md5(const char* text, unsigned int length); #endif \ No newline at end of file diff --git a/projects/frontend/es-core/src/utils/json/JSONBuilder.h b/projects/frontend/es-core/src/utils/json/JSONBuilder.h new file mode 100755 index 0000000000000000000000000000000000000000..5fb9428b018fea2be327be91be7bf1490751a242 --- /dev/null +++ b/projects/frontend/es-core/src/utils/json/JSONBuilder.h @@ -0,0 +1,377 @@ +/* + * JSONBuilder.h + * + * Created on: 20 mai 2013 + * Author: Bkg2k + */ +#pragma once + +#include +#include +#include + +/*! + * A very simple JSON string builder that *exactly* suits our needs. + * It is not intended to be a complete JSON builder, and yet less a serializer! + */ +class JSONBuilder : public std::string +{ + private: + //! JSON container + std::string mString; + //! Do not insert CRLF when this flag is true + bool mOptimized; + + /*! + * Add field separator if required + */ + void FieldCheck() + { + if (!empty()) + if (back() != '{' && back() != '[') + append(1, ','); + if (!mOptimized) append(1, '\n'); + } + + /*! + * Add the stringized field name + * @param name Field name + * @return Inherited sting instance for method chaining + */ + JSONBuilder& FieldName(const char* name) + { + FieldCheck(); + append(1, '\"').append(Escape(name)).append(1, '\"'); + return *this; + } + + /*! + * @brief Escape special chars from the gigen string + * @param source Source string + * @return Escaped string + */ + static std::string Escape(const std::string& source) + { + #define __LENGTHY_STRING(x) x, (int)sizeof(x) - 1 + std::string result = source; + Strings::ReplaceAllIn(result, '\\', __LENGTHY_STRING("\\\\")); + Strings::ReplaceAllIn(result, '\b', __LENGTHY_STRING("\\b")); + Strings::ReplaceAllIn(result, '\f', __LENGTHY_STRING("\\f")); + Strings::ReplaceAllIn(result, '\n', __LENGTHY_STRING("\\n")); + Strings::ReplaceAllIn(result, '\r', __LENGTHY_STRING("\\r")); + Strings::ReplaceAllIn(result, '\t', __LENGTHY_STRING("\\t")); + Strings::ReplaceAllIn(result, '\"', __LENGTHY_STRING("\\\"")); + return result; + #undef __LENGTHY_STRING + } + + /*! + * @brief Escape special chars from the gigen string + * @param source Source string + * @return Escaped string + */ + static std::string Escape(const char* source) + { + return Escape(std::string(source)); + } + + /*! + * @brief Escape special chars from the gigen string + * @param source Source string + * @param length Source string length + * @return Escaped string + */ + static std::string Escape(const char* source, int length) + { + return Escape(std::string(source, length)); + } + + public: + /*! + * Return the inherited string + * @return String reference + */ + const std::string& GetJSONString() const { return mString; } + + + explicit JSONBuilder(bool optimized = true) + : mOptimized(optimized) + { + } + + explicit JSONBuilder(int reserved, bool optimized = true) + : mOptimized(optimized) + { + mString.reserve(reserved); + } + + /*! + * Open the whole JSON builder + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Open() + { + std::string::operator=('{'); + return *this; + } + + /*! + * Close the builder + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Close() + { + append(1, '}'); + return *this; + } + + /*! + * Open a new object + * @param name Object's name + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& OpenObject(const char* name) + { + if (name != nullptr) FieldName(name).append(1, ':'); + else FieldCheck(); + append(1, '{'); + return *this; + } + + /*! + * Close an object + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& CloseObject() { return Close(); } + + /*! + * Open a new array + * @param name Array name + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& OpenArray(const char* name) + { + FieldName(name).append(":["); + return *this; + } + + /*! + * Close an array + * @return The current JSON Instance for method chaining + */ + JSONBuilder& CloseArray() + { + append(1, ']'); + return *this; + } + + /*! + * Add a string field + * @param name Field name + * @param value Field value + * @param length Field value length + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, const char* value, int length) + { + if (name != nullptr) FieldName(name).append(":\"").append(Escape(value, length)).append(1, '"'); + return *this; + } + + /*! + * Add a string field + * @param name Field name + * @param value Field value + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, const char* value) + { + if (name != nullptr) FieldName(name).append(":\"").append(Escape(value)).append(1, '"'); + return *this; + } + + /*! + * Add a string field + * @param name Field name + * @param value Field value + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, const std::string& value) + { + if (name != nullptr) FieldName(name).append(":\"").append(Escape(value)).append(1, '"'); + return *this; + } + + /*! + * Add an integer field + * @param name Field name + * @param value Field value + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, int value) + { + if (name != nullptr) FieldName(name).append(1, ':').append(Strings::ToString(value)); + return *this; + } + + /*! + * Add a 64-bit integer field + * @param name Field name + * @param value Field value + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, long long value) + { + if (name != nullptr) FieldName(name).append(1, ':').append(Strings::ToString(value)); + return *this; + } + + /*! + * Add a float field + * @param name Field name + * @param value Field value + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, float value) + { + if (name != nullptr) FieldName(name).append(1, ':').append(Strings::ToString(value, 2)); + return *this; + } + + /*! + * Add a boolean field + * @param name Field name + * @param value Field value + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, bool value) + { + if (name != nullptr) FieldName(name).append(1, ':').append(value ? "true" : "false"); + return *this; + } + + /*! + * Add a map as a subobject + * @param name Field name + * @param value Map + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, const std::map& values) + { + if (name != nullptr) FieldName(name).append(":{"); + for(const auto& value : values) + Field(value.first.c_str(), value.second); + append(1, '}'); + return *this; + } + + /*! + * Add a string array field + * @param name Field name + * @param value Field array + * @param count value count + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, const std::vector& values) + { + if (name != nullptr) FieldName(name).append(":["); + for(const std::string& value : values) + { + if (back() != '[') append(1, ','); + append(1, '"').append(value).append(1, '"'); + } + append(1, ']'); + return *this; + } + + /*! + * Add a float array field + * @param name Field name + * @param value Field array + * @param count value count + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, const float value[], int count) + { + if (name != nullptr) FieldName(name).append(":["); + for(int i = 0; --count >= 0; ++i) + { + append(Strings::ToString(value[i], 2)); + if (count != 0) append(1, ','); + } + append(1, ']'); + return *this; + } + + /*! + * Add a long long array field + * @param name Field name + * @param value Field array + * @param count value count + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, const long long value[], int count) + { + if (name != nullptr) FieldName(name).append(":["); + for(int i = 0; --count >= 0; i++) + { + append(Strings::ToString(value[i])); + if (count != 0) append(1, ','); + } + append(1, ']'); + return *this; + } + + /*! + * Add an int array field + * @param name Field name + * @param value Field array + * @param count value count + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, const int value[], int count) + { + if (name != nullptr) FieldName(name).append(":["); + for(int i = 0; --count >= 0; i++) + { + append(Strings::ToString(value[i])); + if (count != 0) append(1, ','); + } + append(1, ']'); + return *this; + } + + /*! + * Add a hexadecimal array field + * @param name Field name + * @param value Field array + * @param count value count + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& FieldHexa(const char* name, int value[], int count) + { + if (name != nullptr) + { + OpenArray(name); + for(int i = 0; --count >= 0; i++) + { + append(1, '\"').append(Strings::ToHexa(value[i])).append(1, '\"'); + if (count != 0) append(1, ','); + } + CloseArray(); + } + return *this; + } + + /*! + * Add ab object from another JSON Builder + * @param name Field name + * @param object Object value + * @return The current JSON Instance for method chaining. + */ + JSONBuilder& Field(const char* name, const JSONBuilder& object) + { + if (name != nullptr) FieldName(name).append(1, ':').append(object); + return *this; + } +}; diff --git a/projects/frontend/external/rapidjson/CMakeLists.txt b/projects/frontend/external/rapidjson/CMakeLists.txt index 877399c9a40543721a4fb2db441d2f87acbff19e..6634c1df336f5f7e3480b51a86d03d37bc12101d 100644 --- a/projects/frontend/external/rapidjson/CMakeLists.txt +++ b/projects/frontend/external/rapidjson/CMakeLists.txt @@ -16,5 +16,4 @@ if(RAPIDJSON_HAS_STDSTRING) add_definitions(-DRAPIDJSON_HAS_STDSTRING) endif() -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) - +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) \ No newline at end of file diff --git a/projects/frontend/server-webapi/CMakeLists.txt b/projects/frontend/server-webapi/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..4fbc7c72f402e7e7a35bae0a6976f6be6cb0223d --- /dev/null +++ b/projects/frontend/server-webapi/CMakeLists.txt @@ -0,0 +1,165 @@ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DPURE_BIOS_ONLY") + +message("Flags: ${CMAKE_CXX_FLAGS}") + +# Source code +file(GLOB_RECURSE SOURCES + main.cpp + Parameters.cpp + Parameters.h + server/IRouter.h + server/handlers/RequestHandler.cpp + server/handlers/RequestHandler.h + server/handlers/RequestHandlerTools.cpp + server/handlers/RequestHandlerTools.h + server/handlers/Mime.cpp + server/handlers/Mime.h + server/handlers/Validator.cpp + server/handlers/Validator.h + server/Server.cpp + server/Server.h + server/handlers/providers/SysInfos.cpp + server/handlers/providers/SysInfos.h + server/handlers/providers/EmulationStationWatcher.cpp + server/handlers/providers/EmulationStationWatcher.h + server/handlers/providers/FakeInternationalizer.cpp + server/handlers/providers/FakeInternationalizer.h +) + +# ES dependencies +file(GLOB_RECURSE DEPENDENCIES + ../es-core/src/utils/Strings.cpp + ../es-core/src/utils/Files.cpp + ../es-core/src/utils/os/fs/Path.cpp + ../es-core/src/utils/IniFile.cpp + ../es-core/src/utils/Log.cpp + ../es-core/src/utils/Zip.cpp + ../es-core/src/utils/os/system/Thread.cpp + ../es-core/src/utils/os/system/Mutex.cpp + ../es-core/src/utils/hash/Md5.cpp + ../es-core/src/utils/datetime/DateTime.cpp + ../es-core/src/utils/datetime/TimeSpan.cpp + ../es-core/src/utils/storage/*.h + ../es-core/src/RootFolders.cpp + ../es-app/src/bios/*.cpp + ../es-app/src/emulators/EmulatorList.cpp + ../es-app/src/systems/SystemDescriptor.cpp + ../es-app/src/systems/SystemDeserializer.cpp + ../es-app/src/systems/SystemDeserializer.h + ../es-app/src/systems/PlatformId.cpp + ../es-app/src/systems/PlatformId.h + ../es-app/src/mqtt/MqttClient.cpp + ../es-app/src/mqtt/MqttClient.cpp + ../es-app/src/LibretroRatio.cpp + ../es-app/src/LibretroRatio.h + + # Paho C + ../es-app/src/mqtt/paho/c/MQTTTime.c + ../es-app/src/mqtt/paho/c/MQTTProtocolClient.c + ../es-app/src/mqtt/paho/c/Clients.c + ../es-app/src/mqtt/paho/c/utf-8.c + ../es-app/src/mqtt/paho/c/MQTTPacket.c + ../es-app/src/mqtt/paho/c/MQTTPacketOut.c + ../es-app/src/mqtt/paho/c/Messages.c + ../es-app/src/mqtt/paho/c/Tree.c + ../es-app/src/mqtt/paho/c/Socket.c + ../es-app/src/mqtt/paho/c/Log.c + ../es-app/src/mqtt/paho/c/MQTTPersistence.c + ../es-app/src/mqtt/paho/c/Thread.c + ../es-app/src/mqtt/paho/c/MQTTProtocolOut.c + ../es-app/src/mqtt/paho/c/MQTTPersistenceDefault.c + ../es-app/src/mqtt/paho/c/SocketBuffer.c + ../es-app/src/mqtt/paho/c/LinkedList.c + ../es-app/src/mqtt/paho/c/MQTTProperties.c + ../es-app/src/mqtt/paho/c/MQTTReasonCodes.c + ../es-app/src/mqtt/paho/c/Base64.c + ../es-app/src/mqtt/paho/c/SHA1.c + ../es-app/src/mqtt/paho/c/WebSocket.c + ../es-app/src/mqtt/paho/c/MQTTAsync.c + ../es-app/src/mqtt/paho/c/MQTTTime.h + ../es-app/src/mqtt/paho/c/MQTTProtocolClient.h + ../es-app/src/mqtt/paho/c/Clients.h + ../es-app/src/mqtt/paho/c/utf-8.h + ../es-app/src/mqtt/paho/c/MQTTPacket.h + ../es-app/src/mqtt/paho/c/MQTTPacketOut.h + ../es-app/src/mqtt/paho/c/Messages.h + ../es-app/src/mqtt/paho/c/Tree.h + ../es-app/src/mqtt/paho/c/Socket.h + ../es-app/src/mqtt/paho/c/Log.h + ../es-app/src/mqtt/paho/c/MQTTPersistence.h + ../es-app/src/mqtt/paho/c/Thread.h + ../es-app/src/mqtt/paho/c/MQTTProtocolOut.h + ../es-app/src/mqtt/paho/c/MQTTPersistenceDefault.h + ../es-app/src/mqtt/paho/c/SocketBuffer.h + ../es-app/src/mqtt/paho/c/LinkedList.h + ../es-app/src/mqtt/paho/c/MQTTProperties.h + ../es-app/src/mqtt/paho/c/MQTTReasonCodes.h + ../es-app/src/mqtt/paho/c/Base64.h + ../es-app/src/mqtt/paho/c/SHA1.h + ../es-app/src/mqtt/paho/c/WebSocket.h + ../es-app/src/mqtt/paho/c/MQTTAsync.h + + # Paho cpp + ../es-app/src/mqtt/paho/cpp/async_client.h + ../es-app/src/mqtt/paho/cpp/buffer_ref.h + ../es-app/src/mqtt/paho/cpp/buffer_view.h + ../es-app/src/mqtt/paho/cpp/callback.h + ../es-app/src/mqtt/paho/cpp/client.h + ../es-app/src/mqtt/paho/cpp/connect_options.h + ../es-app/src/mqtt/paho/cpp/delivery_token.h + ../es-app/src/mqtt/paho/cpp/disconnect_options.h + ../es-app/src/mqtt/paho/cpp/exception.h + ../es-app/src/mqtt/paho/cpp/iaction_listener.h + ../es-app/src/mqtt/paho/cpp/iasync_client.h + ../es-app/src/mqtt/paho/cpp/iclient_persistence.h + ../es-app/src/mqtt/paho/cpp/message.h + ../es-app/src/mqtt/paho/cpp/properties.h + ../es-app/src/mqtt/paho/cpp/response_options.h + ../es-app/src/mqtt/paho/cpp/server_response.h + ../es-app/src/mqtt/paho/cpp/ssl_options.h + ../es-app/src/mqtt/paho/cpp/string_collection.h + ../es-app/src/mqtt/paho/cpp/subscribe_options.h + ../es-app/src/mqtt/paho/cpp/thread_queue.h + ../es-app/src/mqtt/paho/cpp/token.h + ../es-app/src/mqtt/paho/cpp/topic.h + ../es-app/src/mqtt/paho/cpp/types.h + ../es-app/src/mqtt/paho/cpp/will_options.h + ../es-app/src/mqtt/paho/cpp/async_client.cpp + ../es-app/src/mqtt/paho/cpp/client.cpp + ../es-app/src/mqtt/paho/cpp/connect_options.cpp + ../es-app/src/mqtt/paho/cpp/disconnect_options.cpp + ../es-app/src/mqtt/paho/cpp/iclient_persistence.cpp + ../es-app/src/mqtt/paho/cpp/message.cpp + ../es-app/src/mqtt/paho/cpp/properties.cpp + ../es-app/src/mqtt/paho/cpp/response_options.cpp + ../es-app/src/mqtt/paho/cpp/ssl_options.cpp + ../es-app/src/mqtt/paho/cpp/string_collection.cpp + ../es-app/src/mqtt/paho/cpp/subscribe_options.cpp + ../es-app/src/mqtt/paho/cpp/token.cpp + ../es-app/src/mqtt/paho/cpp/topic.cpp + ../es-app/src/mqtt/paho/cpp/will_options.cpp +) + +include_directories( + . + ../es-core/src + ../es-app/src + ../external + pistache/include +) + +add_executable( + webapi-server + ${SOURCES} + ${DEPENDENCIES} +) + +add_subdirectory(pistache/src) + +target_link_libraries(webapi-server pistache_static zip pugixml) + +#------------------------------------------------------------------------------- +# set up install stuff so `make install` does something useful +install(TARGETS webapi-server + RUNTIME + DESTINATION bin) \ No newline at end of file diff --git a/projects/frontend/server-webapi/Parameters.cpp b/projects/frontend/server-webapi/Parameters.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3bb336443e64c10ae1adefc0326c1b66f8b47e37 --- /dev/null +++ b/projects/frontend/server-webapi/Parameters.cpp @@ -0,0 +1,86 @@ +// +// Created by bkg2k on 30/03/2020. +// + +#include +#include +#include +#include "Parameters.h" + +Parameters::Parameters(int argc, char** argv) + : Parameters() +{ + bool error = false; + for(int i = 1; i < argc; i++) + { + if ((strcmp(argv[i], "-h") == 0) || (strcmp(argv[i], "--host") == 0)) + { + if (i + 1 >= argc) error = true; + else mIP = argv[++i]; + } + else if ((strcmp(argv[i], "-p") == 0) || (strcmp(argv[i], "--port") == 0)) + { + if (i + 1 >= argc) error = true; + else error = !Strings::ToInt(argv[++i], mPort); + } + else if ((strcmp(argv[i], "-t") == 0) || (strcmp(argv[i], "--threads") == 0)) + { + if (i + 1 >= argc) error = true; + else error = !Strings::ToInt(argv[++i], mThreads); + } + else if ((strcmp(argv[i], "-r") == 0) || (strcmp(argv[i], "--wwwroot") == 0)) + { + if (i + 1 >= argc) error = true; + else mWWWRoot = argv[++i]; + } + else if ((strcmp(argv[i], "-f") == 0) || (strcmp(argv[i], "--file") == 0)) + { + if (i + 1 >= argc) error = true; + else mDefaultFile = argv[++i]; + } + else if ((strcmp(argv[i], "-v") == 0) || (strcmp(argv[i], "--verbose") == 0) || + (strcmp(argv[i], "-d") == 0) || (strcmp(argv[i], "--debug") == 0)) + { + mDebug = true; + } + else if ((strcmp(argv[i], "-h") == 0) || (strcmp(argv[i], "--help") == 0)) + { + error = true; + } + else + { + LOG(LogError) << "Unknown parameter " << argv[i]; + error = true; + } + } + + if (error) + { + Help(); + exit(1); + } +} + +void Parameters::Help() +{ + puts("Usage: webapi-server [options]\n" + " options:\n" + " -h, --host : bind the server to the given host/ip. default value: 0.0.0.0\n" + " -p, --port : bind the server to the port P. default value: 20666\n" + " -t, --threads : maximum simultaneous processing threads. default: 5\n" + " -r, --wwwroot : www root folder. Default: /recalbox/web/manager-v3\n" + " -f, --file : www root folder default file. Default: index.html\n" + " -d, --debug : enable debug logs.\n" + ); +} + +void Parameters::LogConfig() +{ + LOG(LogInfo) << "Parameters:"; + LOG(LogInfo) << " Interface : " << mIP; + LOG(LogInfo) << " Port : " << mPort; + LOG(LogInfo) << " Threads : " << mThreads; + LOG(LogInfo) << " Root folder : " << mWWWRoot; + LOG(LogInfo) << " Default file : " << mDefaultFile; + LOG(LogInfo) << " Debug logs : " << mDebug; +} diff --git a/projects/frontend/server-webapi/Parameters.h b/projects/frontend/server-webapi/Parameters.h new file mode 100644 index 0000000000000000000000000000000000000000..86868f2ce7186a6b4fad05985d28a8d279846f0a --- /dev/null +++ b/projects/frontend/server-webapi/Parameters.h @@ -0,0 +1,65 @@ +// +// Created by bkg2k on 30/03/2020. +// +#pragma once + +#include + +class Parameters +{ + private: + //! WWW Root. Default /recalbox/web + std::string mWWWRoot; + //! Root default file + std::string mDefaultFile; + //! Bind tp IP. Default is 0.0.0.0 + std::string mIP; + //! Bind to port. Default is 20666 + int mPort; + //! Number of simultaneous processor (threads) + int mThreads; + //! Debug log + bool mDebug; + + /*! + * @brief Display help message + */ + static void Help(); + + public: + /*! + * @brief Default constructor + */ + Parameters() + : mWWWRoot("/recalbox/web/manager-v3"), + mDefaultFile("/index.html"), + mIP("0.0.0.0"), + mPort(20666), + mThreads(5), + mDebug(false) + { + } + + /*! + * @brief Build a parameter class using command line arguments if available + * @param argc Main argument count + * @param argv Main arguments + */ + Parameters(int argc, char* argv[]); + + /*! + * @brief Log configuration + */ + void LogConfig(); + + /* + * Getters + */ + + const std::string& WWWRoot() const { return mWWWRoot; } + const std::string& DefaultFile() const { return mDefaultFile; } + const std::string& IP() const { return mIP; } + int Port() const { return mPort; } + int Threads() const { return mThreads; } + bool Debug() const { return mDebug; } +}; diff --git a/projects/frontend/server-webapi/main.cpp b/projects/frontend/server-webapi/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..174fa3bce16eb48ec670168c09091877acfbf2e3 --- /dev/null +++ b/projects/frontend/server-webapi/main.cpp @@ -0,0 +1,78 @@ +// +// Created by bkg2k on 30/03/2020. +// + +#include +#include "Parameters.h" +#include "server/handlers/RequestHandler.h" +#include "server/Server.h" +#include + +static Server* serverInstance = nullptr; + +void my_handler(int s) +{ + LOG(LogError) << "Signal " << s << " received."; + switch(s) + { + case SIGTERM: + case SIGQUIT: + case SIGKILL: + { + if (serverInstance != nullptr) + serverInstance->Cancel(); + // Let the server shutdown gracefully + sleep(1); + break; + } + default: + { + exit(1); + } + } + exit(0); +} + +void InstallSignalCatcher() +{ + struct sigaction sigIntHandler{}; + + sigIntHandler.sa_handler = my_handler; + sigemptyset(&sigIntHandler.sa_mask); + sigIntHandler.sa_flags = 0; + + sigaction(SIGINT, &sigIntHandler, nullptr); + sigaction(SIGILL, &sigIntHandler, nullptr); + sigaction(SIGABRT, &sigIntHandler, nullptr); + sigaction(SIGFPE, &sigIntHandler, nullptr); + sigaction(SIGSEGV, &sigIntHandler, nullptr); + sigaction(SIGTERM, &sigIntHandler, nullptr); + sigaction(SIGQUIT, &sigIntHandler, nullptr); + sigaction(SIGKILL, &sigIntHandler, nullptr); + sigaction(SIGBUS, &sigIntHandler, nullptr); + sigaction(SIGSYS, &sigIntHandler, nullptr); +} + +int main(int argc, char* argv[]) +{ + LOG(LogInfo) << "Recalbox WebApi Server 1.0"; + + InstallSignalCatcher(); + + // Get parameters + Parameters param(argc, argv); + + Log::open("/recalbox/share/system/logs/webapi.log"); + if (param.Debug()) Log::setReportingLevel(LogLevel::LogDebug); + + param.LogConfig(); + + // Build server + RequestHandler requestHandler(param.WWWRoot(), param.DefaultFile()); + Server server(param, &requestHandler); + + // Run! + serverInstance = &server; + server.Run(); + serverInstance = nullptr; +} \ No newline at end of file diff --git a/projects/frontend/server-webapi/pistache/include/pistache/async.h b/projects/frontend/server-webapi/pistache/include/pistache/async.h new file mode 100755 index 0000000000000000000000000000000000000000..bf3524ccc0281b9ec0aa9ab1278591bacae15569 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/async.h @@ -0,0 +1,1380 @@ +/* async.h + Mathieu Stefani, 05 novembre 2015 + + This header brings a Promise class inspired by the Promises/A+ + specification for asynchronous operations +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Pistache { +namespace Async { + +class Error : public std::runtime_error { +public: + explicit Error(const char *what) : std::runtime_error(what) {} + explicit Error(const std::string &what) : std::runtime_error(what) {} +}; + +class BadType : public Error { +public: + explicit BadType(TypeId id) + : Error("Argument type can not be used to resolve the promise " + " (TypeId does not match)"), + id_(std::move(id)) {} + + TypeId typeId() const { return id_; } + +private: + TypeId id_; +}; + +class BadAnyCast : public std::bad_cast { +public: + const char *what() const noexcept override { return "Bad any cast"; } + virtual ~BadAnyCast() {} +}; + +enum class State { Pending, Fulfilled, Rejected }; + +template class Promise; + +class PromiseBase { +public: + virtual ~PromiseBase() {} + virtual bool isPending() const = 0; + virtual bool isFulfilled() const = 0; + virtual bool isRejected() const = 0; + + bool isSettled() const { return isFulfilled() || isRejected(); } +}; + +namespace detail { +template struct IsCallable { + + template + static auto test(U *) + -> decltype(std::declval()(std::declval()), std::true_type()); + + template static auto test(...) -> std::false_type; + + static constexpr bool value = + std::is_same(0)), std::true_type>::value; +}; + +template +struct IsMoveCallable : public IsMoveCallable {}; + +template +struct IsMoveCallable + : public std::is_rvalue_reference {}; + +template +typename std::conditional::value, Arg &&, + const Arg &>::type +tryMove(Arg &arg) { + return std::move(arg); +} + +template +struct FunctionTrait : public FunctionTrait {}; + +template +struct FunctionTrait { + typedef R ReturnType; + + static constexpr size_t ArgsCount = sizeof...(Args); +}; + +template +struct FunctionTrait { + typedef R ReturnType; + + static constexpr size_t ArgsCount = sizeof...(Args); +}; + +template struct RemovePromise { typedef T Type; }; + +template struct RemovePromise> { typedef T Type; }; + +template struct nth_element; + +template +struct nth_element<0, Head, Tail...> { + typedef Head type; +}; + +template +struct nth_element { + typedef typename nth_element::type type; +}; + +} // namespace detail + +namespace Private { + +struct InternalRethrow { + explicit InternalRethrow(std::exception_ptr _exc) : exc(std::move(_exc)) {} + + std::exception_ptr exc; +}; + +struct IgnoreException { + void operator()(std::exception_ptr) const {} +}; + +struct NoExcept { + void operator()(std::exception_ptr) const { std::terminate(); } +}; + +struct Throw { + void operator()(std::exception_ptr exc) const { + throw InternalRethrow(std::move(exc)); + } +}; + +struct Core; + +class Request { +public: + virtual void resolve(const std::shared_ptr &core) = 0; + virtual void reject(const std::shared_ptr &core) = 0; + virtual ~Request() {} +}; + +struct Core { + Core(State _state, TypeId _id) + : allocated(false), state(_state), exc(), mtx(), requests(), id(_id) {} + + bool allocated; + std::atomic state; + std::exception_ptr exc; + + /* + * We need this lock because a Promise might be resolved or rejected from a + * thread A while a continuation to the same Promise (Core) might be attached + * at the same from a thread B. If that's the case, then we need to serialize + * operations so that we avoid a race-condition. + * + * Since we have a lock, we have a blocking progress guarantee but I don't + * expect this to be a major bottleneck as I don't expect major contention on + * the lock If it ends up being a bottlenick, try @improving it by + * experimenting with a lock-free scheme + */ + std::mutex mtx; + std::vector> requests; + TypeId id; + + virtual void *memory() = 0; + + virtual bool isVoid() const = 0; + + template void construct(Args &&... args) { + if (isVoid()) + throw Error("Can not construct a void core"); + + if (id != TypeId::of()) { + throw BadType(id); + } + + void *mem = memory(); + + if (allocated) { + reinterpret_cast(mem)->~T(); + allocated = false; + } + + new (mem) T(std::forward(args)...); + allocated = true; + state = State::Fulfilled; + } + + virtual ~Core() {} +}; + +template struct CoreT : public Core { + CoreT() : Core(State::Pending, TypeId::of()), storage() {} + + ~CoreT() { + if (allocated) { + reinterpret_cast(&storage)->~T(); + allocated = false; + } + } + + template struct Rebind { typedef CoreT Type; }; + + T &value() { + if (state != State::Fulfilled) + throw Error("Attempted to take the value of a not fulfilled promise"); + + return *reinterpret_cast(&storage); + } + + bool isVoid() const override { return false; } + +protected: + void *memory() override { return &storage; } + +private: + typedef typename std::aligned_storage::type Storage; + Storage storage; +}; + +template <> struct CoreT : public Core { + CoreT() : Core(State::Pending, TypeId::of()) {} + + bool isVoid() const override { return true; } + +protected: + void *memory() override { return nullptr; } +}; + +template struct Continuable : public Request { + explicit Continuable(const std::shared_ptr &chain) + : resolveCount_(0), rejectCount_(0), chain_(chain) {} + + void resolve(const std::shared_ptr &core) override { + if (resolveCount_ >= 1) + return; // TODO is this the right thing? + // throw Error("Resolve must not be called more than once"); + + ++resolveCount_; + doResolve(coreCast(core)); + } + + void reject(const std::shared_ptr &core) override { + if (rejectCount_ >= 1) + return; // TODO is this the right thing? + // throw Error("Reject must not be called more than once"); + + ++rejectCount_; + try { + doReject(coreCast(core)); + } catch (const InternalRethrow &e) { + chain_->exc = std::move(e.exc); + chain_->state = State::Rejected; + for (const auto &req : chain_->requests) { + req->reject(chain_); + } + } + } + + std::shared_ptr> coreCast(const std::shared_ptr &core) const { + return std::static_pointer_cast>(core); + } + + virtual void doResolve(const std::shared_ptr> &core) = 0; + virtual void doReject(const std::shared_ptr> &core) = 0; + + virtual ~Continuable() {} + + size_t resolveCount_; + size_t rejectCount_; + std::shared_ptr chain_; +}; + +namespace impl { + +template +struct Continuation; + +template +struct Continuation + : public Continuation { + typedef Continuation Base; + + Continuation(const std::shared_ptr &chain, Resolve resolve, + Reject reject) + : Base(chain, std::move(resolve), std::move(reject)) {} +}; + +template +struct Continuation + : public Continuation { + typedef Continuation Base; + + Continuation(const std::shared_ptr &chain, Resolve resolve, + Reject reject) + : Base(chain, std::move(resolve), std::move(reject)) {} +}; + +// General specialization +template +struct Continuation : public Continuable { + + static_assert(sizeof...(Args) == 1, + "A continuation should only take one single argument"); + + typedef typename detail::nth_element<0, Args...>::type Arg; + + static_assert(std::is_same::value || + std::is_convertible::value, + "Incompatible types detected"); + + Continuation(const std::shared_ptr &chain, Resolve resolve, + Reject reject) + : Continuable(chain), resolve_(resolve), reject_(reject) {} + + void doResolve(const std::shared_ptr> &core) override { + finishResolve(resolve_(detail::tryMove(core->value()))); + } + + void doReject(const std::shared_ptr> &core) override { + reject_(core->exc); + for (const auto &req : this->chain_->requests) { + req->reject(this->chain_); + } + } + + template void finishResolve(Ret &&ret) const { + typedef typename std::decay::type CleanRet; + this->chain_->template construct(std::forward(ret)); + for (const auto &req : this->chain_->requests) { + req->resolve(this->chain_); + } + } + + Resolve resolve_; + Reject reject_; +}; + +// Specialization for a void-Promise +template +struct Continuation + : public Continuable { + + Continuation(const std::shared_ptr &chain, Resolve resolve, + Reject reject) + : Continuable(chain), resolve_(resolve), reject_(reject) {} + + static_assert(sizeof...(Args) == 0, + "Can not attach a non-void continuation to a void-Promise"); + + void doResolve(const std::shared_ptr> & /*core*/) { + finishResolve(resolve_()); + } + + void doReject(const std::shared_ptr> &core) { + reject_(core->exc); + for (const auto &req : this->chain_->requests) { + req->reject(this->chain_); + } + } + + template void finishResolve(Ret &&ret) const { + typedef typename std::remove_reference::type CleanRet; + this->chain_->template construct(std::forward(ret)); + for (const auto &req : this->chain_->requests) { + req->resolve(this->chain_); + } + } + + Resolve resolve_; + Reject reject_; +}; + +// Specialization for a callback returning void +template +struct Continuation : public Continuable { + + Continuation(const std::shared_ptr &chain, Resolve resolve, + Reject reject) + : Continuable(chain), resolve_(resolve), reject_(reject) {} + + static_assert(sizeof...(Args) == 1, + "A continuation should only take one single argument"); + + typedef typename detail::nth_element<0, Args...>::type Arg; + + static_assert(std::is_same::value || + std::is_convertible::value, + "Incompatible types detected"); + + void doResolve(const std::shared_ptr> &core) override { + resolve_(core->value()); + } + + void doReject(const std::shared_ptr> &core) override { + reject_(core->exc); + } + + Resolve resolve_; + Reject reject_; +}; + +// Specialization for a void-Promise on a callback returning void +template +struct Continuation + : public Continuable { + + Continuation(const std::shared_ptr &chain, Resolve resolve, + Reject reject) + : Continuable(chain), resolve_(resolve), reject_(reject) {} + + static_assert(sizeof...(Args) == 0, + "Can not attach a non-void continuation to a void-Promise"); + + void doResolve(const std::shared_ptr> & /*core*/) override { + resolve_(); + } + + void doReject(const std::shared_ptr> &core) override { + reject_(core->exc); + } + + Resolve resolve_; + Reject reject_; +}; + +// Specialization for a callback returning a Promise +template +struct Continuation(Args...)> + : public Continuable { + + static_assert(sizeof...(Args) == 1, + "A continuation should only take one single argument"); + + typedef typename detail::nth_element<0, Args...>::type Arg; + + static_assert(std::is_same::value || + std::is_convertible::value, + "Incompatible types detected"); + + Continuation(const std::shared_ptr &chain, Resolve resolve, + Reject reject) + : Continuable(chain), resolve_(resolve), reject_(reject) {} + + void doResolve(const std::shared_ptr> &core) override { + auto promise = resolve_(detail::tryMove(core->value())); + finishResolve(promise); + } + + void doReject(const std::shared_ptr> &core) override { + reject_(core->exc); + for (const auto &req : core->requests) { + req->reject(core); + } + } + + template struct Chainer { + explicit Chainer(const std::shared_ptr &core) + : chainCore(core) {} + + void operator()(const PromiseType &val) { + chainCore->construct(val); + for (const auto &req : chainCore->requests) { + req->resolve(chainCore); + } + } + + std::shared_ptr chainCore; + }; + + template ::Type> + Chainer makeChainer(const Promise &) { + return Chainer(this->chain_); + } + + template void finishResolve(P &promise) { + auto chainer = makeChainer(promise); + std::weak_ptr weakPtr = this->chain_; + promise.then(std::move(chainer), [weakPtr](std::exception_ptr exc) { + if (auto core = weakPtr.lock()) { + core->exc = std::move(exc); + core->state = State::Rejected; + + for (const auto &req : core->requests) { + req->reject(core); + } + } + }); + } + + Resolve resolve_; + Reject reject_; +}; + +// Specialization for a void callback returning a Promise +template +struct Continuation(Args...)> + : public Continuable { + + static_assert(sizeof...(Args) == 0, + "Can not attach a non-void continuation to a void-Promise"); + + Continuation(const std::shared_ptr &chain, Resolve resolve, + Reject reject) + : Continuable(chain), resolve_(resolve), reject_(reject) {} + + void doResolve(const std::shared_ptr> & /*core*/) { + auto promise = resolve_(); + finishResolve(promise); + } + + void doReject(const std::shared_ptr> &core) { + reject_(core->exc); + for (const auto &req : core->requests) { + req->reject(core); + } + } + + template struct Chainer { + explicit Chainer(const std::shared_ptr &core) + : chainCore(core) {} + + void operator()(const PromiseType &val) { + chainCore->construct(val); + for (const auto &req : chainCore->requests) { + req->resolve(chainCore); + } + } + + std::shared_ptr chainCore; + }; + + template struct Chainer { + explicit Chainer(const std::shared_ptr &core) + : chainCore(core) {} + + void operator()() { + auto core = this->chain_; + core->state = State::Fulfilled; + + for (const auto &req : chainCore->requests) { + req->resolve(chainCore); + } + } + + std::shared_ptr chainCore; + }; + + template ::Type> + Chainer makeChainer(const Promise &) { + return Chainer(this->chain_); + } + + template void finishResolve(P &promise) { + auto chainer = makeChainer(promise); + promise.then(std::move(chainer), [=](std::exception_ptr exc) { + auto core = this->chain_; + core->exc = std::move(exc); + core->state = State::Rejected; + + for (const auto &req : core->requests) { + req->reject(core); + } + }); + } + + Resolve resolve_; + Reject reject_; +}; + +} // namespace impl + +template +struct Continuation : public impl::Continuation { + + typedef impl::Continuation + Base; + + Continuation(const std::shared_ptr &core, Resolve resolve, + Reject reject) + : Base(core, std::move(resolve), std::move(reject)) {} +}; + +template +struct Continuation + : public impl::Continuation { + typedef impl::Continuation Base; + + Continuation(const std::shared_ptr &core, Resolve resolve, + Reject reject) + : Base(core, std::move(resolve), std::move(reject)) {} +}; + +template +struct Continuation + : public impl::Continuation { + typedef impl::Continuation Base; + + Continuation(const std::shared_ptr &core, Resolve resolve, + Reject reject) + : Base(core, std::move(resolve), std::move(reject)) {} +}; + +template +struct Continuation + : public impl::Continuation { + typedef impl::Continuation Base; + + Continuation(const std::shared_ptr &core, Resolve resolve, + Reject reject) + : Base(core, std::move(resolve), std::move(reject)) {} +}; + +template +struct Continuation> + : public impl::Continuation { + typedef impl::Continuation Base; + + Continuation(const std::shared_ptr &core, Resolve resolve, + Reject reject) + : Base(core, std::move(resolve), std::move(reject)) {} +}; +} // namespace Private + +class Resolver { +public: + explicit Resolver(const std::shared_ptr &core) : core_(core) {} + + Resolver(const Resolver &other) = delete; + Resolver &operator=(const Resolver &other) = delete; + + Resolver(Resolver &&other) = default; + Resolver &operator=(Resolver &&other) = default; + + template bool operator()(Arg &&arg) const { + if (!core_) + return false; + + typedef typename std::remove_reference::type Type; + + if (core_->state != State::Pending) + throw Error("Attempt to resolve a fulfilled promise"); + + /* In a ideal world, this should be checked at compile-time rather + * than runtime. However, since types are erased, this looks like + * a difficult task + */ + if (core_->isVoid()) { + throw Error("Attempt to resolve a void promise with arguments"); + } + + std::unique_lock guard(core_->mtx); + core_->construct(std::forward(arg)); + + for (const auto &req : core_->requests) { + req->resolve(core_); + } + + return true; + } + + bool operator()() const { + if (!core_) + return false; + + if (core_->state != State::Pending) + throw Error("Attempt to resolve a fulfilled promise"); + + if (!core_->isVoid()) + throw Error("Attempt ro resolve a non-void promise with no argument"); + + std::unique_lock guard(core_->mtx); + core_->state = State::Fulfilled; + for (const auto &req : core_->requests) { + req->resolve(core_); + } + + return true; + } + + void clear() { core_ = nullptr; } + + Resolver clone() { return Resolver(core_); } + +private: + std::shared_ptr core_; +}; + +class Rejection { +public: + explicit Rejection(const std::shared_ptr &core) + : core_(core) {} + + Rejection(const Rejection &other) = delete; + Rejection &operator=(const Rejection &other) = delete; + + Rejection(Rejection &&other) = default; + Rejection &operator=(Rejection &&other) = default; + + template bool operator()(Exc exc) const { + if (!core_) + return false; + + if (core_->state != State::Pending) + throw Error("Attempt to reject a fulfilled promise"); + + std::unique_lock guard(core_->mtx); + core_->exc = std::make_exception_ptr(exc); + core_->state = State::Rejected; + for (const auto &req : core_->requests) { + req->reject(core_); + } + + return true; + } + + void clear() { core_ = nullptr; } + + Rejection clone() { return Rejection(core_); } + +private: + std::shared_ptr core_; +}; + +template class Deferred { +public: + Deferred() : resolver(nullptr), rejection(nullptr) {} + + Deferred(const Deferred &other) = delete; + Deferred &operator=(const Deferred &other) = delete; + + Deferred(Deferred &&other) = default; + Deferred &operator=(Deferred &&other) = default; + + Deferred(Resolver _resolver, Rejection _reject) + : resolver(std::move(_resolver)), rejection(std::move(_reject)) {} + + template bool resolve(U &&arg) { + typedef typename std::remove_reference::type CleanU; + + static_assert(std::is_same::value || + std::is_convertible::value, + "Types mismatch"); + + return resolver(std::forward(arg)); + } + + template void emplaceResolve(Args &&...) {} + + template bool reject(Exc exc) { + return rejection(std::move(exc)); + } + + void clear() { + resolver.clear(); + rejection.clear(); + } + +private: + Resolver resolver; + Rejection rejection; +}; + +template <> class Deferred { +public: + Deferred() : resolver(nullptr), rejection(nullptr) {} + + Deferred(const Deferred &other) = delete; + Deferred &operator=(const Deferred &other) = delete; + + Deferred(Deferred &&other) = default; + Deferred &operator=(Deferred &&other) = default; + + Deferred(Resolver _resolver, Rejection _reject) + : resolver(std::move(_resolver)), rejection(std::move(_reject)) {} + + void resolve() { resolver(); } + + template void reject(Exc _exc) { rejection(std::move(_exc)); } + +private: + Resolver resolver; + Rejection rejection; +}; + +static constexpr Private::IgnoreException IgnoreException{}; +static constexpr Private::NoExcept NoExcept{}; +static constexpr Private::Throw Throw{}; + +namespace details { + +/* + * Note that we could use std::result_of to SFINAE-out and dispatch to the right + * call However, gcc 4.7 does not correctly support std::result_of for SFINAE + * purposes, so we use a decltype SFINAE-expression instead. + * + * See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3462.html and + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56283 for reference + */ +template +auto callAsync(Func func, Resolver &resolver, Rejection &rejection) + -> decltype(std::declval()(resolver, rejection), void()) { + func(resolver, rejection); +} + +template +auto callAsync(Func func, Resolver &resolver, Rejection &rejection) + -> decltype(std::declval()(Deferred()), void()) { + func(Deferred(std::move(resolver), std::move(rejection))); +} +} // namespace details + +template class Promise : public PromiseBase { +public: + template friend class Promise; + + typedef Private::CoreT Core; + + template + explicit Promise(Func func) + : core_(std::make_shared()), resolver_(core_), rejection_(core_) { + details::callAsync(func, resolver_, rejection_); + } + + Promise(const Promise &other) = delete; + Promise &operator=(const Promise &other) = delete; + + Promise(Promise &&other) = default; + Promise &operator=(Promise &&other) = default; + + virtual ~Promise() {} + + template static Promise resolved(U &&value) { + static_assert(!std::is_void::value, + "Can not resolve a void promise with parameters"); + static_assert(std::is_same::value || std::is_convertible::value, + "Incompatible value type"); + + auto core = std::make_shared(); + core->template construct(std::forward(value)); + return Promise(std::move(core)); + } + + static Promise resolved() { + static_assert(std::is_void::value, + "Resolving a non-void promise requires parameters"); + + auto core = std::make_shared(); + core->state = State::Fulfilled; + return Promise(std::move(core)); + } + + template static Promise rejected(Exc exc) { + auto core = std::make_shared(); + core->exc = std::make_exception_ptr(exc); + core->state = State::Rejected; + return Promise(std::move(core)); + } + + bool isPending() const override { return core_->state == State::Pending; } + bool isFulfilled() const override { return core_->state == State::Fulfilled; } + bool isRejected() const override { return core_->state == State::Rejected; } + + template + auto then(ResolveFunc resolveFunc, RejectFunc rejectFunc) + -> Promise::ReturnType>::Type> { + + typedef typename detail::RemovePromise< + typename detail::FunctionTrait::ReturnType>::Type RetType; + + Promise promise; + + typedef Private::Continuation + Continuation; + std::shared_ptr req = + std::make_shared(promise.core_, resolveFunc, rejectFunc); + + std::unique_lock guard(core_->mtx); + if (isFulfilled()) { + req->resolve(core_); + } else if (isRejected()) { + req->reject(core_); + } + + core_->requests.push_back(req); + + return promise; + } + +private: + Promise() + : core_(std::make_shared()), resolver_(core_), rejection_(core_) {} + + explicit Promise(std::shared_ptr &&core) + : core_(core), resolver_(core_), rejection_(core_) {} + + std::shared_ptr core_; + Resolver resolver_; + Rejection rejection_; +}; + +template class Barrier { +public: + explicit Barrier(Promise &promise) : promise_(promise) {} + + void wait() { + if (promise_.isFulfilled() || promise_.isRejected()) + return; + + promise_.then( + [&](const T &) mutable { + std::unique_lock guard(mtx); + cv.notify_one(); + }, + [&](std::exception_ptr) mutable { + std::unique_lock guard(mtx); + cv.notify_one(); + }); + + std::unique_lock guard(mtx); + cv.wait(guard, + [&] { return promise_.isFulfilled() || promise_.isRejected(); }); + } + + template + std::cv_status wait_for(const std::chrono::duration &period) { + if (promise_.isFulfilled() || promise_.isRejected()) + return std::cv_status::no_timeout; + + promise_.then( + [&](const T &) mutable { + std::unique_lock guard(mtx); + cv.notify_one(); + }, + [&](std::exception_ptr) mutable { + std::unique_lock guard(mtx); + cv.notify_one(); + }); + + std::unique_lock guard(mtx); + return cv.wait_for(guard, period); + } + +private: + Promise &promise_; + mutable std::mutex mtx; + std::condition_variable cv; +}; + +namespace Impl { +struct Any; +} + +class Any { +public: + friend struct Impl::Any; + + Any(const Any &other) = default; + Any &operator=(const Any &other) = default; + + Any(Any &&other) = default; + Any &operator=(Any &&other) = default; + + template bool is() const { return core_->id == TypeId::of(); } + + template T cast() const { + if (!is()) + throw BadAnyCast(); + + auto core = std::static_pointer_cast>(core_); + return core->value(); + } + +private: + explicit Any(const std::shared_ptr &core) : core_(core) {} + std::shared_ptr core_; +}; + +namespace Impl { + +/* Instead of duplicating the code between whenAll and whenAny functions, the + * main implementation is in the When class below and we configure the class + * with a policy instead, depending if we are executing an "all" or "any" + * operation, how cool is that ? + */ +struct All { + + struct Data { + Data(const size_t _total, Resolver _resolver, Rejection _rejection) + : total(_total), resolved(0), rejected(false), mtx(), + resolve(std::move(_resolver)), reject(std::move(_rejection)) {} + + const size_t total; + size_t resolved; + bool rejected; + std::mutex mtx; + + Resolver resolve; + Rejection reject; + }; + + template + static void resolveT(const T &val, Data &data) { + std::lock_guard guard(data->mtx); + + if (data->rejected) + return; + + // @Check thread-safety of std::get ? + std::get(data->results) = val; + data->resolved++; + + if (data->resolved == data->total) { + data->resolve(data->results); + } + } + + template static void resolveVoid(Data &data) { + std::lock_guard guard(data->mtx); + + if (data->rejected) + return; + + data->resolved++; + + if (data->resolved == data->total) { + data->resolve(data->results); + } + } + + template + static void reject(std::exception_ptr exc, Data &data) { + std::lock_guard guard(data->mtx); + + data->rejected = true; + data->reject(exc); + } +}; + +struct Any { + + struct Data { + Data(size_t, Resolver resolver, Rejection rejection) + : done(false), mtx(), resolve(std::move(resolver)), + reject(std::move(rejection)) {} + + bool done; + std::mutex mtx; + + Resolver resolve; + Rejection reject; + }; + + template + static void resolveT(const T &val, Data &data) { + std::lock_guard guard(data->mtx); + + if (data->done) + return; + + // Instead of allocating a new core, ideally we could share the same core as + // the relevant promise but we do not have access to the promise here is so + // meh + auto core = std::make_shared>(); + core->template construct(val); + data->resolve(Async::Any(core)); + + data->done = true; + } + + template static void resolveVoid(Data &data) { + std::lock_guard guard(data->mtx); + + if (data->done) + return; + + auto core = std::make_shared>(); + data->resolve(Async::Any(core)); + + data->done = true; + } + + template + static void reject(std::exception_ptr exc, Data &data) { + std::lock_guard guard(data->mtx); + + data->done = true; + data->reject(exc); + } +}; + +template struct When { + When(Resolver resolver, Rejection rejection) + : resolve(std::move(resolver)), reject(std::move(rejection)) {} + + template void operator()(Args &&... args) { + whenArgs(std::forward(args)...); + } + +private: + template struct WhenContinuation { + explicit WhenContinuation(Data _data) : data(std::move(_data)) {} + + void operator()(const T &val) const { + ContinuationPolicy::template resolveT(val, data); + } + + Data data; + }; + + template + struct WhenContinuation { + explicit WhenContinuation(Data _data) : data(std::move(_data)) {} + + void operator()() const { ContinuationPolicy::resolveVoid(data); } + + Data data; + }; + + template + WhenContinuation makeContinuation(const Data &data) { + return WhenContinuation(data); + } + + template + void when(const Data &data, Promise &promise) { + promise.then(makeContinuation(data), [=](std::exception_ptr ptr) { + ContinuationPolicy::reject(std::move(ptr), data); + }); + } + + template + void when(const Data &data, T &&arg) { + typedef typename std::remove_reference::type CleanT; + auto promise = Promise::resolved(std::forward(arg)); + when(data, promise); + } + + template void whenArgs(Args &&... args) { + typedef std::tuple::type>::Type...> + Results; + /* We need to keep the results alive until the last promise + * finishes its execution + */ + + /* See the trick here ? Basically, we only have access to the real type of + * the results in this function. The policy classes do not have access to + * the full type (std::tuple), but, instead, take a generic template data + * type as a parameter. They only need to know that results is a tuple, they + * do not need to know the real type of the results. + * + * This is some sort of compile-time template type-erasing, hue + */ + struct Data : public ContinuationPolicy::Data { + Data(size_t total, Resolver resolver, Rejection rejection) + : ContinuationPolicy::Data(total, std::move(resolver), + std::move(rejection)) {} + + Results results; + }; + + auto data = std::make_shared(sizeof...(Args), std::move(resolve), + std::move(reject)); + whenArgs<0>(data, std::forward(args)...); + } + + template + void whenArgs(const Data &data, Head &&head, Rest &&... rest) { + when(data, std::forward(head)); + whenArgs(data, std::forward(rest)...); + } + + template + void whenArgs(const Data &data, Head &&head) { + when(data, std::forward(head)); + } + + Resolver resolve; + Rejection reject; +}; + +template struct WhenAllRange { + WhenAllRange(Resolver _resolve, Rejection _reject) + : resolve(std::move(_resolve)), reject(std::move(_reject)) {} + + template void operator()(Iterator first, Iterator last) { + auto data = std::make_shared>( + static_cast(std::distance(first, last)), std::move(resolve), + std::move(reject)); + + size_t index = 0; + for (auto it = first; it != last; ++it) { + + WhenContinuation cont(data, index); + + it->then(std::move(cont), [=](std::exception_ptr ptr) { + std::lock_guard guard(data->mtx); + + if (data->rejected) + return; + + data->rejected = true; + data->reject(std::move(ptr)); + }); + + ++index; + } + } + +private: + struct Data { + Data(size_t _total, Resolver _resolver, Rejection _rejection) + : total(_total), resolved(0), rejected(false), mtx(), + resolve(std::move(_resolver)), reject(std::move(_rejection)) {} + + const size_t total; + size_t resolved; + bool rejected; + std::mutex mtx; + + Resolver resolve; + Rejection reject; + }; + + /* Ok so apparently I can not fully specialize a template structure + * here, so you know what, compiler ? Take that Dummy type and leave + * me alone + */ + template + struct DataT : public Data { + DataT(size_t total, Resolver resolver, Rejection rejection) + : Data(total, std::move(resolver), std::move(rejection)) { + results.resize(total); + } + + Results results; + }; + + /* For a vector of void promises, we do not have any results, that's + * why we need a distinct specialization for the void case + */ + template struct DataT : public Data { + DataT(size_t total, Resolver resolver, Rejection rejection) + : Data(total, std::move(resolver), std::move(rejection)) {} + }; + + template struct WhenContinuation { + using D = std::shared_ptr>; + + WhenContinuation(const D &_data, size_t _index) + : data(_data), index(_index) {} + + void operator()(const ValueType &val) const { + std::lock_guard guard(data->mtx); + + if (data->rejected) + return; + + data->results[index] = val; + data->resolved++; + if (data->resolved == data->total) { + data->resolve(data->results); + } + } + + D data; + size_t index; + }; + + template struct WhenContinuation { + using D = std::shared_ptr>; + + WhenContinuation(const D &_data, size_t) : data(_data) {} + + void operator()() const { + std::lock_guard guard(data->mtx); + + if (data->rejected) + return; + + data->resolved++; + if (data->resolved == data->total) { + data->resolve(); + } + } + + D data; + }; + + Resolver resolve; + Rejection reject; +}; + +} // namespace Impl + +template ::type>::Type...>> +Promise whenAll(Args &&... args) { + // As ugly as it looks, this is needed to bypass a bug of gcc < 4.9 + // whereby template parameters pack inside a lambda expression are not + // captured correctly and can not be expanded inside the lambda. + Resolver *resolve; + Rejection *reject; + + Promise promise([&](Resolver &resolver, Rejection &rejection) { + resolve = &resolver; + reject = &rejection; + }); + + Impl::When impl(std::move(*resolve), std::move(*reject)); + // So we capture everything we need inside the lambda and then call the + // implementation and expand the parameters pack here + impl(std::forward(args)...); + + return promise; +} + +template Promise whenAny(Args &&... args) { + // Same trick as above; + Resolver *resolve; + Rejection *reject; + + Promise promise([&](Resolver &resolver, Rejection &rejection) { + resolve = &resolver; + reject = &rejection; + }); + + Impl::When impl(std::move(*resolve), std::move(*reject)); + impl(std::forward(args)...); + return promise; +} + +template ::value_type>::Type, + typename Results = + typename std::conditional::value, + void, std::vector>::type> +Promise whenAll(Iterator first, Iterator last) { + /* @Security, assert that last >= first */ + + return Promise([=](Resolver &resolve, Rejection &rejection) { + Impl::WhenAllRange impl(std::move(resolve), + std::move(rejection)); + + impl(first, last); + }); +} + +} // namespace Async +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/base64.h b/projects/frontend/server-webapi/pistache/include/pistache/base64.h new file mode 100755 index 0000000000000000000000000000000000000000..2ad73c7370614a08b0e0a6b5f414ae7fc41ac6fe --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/base64.h @@ -0,0 +1,101 @@ +/* + Copyright (C) 2019-2020, Kip Warner. + Released under the terms of Apache License 2.0. +*/ + +// Multiple include protection... +#ifndef _BASE_64_H_ +#define _BASE_64_H_ + +// Includes... + +// Build environment configuration... +#include + +// Standard C++ / POSIX system headers... +#include +#include +#include + +#if __cplusplus < 201703L +namespace std { + typedef uint8_t byte; +} +#endif + +// A class for performing decoding to raw bytes from base 64 encoding... +class Base64Decoder { + // Public methods... +public: + // Constructor... + explicit Base64Decoder(const std::string &Base64EncodedString) + : m_Base64EncodedString(Base64EncodedString) {} + + // Calculate length of decoded raw bytes from that would be generated if + // the base 64 encoded input buffer was decoded. This is not a static + // method because we need to examine the string... + std::vector::size_type CalculateDecodedSize() const; + + // Decode base 64 encoding into raw bytes... + const std::vector &Decode(); + + // Get raw decoded data... + const std::vector &GetRawDecodedData() const noexcept { + return m_DecodedData; + } + + // Protected methods... +protected: + // Convert an octet character to corresponding sextet, provided it can + // safely be represented as such. Otherwise return 0xff... + std::byte DecodeCharacter(const unsigned char Character) const; + + // Protected attributes... +protected: + // Base 64 encoded string to decode... + const std::string &m_Base64EncodedString; + + // Decoded raw data... + std::vector m_DecodedData; +}; + +// A class for performing base 64 encoding from raw bytes... +class Base64Encoder { + // Public methods... +public: + // Construct encoder to encode from a raw input buffer... + explicit Base64Encoder(const std::vector &InputBuffer) + : m_InputBuffer(InputBuffer) {} + + // Calculate length of base 64 string that would need to be generated + // for raw data of a given length... + static std::string::size_type CalculateEncodedSize( + const std::vector::size_type DecodedSize) noexcept; + + // Encode raw data input buffer to base 64... + const std::string &Encode() noexcept; + + // Encode a string into base 64 format... + static std::string EncodeString(const std::string &StringInput); + + // Get the encoded data... + const std::string &GetBase64EncodedString() const noexcept { + return m_Base64EncodedString; + } + + // Protected methods... +protected: + // Encode single binary byte to 6-bit base 64 character... + unsigned char EncodeByte(const std::byte Byte) const; + + // Protected attributes... +protected: + // Raw bytes to encode to base 64 string... + const std::vector &m_InputBuffer; + + // Base64 encoded string... + std::string m_Base64EncodedString; +}; + +// Multiple include protection... +#endif diff --git a/projects/frontend/server-webapi/pistache/include/pistache/client.h b/projects/frontend/server-webapi/pistache/include/pistache/client.h new file mode 100755 index 0000000000000000000000000000000000000000..74b94588033a8b0e39e4c98e6c732345b7ca3b81 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/client.h @@ -0,0 +1,236 @@ +/* + Mathieu Stefani, 29 janvier 2016 + + The Http client +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace Pistache { +namespace Http { + +namespace Default { +constexpr int Threads = 1; +constexpr int MaxConnectionsPerHost = 8; +constexpr bool KeepAlive = true; +constexpr size_t MaxResponseSize = std::numeric_limits::max(); +} // namespace Default + +class Transport; + +struct Connection : public std::enable_shared_from_this { + + using OnDone = std::function; + + explicit Connection(size_t maxResponseSize); + + struct RequestData { + + RequestData(Async::Resolver resolve, Async::Rejection reject, + const Http::Request &request, OnDone onDone) + : resolve(std::move(resolve)), reject(std::move(reject)), + request(request), onDone(std::move(onDone)) {} + Async::Resolver resolve; + Async::Rejection reject; + + Http::Request request; + OnDone onDone; + }; + + enum State : uint32_t { Idle, Used }; + + enum ConnectionState { NotConnected, Connecting, Connected }; + + void connect(const Address &addr); + void close(); + bool isIdle() const; + bool tryUse(); + void setAsIdle(); + bool isConnected() const; + bool hasTransport() const; + void associateTransport(const std::shared_ptr &transport); + + Async::Promise perform(const Http::Request &request, OnDone onDone); + + Async::Promise asyncPerform(const Http::Request &request, + OnDone onDone); + + void performImpl(const Http::Request &request, Async::Resolver resolve, + Async::Rejection reject, OnDone onDone); + + Fd fd() const; + void handleResponsePacket(const char *buffer, size_t totalBytes); + void handleError(const char *error); + void handleTimeout(); + + std::string dump() const; + +private: + void processRequestQueue(); + + struct RequestEntry { + RequestEntry(Async::Resolver resolve, Async::Rejection reject, + std::shared_ptr timer, OnDone onDone) + : resolve(std::move(resolve)), reject(std::move(reject)), + timer(std::move(timer)), onDone(std::move(onDone)) {} + + Async::Resolver resolve; + Async::Rejection reject; + std::shared_ptr timer; + OnDone onDone; + }; + + Fd fd_; + + struct sockaddr_in saddr; + std::unique_ptr requestEntry; + std::atomic state_; + std::atomic connectionState_; + std::shared_ptr transport_; + Queue requestsQueue; + + TimerPool timerPool_; + ResponseParser parser; +}; + +class ConnectionPool { +public: + ConnectionPool() = default; + + void init(size_t maxConnsPerHost, size_t maxResponseSize); + + std::shared_ptr pickConnection(const std::string &domain); + static void releaseConnection(const std::shared_ptr &connection); + + size_t usedConnections(const std::string &domain) const; + size_t idleConnections(const std::string &domain) const; + + size_t availableConnections(const std::string &domain) const; + + void closeIdleConnections(const std::string &domain); + +private: + using Connections = std::vector>; + using Lock = std::mutex; + using Guard = std::lock_guard; + + mutable Lock connsLock; + std::unordered_map conns; + size_t maxConnectionsPerHost; + size_t maxResponseSize; +}; + +class Client; + +class RequestBuilder { +public: + friend class Client; + + RequestBuilder &method(Method method); + RequestBuilder &resource(const std::string &val); + RequestBuilder ¶ms(const Uri::Query &query); + RequestBuilder &header(const std::shared_ptr &header); + + template + typename std::enable_if::value, RequestBuilder &>::type + header(Args &&... args) { + return header(std::make_shared(std::forward(args)...)); + } + + RequestBuilder &cookie(const Cookie &cookie); + RequestBuilder &body(const std::string &val); + RequestBuilder &body(std::string &&val); + RequestBuilder &timeout(std::chrono::milliseconds val); + + Async::Promise send(); + +private: + explicit RequestBuilder(Client *const client) : client_(client), request_() {} + + Client *const client_; + + Request request_; +}; + +class Client { +public: + friend class RequestBuilder; + + struct Options { + friend class Client; + + Options() + : threads_(Default::Threads), + maxConnectionsPerHost_(Default::MaxConnectionsPerHost), + keepAlive_(Default::KeepAlive), + maxResponseSize_(Default::MaxResponseSize) {} + + Options &threads(int val); + Options &keepAlive(bool val); + Options &maxConnectionsPerHost(int val); + Options &maxResponseSize(size_t val); + + private: + int threads_; + int maxConnectionsPerHost_; + bool keepAlive_; + size_t maxResponseSize_; + }; + + Client(); + ~Client(); + + static Options options(); + void init(const Options &options = Options()); + + RequestBuilder get(const std::string &resource); + RequestBuilder post(const std::string &resource); + RequestBuilder put(const std::string &resource); + RequestBuilder patch(const std::string &resource); + RequestBuilder del(const std::string &resource); + + void shutdown(); + +private: + std::shared_ptr reactor_; + + ConnectionPool pool; + Aio::Reactor::Key transportKey; + + std::atomic ioIndex; + + using Lock = std::mutex; + using Guard = std::lock_guard; + + Lock queuesLock; + std::unordered_map, 2048>> + requestsQueues; + bool stopProcessPequestsQueues; + +private: + RequestBuilder prepareRequest(const std::string &resource, + Http::Method method); + + Async::Promise doRequest(Http::Request request); + + void processRequestQueue(); +}; + +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/common.h b/projects/frontend/server-webapi/pistache/include/pistache/common.h new file mode 100755 index 0000000000000000000000000000000000000000..5f643e053c88b61e21092eec63af2a44e2eab906 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/common.h @@ -0,0 +1,63 @@ +/* common.h + Mathieu Stefani, 12 August 2015 + + A collection of macro / utilities / constants +*/ + +#pragma once + +#include +#include +#include + +#include + +#include +#include +#include + +#define TRY(...) \ + do { \ + auto ret = __VA_ARGS__; \ + if (ret < 0) { \ + const char *str = #__VA_ARGS__; \ + std::ostringstream oss; \ + oss << str << ": "; \ + if (errno == 0) { \ + oss << gai_strerror(static_cast(ret)); \ + } else { \ + oss << strerror(errno); \ + } \ + oss << " (" << __FILE__ << ":" << __LINE__ << ")"; \ + throw std::runtime_error(oss.str()); \ + } \ + } while (0) + +#define TRY_RET(...) \ + [&]() { \ + auto ret = __VA_ARGS__; \ + if (ret < 0) { \ + const char *str = #__VA_ARGS__; \ + std::ostringstream oss; \ + oss << str << ": " << strerror(errno); \ + oss << " (" << __FILE__ << ":" << __LINE__ << ")"; \ + throw std::runtime_error(oss.str()); \ + } \ + return ret; \ + }(); \ + (void)0 + +struct PrintException { + void operator()(std::exception_ptr exc) const { + try { + std::rethrow_exception(exc); + } catch (const std::exception &e) { + std::cerr << "An exception occured: " << e.what() << std::endl; + } + } +}; + +#define unreachable() __builtin_unreachable() + +// Until we require C++17 compiler with [[maybe_unused]] +#define UNUSED(x) (void)(x); diff --git a/projects/frontend/server-webapi/pistache/include/pistache/config.h b/projects/frontend/server-webapi/pistache/include/pistache/config.h new file mode 100755 index 0000000000000000000000000000000000000000..9c68169d1274cdc24686bc85ed1c453c632da60b --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/config.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +// Allow compile-time overload +namespace Pistache { +namespace Const { +static constexpr size_t MaxBacklog = 128; +static constexpr size_t MaxEvents = 1024; +static constexpr size_t MaxBuffer = 4 << 20; +static constexpr size_t DefaultWorkers = 1; + +static constexpr size_t DefaultTimerPoolSize = 128; + +// Defined from CMakeLists.txt in project root +static constexpr size_t DefaultMaxRequestSize = 4 << 20; +static constexpr size_t DefaultMaxResponseSize = + std::numeric_limits::max(); +static constexpr size_t ChunkSize = 1024; + +static constexpr uint16_t HTTP_STANDARD_PORT = 80; +} // namespace Const +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/cookie.h b/projects/frontend/server-webapi/pistache/include/pistache/cookie.h new file mode 100755 index 0000000000000000000000000000000000000000..99c1449df403985d65adb2ea0ea99cd83f1cc49a --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/cookie.h @@ -0,0 +1,130 @@ +/* + Mathieu Stefani, 16 janvier 2016 + + Representation of a Cookie as per http://tools.ietf.org/html/rfc6265 +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Pistache { +namespace Http { + +struct Cookie { + friend std::ostream &operator<<(std::ostream &os, const Cookie &cookie); + + Cookie(std::string name, std::string value); + + std::string name; + std::string value; + + Optional path; + Optional domain; + Optional expires; + + Optional maxAge; + bool secure; + bool httpOnly; + + std::map ext; + + static Cookie fromRaw(const char *str, size_t len); + static Cookie fromString(const std::string &str); + +private: + void write(std::ostream &os) const; +}; + +std::ostream &operator<<(std::ostream &os, const Cookie &cookie); + +class CookieJar { +public: + using HashMapCookies = + std::unordered_map; // "value" -> Cookie + using Storage = std::unordered_map< + std::string, HashMapCookies>; // "name" -> Hashmap("value" -> Cookie) + + struct iterator : std::iterator { + explicit iterator(const Storage::const_iterator &_iterator) + : iter_storage(_iterator), iter_cookie_values(), iter_storage_end() {} + + iterator(const Storage::const_iterator &_iterator, + const Storage::const_iterator &end) + : iter_storage(_iterator), iter_cookie_values(), iter_storage_end(end) { + if (iter_storage != iter_storage_end) { + iter_cookie_values = iter_storage->second.begin(); + } + } + + Cookie operator*() const { + return iter_cookie_values->second; // return iter_storage->second; + } + + const Cookie *operator->() const { return &(iter_cookie_values->second); } + + iterator &operator++() { + ++iter_cookie_values; + if (iter_cookie_values == iter_storage->second.end()) { + ++iter_storage; + if (iter_storage != iter_storage_end) + iter_cookie_values = iter_storage->second.begin(); + } + return *this; + } + + iterator operator++(int) { + iterator ret(iter_storage, iter_storage_end); + ++iter_cookie_values; + if (iter_cookie_values == iter_storage->second.end()) { + ++iter_storage; + if (iter_storage != iter_storage_end) // this check is important + iter_cookie_values = iter_storage->second.begin(); + } + + return ret; + } + + bool operator!=(iterator other) const { + return iter_storage != other.iter_storage; + } + + bool operator==(iterator other) const { + return iter_storage == other.iter_storage; + } + + private: + Storage::const_iterator iter_storage; + HashMapCookies::const_iterator iter_cookie_values; + Storage::const_iterator + iter_storage_end; // we need to know where main hashmap ends. + }; + + CookieJar(); + + void add(const Cookie &cookie); + void removeAllCookies(); + + void addFromRaw(const char *str, size_t len); + Cookie get(const std::string &name) const; + + bool has(const std::string &name) const; + + iterator begin() const { return iterator(cookies.begin(), cookies.end()); } + + iterator end() const { return iterator(cookies.end()); } + +private: + Storage cookies; +}; + +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/date.h b/projects/frontend/server-webapi/pistache/include/pistache/date.h new file mode 100755 index 0000000000000000000000000000000000000000..7e3162832bc22744eaf412aaa99c784bfd902f81 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/date.h @@ -0,0 +1,6470 @@ +#ifndef DATE_H +#define DATE_H + +// The MIT License (MIT) +// +// Copyright (c) 2015, 2016, 2017 Howard Hinnant +// Copyright (c) 2016 Adrian Colomitchi +// Copyright (c) 2017 Florian Dang +// Copyright (c) 2017 Paul Thompson +// +// 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. +// +// Our apologies. When the previous paragraph was written, lowercase had not +// yet been invented (that would involve another several millennia of +// evolution). We did not mean to shout. + +#include +#include +#include +#if !(__cplusplus >= 201402) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#if __GNUC__ < 5 +// GCC 4.9 Bug 61489 Wrong warning with -Wmissing-field-initializers +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif +#endif + +namespace date { + +//---------------+ +// Configuration | +//---------------+ + +#ifndef ONLY_C_LOCALE +#define ONLY_C_LOCALE 0 +#endif + +#if defined(_MSC_VER) && (!defined(__clang__) || (_MSC_VER < 1910)) +// MSVC +#if _MSC_VER < 1910 +// before VS2017 +#define CONSTDATA const +#define CONSTCD11 +#define CONSTCD14 +#define NOEXCEPT _NOEXCEPT +#else +// VS2017 and later +#define CONSTDATA constexpr const +#define CONSTCD11 constexpr +#define CONSTCD14 constexpr +#define NOEXCEPT noexcept +#endif + +#elif defined(__SUNPRO_CC) && __SUNPRO_CC <= 0x5150 +// Oracle Developer Studio 12.6 and earlier +#define CONSTDATA constexpr const +#define CONSTCD11 constexpr +#define CONSTCD14 +#define NOEXCEPT noexcept + +#elif __cplusplus >= 201402 +// C++14 +#define CONSTDATA constexpr const +#define CONSTCD11 constexpr +#define CONSTCD14 constexpr +#define NOEXCEPT noexcept +#else +// C++11 +#define CONSTDATA constexpr const +#define CONSTCD11 constexpr +#define CONSTCD14 +#define NOEXCEPT noexcept +#endif + +#ifndef HAS_VOID_T +#if __cplusplus >= 201703 +#define HAS_VOID_T 1 +#else +#define HAS_VOID_T 0 +#endif +#endif // HAS_VOID_T + +// Protect from Oracle sun macro +#ifdef sun +#undef sun +#endif + +//-----------+ +// Interface | +//-----------+ + +// durations + +using days = std::chrono::duration< + int, std::ratio_multiply, std::chrono::hours::period>>; + +using weeks = + std::chrono::duration, days::period>>; + +using years = std::chrono::duration< + int, std::ratio_multiply, days::period>>; + +using months = + std::chrono::duration>>; + +// time_point + +template +using sys_time = std::chrono::time_point; + +using sys_days = sys_time; +using sys_seconds = sys_time; + +struct local_t {}; + +template +using local_time = std::chrono::time_point; + +using local_seconds = local_time; +using local_days = local_time; + +// types + +struct last_spec { + explicit last_spec() = default; +}; + +class day; +class month; +class year; + +class weekday; +class weekday_indexed; +class weekday_last; + +class month_day; +class month_day_last; +class month_weekday; +class month_weekday_last; + +class year_month; + +class year_month_day; +class year_month_day_last; +class year_month_weekday; +class year_month_weekday_last; + +// date composition operators + +CONSTCD11 year_month operator/(const year &y, const month &m) NOEXCEPT; +CONSTCD11 year_month operator/(const year &y, int m) NOEXCEPT; + +CONSTCD11 month_day operator/(const day &d, const month &m) NOEXCEPT; +CONSTCD11 month_day operator/(const day &d, int m) NOEXCEPT; +CONSTCD11 month_day operator/(const month &m, const day &d) NOEXCEPT; +CONSTCD11 month_day operator/(const month &m, int d) NOEXCEPT; +CONSTCD11 month_day operator/(int m, const day &d) NOEXCEPT; + +CONSTCD11 month_day_last operator/(const month &m, last_spec) NOEXCEPT; +CONSTCD11 month_day_last operator/(int m, last_spec) NOEXCEPT; +CONSTCD11 month_day_last operator/(last_spec, const month &m) NOEXCEPT; +CONSTCD11 month_day_last operator/(last_spec, int m) NOEXCEPT; + +CONSTCD11 month_weekday operator/(const month &m, + const weekday_indexed &wdi) NOEXCEPT; +CONSTCD11 month_weekday operator/(int m, const weekday_indexed &wdi) NOEXCEPT; +CONSTCD11 month_weekday operator/(const weekday_indexed &wdi, + const month &m) NOEXCEPT; +CONSTCD11 month_weekday operator/(const weekday_indexed &wdi, int m) NOEXCEPT; + +CONSTCD11 month_weekday_last operator/(const month &m, + const weekday_last &wdl) NOEXCEPT; +CONSTCD11 month_weekday_last operator/(int m, const weekday_last &wdl) NOEXCEPT; +CONSTCD11 month_weekday_last operator/(const weekday_last &wdl, + const month &m) NOEXCEPT; +CONSTCD11 month_weekday_last operator/(const weekday_last &wdl, int m) NOEXCEPT; + +CONSTCD11 year_month_day operator/(const year_month &ym, const day &d) NOEXCEPT; +CONSTCD11 year_month_day operator/(const year_month &ym, int d) NOEXCEPT; +CONSTCD11 year_month_day operator/(const year &y, const month_day &md) NOEXCEPT; +CONSTCD11 year_month_day operator/(int y, const month_day &md) NOEXCEPT; +CONSTCD11 year_month_day operator/(const month_day &md, const year &y) NOEXCEPT; +CONSTCD11 year_month_day operator/(const month_day &md, int y) NOEXCEPT; + +CONSTCD11 +year_month_day_last operator/(const year_month &ym, last_spec) NOEXCEPT; +CONSTCD11 +year_month_day_last operator/(const year &y, + const month_day_last &mdl) NOEXCEPT; +CONSTCD11 +year_month_day_last operator/(int y, const month_day_last &mdl) NOEXCEPT; +CONSTCD11 +year_month_day_last operator/(const month_day_last &mdl, + const year &y) NOEXCEPT; +CONSTCD11 +year_month_day_last operator/(const month_day_last &mdl, int y) NOEXCEPT; + +CONSTCD11 +year_month_weekday operator/(const year_month &ym, + const weekday_indexed &wdi) NOEXCEPT; + +CONSTCD11 +year_month_weekday operator/(const year &y, const month_weekday &mwd) NOEXCEPT; + +CONSTCD11 +year_month_weekday operator/(int y, const month_weekday &mwd) NOEXCEPT; + +CONSTCD11 +year_month_weekday operator/(const month_weekday &mwd, const year &y) NOEXCEPT; + +CONSTCD11 +year_month_weekday operator/(const month_weekday &mwd, int y) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last operator/(const year_month &ym, + const weekday_last &wdl) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last operator/(const year &y, + const month_weekday_last &mwdl) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last operator/(int y, + const month_weekday_last &mwdl) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last operator/(const month_weekday_last &mwdl, + const year &y) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last operator/(const month_weekday_last &mwdl, + int y) NOEXCEPT; + +// Detailed interface + +// day + +class day { + unsigned char d_; + +public: + day() = default; + explicit CONSTCD11 day(unsigned d) NOEXCEPT; + + CONSTCD14 day &operator++() NOEXCEPT; + CONSTCD14 day operator++(int) NOEXCEPT; + CONSTCD14 day &operator--() NOEXCEPT; + CONSTCD14 day operator--(int) NOEXCEPT; + + CONSTCD14 day &operator+=(const days &d) NOEXCEPT; + CONSTCD14 day &operator-=(const days &d) NOEXCEPT; + + CONSTCD11 explicit operator unsigned() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const day &x, const day &y) NOEXCEPT; +CONSTCD11 bool operator!=(const day &x, const day &y) NOEXCEPT; +CONSTCD11 bool operator<(const day &x, const day &y) NOEXCEPT; +CONSTCD11 bool operator>(const day &x, const day &y) NOEXCEPT; +CONSTCD11 bool operator<=(const day &x, const day &y) NOEXCEPT; +CONSTCD11 bool operator>=(const day &x, const day &y) NOEXCEPT; + +CONSTCD11 day operator+(const day &x, const days &y) NOEXCEPT; +CONSTCD11 day operator+(const days &x, const day &y) NOEXCEPT; +CONSTCD11 day operator-(const day &x, const days &y) NOEXCEPT; +CONSTCD11 days operator-(const day &x, const day &y) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const day &d); + +// month + +class month { + unsigned char m_; + +public: + month() = default; + explicit CONSTCD11 month(unsigned m) NOEXCEPT; + + CONSTCD14 month &operator++() NOEXCEPT; + CONSTCD14 month operator++(int) NOEXCEPT; + CONSTCD14 month &operator--() NOEXCEPT; + CONSTCD14 month operator--(int) NOEXCEPT; + + CONSTCD14 month &operator+=(const months &m) NOEXCEPT; + CONSTCD14 month &operator-=(const months &m) NOEXCEPT; + + CONSTCD11 explicit operator unsigned() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const month &x, const month &y) NOEXCEPT; +CONSTCD11 bool operator!=(const month &x, const month &y) NOEXCEPT; +CONSTCD11 bool operator<(const month &x, const month &y) NOEXCEPT; +CONSTCD11 bool operator>(const month &x, const month &y) NOEXCEPT; +CONSTCD11 bool operator<=(const month &x, const month &y) NOEXCEPT; +CONSTCD11 bool operator>=(const month &x, const month &y) NOEXCEPT; + +CONSTCD14 month operator+(const month &x, const months &y) NOEXCEPT; +CONSTCD14 month operator+(const months &x, const month &y) NOEXCEPT; +CONSTCD14 month operator-(const month &x, const months &y) NOEXCEPT; +CONSTCD14 months operator-(const month &x, const month &y) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const month &m); + +// year + +class year { + short y_; + +public: + year() = default; + explicit CONSTCD11 year(int y) NOEXCEPT; + + CONSTCD14 year &operator++() NOEXCEPT; + CONSTCD14 year operator++(int) NOEXCEPT; + CONSTCD14 year &operator--() NOEXCEPT; + CONSTCD14 year operator--(int) NOEXCEPT; + + CONSTCD14 year &operator+=(const years &y) NOEXCEPT; + CONSTCD14 year &operator-=(const years &y) NOEXCEPT; + + CONSTCD11 year operator-() const NOEXCEPT; + CONSTCD11 year operator+() const NOEXCEPT; + + CONSTCD11 bool is_leap() const NOEXCEPT; + + CONSTCD11 explicit operator int() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; + + static CONSTCD11 year min() NOEXCEPT; + static CONSTCD11 year max() NOEXCEPT; +}; + +CONSTCD11 bool operator==(const year &x, const year &y) NOEXCEPT; +CONSTCD11 bool operator!=(const year &x, const year &y) NOEXCEPT; +CONSTCD11 bool operator<(const year &x, const year &y) NOEXCEPT; +CONSTCD11 bool operator>(const year &x, const year &y) NOEXCEPT; +CONSTCD11 bool operator<=(const year &x, const year &y) NOEXCEPT; +CONSTCD11 bool operator>=(const year &x, const year &y) NOEXCEPT; + +CONSTCD11 year operator+(const year &x, const years &y) NOEXCEPT; +CONSTCD11 year operator+(const years &x, const year &y) NOEXCEPT; +CONSTCD11 year operator-(const year &x, const years &y) NOEXCEPT; +CONSTCD11 years operator-(const year &x, const year &y) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const year &y); + +// weekday + +class weekday { + unsigned char wd_; + +public: + weekday() = default; + explicit CONSTCD11 weekday(unsigned wd) NOEXCEPT; + CONSTCD11 weekday(const sys_days &dp) NOEXCEPT; + CONSTCD11 explicit weekday(const local_days &dp) NOEXCEPT; + + CONSTCD14 weekday &operator++() NOEXCEPT; + CONSTCD14 weekday operator++(int) NOEXCEPT; + CONSTCD14 weekday &operator--() NOEXCEPT; + CONSTCD14 weekday operator--(int) NOEXCEPT; + + CONSTCD14 weekday &operator+=(const days &d) NOEXCEPT; + CONSTCD14 weekday &operator-=(const days &d) NOEXCEPT; + + CONSTCD11 explicit operator unsigned() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; + + CONSTCD11 weekday_indexed operator[](unsigned index) const NOEXCEPT; + CONSTCD11 weekday_last operator[](last_spec) const NOEXCEPT; + +private: + static CONSTCD11 unsigned char weekday_from_days(int z) NOEXCEPT; +}; + +CONSTCD11 bool operator==(const weekday &x, const weekday &y) NOEXCEPT; +CONSTCD11 bool operator!=(const weekday &x, const weekday &y) NOEXCEPT; + +CONSTCD14 weekday operator+(const weekday &x, const days &y) NOEXCEPT; +CONSTCD14 weekday operator+(const days &x, const weekday &y) NOEXCEPT; +CONSTCD14 weekday operator-(const weekday &x, const days &y) NOEXCEPT; +CONSTCD14 days operator-(const weekday &x, const weekday &y) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const weekday &wd); + +// weekday_indexed + +class weekday_indexed { + unsigned char wd_ : 4; + unsigned char index_ : 4; + +public: + weekday_indexed() = default; + CONSTCD11 weekday_indexed(const date::weekday &wd, unsigned index) NOEXCEPT; + + CONSTCD11 date::weekday weekday() const NOEXCEPT; + CONSTCD11 unsigned index() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const weekday_indexed &x, + const weekday_indexed &y) NOEXCEPT; +CONSTCD11 bool operator!=(const weekday_indexed &x, + const weekday_indexed &y) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const weekday_indexed &wdi); + +// weekday_last + +class weekday_last { + date::weekday wd_; + +public: + explicit CONSTCD11 weekday_last(const date::weekday &wd) NOEXCEPT; + + CONSTCD11 date::weekday weekday() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const weekday_last &x, + const weekday_last &y) NOEXCEPT; +CONSTCD11 bool operator!=(const weekday_last &x, + const weekday_last &y) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const weekday_last &wdl); + +// year_month + +class year_month { + date::year y_; + date::month m_; + +public: + year_month() = default; + CONSTCD11 year_month(const date::year &y, const date::month &m) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + + CONSTCD14 year_month &operator+=(const months &dm) NOEXCEPT; + CONSTCD14 year_month &operator-=(const months &dm) NOEXCEPT; + CONSTCD14 year_month &operator+=(const years &dy) NOEXCEPT; + CONSTCD14 year_month &operator-=(const years &dy) NOEXCEPT; + + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const year_month &x, const year_month &y) NOEXCEPT; +CONSTCD11 bool operator!=(const year_month &x, const year_month &y) NOEXCEPT; +CONSTCD11 bool operator<(const year_month &x, const year_month &y) NOEXCEPT; +CONSTCD11 bool operator>(const year_month &x, const year_month &y) NOEXCEPT; +CONSTCD11 bool operator<=(const year_month &x, const year_month &y) NOEXCEPT; +CONSTCD11 bool operator>=(const year_month &x, const year_month &y) NOEXCEPT; + +CONSTCD14 year_month operator+(const year_month &ym, const months &dm) NOEXCEPT; +CONSTCD14 year_month operator+(const months &dm, const year_month &ym) NOEXCEPT; +CONSTCD14 year_month operator-(const year_month &ym, const months &dm) NOEXCEPT; + +CONSTCD11 months operator-(const year_month &x, const year_month &y) NOEXCEPT; +CONSTCD11 year_month operator+(const year_month &ym, const years &dy) NOEXCEPT; +CONSTCD11 year_month operator+(const years &dy, const year_month &ym) NOEXCEPT; +CONSTCD11 year_month operator-(const year_month &ym, const years &dy) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const year_month &ym); + +// month_day + +class month_day { + date::month m_; + date::day d_; + +public: + month_day() = default; + CONSTCD11 month_day(const date::month &m, const date::day &d) NOEXCEPT; + + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::day day() const NOEXCEPT; + + CONSTCD14 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const month_day &x, const month_day &y) NOEXCEPT; +CONSTCD11 bool operator!=(const month_day &x, const month_day &y) NOEXCEPT; +CONSTCD11 bool operator<(const month_day &x, const month_day &y) NOEXCEPT; +CONSTCD11 bool operator>(const month_day &x, const month_day &y) NOEXCEPT; +CONSTCD11 bool operator<=(const month_day &x, const month_day &y) NOEXCEPT; +CONSTCD11 bool operator>=(const month_day &x, const month_day &y) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const month_day &md); + +// month_day_last + +class month_day_last { + date::month m_; + +public: + CONSTCD11 explicit month_day_last(const date::month &m) NOEXCEPT; + + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const month_day_last &x, + const month_day_last &y) NOEXCEPT; +CONSTCD11 bool operator!=(const month_day_last &x, + const month_day_last &y) NOEXCEPT; +CONSTCD11 bool operator<(const month_day_last &x, + const month_day_last &y) NOEXCEPT; +CONSTCD11 bool operator>(const month_day_last &x, + const month_day_last &y) NOEXCEPT; +CONSTCD11 bool operator<=(const month_day_last &x, + const month_day_last &y) NOEXCEPT; +CONSTCD11 bool operator>=(const month_day_last &x, + const month_day_last &y) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const month_day_last &mdl); + +// month_weekday + +class month_weekday { + date::month m_; + date::weekday_indexed wdi_; + +public: + CONSTCD11 month_weekday(const date::month &m, + const date::weekday_indexed &wdi) NOEXCEPT; + + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; + + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const month_weekday &x, + const month_weekday &y) NOEXCEPT; +CONSTCD11 bool operator!=(const month_weekday &x, + const month_weekday &y) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const month_weekday &mwd); + +// month_weekday_last + +class month_weekday_last { + date::month m_; + date::weekday_last wdl_; + +public: + CONSTCD11 month_weekday_last(const date::month &m, + const date::weekday_last &wdl) NOEXCEPT; + + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; + + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 +bool operator==(const month_weekday_last &x, + const month_weekday_last &y) NOEXCEPT; +CONSTCD11 +bool operator!=(const month_weekday_last &x, + const month_weekday_last &y) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, + const month_weekday_last &mwdl); + +// class year_month_day + +class year_month_day { + date::year y_; + date::month m_; + date::day d_; + +public: + year_month_day() = default; + CONSTCD11 year_month_day(const date::year &y, const date::month &m, + const date::day &d) NOEXCEPT; + CONSTCD14 year_month_day(const year_month_day_last &ymdl) NOEXCEPT; + + CONSTCD14 year_month_day(sys_days dp) NOEXCEPT; + CONSTCD14 explicit year_month_day(local_days dp) NOEXCEPT; + + CONSTCD14 year_month_day &operator+=(const months &m) NOEXCEPT; + CONSTCD14 year_month_day &operator-=(const months &m) NOEXCEPT; + CONSTCD14 year_month_day &operator+=(const years &y) NOEXCEPT; + CONSTCD14 year_month_day &operator-=(const years &y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::day day() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD14 bool ok() const NOEXCEPT; + +private: + static CONSTCD14 year_month_day from_days(days dp) NOEXCEPT; + CONSTCD14 days to_days() const NOEXCEPT; +}; + +CONSTCD11 bool operator==(const year_month_day &x, + const year_month_day &y) NOEXCEPT; +CONSTCD11 bool operator!=(const year_month_day &x, + const year_month_day &y) NOEXCEPT; +CONSTCD11 bool operator<(const year_month_day &x, + const year_month_day &y) NOEXCEPT; +CONSTCD11 bool operator>(const year_month_day &x, + const year_month_day &y) NOEXCEPT; +CONSTCD11 bool operator<=(const year_month_day &x, + const year_month_day &y) NOEXCEPT; +CONSTCD11 bool operator>=(const year_month_day &x, + const year_month_day &y) NOEXCEPT; + +CONSTCD14 year_month_day operator+(const year_month_day &ymd, + const months &dm) NOEXCEPT; +CONSTCD14 year_month_day operator+(const months &dm, + const year_month_day &ymd) NOEXCEPT; +CONSTCD14 year_month_day operator-(const year_month_day &ymd, + const months &dm) NOEXCEPT; +CONSTCD11 year_month_day operator+(const year_month_day &ymd, + const years &dy) NOEXCEPT; +CONSTCD11 year_month_day operator+(const years &dy, + const year_month_day &ymd) NOEXCEPT; +CONSTCD11 year_month_day operator-(const year_month_day &ymd, + const years &dy) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, const year_month_day &ymd); + +// year_month_day_last + +class year_month_day_last { + date::year y_; + date::month_day_last mdl_; + +public: + CONSTCD11 year_month_day_last(const date::year &y, + const date::month_day_last &mdl) NOEXCEPT; + + CONSTCD14 year_month_day_last &operator+=(const months &m) NOEXCEPT; + CONSTCD14 year_month_day_last &operator-=(const months &m) NOEXCEPT; + CONSTCD14 year_month_day_last &operator+=(const years &y) NOEXCEPT; + CONSTCD14 year_month_day_last &operator-=(const years &y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::month_day_last month_day_last() const NOEXCEPT; + CONSTCD14 date::day day() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; +}; + +CONSTCD11 +bool operator==(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT; +CONSTCD11 +bool operator!=(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT; +CONSTCD11 +bool operator<(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT; +CONSTCD11 +bool operator>(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT; +CONSTCD11 +bool operator<=(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT; +CONSTCD11 +bool operator>=(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT; + +CONSTCD14 +year_month_day_last operator+(const year_month_day_last &ymdl, + const months &dm) NOEXCEPT; + +CONSTCD14 +year_month_day_last operator+(const months &dm, + const year_month_day_last &ymdl) NOEXCEPT; + +CONSTCD11 +year_month_day_last operator+(const year_month_day_last &ymdl, + const years &dy) NOEXCEPT; + +CONSTCD11 +year_month_day_last operator+(const years &dy, + const year_month_day_last &ymdl) NOEXCEPT; + +CONSTCD14 +year_month_day_last operator-(const year_month_day_last &ymdl, + const months &dm) NOEXCEPT; + +CONSTCD11 +year_month_day_last operator-(const year_month_day_last &ymdl, + const years &dy) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, + const year_month_day_last &ymdl); + +// year_month_weekday + +class year_month_weekday { + date::year y_; + date::month m_; + date::weekday_indexed wdi_; + +public: + year_month_weekday() = default; + CONSTCD11 year_month_weekday(const date::year &y, const date::month &m, + const date::weekday_indexed &wdi) NOEXCEPT; + CONSTCD14 year_month_weekday(const sys_days &dp) NOEXCEPT; + CONSTCD14 explicit year_month_weekday(const local_days &dp) NOEXCEPT; + + CONSTCD14 year_month_weekday &operator+=(const months &m) NOEXCEPT; + CONSTCD14 year_month_weekday &operator-=(const months &m) NOEXCEPT; + CONSTCD14 year_month_weekday &operator+=(const years &y) NOEXCEPT; + CONSTCD14 year_month_weekday &operator-=(const years &y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday weekday() const NOEXCEPT; + CONSTCD11 unsigned index() const NOEXCEPT; + CONSTCD11 date::weekday_indexed weekday_indexed() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD14 bool ok() const NOEXCEPT; + +private: + static CONSTCD14 year_month_weekday from_days(days dp) NOEXCEPT; + CONSTCD14 days to_days() const NOEXCEPT; +}; + +CONSTCD11 +bool operator==(const year_month_weekday &x, + const year_month_weekday &y) NOEXCEPT; +CONSTCD11 +bool operator!=(const year_month_weekday &x, + const year_month_weekday &y) NOEXCEPT; + +CONSTCD14 +year_month_weekday operator+(const year_month_weekday &ymwd, + const months &dm) NOEXCEPT; + +CONSTCD14 +year_month_weekday operator+(const months &dm, + const year_month_weekday &ymwd) NOEXCEPT; + +CONSTCD11 +year_month_weekday operator+(const year_month_weekday &ymwd, + const years &dy) NOEXCEPT; + +CONSTCD11 +year_month_weekday operator+(const years &dy, + const year_month_weekday &ymwd) NOEXCEPT; + +CONSTCD14 +year_month_weekday operator-(const year_month_weekday &ymwd, + const months &dm) NOEXCEPT; + +CONSTCD11 +year_month_weekday operator-(const year_month_weekday &ymwd, + const years &dy) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, + const year_month_weekday &ymwdi); + +// year_month_weekday_last + +class year_month_weekday_last { + date::year y_; + date::month m_; + date::weekday_last wdl_; + +public: + CONSTCD11 year_month_weekday_last(const date::year &y, const date::month &m, + const date::weekday_last &wdl) NOEXCEPT; + + CONSTCD14 year_month_weekday_last &operator+=(const months &m) NOEXCEPT; + CONSTCD14 year_month_weekday_last &operator-=(const months &m) NOEXCEPT; + CONSTCD14 year_month_weekday_last &operator+=(const years &y) NOEXCEPT; + CONSTCD14 year_month_weekday_last &operator-=(const years &y) NOEXCEPT; + + CONSTCD11 date::year year() const NOEXCEPT; + CONSTCD11 date::month month() const NOEXCEPT; + CONSTCD11 date::weekday weekday() const NOEXCEPT; + CONSTCD11 date::weekday_last weekday_last() const NOEXCEPT; + + CONSTCD14 operator sys_days() const NOEXCEPT; + CONSTCD14 explicit operator local_days() const NOEXCEPT; + CONSTCD11 bool ok() const NOEXCEPT; + +private: + CONSTCD14 days to_days() const NOEXCEPT; +}; + +CONSTCD11 +bool operator==(const year_month_weekday_last &x, + const year_month_weekday_last &y) NOEXCEPT; + +CONSTCD11 +bool operator!=(const year_month_weekday_last &x, + const year_month_weekday_last &y) NOEXCEPT; + +CONSTCD14 +year_month_weekday_last operator+(const year_month_weekday_last &ymwdl, + const months &dm) NOEXCEPT; + +CONSTCD14 +year_month_weekday_last +operator+(const months &dm, const year_month_weekday_last &ymwdl) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last operator+(const year_month_weekday_last &ymwdl, + const years &dy) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last +operator+(const years &dy, const year_month_weekday_last &ymwdl) NOEXCEPT; + +CONSTCD14 +year_month_weekday_last operator-(const year_month_weekday_last &ymwdl, + const months &dm) NOEXCEPT; + +CONSTCD11 +year_month_weekday_last operator-(const year_month_weekday_last &ymwdl, + const years &dy) NOEXCEPT; + +template +std::basic_ostream & +operator<<(std::basic_ostream &os, + const year_month_weekday_last &ymwdl); + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +inline namespace literals { + +CONSTCD11 date::day operator"" _d(unsigned long long d) NOEXCEPT; +CONSTCD11 date::year operator"" _y(unsigned long long y) NOEXCEPT; + +// CONSTDATA date::month jan{1}; +// CONSTDATA date::month feb{2}; +// CONSTDATA date::month mar{3}; +// CONSTDATA date::month apr{4}; +// CONSTDATA date::month may{5}; +// CONSTDATA date::month jun{6}; +// CONSTDATA date::month jul{7}; +// CONSTDATA date::month aug{8}; +// CONSTDATA date::month sep{9}; +// CONSTDATA date::month oct{10}; +// CONSTDATA date::month nov{11}; +// CONSTDATA date::month dec{12}; +// +// CONSTDATA date::weekday sun{0u}; +// CONSTDATA date::weekday mon{1u}; +// CONSTDATA date::weekday tue{2u}; +// CONSTDATA date::weekday wed{3u}; +// CONSTDATA date::weekday thu{4u}; +// CONSTDATA date::weekday fri{5u}; +// CONSTDATA date::weekday sat{6u}; + +} // namespace literals +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) + +#if HAS_VOID_T + +template > struct is_clock : std::false_type {}; + +template +struct is_clock> + : std::true_type {}; + +#endif // HAS_VOID_T + +//----------------+ +// Implementation | +//----------------+ + +// utilities +namespace detail { + +template > +class save_stream { + std::basic_ostream &os_; + CharT fill_; + std::ios::fmtflags flags_; + std::locale loc_; + +public: + ~save_stream() { + os_.fill(fill_); + os_.flags(flags_); + os_.imbue(loc_); + } + + save_stream(const save_stream &) = delete; + save_stream &operator=(const save_stream &) = delete; + + explicit save_stream(std::basic_ostream &os) + : os_(os), fill_(os.fill()), flags_(os.flags()), loc_(os.getloc()) {} +}; + +template struct choose_trunc_type { + static const int digits = std::numeric_limits::digits; + using type = typename std::conditional < digits < 32, std::int32_t, + typename std::conditional::type>::type; +}; + +template +CONSTCD11 inline + typename std::enable_if::value, + T>::type + trunc(T t) NOEXCEPT { + return t; +} + +template +CONSTCD14 inline + typename std::enable_if::value, + T>::type + trunc(T t) NOEXCEPT { + using namespace std; + using I = typename choose_trunc_type::type; + CONSTDATA auto digits = numeric_limits::digits; + static_assert(digits < numeric_limits::digits, ""); + CONSTDATA auto max = I{1} << (digits - 1); + CONSTDATA auto min = -max; + if (min <= t && t <= max && t != 0 && t == t) { + const auto negative = t < T{0}; + t = static_cast(static_cast(t)); + if (t == 0 && negative) + t = -t; + } + return t; +} + +} // namespace detail + +// trunc towards zero +template +CONSTCD11 inline To trunc(const std::chrono::duration &d) { + return To{detail::trunc(std::chrono::duration_cast(d).count())}; +} + +#ifndef HAS_CHRONO_ROUNDING +#if defined(_MSC_FULL_VER) && _MSC_FULL_VER >= 190023918 +#define HAS_CHRONO_ROUNDING 1 +#elif defined(__cpp_lib_chrono) && __cplusplus > 201402 && \ + __cpp_lib_chrono >= 201510 +#define HAS_CHRONO_ROUNDING 1 +#elif defined(_LIBCPP_VERSION) && __cplusplus > 201402 && \ + _LIBCPP_VERSION >= 3800 +#define HAS_CHRONO_ROUNDING 1 +#else +#define HAS_CHRONO_ROUNDING 0 +#endif +#endif // HAS_CHRONO_ROUNDING + +#if HAS_CHRONO_ROUNDING == 0 + +// round down +template +CONSTCD14 inline To floor(const std::chrono::duration &d) { + auto t = trunc(d); + if (t > d) + return t - To{1}; + return t; +} + +// round to nearest, to even on tie +template +CONSTCD14 inline To round(const std::chrono::duration &d) { + auto t0 = floor(d); + auto t1 = t0 + To{1}; + if (t1 == To{0} && t0 < To{0}) + t1 = -t1; + auto diff0 = d - t0; + auto diff1 = t1 - d; + if (diff0 == diff1) { + if (t0 - trunc(t0 / 2) * 2 == To{0}) + return t0; + return t1; + } + if (diff0 < diff1) + return t0; + return t1; +} + +// round up +template +CONSTCD14 inline To ceil(const std::chrono::duration &d) { + auto t = trunc(d); + if (t < d) + return t + To{1}; + return t; +} + +template < + class Rep, class Period, + class = typename std::enable_if::is_signed>::type> +CONSTCD11 std::chrono::duration +abs(std::chrono::duration d) { + return d >= d.zero() ? d : -d; +} + +// round down +template +CONSTCD11 inline std::chrono::time_point +floor(const std::chrono::time_point &tp) { + using std::chrono::time_point; + return time_point{date::floor(tp.time_since_epoch())}; +} + +// round to nearest, to even on tie +template +CONSTCD11 inline std::chrono::time_point +round(const std::chrono::time_point &tp) { + using std::chrono::time_point; + return time_point{round(tp.time_since_epoch())}; +} + +// round up +template +CONSTCD11 inline std::chrono::time_point +ceil(const std::chrono::time_point &tp) { + using std::chrono::time_point; + return time_point{ceil(tp.time_since_epoch())}; +} + +#else // HAS_CHRONO_ROUNDING == 1 + +using std::chrono::abs; +using std::chrono::ceil; +using std::chrono::floor; +using std::chrono::round; + +#endif // HAS_CHRONO_ROUNDING + +// trunc towards zero +template +CONSTCD11 inline std::chrono::time_point +trunc(const std::chrono::time_point &tp) { + using std::chrono::time_point; + return time_point{trunc(tp.time_since_epoch())}; +} + +// day + +CONSTCD11 inline day::day(unsigned d) NOEXCEPT + : d_(static_cast(d)) {} +CONSTCD14 inline day &day::operator++() NOEXCEPT { + ++d_; + return *this; +} +CONSTCD14 inline day day::operator++(int) NOEXCEPT { + auto tmp(*this); + ++(*this); + return tmp; +} +CONSTCD14 inline day &day::operator--() NOEXCEPT { + --d_; + return *this; +} +CONSTCD14 inline day day::operator--(int) NOEXCEPT { + auto tmp(*this); + --(*this); + return tmp; +} +CONSTCD14 inline day &day::operator+=(const days &d) NOEXCEPT { + *this = *this + d; + return *this; +} +CONSTCD14 inline day &day::operator-=(const days &d) NOEXCEPT { + *this = *this - d; + return *this; +} +CONSTCD11 inline day::operator unsigned() const NOEXCEPT { return d_; } +CONSTCD11 inline bool day::ok() const NOEXCEPT { return 1 <= d_ && d_ <= 31; } + +CONSTCD11 +inline bool operator==(const day &x, const day &y) NOEXCEPT { + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline bool operator!=(const day &x, const day &y) NOEXCEPT { + return !(x == y); +} + +CONSTCD11 +inline bool operator<(const day &x, const day &y) NOEXCEPT { + return static_cast(x) < static_cast(y); +} + +CONSTCD11 +inline bool operator>(const day &x, const day &y) NOEXCEPT { return y < x; } + +CONSTCD11 +inline bool operator<=(const day &x, const day &y) NOEXCEPT { return !(y < x); } + +CONSTCD11 +inline bool operator>=(const day &x, const day &y) NOEXCEPT { return !(x < y); } + +CONSTCD11 +inline days operator-(const day &x, const day &y) NOEXCEPT { + return days{static_cast(static_cast(x) - + static_cast(y))}; +} + +CONSTCD11 +inline day operator+(const day &x, const days &y) NOEXCEPT { + return day{static_cast(x) + static_cast(y.count())}; +} + +CONSTCD11 +inline day operator+(const days &x, const day &y) NOEXCEPT { return y + x; } + +CONSTCD11 +inline day operator-(const day &x, const days &y) NOEXCEPT { return x + -y; } + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const day &d) { + detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << static_cast(d); + return os; +} + +// month + +CONSTCD11 inline month::month(unsigned m) NOEXCEPT + : m_(static_cast(m)) {} +CONSTCD14 inline month &month::operator++() NOEXCEPT { + if (++m_ == 13) + m_ = 1; + return *this; +} +CONSTCD14 inline month month::operator++(int) NOEXCEPT { + auto tmp(*this); + ++(*this); + return tmp; +} +CONSTCD14 inline month &month::operator--() NOEXCEPT { + if (--m_ == 0) + m_ = 12; + return *this; +} +CONSTCD14 inline month month::operator--(int) NOEXCEPT { + auto tmp(*this); + --(*this); + return tmp; +} + +CONSTCD14 +inline month &month::operator+=(const months &m) NOEXCEPT { + *this = *this + m; + return *this; +} + +CONSTCD14 +inline month &month::operator-=(const months &m) NOEXCEPT { + *this = *this - m; + return *this; +} + +CONSTCD11 inline month::operator unsigned() const NOEXCEPT { return m_; } +CONSTCD11 inline bool month::ok() const NOEXCEPT { return 1 <= m_ && m_ <= 12; } + +CONSTCD11 +inline bool operator==(const month &x, const month &y) NOEXCEPT { + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline bool operator!=(const month &x, const month &y) NOEXCEPT { + return !(x == y); +} + +CONSTCD11 +inline bool operator<(const month &x, const month &y) NOEXCEPT { + return static_cast(x) < static_cast(y); +} + +CONSTCD11 +inline bool operator>(const month &x, const month &y) NOEXCEPT { return y < x; } + +CONSTCD11 +inline bool operator<=(const month &x, const month &y) NOEXCEPT { + return !(y < x); +} + +CONSTCD11 +inline bool operator>=(const month &x, const month &y) NOEXCEPT { + return !(x < y); +} + +CONSTCD14 +inline months operator-(const month &x, const month &y) NOEXCEPT { + auto const d = static_cast(x) - static_cast(y); + return months(d <= 11 ? d : d + 12); +} + +CONSTCD14 +inline month operator+(const month &x, const months &y) NOEXCEPT { + auto const mu = + static_cast(static_cast(x)) - 1 + y.count(); + auto const yr = (mu >= 0 ? mu : mu - 11) / 12; + return month{static_cast(mu - yr * 12 + 1)}; +} + +CONSTCD14 +inline month operator+(const months &x, const month &y) NOEXCEPT { + return y + x; +} + +CONSTCD14 +inline month operator-(const month &x, const months &y) NOEXCEPT { + return x + -y; +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const month &m) { + switch (static_cast(m)) { + case 1: + os << "Jan"; + break; + case 2: + os << "Feb"; + break; + case 3: + os << "Mar"; + break; + case 4: + os << "Apr"; + break; + case 5: + os << "May"; + break; + case 6: + os << "Jun"; + break; + case 7: + os << "Jul"; + break; + case 8: + os << "Aug"; + break; + case 9: + os << "Sep"; + break; + case 10: + os << "Oct"; + break; + case 11: + os << "Nov"; + break; + case 12: + os << "Dec"; + break; + default: + os << static_cast(m) << " is not a valid month"; + break; + } + return os; +} + +// year + +CONSTCD11 inline year::year(int y) NOEXCEPT : y_(static_cast(y)) { +} +CONSTCD14 inline year &year::operator++() NOEXCEPT { + ++y_; + return *this; +} +CONSTCD14 inline year year::operator++(int) NOEXCEPT { + auto tmp(*this); + ++(*this); + return tmp; +} +CONSTCD14 inline year &year::operator--() NOEXCEPT { + --y_; + return *this; +} +CONSTCD14 inline year year::operator--(int) NOEXCEPT { + auto tmp(*this); + --(*this); + return tmp; +} +CONSTCD14 inline year &year::operator+=(const years &y) NOEXCEPT { + *this = *this + y; + return *this; +} +CONSTCD14 inline year &year::operator-=(const years &y) NOEXCEPT { + *this = *this - y; + return *this; +} +CONSTCD11 inline year year::operator-() const NOEXCEPT { return year{-y_}; } +CONSTCD11 inline year year::operator+() const NOEXCEPT { return *this; } + +CONSTCD11 +inline bool year::is_leap() const NOEXCEPT { + return y_ % 4 == 0 && (y_ % 100 != 0 || y_ % 400 == 0); +} + +CONSTCD11 inline year::operator int() const NOEXCEPT { return y_; } + +CONSTCD11 +inline bool year::ok() const NOEXCEPT { + return y_ != std::numeric_limits::min(); +} + +CONSTCD11 +inline year year::min() NOEXCEPT { return year{-32767}; } + +CONSTCD11 +inline year year::max() NOEXCEPT { return year{32767}; } + +CONSTCD11 +inline bool operator==(const year &x, const year &y) NOEXCEPT { + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline bool operator!=(const year &x, const year &y) NOEXCEPT { + return !(x == y); +} + +CONSTCD11 +inline bool operator<(const year &x, const year &y) NOEXCEPT { + return static_cast(x) < static_cast(y); +} + +CONSTCD11 +inline bool operator>(const year &x, const year &y) NOEXCEPT { return y < x; } + +CONSTCD11 +inline bool operator<=(const year &x, const year &y) NOEXCEPT { + return !(y < x); +} + +CONSTCD11 +inline bool operator>=(const year &x, const year &y) NOEXCEPT { + return !(x < y); +} + +CONSTCD11 +inline years operator-(const year &x, const year &y) NOEXCEPT { + return years{static_cast(x) - static_cast(y)}; +} + +CONSTCD11 +inline year operator+(const year &x, const years &y) NOEXCEPT { + return year{static_cast(x) + y.count()}; +} + +CONSTCD11 +inline year operator+(const years &x, const year &y) NOEXCEPT { return y + x; } + +CONSTCD11 +inline year operator-(const year &x, const years &y) NOEXCEPT { + return year{static_cast(x) - y.count()}; +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const year &y) { + detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::internal); + os.width(4 + (y < year{0})); + os << static_cast(y); + return os; +} + +// weekday + +CONSTCD11 +inline unsigned char weekday::weekday_from_days(int z) NOEXCEPT { + return static_cast( + static_cast(z >= -4 ? (z + 4) % 7 : (z + 5) % 7 + 6)); +} + +CONSTCD11 +inline weekday::weekday(unsigned wd) NOEXCEPT + : wd_(static_cast(wd)) {} + +CONSTCD11 +inline weekday::weekday(const sys_days &dp) NOEXCEPT + : wd_(weekday_from_days(dp.time_since_epoch().count())) {} + +CONSTCD11 +inline weekday::weekday(const local_days &dp) NOEXCEPT + : wd_(weekday_from_days(dp.time_since_epoch().count())) {} + +CONSTCD14 inline weekday &weekday::operator++() NOEXCEPT { + if (++wd_ == 7) + wd_ = 0; + return *this; +} +CONSTCD14 inline weekday weekday::operator++(int) NOEXCEPT { + auto tmp(*this); + ++(*this); + return tmp; +} +CONSTCD14 inline weekday &weekday::operator--() NOEXCEPT { + if (wd_-- == 0) + wd_ = 6; + return *this; +} +CONSTCD14 inline weekday weekday::operator--(int) NOEXCEPT { + auto tmp(*this); + --(*this); + return tmp; +} + +CONSTCD14 +inline weekday &weekday::operator+=(const days &d) NOEXCEPT { + *this = *this + d; + return *this; +} + +CONSTCD14 +inline weekday &weekday::operator-=(const days &d) NOEXCEPT { + *this = *this - d; + return *this; +} + +CONSTCD11 +inline weekday::operator unsigned() const NOEXCEPT { + return static_cast(wd_); +} + +CONSTCD11 inline bool weekday::ok() const NOEXCEPT { return wd_ <= 6; } + +CONSTCD11 +inline bool operator==(const weekday &x, const weekday &y) NOEXCEPT { + return static_cast(x) == static_cast(y); +} + +CONSTCD11 +inline bool operator!=(const weekday &x, const weekday &y) NOEXCEPT { + return !(x == y); +} + +CONSTCD14 +inline days operator-(const weekday &x, const weekday &y) NOEXCEPT { + auto const diff = static_cast(x) - static_cast(y); + return days{diff <= 6 ? diff : diff + 7}; +} + +CONSTCD14 +inline weekday operator+(const weekday &x, const days &y) NOEXCEPT { + auto const wdu = static_cast(static_cast(x)) + y.count(); + auto const wk = (wdu >= 0 ? wdu : wdu - 6) / 7; + return weekday{static_cast(wdu - wk * 7)}; +} + +CONSTCD14 +inline weekday operator+(const days &x, const weekday &y) NOEXCEPT { + return y + x; +} + +CONSTCD14 +inline weekday operator-(const weekday &x, const days &y) NOEXCEPT { + return x + -y; +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const weekday &wd) { + switch (static_cast(wd)) { + case 0: + os << "Sun"; + break; + case 1: + os << "Mon"; + break; + case 2: + os << "Tue"; + break; + case 3: + os << "Wed"; + break; + case 4: + os << "Thu"; + break; + case 5: + os << "Fri"; + break; + case 6: + os << "Sat"; + break; + default: + os << static_cast(wd) << " is not a valid weekday"; + break; + } + return os; +} + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +inline namespace literals { + +CONSTCD11 +inline date::day operator"" _d(unsigned long long d) NOEXCEPT { + return date::day{static_cast(d)}; +} + +CONSTCD11 +inline date::year operator"" _y(unsigned long long y) NOEXCEPT { + return date::year(static_cast(y)); +} +#endif // !defined(_MSC_VER) || (_MSC_VER >= 1900) + +CONSTDATA date::last_spec last{}; + +CONSTDATA date::month jan{1}; +CONSTDATA date::month feb{2}; +CONSTDATA date::month mar{3}; +CONSTDATA date::month apr{4}; +CONSTDATA date::month may{5}; +CONSTDATA date::month jun{6}; +CONSTDATA date::month jul{7}; +CONSTDATA date::month aug{8}; +CONSTDATA date::month sep{9}; +CONSTDATA date::month oct{10}; +CONSTDATA date::month nov{11}; +CONSTDATA date::month dec{12}; + +CONSTDATA date::weekday sun{0u}; +CONSTDATA date::weekday mon{1u}; +CONSTDATA date::weekday tue{2u}; +CONSTDATA date::weekday wed{3u}; +CONSTDATA date::weekday thu{4u}; +CONSTDATA date::weekday fri{5u}; +CONSTDATA date::weekday sat{6u}; + +#if !defined(_MSC_VER) || (_MSC_VER >= 1900) +} // inline namespace literals +#endif + +// weekday_indexed + +CONSTCD11 +inline weekday weekday_indexed::weekday() const NOEXCEPT { + return date::weekday{static_cast(wd_)}; +} + +CONSTCD11 inline unsigned weekday_indexed::index() const NOEXCEPT { + return index_; +} + +CONSTCD11 +inline bool weekday_indexed::ok() const NOEXCEPT { + return weekday().ok() && 1 <= index_ && index_ <= 5; +} + +CONSTCD11 +inline weekday_indexed::weekday_indexed(const date::weekday &wd, + unsigned index) NOEXCEPT + : wd_(static_cast(static_cast(wd))), + index_(static_cast(index)) {} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const weekday_indexed &wdi) { + return os << wdi.weekday() << '[' << wdi.index() << ']'; +} + +CONSTCD11 +inline weekday_indexed weekday::operator[](unsigned index) const NOEXCEPT { + return {*this, index}; +} + +CONSTCD11 +inline bool operator==(const weekday_indexed &x, + const weekday_indexed &y) NOEXCEPT { + return x.weekday() == y.weekday() && x.index() == y.index(); +} + +CONSTCD11 +inline bool operator!=(const weekday_indexed &x, + const weekday_indexed &y) NOEXCEPT { + return !(x == y); +} + +// weekday_last + +CONSTCD11 inline date::weekday weekday_last::weekday() const NOEXCEPT { + return wd_; +} +CONSTCD11 inline bool weekday_last::ok() const NOEXCEPT { return wd_.ok(); } +CONSTCD11 inline weekday_last::weekday_last(const date::weekday &wd) NOEXCEPT + : wd_(wd) {} + +CONSTCD11 +inline bool operator==(const weekday_last &x, const weekday_last &y) NOEXCEPT { + return x.weekday() == y.weekday(); +} + +CONSTCD11 +inline bool operator!=(const weekday_last &x, const weekday_last &y) NOEXCEPT { + return !(x == y); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const weekday_last &wdl) { + return os << wdl.weekday() << "[last]"; +} + +CONSTCD11 +inline weekday_last weekday::operator[](last_spec) const NOEXCEPT { + return weekday_last{*this}; +} + +// year_month + +CONSTCD11 +inline year_month::year_month(const date::year &y, + const date::month &m) NOEXCEPT : y_(y), + m_(m) {} + +CONSTCD11 inline year year_month::year() const NOEXCEPT { return y_; } +CONSTCD11 inline month year_month::month() const NOEXCEPT { return m_; } +CONSTCD11 inline bool year_month::ok() const NOEXCEPT { + return y_.ok() && m_.ok(); +} + +CONSTCD14 +inline year_month &year_month::operator+=(const months &dm) NOEXCEPT { + *this = *this + dm; + return *this; +} + +CONSTCD14 +inline year_month &year_month::operator-=(const months &dm) NOEXCEPT { + *this = *this - dm; + return *this; +} + +CONSTCD14 +inline year_month &year_month::operator+=(const years &dy) NOEXCEPT { + *this = *this + dy; + return *this; +} + +CONSTCD14 +inline year_month &year_month::operator-=(const years &dy) NOEXCEPT { + *this = *this - dy; + return *this; +} + +CONSTCD11 +inline bool operator==(const year_month &x, const year_month &y) NOEXCEPT { + return x.year() == y.year() && x.month() == y.month(); +} + +CONSTCD11 +inline bool operator!=(const year_month &x, const year_month &y) NOEXCEPT { + return !(x == y); +} + +CONSTCD11 +inline bool operator<(const year_month &x, const year_month &y) NOEXCEPT { + return x.year() < y.year() + ? true + : (x.year() > y.year() ? false : (x.month() < y.month())); +} + +CONSTCD11 +inline bool operator>(const year_month &x, const year_month &y) NOEXCEPT { + return y < x; +} + +CONSTCD11 +inline bool operator<=(const year_month &x, const year_month &y) NOEXCEPT { + return !(y < x); +} + +CONSTCD11 +inline bool operator>=(const year_month &x, const year_month &y) NOEXCEPT { + return !(x < y); +} + +CONSTCD14 +inline year_month operator+(const year_month &ym, const months &dm) NOEXCEPT { + auto dmi = + static_cast(static_cast(ym.month())) - 1 + dm.count(); + auto dy = (dmi >= 0 ? dmi : dmi - 11) / 12; + dmi = dmi - dy * 12 + 1; + return (ym.year() + years(dy)) / month(static_cast(dmi)); +} + +CONSTCD14 +inline year_month operator+(const months &dm, const year_month &ym) NOEXCEPT { + return ym + dm; +} + +CONSTCD14 +inline year_month operator-(const year_month &ym, const months &dm) NOEXCEPT { + return ym + -dm; +} + +CONSTCD11 +inline months operator-(const year_month &x, const year_month &y) NOEXCEPT { + return (x.year() - y.year()) + months(static_cast(x.month()) - + static_cast(y.month())); +} + +CONSTCD11 +inline year_month operator+(const year_month &ym, const years &dy) NOEXCEPT { + return (ym.year() + dy) / ym.month(); +} + +CONSTCD11 +inline year_month operator+(const years &dy, const year_month &ym) NOEXCEPT { + return ym + dy; +} + +CONSTCD11 +inline year_month operator-(const year_month &ym, const years &dy) NOEXCEPT { + return ym + -dy; +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const year_month &ym) { + return os << ym.year() << '/' << ym.month(); +} + +// month_day + +CONSTCD11 +inline month_day::month_day(const date::month &m, const date::day &d) NOEXCEPT + : m_(m), + d_(d) {} + +CONSTCD11 inline date::month month_day::month() const NOEXCEPT { return m_; } +CONSTCD11 inline date::day month_day::day() const NOEXCEPT { return d_; } + +CONSTCD14 +inline bool month_day::ok() const NOEXCEPT { + CONSTDATA date::day d[] = {date::day(31), date::day(29), date::day(31), + date::day(30), date::day(31), date::day(30), + date::day(31), date::day(31), date::day(30), + date::day(31), date::day(30), date::day(31)}; + return m_.ok() && date::day{1} <= d_ && + d_ <= d[static_cast(m_) - 1]; +} + +CONSTCD11 +inline bool operator==(const month_day &x, const month_day &y) NOEXCEPT { + return x.month() == y.month() && x.day() == y.day(); +} + +CONSTCD11 +inline bool operator!=(const month_day &x, const month_day &y) NOEXCEPT { + return !(x == y); +} + +CONSTCD11 +inline bool operator<(const month_day &x, const month_day &y) NOEXCEPT { + return x.month() < y.month() + ? true + : (x.month() > y.month() ? false : (x.day() < y.day())); +} + +CONSTCD11 +inline bool operator>(const month_day &x, const month_day &y) NOEXCEPT { + return y < x; +} + +CONSTCD11 +inline bool operator<=(const month_day &x, const month_day &y) NOEXCEPT { + return !(y < x); +} + +CONSTCD11 +inline bool operator>=(const month_day &x, const month_day &y) NOEXCEPT { + return !(x < y); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const month_day &md) { + return os << md.month() << '/' << md.day(); +} + +// month_day_last + +CONSTCD11 inline month month_day_last::month() const NOEXCEPT { return m_; } +CONSTCD11 inline bool month_day_last::ok() const NOEXCEPT { return m_.ok(); } +CONSTCD11 inline month_day_last::month_day_last(const date::month &m) NOEXCEPT + : m_(m) {} + +CONSTCD11 +inline bool operator==(const month_day_last &x, + const month_day_last &y) NOEXCEPT { + return x.month() == y.month(); +} + +CONSTCD11 +inline bool operator!=(const month_day_last &x, + const month_day_last &y) NOEXCEPT { + return !(x == y); +} + +CONSTCD11 +inline bool operator<(const month_day_last &x, + const month_day_last &y) NOEXCEPT { + return x.month() < y.month(); +} + +CONSTCD11 +inline bool operator>(const month_day_last &x, + const month_day_last &y) NOEXCEPT { + return y < x; +} + +CONSTCD11 +inline bool operator<=(const month_day_last &x, + const month_day_last &y) NOEXCEPT { + return !(y < x); +} + +CONSTCD11 +inline bool operator>=(const month_day_last &x, + const month_day_last &y) NOEXCEPT { + return !(x < y); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const month_day_last &mdl) { + return os << mdl.month() << "/last"; +} + +// month_weekday + +CONSTCD11 +inline month_weekday::month_weekday(const date::month &m, + const date::weekday_indexed &wdi) NOEXCEPT + : m_(m), + wdi_(wdi) {} + +CONSTCD11 inline month month_weekday::month() const NOEXCEPT { return m_; } + +CONSTCD11 +inline weekday_indexed month_weekday::weekday_indexed() const NOEXCEPT { + return wdi_; +} + +CONSTCD11 +inline bool month_weekday::ok() const NOEXCEPT { return m_.ok() && wdi_.ok(); } + +CONSTCD11 +inline bool operator==(const month_weekday &x, + const month_weekday &y) NOEXCEPT { + return x.month() == y.month() && x.weekday_indexed() == y.weekday_indexed(); +} + +CONSTCD11 +inline bool operator!=(const month_weekday &x, + const month_weekday &y) NOEXCEPT { + return !(x == y); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const month_weekday &mwd) { + return os << mwd.month() << '/' << mwd.weekday_indexed(); +} + +// month_weekday_last + +CONSTCD11 +inline month_weekday_last::month_weekday_last( + const date::month &m, const date::weekday_last &wdl) NOEXCEPT : m_(m), + wdl_(wdl) {} + +CONSTCD11 inline month month_weekday_last::month() const NOEXCEPT { return m_; } + +CONSTCD11 +inline weekday_last month_weekday_last::weekday_last() const NOEXCEPT { + return wdl_; +} + +CONSTCD11 +inline bool month_weekday_last::ok() const NOEXCEPT { + return m_.ok() && wdl_.ok(); +} + +CONSTCD11 +inline bool operator==(const month_weekday_last &x, + const month_weekday_last &y) NOEXCEPT { + return x.month() == y.month() && x.weekday_last() == y.weekday_last(); +} + +CONSTCD11 +inline bool operator!=(const month_weekday_last &x, + const month_weekday_last &y) NOEXCEPT { + return !(x == y); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, + const month_weekday_last &mwdl) { + return os << mwdl.month() << '/' << mwdl.weekday_last(); +} + +// year_month_day_last + +CONSTCD11 +inline year_month_day_last::year_month_day_last( + const date::year &y, const date::month_day_last &mdl) NOEXCEPT : y_(y), + mdl_(mdl) { +} + +CONSTCD14 +inline year_month_day_last &year_month_day_last:: +operator+=(const months &m) NOEXCEPT { + *this = *this + m; + return *this; +} + +CONSTCD14 +inline year_month_day_last &year_month_day_last:: +operator-=(const months &m) NOEXCEPT { + *this = *this - m; + return *this; +} + +CONSTCD14 +inline year_month_day_last &year_month_day_last:: +operator+=(const years &y) NOEXCEPT { + *this = *this + y; + return *this; +} + +CONSTCD14 +inline year_month_day_last &year_month_day_last:: +operator-=(const years &y) NOEXCEPT { + *this = *this - y; + return *this; +} + +CONSTCD11 inline year year_month_day_last::year() const NOEXCEPT { return y_; } +CONSTCD11 inline month year_month_day_last::month() const NOEXCEPT { + return mdl_.month(); +} + +CONSTCD11 +inline month_day_last year_month_day_last::month_day_last() const NOEXCEPT { + return mdl_; +} + +CONSTCD14 +inline day year_month_day_last::day() const NOEXCEPT { + CONSTDATA date::day d[] = {date::day(31), date::day(28), date::day(31), + date::day(30), date::day(31), date::day(30), + date::day(31), date::day(31), date::day(30), + date::day(31), date::day(30), date::day(31)}; + return month() != feb || !y_.is_leap() ? d[static_cast(month()) - 1] + : date::day{29}; +} + +CONSTCD14 +inline year_month_day_last::operator sys_days() const NOEXCEPT { + return sys_days(year() / month() / day()); +} + +CONSTCD14 +inline year_month_day_last::operator local_days() const NOEXCEPT { + return local_days(year() / month() / day()); +} + +CONSTCD11 +inline bool year_month_day_last::ok() const NOEXCEPT { + return y_.ok() && mdl_.ok(); +} + +CONSTCD11 +inline bool operator==(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT { + return x.year() == y.year() && x.month_day_last() == y.month_day_last(); +} + +CONSTCD11 +inline bool operator!=(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT { + return !(x == y); +} + +CONSTCD11 +inline bool operator<(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT { + return x.year() < y.year() + ? true + : (x.year() > y.year() + ? false + : (x.month_day_last() < y.month_day_last())); +} + +CONSTCD11 +inline bool operator>(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT { + return y < x; +} + +CONSTCD11 +inline bool operator<=(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT { + return !(y < x); +} + +CONSTCD11 +inline bool operator>=(const year_month_day_last &x, + const year_month_day_last &y) NOEXCEPT { + return !(x < y); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, + const year_month_day_last &ymdl) { + return os << ymdl.year() << '/' << ymdl.month_day_last(); +} + +CONSTCD14 +inline year_month_day_last operator+(const year_month_day_last &ymdl, + const months &dm) NOEXCEPT { + return (ymdl.year() / ymdl.month() + dm) / last; +} + +CONSTCD14 +inline year_month_day_last operator+(const months &dm, + const year_month_day_last &ymdl) NOEXCEPT { + return ymdl + dm; +} + +CONSTCD14 +inline year_month_day_last operator-(const year_month_day_last &ymdl, + const months &dm) NOEXCEPT { + return ymdl + (-dm); +} + +CONSTCD11 +inline year_month_day_last operator+(const year_month_day_last &ymdl, + const years &dy) NOEXCEPT { + return {ymdl.year() + dy, ymdl.month_day_last()}; +} + +CONSTCD11 +inline year_month_day_last operator+(const years &dy, + const year_month_day_last &ymdl) NOEXCEPT { + return ymdl + dy; +} + +CONSTCD11 +inline year_month_day_last operator-(const year_month_day_last &ymdl, + const years &dy) NOEXCEPT { + return ymdl + (-dy); +} + +// year_month_day + +CONSTCD11 +inline year_month_day::year_month_day(const date::year &y, const date::month &m, + const date::day &d) NOEXCEPT : y_(y), + m_(m), + d_(d) {} + +CONSTCD14 +inline year_month_day::year_month_day(const year_month_day_last &ymdl) NOEXCEPT + : y_(ymdl.year()), + m_(ymdl.month()), + d_(ymdl.day()) {} + +CONSTCD14 +inline year_month_day::year_month_day(sys_days dp) NOEXCEPT + : year_month_day(from_days(dp.time_since_epoch())) {} + +CONSTCD14 +inline year_month_day::year_month_day(local_days dp) NOEXCEPT + : year_month_day(from_days(dp.time_since_epoch())) {} + +CONSTCD11 inline year year_month_day::year() const NOEXCEPT { return y_; } +CONSTCD11 inline month year_month_day::month() const NOEXCEPT { return m_; } +CONSTCD11 inline day year_month_day::day() const NOEXCEPT { return d_; } + +CONSTCD14 +inline year_month_day &year_month_day::operator+=(const months &m) NOEXCEPT { + *this = *this + m; + return *this; +} + +CONSTCD14 +inline year_month_day &year_month_day::operator-=(const months &m) NOEXCEPT { + *this = *this - m; + return *this; +} + +CONSTCD14 +inline year_month_day &year_month_day::operator+=(const years &y) NOEXCEPT { + *this = *this + y; + return *this; +} + +CONSTCD14 +inline year_month_day &year_month_day::operator-=(const years &y) NOEXCEPT { + *this = *this - y; + return *this; +} + +CONSTCD14 +inline days year_month_day::to_days() const NOEXCEPT { + static_assert( + std::numeric_limits::digits >= 18, + "This algorithm has not been ported to a 16 bit unsigned integer"); + static_assert( + std::numeric_limits::digits >= 20, + "This algorithm has not been ported to a 16 bit signed integer"); + auto const y = static_cast(y_) - (m_ <= feb); + auto const m = static_cast(m_); + auto const d = static_cast(d_); + auto const era = (y >= 0 ? y : y - 399) / 400; + auto const yoe = static_cast(y - era * 400); // [0, 399] + auto const doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1; // [0, 365] + auto const doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] + return days{era * 146097 + static_cast(doe) - 719468}; +} + +CONSTCD14 +inline year_month_day::operator sys_days() const NOEXCEPT { + return sys_days{to_days()}; +} + +CONSTCD14 +inline year_month_day::operator local_days() const NOEXCEPT { + return local_days{to_days()}; +} + +CONSTCD14 +inline bool year_month_day::ok() const NOEXCEPT { + if (!(y_.ok() && m_.ok())) + return false; + return date::day{1} <= d_ && d_ <= (y_ / m_ / last).day(); +} + +CONSTCD11 +inline bool operator==(const year_month_day &x, + const year_month_day &y) NOEXCEPT { + return x.year() == y.year() && x.month() == y.month() && x.day() == y.day(); +} + +CONSTCD11 +inline bool operator!=(const year_month_day &x, + const year_month_day &y) NOEXCEPT { + return !(x == y); +} + +CONSTCD11 +inline bool operator<(const year_month_day &x, + const year_month_day &y) NOEXCEPT { + return x.year() < y.year() + ? true + : (x.year() > y.year() + ? false + : (x.month() < y.month() + ? true + : (x.month() > y.month() ? false + : (x.day() < y.day())))); +} + +CONSTCD11 +inline bool operator>(const year_month_day &x, + const year_month_day &y) NOEXCEPT { + return y < x; +} + +CONSTCD11 +inline bool operator<=(const year_month_day &x, + const year_month_day &y) NOEXCEPT { + return !(y < x); +} + +CONSTCD11 +inline bool operator>=(const year_month_day &x, + const year_month_day &y) NOEXCEPT { + return !(x < y); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const year_month_day &ymd) { + detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os << ymd.year() << '-'; + os.width(2); + os << static_cast(ymd.month()) << '-'; + os << ymd.day(); + return os; +} + +CONSTCD14 +inline year_month_day year_month_day::from_days(days dp) NOEXCEPT { + static_assert( + std::numeric_limits::digits >= 18, + "This algorithm has not been ported to a 16 bit unsigned integer"); + static_assert( + std::numeric_limits::digits >= 20, + "This algorithm has not been ported to a 16 bit signed integer"); + auto const z = dp.count() + 719468; + auto const era = (z >= 0 ? z : z - 146096) / 146097; + auto const doe = static_cast(z - era * 146097); // [0, 146096] + auto const yoe = + (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] + auto const y = static_cast(yoe) + era * 400; + auto const doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] + auto const mp = (5 * doy + 2) / 153; // [0, 11] + auto const d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] + auto const m = mp < 10 ? mp + 3 : mp - 9; // [1, 12] + return year_month_day{date::year{y + (m <= 2)}, date::month(m), date::day(d)}; +} + +CONSTCD14 +inline year_month_day operator+(const year_month_day &ymd, + const months &dm) NOEXCEPT { + return (ymd.year() / ymd.month() + dm) / ymd.day(); +} + +CONSTCD14 +inline year_month_day operator+(const months &dm, + const year_month_day &ymd) NOEXCEPT { + return ymd + dm; +} + +CONSTCD14 +inline year_month_day operator-(const year_month_day &ymd, + const months &dm) NOEXCEPT { + return ymd + (-dm); +} + +CONSTCD11 +inline year_month_day operator+(const year_month_day &ymd, + const years &dy) NOEXCEPT { + return (ymd.year() + dy) / ymd.month() / ymd.day(); +} + +CONSTCD11 +inline year_month_day operator+(const years &dy, + const year_month_day &ymd) NOEXCEPT { + return ymd + dy; +} + +CONSTCD11 +inline year_month_day operator-(const year_month_day &ymd, + const years &dy) NOEXCEPT { + return ymd + (-dy); +} + +// year_month_weekday + +CONSTCD11 +inline year_month_weekday::year_month_weekday( + const date::year &y, const date::month &m, + const date::weekday_indexed &wdi) NOEXCEPT : y_(y), + m_(m), + wdi_(wdi) {} + +CONSTCD14 +inline year_month_weekday::year_month_weekday(const sys_days &dp) NOEXCEPT + : year_month_weekday(from_days(dp.time_since_epoch())) {} + +CONSTCD14 +inline year_month_weekday::year_month_weekday(const local_days &dp) NOEXCEPT + : year_month_weekday(from_days(dp.time_since_epoch())) {} + +CONSTCD14 +inline year_month_weekday &year_month_weekday:: +operator+=(const months &m) NOEXCEPT { + *this = *this + m; + return *this; +} + +CONSTCD14 +inline year_month_weekday &year_month_weekday:: +operator-=(const months &m) NOEXCEPT { + *this = *this - m; + return *this; +} + +CONSTCD14 +inline year_month_weekday &year_month_weekday:: +operator+=(const years &y) NOEXCEPT { + *this = *this + y; + return *this; +} + +CONSTCD14 +inline year_month_weekday &year_month_weekday:: +operator-=(const years &y) NOEXCEPT { + *this = *this - y; + return *this; +} + +CONSTCD11 inline year year_month_weekday::year() const NOEXCEPT { return y_; } +CONSTCD11 inline month year_month_weekday::month() const NOEXCEPT { return m_; } + +CONSTCD11 +inline weekday year_month_weekday::weekday() const NOEXCEPT { + return wdi_.weekday(); +} + +CONSTCD11 +inline unsigned year_month_weekday::index() const NOEXCEPT { + return wdi_.index(); +} + +CONSTCD11 +inline weekday_indexed year_month_weekday::weekday_indexed() const NOEXCEPT { + return wdi_; +} + +CONSTCD14 +inline year_month_weekday::operator sys_days() const NOEXCEPT { + return sys_days{to_days()}; +} + +CONSTCD14 +inline year_month_weekday::operator local_days() const NOEXCEPT { + return local_days{to_days()}; +} + +CONSTCD14 +inline bool year_month_weekday::ok() const NOEXCEPT { + if (!y_.ok() || !m_.ok() || !wdi_.weekday().ok() || wdi_.index() < 1) + return false; + if (wdi_.index() <= 4) + return true; + auto d2 = wdi_.weekday() - date::weekday(static_cast(y_ / m_ / 1)) + + days((wdi_.index() - 1) * 7 + 1); + return static_cast(d2.count()) <= + static_cast((y_ / m_ / last).day()); +} + +CONSTCD14 +inline year_month_weekday year_month_weekday::from_days(days d) NOEXCEPT { + sys_days dp{d}; + auto const wd = date::weekday(dp); + auto const ymd = year_month_day(dp); + return {ymd.year(), ymd.month(), + wd[(static_cast(ymd.day()) - 1) / 7 + 1]}; +} + +CONSTCD14 +inline days year_month_weekday::to_days() const NOEXCEPT { + auto d = sys_days(y_ / m_ / 1); + return (d + + (wdi_.weekday() - date::weekday(d) + days{(wdi_.index() - 1) * 7})) + .time_since_epoch(); +} + +CONSTCD11 +inline bool operator==(const year_month_weekday &x, + const year_month_weekday &y) NOEXCEPT { + return x.year() == y.year() && x.month() == y.month() && + x.weekday_indexed() == y.weekday_indexed(); +} + +CONSTCD11 +inline bool operator!=(const year_month_weekday &x, + const year_month_weekday &y) NOEXCEPT { + return !(x == y); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, + const year_month_weekday &ymwdi) { + return os << ymwdi.year() << '/' << ymwdi.month() << '/' + << ymwdi.weekday_indexed(); +} + +CONSTCD14 +inline year_month_weekday operator+(const year_month_weekday &ymwd, + const months &dm) NOEXCEPT { + return (ymwd.year() / ymwd.month() + dm) / ymwd.weekday_indexed(); +} + +CONSTCD14 +inline year_month_weekday operator+(const months &dm, + const year_month_weekday &ymwd) NOEXCEPT { + return ymwd + dm; +} + +CONSTCD14 +inline year_month_weekday operator-(const year_month_weekday &ymwd, + const months &dm) NOEXCEPT { + return ymwd + (-dm); +} + +CONSTCD11 +inline year_month_weekday operator+(const year_month_weekday &ymwd, + const years &dy) NOEXCEPT { + return {ymwd.year() + dy, ymwd.month(), ymwd.weekday_indexed()}; +} + +CONSTCD11 +inline year_month_weekday operator+(const years &dy, + const year_month_weekday &ymwd) NOEXCEPT { + return ymwd + dy; +} + +CONSTCD11 +inline year_month_weekday operator-(const year_month_weekday &ymwd, + const years &dy) NOEXCEPT { + return ymwd + (-dy); +} + +// year_month_weekday_last + +CONSTCD11 +inline year_month_weekday_last::year_month_weekday_last( + const date::year &y, const date::month &m, + const date::weekday_last &wdl) NOEXCEPT : y_(y), + m_(m), + wdl_(wdl) {} + +CONSTCD14 +inline year_month_weekday_last &year_month_weekday_last:: +operator+=(const months &m) NOEXCEPT { + *this = *this + m; + return *this; +} + +CONSTCD14 +inline year_month_weekday_last &year_month_weekday_last:: +operator-=(const months &m) NOEXCEPT { + *this = *this - m; + return *this; +} + +CONSTCD14 +inline year_month_weekday_last &year_month_weekday_last:: +operator+=(const years &y) NOEXCEPT { + *this = *this + y; + return *this; +} + +CONSTCD14 +inline year_month_weekday_last &year_month_weekday_last:: +operator-=(const years &y) NOEXCEPT { + *this = *this - y; + return *this; +} + +CONSTCD11 inline year year_month_weekday_last::year() const NOEXCEPT { + return y_; +} +CONSTCD11 inline month year_month_weekday_last::month() const NOEXCEPT { + return m_; +} + +CONSTCD11 +inline weekday year_month_weekday_last::weekday() const NOEXCEPT { + return wdl_.weekday(); +} + +CONSTCD11 +inline weekday_last year_month_weekday_last::weekday_last() const NOEXCEPT { + return wdl_; +} + +CONSTCD14 +inline year_month_weekday_last::operator sys_days() const NOEXCEPT { + return sys_days{to_days()}; +} + +CONSTCD14 +inline year_month_weekday_last::operator local_days() const NOEXCEPT { + return local_days{to_days()}; +} + +CONSTCD11 +inline bool year_month_weekday_last::ok() const NOEXCEPT { + return y_.ok() && m_.ok() && wdl_.ok(); +} + +CONSTCD14 +inline days year_month_weekday_last::to_days() const NOEXCEPT { + auto const d = sys_days(y_ / m_ / last); + return (d - (date::weekday{d} - wdl_.weekday())).time_since_epoch(); +} + +CONSTCD11 +inline bool operator==(const year_month_weekday_last &x, + const year_month_weekday_last &y) NOEXCEPT { + return x.year() == y.year() && x.month() == y.month() && + x.weekday_last() == y.weekday_last(); +} + +CONSTCD11 +inline bool operator!=(const year_month_weekday_last &x, + const year_month_weekday_last &y) NOEXCEPT { + return !(x == y); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, + const year_month_weekday_last &ymwdl) { + return os << ymwdl.year() << '/' << ymwdl.month() << '/' + << ymwdl.weekday_last(); +} + +CONSTCD14 +inline year_month_weekday_last operator+(const year_month_weekday_last &ymwdl, + const months &dm) NOEXCEPT { + return (ymwdl.year() / ymwdl.month() + dm) / ymwdl.weekday_last(); +} + +CONSTCD14 +inline year_month_weekday_last +operator+(const months &dm, const year_month_weekday_last &ymwdl) NOEXCEPT { + return ymwdl + dm; +} + +CONSTCD14 +inline year_month_weekday_last operator-(const year_month_weekday_last &ymwdl, + const months &dm) NOEXCEPT { + return ymwdl + (-dm); +} + +CONSTCD11 +inline year_month_weekday_last operator+(const year_month_weekday_last &ymwdl, + const years &dy) NOEXCEPT { + return {ymwdl.year() + dy, ymwdl.month(), ymwdl.weekday_last()}; +} + +CONSTCD11 +inline year_month_weekday_last +operator+(const years &dy, const year_month_weekday_last &ymwdl) NOEXCEPT { + return ymwdl + dy; +} + +CONSTCD11 +inline year_month_weekday_last operator-(const year_month_weekday_last &ymwdl, + const years &dy) NOEXCEPT { + return ymwdl + (-dy); +} + +// year_month from operator/() + +CONSTCD11 +inline year_month operator/(const year &y, const month &m) NOEXCEPT { + return {y, m}; +} + +CONSTCD11 +inline year_month operator/(const year &y, int m) NOEXCEPT { + return y / month(static_cast(m)); +} + +// month_day from operator/() + +CONSTCD11 +inline month_day operator/(const month &m, const day &d) NOEXCEPT { + return {m, d}; +} + +CONSTCD11 +inline month_day operator/(const day &d, const month &m) NOEXCEPT { + return m / d; +} + +CONSTCD11 +inline month_day operator/(const month &m, int d) NOEXCEPT { + return m / day(static_cast(d)); +} + +CONSTCD11 +inline month_day operator/(int m, const day &d) NOEXCEPT { + return month(static_cast(m)) / d; +} + +CONSTCD11 inline month_day operator/(const day &d, int m) NOEXCEPT { + return m / d; +} + +// month_day_last from operator/() + +CONSTCD11 +inline month_day_last operator/(const month &m, last_spec) NOEXCEPT { + return month_day_last{m}; +} + +CONSTCD11 +inline month_day_last operator/(last_spec, const month &m) NOEXCEPT { + return m / last; +} + +CONSTCD11 +inline month_day_last operator/(int m, last_spec) NOEXCEPT { + return month(static_cast(m)) / last; +} + +CONSTCD11 +inline month_day_last operator/(last_spec, int m) NOEXCEPT { return m / last; } + +// month_weekday from operator/() + +CONSTCD11 +inline month_weekday operator/(const month &m, + const weekday_indexed &wdi) NOEXCEPT { + return {m, wdi}; +} + +CONSTCD11 +inline month_weekday operator/(const weekday_indexed &wdi, + const month &m) NOEXCEPT { + return m / wdi; +} + +CONSTCD11 +inline month_weekday operator/(int m, const weekday_indexed &wdi) NOEXCEPT { + return month(static_cast(m)) / wdi; +} + +CONSTCD11 +inline month_weekday operator/(const weekday_indexed &wdi, int m) NOEXCEPT { + return m / wdi; +} + +// month_weekday_last from operator/() + +CONSTCD11 +inline month_weekday_last operator/(const month &m, + const weekday_last &wdl) NOEXCEPT { + return {m, wdl}; +} + +CONSTCD11 +inline month_weekday_last operator/(const weekday_last &wdl, + const month &m) NOEXCEPT { + return m / wdl; +} + +CONSTCD11 +inline month_weekday_last operator/(int m, const weekday_last &wdl) NOEXCEPT { + return month(static_cast(m)) / wdl; +} + +CONSTCD11 +inline month_weekday_last operator/(const weekday_last &wdl, int m) NOEXCEPT { + return m / wdl; +} + +// year_month_day from operator/() + +CONSTCD11 +inline year_month_day operator/(const year_month &ym, const day &d) NOEXCEPT { + return {ym.year(), ym.month(), d}; +} + +CONSTCD11 +inline year_month_day operator/(const year_month &ym, int d) NOEXCEPT { + return ym / day(static_cast(d)); +} + +CONSTCD11 +inline year_month_day operator/(const year &y, const month_day &md) NOEXCEPT { + return y / md.month() / md.day(); +} + +CONSTCD11 +inline year_month_day operator/(int y, const month_day &md) NOEXCEPT { + return year(y) / md; +} + +CONSTCD11 +inline year_month_day operator/(const month_day &md, const year &y) NOEXCEPT { + return y / md; +} + +CONSTCD11 +inline year_month_day operator/(const month_day &md, int y) NOEXCEPT { + return year(y) / md; +} + +// year_month_day_last from operator/() + +CONSTCD11 +inline year_month_day_last operator/(const year_month &ym, last_spec) NOEXCEPT { + return {ym.year(), month_day_last{ym.month()}}; +} + +CONSTCD11 +inline year_month_day_last operator/(const year &y, + const month_day_last &mdl) NOEXCEPT { + return {y, mdl}; +} + +CONSTCD11 +inline year_month_day_last operator/(int y, + const month_day_last &mdl) NOEXCEPT { + return year(y) / mdl; +} + +CONSTCD11 +inline year_month_day_last operator/(const month_day_last &mdl, + const year &y) NOEXCEPT { + return y / mdl; +} + +CONSTCD11 +inline year_month_day_last operator/(const month_day_last &mdl, + int y) NOEXCEPT { + return year(y) / mdl; +} + +// year_month_weekday from operator/() + +CONSTCD11 +inline year_month_weekday operator/(const year_month &ym, + const weekday_indexed &wdi) NOEXCEPT { + return {ym.year(), ym.month(), wdi}; +} + +CONSTCD11 +inline year_month_weekday operator/(const year &y, + const month_weekday &mwd) NOEXCEPT { + return {y, mwd.month(), mwd.weekday_indexed()}; +} + +CONSTCD11 +inline year_month_weekday operator/(int y, const month_weekday &mwd) NOEXCEPT { + return year(y) / mwd; +} + +CONSTCD11 +inline year_month_weekday operator/(const month_weekday &mwd, + const year &y) NOEXCEPT { + return y / mwd; +} + +CONSTCD11 +inline year_month_weekday operator/(const month_weekday &mwd, int y) NOEXCEPT { + return year(y) / mwd; +} + +// year_month_weekday_last from operator/() + +CONSTCD11 +inline year_month_weekday_last operator/(const year_month &ym, + const weekday_last &wdl) NOEXCEPT { + return {ym.year(), ym.month(), wdl}; +} + +CONSTCD11 +inline year_month_weekday_last +operator/(const year &y, const month_weekday_last &mwdl) NOEXCEPT { + return {y, mwdl.month(), mwdl.weekday_last()}; +} + +CONSTCD11 +inline year_month_weekday_last +operator/(int y, const month_weekday_last &mwdl) NOEXCEPT { + return year(y) / mwdl; +} + +CONSTCD11 +inline year_month_weekday_last operator/(const month_weekday_last &mwdl, + const year &y) NOEXCEPT { + return y / mwdl; +} + +CONSTCD11 +inline year_month_weekday_last operator/(const month_weekday_last &mwdl, + int y) NOEXCEPT { + return year(y) / mwdl; +} + +template struct fields; + +template +std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const fields &fds, const std::string *abbrev = nullptr, + const std::chrono::seconds *offset_sec = nullptr); + +template +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, + fields &fds, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr); + +// time_of_day + +enum { am = 1, pm }; + +namespace detail { + +// width::value is the number of fractional decimal digits in 1/n +// width<0>::value and width<1>::value are defined to be 0 +// If 1/n takes more than 18 fractional decimal digits, +// the result is truncated to 19. +// Example: width<2>::value == 1 +// Example: width<3>::value == 19 +// Example: width<4>::value == 2 +// Example: width<10>::value == 1 +// Example: width<1000>::value == 3 +template +struct width { + static CONSTDATA unsigned value = 1 + width::value; +}; + +template +struct width { + static CONSTDATA unsigned value = 0; +}; + +template struct static_pow10 { +private: + static CONSTDATA std::uint64_t h = static_pow10::value; + +public: + static CONSTDATA std::uint64_t value = h * h * (exp % 2 ? 10 : 1); +}; + +template <> struct static_pow10<0> { + static CONSTDATA std::uint64_t value = 1; +}; + +template struct make_precision { + using type = std::chrono::duration::value>>; + static CONSTDATA unsigned width = w; +}; + +template struct make_precision { + using type = std::chrono::microseconds; + static CONSTDATA unsigned width = 6; +}; + +template ::type::period::den>::value> +class decimal_format_seconds { +public: + using precision = typename make_precision::type; + static auto CONSTDATA width = make_precision::width; + +private: + std::chrono::seconds s_; + precision sub_s_; + +public: + CONSTCD11 decimal_format_seconds() : s_(), sub_s_() {} + + CONSTCD11 explicit decimal_format_seconds(const Duration &d) NOEXCEPT + : s_(std::chrono::duration_cast(d)), + sub_s_(std::chrono::duration_cast(d - s_)) {} + + CONSTCD14 std::chrono::seconds &seconds() NOEXCEPT { return s_; } + CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT { return s_; } + CONSTCD11 precision subseconds() const NOEXCEPT { return sub_s_; } + + CONSTCD14 precision to_duration() const NOEXCEPT { return s_ + sub_s_; } + + CONSTCD11 bool in_conventional_range() const NOEXCEPT { + using namespace std::chrono; + return sub_s_ < std::chrono::seconds{1} && s_ < minutes{1}; + } + + template + friend std::basic_ostream & + operator<<(std::basic_ostream &os, + const decimal_format_seconds &x) { + date::detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << x.s_.count() + << std::use_facet>(os.getloc()).decimal_point(); + os.width(width); + os << x.sub_s_.count(); + return os; + } +}; + +template class decimal_format_seconds { + static CONSTDATA unsigned w = 0; + +public: + using precision = std::chrono::seconds; + static auto CONSTDATA width = make_precision::width; + +private: + std::chrono::seconds s_; + +public: + CONSTCD11 decimal_format_seconds() : s_() {} + CONSTCD11 explicit decimal_format_seconds(const precision &s) NOEXCEPT + : s_(s) {} + + CONSTCD14 std::chrono::seconds &seconds() NOEXCEPT { return s_; } + CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT { return s_; } + CONSTCD14 precision to_duration() const NOEXCEPT { return s_; } + + CONSTCD11 bool in_conventional_range() const NOEXCEPT { + using namespace std::chrono; + return s_ < minutes{1}; + } + + template + friend std::basic_ostream & + operator<<(std::basic_ostream &os, + const decimal_format_seconds &x) { + date::detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << x.s_.count(); + return os; + } +}; + +enum class classify { not_valid, hour, minute, second, subsecond }; + +template struct classify_duration { + static CONSTDATA classify value = + std::is_convertible::value + ? classify::hour + : std::is_convertible::value + ? classify::minute + : std::is_convertible::value + ? classify::second + : std::chrono::treat_as_floating_point< + typename Duration::rep>::value + ? classify::not_valid + : classify::subsecond; +}; + +template +inline CONSTCD11 + typename std::enable_if::is_signed, + std::chrono::duration>::type + abs(std::chrono::duration d) { + return d >= d.zero() ? d : -d; +} + +template +inline CONSTCD11 + typename std::enable_if::is_signed, + std::chrono::duration>::type + abs(std::chrono::duration d) { + return d; +} + +class time_of_day_base { +protected: + std::chrono::hours h_; + unsigned char mode_; + bool neg_; + + enum { is24hr }; + + CONSTCD11 time_of_day_base() NOEXCEPT + : h_(0), + mode_(static_cast(is24hr)), + neg_(false) {} + + CONSTCD11 time_of_day_base(std::chrono::hours h, bool neg, + unsigned m) NOEXCEPT + : h_(detail::abs(h)), + mode_(static_cast(m)), + neg_(neg) {} + + CONSTCD14 void make24() NOEXCEPT; + CONSTCD14 void make12() NOEXCEPT; + + CONSTCD14 std::chrono::hours to24hr() const; + + CONSTCD11 bool in_conventional_range() const NOEXCEPT { + return !neg_ && h_ < days{1}; + } +}; + +CONSTCD14 +inline std::chrono::hours time_of_day_base::to24hr() const { + auto h = h_; + if (mode_ == am || mode_ == pm) { + CONSTDATA auto h12 = std::chrono::hours(12); + if (mode_ == pm) { + if (h != h12) + h = h + h12; + } else if (h == h12) + h = std::chrono::hours(0); + } + return h; +} + +CONSTCD14 +inline void time_of_day_base::make24() NOEXCEPT { + h_ = to24hr(); + mode_ = is24hr; +} + +CONSTCD14 +inline void time_of_day_base::make12() NOEXCEPT { + if (mode_ == is24hr) { + CONSTDATA auto h12 = std::chrono::hours(12); + if (h_ >= h12) { + if (h_ > h12) + h_ = h_ - h12; + mode_ = pm; + } else { + if (h_ == std::chrono::hours(0)) + h_ = h12; + mode_ = am; + } + } +} + +template ::value> +class time_of_day_storage; + +template +class time_of_day_storage, + detail::classify::hour> + : private detail::time_of_day_base { + using base = detail::time_of_day_base; + +public: + using precision = std::chrono::hours; + +#if !defined(_MSC_VER) || _MSC_VER >= 1900 + CONSTCD11 time_of_day_storage() NOEXCEPT = default; +#else + CONSTCD11 time_of_day_storage() = default; +#endif /* !defined(_MSC_VER) || _MSC_VER >= 1900 */ + + CONSTCD11 explicit time_of_day_storage( + std::chrono::hours since_midnight) NOEXCEPT + : base(since_midnight, since_midnight < std::chrono::hours{0}, is24hr) {} + + CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, + unsigned md) NOEXCEPT + : base(h, h < std::chrono::hours{0}, md) {} + + CONSTCD11 std::chrono::hours hours() const NOEXCEPT { return h_; } + CONSTCD11 unsigned mode() const NOEXCEPT { return mode_; } + + CONSTCD14 explicit operator precision() const NOEXCEPT { + auto p = to24hr(); + if (neg_) + p = -p; + return p; + } + + CONSTCD14 precision to_duration() const NOEXCEPT { + return static_cast(*this); + } + + CONSTCD14 time_of_day_storage &make24() NOEXCEPT { + base::make24(); + return *this; + } + CONSTCD14 time_of_day_storage &make12() NOEXCEPT { + base::make12(); + return *this; + } + + CONSTCD11 bool in_conventional_range() const NOEXCEPT { + return base::in_conventional_range(); + } + + template + friend std::basic_ostream & + operator<<(std::basic_ostream &os, + const time_of_day_storage &t) { + using namespace std; + detail::save_stream _(os); + if (t.neg_) + os << '-'; + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + if (t.mode_ != am && t.mode_ != pm) + os.width(2); + os << t.h_.count(); + switch (t.mode_) { + case time_of_day_storage::is24hr: + os << "00"; + break; + case am: + os << "am"; + break; + case pm: + os << "pm"; + break; + } + return os; + } +}; + +template +class time_of_day_storage, + detail::classify::minute> + : private detail::time_of_day_base { + using base = detail::time_of_day_base; + + std::chrono::minutes m_; + +public: + using precision = std::chrono::minutes; + + CONSTCD11 time_of_day_storage() NOEXCEPT : base(), m_(0) {} + + CONSTCD11 explicit time_of_day_storage( + std::chrono::minutes since_midnight) NOEXCEPT + : base(std::chrono::duration_cast(since_midnight), + since_midnight < std::chrono::minutes{0}, is24hr), + m_(detail::abs(since_midnight) - h_) {} + + CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, + std::chrono::minutes m, + unsigned md) NOEXCEPT + : base(h, false, md), + m_(m) {} + + CONSTCD11 std::chrono::hours hours() const NOEXCEPT { return h_; } + CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT { return m_; } + CONSTCD11 unsigned mode() const NOEXCEPT { return mode_; } + + CONSTCD14 explicit operator precision() const NOEXCEPT { + auto p = to24hr() + m_; + if (neg_) + p = -p; + return p; + } + + CONSTCD14 precision to_duration() const NOEXCEPT { + return static_cast(*this); + } + + CONSTCD14 time_of_day_storage &make24() NOEXCEPT { + base::make24(); + return *this; + } + CONSTCD14 time_of_day_storage &make12() NOEXCEPT { + base::make12(); + return *this; + } + + CONSTCD11 bool in_conventional_range() const NOEXCEPT { + return base::in_conventional_range() && m_ < std::chrono::hours{1}; + } + + template + friend std::basic_ostream & + operator<<(std::basic_ostream &os, + const time_of_day_storage &t) { + using namespace std; + detail::save_stream _(os); + if (t.neg_) + os << '-'; + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + if (t.mode_ != am && t.mode_ != pm) + os.width(2); + os << t.h_.count() << ':'; + os.width(2); + os << t.m_.count(); + switch (t.mode_) { + case am: + os << "am"; + break; + case pm: + os << "pm"; + break; + } + return os; + } +}; + +template +class time_of_day_storage, + detail::classify::second> + : private detail::time_of_day_base { + using base = detail::time_of_day_base; + using dfs = decimal_format_seconds; + + std::chrono::minutes m_; + dfs s_; + +public: + using precision = std::chrono::seconds; + + CONSTCD11 time_of_day_storage() NOEXCEPT : base(), m_(0), s_() {} + + CONSTCD11 explicit time_of_day_storage( + std::chrono::seconds since_midnight) NOEXCEPT + : base(std::chrono::duration_cast(since_midnight), + since_midnight < std::chrono::seconds{0}, is24hr), + m_(std::chrono::duration_cast( + detail::abs(since_midnight) - h_)), + s_(detail::abs(since_midnight) - h_ - m_) {} + + CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, + std::chrono::minutes m, + std::chrono::seconds s, + unsigned md) NOEXCEPT + : base(h, false, md), + m_(m), + s_(s) {} + + CONSTCD11 std::chrono::hours hours() const NOEXCEPT { return h_; } + CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT { return m_; } + CONSTCD14 std::chrono::seconds &seconds() NOEXCEPT { return s_.seconds(); } + CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT { + return s_.seconds(); + } + CONSTCD11 unsigned mode() const NOEXCEPT { return mode_; } + + CONSTCD14 explicit operator precision() const NOEXCEPT { + auto p = to24hr() + s_.to_duration() + m_; + if (neg_) + p = -p; + return p; + } + + CONSTCD14 precision to_duration() const NOEXCEPT { + return static_cast(*this); + } + + CONSTCD14 time_of_day_storage &make24() NOEXCEPT { + base::make24(); + return *this; + } + CONSTCD14 time_of_day_storage &make12() NOEXCEPT { + base::make12(); + return *this; + } + + CONSTCD11 bool in_conventional_range() const NOEXCEPT { + return base::in_conventional_range() && m_ < std::chrono::hours{1} && + s_.in_conventional_range(); + } + + template + friend std::basic_ostream & + operator<<(std::basic_ostream &os, + const time_of_day_storage &t) { + using namespace std; + detail::save_stream _(os); + if (t.neg_) + os << '-'; + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + if (t.mode_ != am && t.mode_ != pm) + os.width(2); + os << t.h_.count() << ':'; + os.width(2); + os << t.m_.count() << ':' << t.s_; + switch (t.mode_) { + case am: + os << "am"; + break; + case pm: + os << "pm"; + break; + } + return os; + } + + template + friend std::basic_ostream & + date::to_stream(std::basic_ostream &os, const CharT *fmt, + const fields &fds, const std::string *abbrev, + const std::chrono::seconds *offset_sec); + + template + friend std::basic_istream & + date::from_stream(std::basic_istream &is, const CharT *fmt, + fields &fds, + std::basic_string *abbrev, + std::chrono::minutes *offset); +}; + +template +class time_of_day_storage, + detail::classify::subsecond> + : private detail::time_of_day_base { +public: + using Duration = std::chrono::duration; + using dfs = decimal_format_seconds< + typename std::common_type::type>; + using precision = typename dfs::precision; + +private: + using base = detail::time_of_day_base; + + std::chrono::minutes m_; + dfs s_; + +public: + CONSTCD11 time_of_day_storage() NOEXCEPT : base(), m_(0), s_() {} + + CONSTCD11 explicit time_of_day_storage( + const Duration &since_midnight) NOEXCEPT + : base(std::chrono::duration_cast(since_midnight), + since_midnight < Duration{0}, is24hr), + m_(std::chrono::duration_cast( + detail::abs(since_midnight) - h_)), + s_(detail::abs(since_midnight) - h_ - m_) {} + + CONSTCD11 explicit time_of_day_storage(std::chrono::hours h, + std::chrono::minutes m, + std::chrono::seconds s, + const precision &sub_s, + unsigned md) NOEXCEPT + : base(h, false, md), + m_(m), + s_(s + sub_s) {} + + CONSTCD11 std::chrono::hours hours() const NOEXCEPT { return h_; } + CONSTCD11 std::chrono::minutes minutes() const NOEXCEPT { return m_; } + CONSTCD14 std::chrono::seconds &seconds() NOEXCEPT { return s_.seconds(); } + CONSTCD11 std::chrono::seconds seconds() const NOEXCEPT { + return s_.seconds(); + } + CONSTCD11 precision subseconds() const NOEXCEPT { return s_.subseconds(); } + CONSTCD11 unsigned mode() const NOEXCEPT { return mode_; } + + CONSTCD14 explicit operator precision() const NOEXCEPT { + auto p = to24hr() + s_.to_duration() + m_; + if (neg_) + p = -p; + return p; + } + + CONSTCD14 precision to_duration() const NOEXCEPT { + return static_cast(*this); + } + + CONSTCD14 time_of_day_storage &make24() NOEXCEPT { + base::make24(); + return *this; + } + CONSTCD14 time_of_day_storage &make12() NOEXCEPT { + base::make12(); + return *this; + } + + CONSTCD11 bool in_conventional_range() const NOEXCEPT { + return base::in_conventional_range() && m_ < std::chrono::hours{1} && + s_.in_conventional_range(); + } + + template + friend std::basic_ostream & + operator<<(std::basic_ostream &os, + const time_of_day_storage &t) { + using namespace std; + detail::save_stream _(os); + if (t.neg_) + os << '-'; + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + if (t.mode_ != am && t.mode_ != pm) + os.width(2); + os << t.h_.count() << ':'; + os.width(2); + os << t.m_.count() << ':' << t.s_; + switch (t.mode_) { + case am: + os << "am"; + break; + case pm: + os << "pm"; + break; + } + return os; + } + + template + friend std::basic_ostream & + date::to_stream(std::basic_ostream &os, const CharT *fmt, + const fields &fds, const std::string *abbrev, + const std::chrono::seconds *offset_sec); + + template + friend std::basic_istream & + date::from_stream(std::basic_istream &is, const CharT *fmt, + fields &fds, + std::basic_string *abbrev, + std::chrono::minutes *offset); +}; + +} // namespace detail + +template +class time_of_day : public detail::time_of_day_storage { + using base = detail::time_of_day_storage; + +public: +#if !defined(_MSC_VER) || _MSC_VER >= 1900 + CONSTCD11 time_of_day() NOEXCEPT = default; +#else + CONSTCD11 time_of_day() = default; +#endif /* !defined(_MSC_VER) || _MSC_VER >= 1900 */ + + CONSTCD11 explicit time_of_day(Duration since_midnight) NOEXCEPT + : base(since_midnight) {} + + template + CONSTCD11 explicit time_of_day(Arg0 &&arg0, Arg1 &&arg1, + Args &&... args) NOEXCEPT + : base(std::forward(arg0), std::forward(arg1), + std::forward(args)...) {} +}; + +template ::value>::type> +CONSTCD11 inline time_of_day> +make_time(const std::chrono::duration &d) { + return time_of_day>(d); +} + +CONSTCD11 +inline time_of_day make_time(const std::chrono::hours &h, + unsigned md) { + return time_of_day(h, md); +} + +CONSTCD11 +inline time_of_day +make_time(const std::chrono::hours &h, const std::chrono::minutes &m, + unsigned md) { + return time_of_day(h, m, md); +} + +CONSTCD11 +inline time_of_day +make_time(const std::chrono::hours &h, const std::chrono::minutes &m, + const std::chrono::seconds &s, unsigned md) { + return time_of_day(h, m, s, md); +} + +template >::value>::type> +CONSTCD11 inline time_of_day> +make_time(const std::chrono::hours &h, const std::chrono::minutes &m, + const std::chrono::seconds &s, + const std::chrono::duration &sub_s, unsigned md) { + return time_of_day>(h, m, s, sub_s, md); +} + +template +inline typename std::enable_if< + !std::chrono::treat_as_floating_point::value && + std::ratio_less::value, + std::basic_ostream &>::type +operator<<(std::basic_ostream &os, + const sys_time &tp) { + auto const dp = date::floor(tp); + return os << year_month_day(dp) << ' ' << make_time(tp - dp); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, const sys_days &dp) { + return os << year_month_day(dp); +} + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, + const local_time &ut) { + return (os << sys_time{ut.time_since_epoch()}); +} + +// to_stream + +template struct fields { + year_month_day ymd{year{0} / 0 / 0}; + weekday wd{7u}; + time_of_day tod{}; + + fields() {} // = default; // Note: doesn't compile with default + + fields(year_month_day ymd_) : ymd(ymd_) {} + fields(weekday wd_) : wd(wd_) {} + fields(time_of_day tod_) : tod(tod_) {} + + fields(year_month_day ymd_, weekday wd_) : ymd(ymd_), wd(wd_) {} + fields(year_month_day ymd_, time_of_day tod_) + : ymd(ymd_), tod(tod_) {} + + fields(weekday wd_, time_of_day tod_) : wd(wd_), tod(tod_) {} + + fields(year_month_day ymd_, weekday wd_, time_of_day tod_) + : ymd(ymd_), wd(wd_), tod(tod_) {} +}; + +namespace detail { + +template +unsigned extract_weekday(std::basic_ostream &os, + const fields &fds) { + if (!fds.ymd.ok() && !fds.wd.ok()) { + // fds does not contain a valid weekday + os.setstate(std::ios::failbit); + return 7; + } + unsigned wd; + if (fds.ymd.ok()) { + wd = static_cast(weekday{fds.ymd}); + if (fds.wd.ok() && wd != static_cast(fds.wd)) { + // fds.ymd and fds.wd are inconsistent + os.setstate(std::ios::failbit); + return 7; + } + } else + wd = static_cast(fds.wd); + return wd; +} + +} // namespace detail + +#if ONLY_C_LOCALE + +namespace detail { + +inline std::pair weekday_names() { + using namespace std; + static const string nm[] = {"Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday", "Sun", + "Mon", "Tue", "Wed", "Thu", + "Fri", "Sat"}; + return make_pair(nm, nm + sizeof(nm) / sizeof(nm[0])); +} + +inline std::pair month_names() { + using namespace std; + static const string nm[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + return make_pair(nm, nm + sizeof(nm) / sizeof(nm[0])); +} + +inline std::pair ampm_names() { + using namespace std; + static const string nm[] = {"AM", "PM"}; + return make_pair(nm, nm + sizeof(nm) / sizeof(nm[0])); +} + +template +FwdIter scan_keyword(std::basic_istream &is, FwdIter kb, + FwdIter ke) { + using namespace std; + size_t nkw = static_cast(std::distance(kb, ke)); + const unsigned char doesnt_match = '\0'; + const unsigned char might_match = '\1'; + const unsigned char does_match = '\2'; + unsigned char statbuf[100]; + unsigned char *status = statbuf; + std::unique_ptr stat_hold(0, free); + if (nkw > sizeof(statbuf)) { + status = (unsigned char *)malloc(nkw); + if (status == nullptr) + throw bad_alloc(); + stat_hold.reset(status); + } + size_t n_might_match = nkw; // At this point, any keyword might match + size_t n_does_match = 0; // but none of them definitely do + // Initialize all statuses to might_match, except for "" keywords are + // does_match + unsigned char *st = status; + for (auto ky = kb; ky != ke; ++ky, ++st) { + if (!ky->empty()) + *st = might_match; + else { + *st = does_match; + --n_might_match; + ++n_does_match; + } + } + // While there might be a match, test keywords against the next CharT + for (size_t indx = 0; is && n_might_match > 0; ++indx) { + // Peek at the next CharT but don't consume it + auto ic = is.peek(); + if (ic == EOF) { + is.setstate(ios::eofbit); + break; + } + auto c = static_cast(toupper(ic)); + bool consume = false; + // For each keyword which might match, see if the indx character is c + // If a match if found, consume c + // If a match is found, and that is the last character in the keyword, + // then that keyword matches. + // If the keyword doesn't match this character, then change the keyword + // to doesn't match + st = status; + for (auto ky = kb; ky != ke; ++ky, ++st) { + if (*st == might_match) { + if (c == static_cast(toupper((*ky)[indx]))) { + consume = true; + if (ky->size() == indx + 1) { + *st = does_match; + --n_might_match; + ++n_does_match; + } + } else { + *st = doesnt_match; + --n_might_match; + } + } + } + // consume if we matched a character + if (consume) { + (void)is.get(); + // If we consumed a character and there might be a matched keyword that + // was marked matched on a previous iteration, then such keywords + // are now marked as not matching. + if (n_might_match + n_does_match > 1) { + st = status; + for (auto ky = kb; ky != ke; ++ky, ++st) { + if (*st == does_match && ky->size() != indx + 1) { + *st = doesnt_match; + --n_does_match; + } + } + } + } + } + // We've exited the loop because we hit eof and/or we have no more "might + // matches". Return the first matching result + for (st = status; kb != ke; ++kb, ++st) + if (*st == does_match) + break; + if (kb == ke) + is.setstate(ios_base::failbit); + return kb; +} + +} // namespace detail + +#endif // ONLY_C_LOCALE + +template +std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const fields &fds, const std::string *abbrev, + const std::chrono::seconds *offset_sec) { + using namespace std; + using namespace std::chrono; + tm tm; +#if !ONLY_C_LOCALE + auto &facet = use_facet>(os.getloc()); +#endif + const CharT *command = nullptr; + CharT modified = CharT{}; + for (; *fmt; ++fmt) { + switch (*fmt) { + case 'a': + case 'A': + if (command) { + if (modified == CharT{}) { + tm.tm_wday = static_cast(detail::extract_weekday(os, fds)); + if (os.fail()) + return os; +#if !ONLY_C_LOCALE + const CharT f[] = {'%', *fmt}; + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); +#else // ONLY_C_LOCALE + os << detail::weekday_names().first[tm.tm_wday + 7 * (*fmt == 'a')]; +#endif // ONLY_C_LOCALE + } else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'b': + case 'B': + case 'h': + if (command) { + if (modified == CharT{}) { + tm.tm_mon = static_cast(unsigned(fds.ymd.month())) - 1; +#if !ONLY_C_LOCALE + const CharT f[] = {'%', *fmt}; + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); +#else // ONLY_C_LOCALE + os << detail::month_names().first[tm.tm_mon + 12 * (*fmt == 'b')]; +#endif // ONLY_C_LOCALE + } else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'c': + case 'x': + if (command) { + if (modified == CharT{'O'}) + os << CharT{'%'} << modified << *fmt; + else { +#if !ONLY_C_LOCALE + tm = std::tm{}; + auto const &ymd = fds.ymd; + auto ld = local_days(ymd); + tm.tm_sec = static_cast(fds.tod.seconds().count()); + tm.tm_min = static_cast(fds.tod.minutes().count()); + tm.tm_hour = static_cast(fds.tod.hours().count()); + tm.tm_mday = static_cast(static_cast(ymd.day())); + tm.tm_mon = static_cast(static_cast(ymd.month()) - 1); + tm.tm_year = static_cast(ymd.year()) - 1900; + tm.tm_wday = static_cast(detail::extract_weekday(os, fds)); + if (os.fail()) + return os; + tm.tm_yday = + static_cast((ld - local_days(ymd.year() / 1 / 1)).count()); + CharT f[3] = {'%'}; + auto fe = begin(f) + 1; + if (modified == CharT{'E'}) + *fe++ = modified; + *fe++ = *fmt; + facet.put(os, os, os.fill(), &tm, begin(f), fe); +#else // ONLY_C_LOCALE + if (*fmt == 'c') { + auto wd = static_cast(detail::extract_weekday(os, fds)); + os << detail::weekday_names().first[static_cast(wd) + 7] + << ' '; + os << detail::month_names() + .first[static_cast(fds.ymd.month()) - 1 + 12] + << ' '; + auto d = static_cast(static_cast(fds.ymd.day())); + if (d < 10) + os << ' '; + os << d << ' ' + << make_time(duration_cast(fds.tod.to_duration())) + << ' ' << fds.ymd.year(); + + } else // *fmt == 'x' + { + auto const &ymd = fds.ymd; + detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << static_cast(ymd.month()) << CharT{'/'}; + os.width(2); + os << static_cast(ymd.day()) << CharT{'/'}; + os.width(2); + os << static_cast(ymd.year()) % 100; + } +#endif // ONLY_C_LOCALE + } + command = nullptr; + modified = CharT{}; + } else + os << *fmt; + break; + case 'C': + if (command) { + auto y = static_cast(fds.ymd.year()); +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + if (y >= 0) { + os.width(2); + os << y / 100; + } else { + os << CharT{'-'}; + os.width(2); + os << -(y - 99) / 100; + } +#if !ONLY_C_LOCALE + } else if (modified == CharT{'E'}) { + tm.tm_year = y - 1900; + CharT f[3] = {'%', 'E', 'C'}; + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + command = nullptr; + modified = CharT{}; + } else + os << *fmt; + break; + case 'd': + case 'e': + if (command) { + auto d = static_cast(static_cast(fds.ymd.day())); +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + detail::save_stream _(os); + if (*fmt == CharT{'d'}) + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << d; +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + tm.tm_mday = d; + CharT f[3] = {'%', 'O', *fmt}; + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + command = nullptr; + modified = CharT{}; + } else + os << *fmt; + break; + case 'D': + if (command) { + if (modified == CharT{}) { + auto const &ymd = fds.ymd; + detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << static_cast(ymd.month()) << CharT{'/'}; + os.width(2); + os << static_cast(ymd.day()) << CharT{'/'}; + os.width(2); + os << static_cast(ymd.year()) % 100; + } else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'F': + if (command) { + if (modified == CharT{}) { + auto const &ymd = fds.ymd; + detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(4); + os << static_cast(ymd.year()) << CharT{'-'}; + os.width(2); + os << static_cast(ymd.month()) << CharT{'-'}; + os.width(2); + os << static_cast(ymd.day()); + } else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'g': + case 'G': + if (command) { + if (modified == CharT{}) { + auto ld = local_days(fds.ymd); + auto y = year_month_day{ld + days{3}}.year(); + auto start = + local_days((y - years{1}) / date::dec / thu[last]) + (mon - thu); + if (ld < start) + --y; + if (*fmt == CharT{'G'}) + os << y; + else { + detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(2); + os << std::abs(static_cast(y)) % 100; + } + } else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'H': + case 'I': + if (command) { + auto hms = fds.tod; +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + if (*fmt == CharT{'I'}) + hms.make12(); + if (hms.hours() < hours{10}) + os << CharT{'0'}; + os << hms.hours().count(); +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_hour = static_cast(hms.hours().count()); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'j': + if (command) { + if (modified == CharT{}) { + auto ld = local_days(fds.ymd); + auto y = fds.ymd.year(); + auto doy = ld - local_days(y / jan / 1) + days{1}; + detail::save_stream _(os); + os.fill('0'); + os.flags(std::ios::dec | std::ios::right); + os.width(3); + os << doy.count(); + } else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'm': + if (command) { + auto m = static_cast(fds.ymd.month()); +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + if (m < 10) + os << CharT{'0'}; + os << m; +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_mon = static_cast(m - 1); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'M': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + if (fds.tod.minutes() < minutes{10}) + os << CharT{'0'}; + os << fds.tod.minutes().count(); +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_min = static_cast(fds.tod.minutes().count()); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'n': + if (command) { + if (modified == CharT{}) + os << CharT{'\n'}; + else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'p': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) { + const CharT f[] = {'%', *fmt}; + tm.tm_hour = static_cast(fds.tod.hours().count()); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#else + if (fds.tod.hours() < hours{12}) + os << detail::ampm_names().first[0]; + else + os << detail::ampm_names().first[1]; +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'r': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) { + const CharT f[] = {'%', *fmt}; + tm.tm_hour = static_cast(fds.tod.hours().count()); + tm.tm_min = static_cast(fds.tod.minutes().count()); + tm.tm_sec = static_cast(fds.tod.seconds().count()); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#else + time_of_day tod(duration_cast(fds.tod.to_duration())); + tod.make12(); + detail::save_stream _(os); + os.fill('0'); + os.width(2); + os << tod.hours().count() << CharT{':'}; + os.width(2); + os << tod.minutes().count() << CharT{':'}; + os.width(2); + os << tod.seconds().count() << CharT{' '}; + tod.make24(); + if (tod.hours() < hours{12}) + os << detail::ampm_names().first[0]; + else + os << detail::ampm_names().first[1]; +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'R': + if (command) { + if (modified == CharT{}) { + if (fds.tod.hours() < hours{10}) + os << CharT{'0'}; + os << fds.tod.hours().count() << CharT{':'}; + if (fds.tod.minutes() < minutes{10}) + os << CharT{'0'}; + os << fds.tod.minutes().count(); + } else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'S': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + os << fds.tod.s_; +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_sec = static_cast(fds.tod.s_.seconds().count()); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 't': + if (command) { + if (modified == CharT{}) + os << CharT{'\t'}; + else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'T': + if (command) { + if (modified == CharT{}) { + os << fds.tod; + } else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'u': + if (command) { + auto wd = detail::extract_weekday(os, fds); + if (os.fail()) + return os; +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + os << (wd != 0 ? wd : 7u); +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_wday = static_cast(wd); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'U': + if (command) { + auto const &ymd = fds.ymd; + auto ld = local_days(ymd); +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + auto st = local_days(sun[1] / jan / ymd.year()); + if (ld < st) + os << CharT{'0'} << CharT{'0'}; + else { + auto wn = duration_cast(ld - st).count() + 1; + if (wn < 10) + os << CharT{'0'}; + os << wn; + } +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_year = static_cast(ymd.year()) - 1900; + tm.tm_wday = static_cast(detail::extract_weekday(os, fds)); + if (os.fail()) + return os; + tm.tm_yday = + static_cast((ld - local_days(ymd.year() / 1 / 1)).count()); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'V': + if (command) { + auto ld = local_days(fds.ymd); +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + auto y = year_month_day{ld + days{3}}.year(); + auto st = local_days((y - years{1}) / 12 / thu[last]) + (mon - thu); + if (ld < st) { + --y; + st = local_days((y - years{1}) / 12 / thu[last]) + (mon - thu); + } + auto wn = duration_cast(ld - st).count() + 1; + if (wn < 10) + os << CharT{'0'}; + os << wn; +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + const CharT f[] = {'%', modified, *fmt}; + auto const &ymd = fds.ymd; + tm.tm_year = static_cast(ymd.year()) - 1900; + tm.tm_wday = static_cast(detail::extract_weekday(os, fds)); + if (os.fail()) + return os; + tm.tm_yday = + static_cast((ld - local_days(ymd.year() / 1 / 1)).count()); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'w': + if (command) { + auto wd = detail::extract_weekday(os, fds); + if (os.fail()) + return os; +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + os << wd; +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_wday = static_cast(wd); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'W': + if (command) { + auto const &ymd = fds.ymd; + auto ld = local_days(ymd); +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + auto st = local_days(mon[1] / jan / ymd.year()); + if (ld < st) + os << CharT{'0'} << CharT{'0'}; + else { + auto wn = duration_cast(ld - st).count() + 1; + if (wn < 10) + os << CharT{'0'}; + os << wn; + } +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_year = static_cast(ymd.year()) - 1900; + tm.tm_wday = static_cast(detail::extract_weekday(os, fds)); + if (os.fail()) + return os; + tm.tm_yday = + static_cast((ld - local_days(ymd.year() / 1 / 1)).count()); + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'X': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{'O'}) + os << CharT{'%'} << modified << *fmt; + else { + tm = std::tm{}; + tm.tm_sec = static_cast(fds.tod.seconds().count()); + tm.tm_min = static_cast(fds.tod.minutes().count()); + tm.tm_hour = static_cast(fds.tod.hours().count()); + CharT f[3] = {'%'}; + auto fe = begin(f) + 1; + if (modified == CharT{'E'}) + *fe++ = modified; + *fe++ = *fmt; + facet.put(os, os, os.fill(), &tm, begin(f), fe); + } +#else + os << fds.tod; +#endif + command = nullptr; + modified = CharT{}; + } else + os << *fmt; + break; + case 'y': + if (command) { + auto y = static_cast(fds.ymd.year()); +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + y = std::abs(y) % 100; + if (y < 10) + os << CharT{'0'}; + os << y; +#if !ONLY_C_LOCALE + } else { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_year = y - 1900; + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'Y': + if (command) { + auto y = fds.ymd.year(); +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + os << y; +#if !ONLY_C_LOCALE + } else if (modified == CharT{'E'}) { + const CharT f[] = {'%', modified, *fmt}; + tm.tm_year = static_cast(y) - 1900; + facet.put(os, os, os.fill(), &tm, begin(f), end(f)); + } else { + os << CharT{'%'} << modified << *fmt; + } +#endif + modified = CharT{}; + command = nullptr; + } else + os << *fmt; + break; + case 'z': + if (command) { + if (offset_sec == nullptr) { + // Can not format %z with unknown offset + os.setstate(ios::failbit); + return os; + } + auto m = duration_cast(*offset_sec); + auto neg = m < minutes{0}; + m = date::abs(m); + auto h = duration_cast(m); + m -= h; + if (neg) + os << CharT{'-'}; + else + os << CharT{'+'}; + if (h < hours{10}) + os << CharT{'0'}; + os << h.count(); + if (modified != CharT{}) + os << CharT{':'}; + if (m < minutes{10}) + os << CharT{'0'}; + os << m.count(); + command = nullptr; + modified = CharT{}; + } else + os << *fmt; + break; + case 'Z': + if (command) { + if (modified == CharT{}) { + if (abbrev == nullptr) { + // Can not format %Z with unknown time_zone + os.setstate(ios::failbit); + return os; + } + for (auto c : *abbrev) + os << CharT(c); + } else { + os << CharT{'%'} << modified << *fmt; + modified = CharT{}; + } + command = nullptr; + } else + os << *fmt; + break; + case 'E': + case 'O': + if (command) { + if (modified == CharT{}) { + modified = *fmt; + } else { + os << CharT{'%'} << modified << *fmt; + command = nullptr; + modified = CharT{}; + } + } else + os << *fmt; + break; + case '%': + if (command) { + if (modified == CharT{}) { + os << CharT{'%'}; + command = nullptr; + } else { + os << CharT{'%'} << modified << CharT{'%'}; + command = nullptr; + modified = CharT{}; + } + } else + command = fmt; + break; + default: + if (command) { + os << CharT{'%'}; + command = nullptr; + } + if (modified != CharT{}) { + os << modified; + modified = CharT{}; + } + os << *fmt; + break; + } + } + if (command) + os << CharT{'%'}; + if (modified != CharT{}) + os << modified; + return os; +} + +template +inline std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const year &y) { + using CT = std::chrono::seconds; + fields fds{y / 0 / 0}; + return to_stream(os, fmt, fds); +} + +template +inline std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const month &m) { + using CT = std::chrono::seconds; + fields fds{m / 0 / 0}; + return to_stream(os, fmt, fds); +} + +template +inline std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const day &d) { + using CT = std::chrono::seconds; + fields fds{d / 0 / 0}; + return to_stream(os, fmt, fds); +} + +template +inline std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const weekday &wd) { + using CT = std::chrono::seconds; + fields fds{wd}; + return to_stream(os, fmt, fds); +} + +template +inline std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const year_month &ym) { + using CT = std::chrono::seconds; + fields fds{ym / 0}; + return to_stream(os, fmt, fds); +} + +template +inline std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const month_day &md) { + using CT = std::chrono::seconds; + fields fds{md / 0}; + return to_stream(os, fmt, fds); +} + +template +inline std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const year_month_day &ymd) { + using CT = std::chrono::seconds; + fields fds{ymd}; + return to_stream(os, fmt, fds); +} + +template +inline std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const std::chrono::duration &d) { + using Duration = std::chrono::duration; + using CT = typename std::common_type::type; + fields fds{time_of_day{d}}; + return to_stream(os, fmt, fds); +} + +template +std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const local_time &tp, const std::string *abbrev = nullptr, + const std::chrono::seconds *offset_sec = nullptr) { + using CT = typename std::common_type::type; + auto ld = floor(tp); + fields fds{year_month_day{ld}, time_of_day{tp - ld}}; + return to_stream(os, fmt, fds, abbrev, offset_sec); +} + +template +std::basic_ostream & +to_stream(std::basic_ostream &os, const CharT *fmt, + const sys_time &tp) { + using CT = typename std::common_type::type; + const std::string abbrev("UTC"); + CONSTDATA std::chrono::seconds offset{0}; + auto sd = floor(tp); + fields fds{year_month_day{sd}, time_of_day{tp - sd}}; + return to_stream(os, fmt, fds, &abbrev, &offset); +} + +// format + +template +auto format(const std::locale &loc, const CharT *fmt, const Streamable &tp) + -> decltype(to_stream(std::declval &>(), fmt, tp), + std::basic_string{}) { + std::basic_ostringstream os; + os.imbue(loc); + to_stream(os, fmt, tp); + return os.str(); +} + +template +auto format(const CharT *fmt, const Streamable &tp) + -> decltype(to_stream(std::declval &>(), fmt, tp), + std::basic_string{}) { + std::basic_ostringstream os; + to_stream(os, fmt, tp); + return os.str(); +} + +template +auto format(const std::locale &loc, + const std::basic_string &fmt, + const Streamable &tp) + -> decltype(to_stream(std::declval &>(), + fmt.c_str(), tp), + std::basic_string{}) { + std::basic_ostringstream os; + os.imbue(loc); + to_stream(os, fmt.c_str(), tp); + return os.str(); +} + +template +auto format(const std::basic_string &fmt, + const Streamable &tp) + -> decltype(to_stream(std::declval &>(), + fmt.c_str(), tp), + std::basic_string{}) { + std::basic_ostringstream os; + to_stream(os, fmt.c_str(), tp); + return os.str(); +} + +// parse + +namespace detail { + +template +bool read_char(std::basic_istream &is, CharT fmt, + std::ios::iostate &err) { + auto ic = is.get(); + if (Traits::eq_int_type(ic, Traits::eof()) || + !Traits::eq(Traits::to_char_type(ic), fmt)) { + err |= std::ios::failbit; + is.setstate(std::ios::failbit); + return false; + } + return true; +} + +template +unsigned read_unsigned(std::basic_istream &is, unsigned m = 1, + unsigned M = 10) { + unsigned x = 0; + unsigned count = 0; + while (true) { + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) + break; + auto c = static_cast(Traits::to_char_type(ic)); + if (!('0' <= c && c <= '9')) + break; + (void)is.get(); + ++count; + x = 10 * x + static_cast(c - '0'); + if (count == M) + break; + } + if (count < m) + is.setstate(std::ios::failbit); + return x; +} + +template +int read_signed(std::basic_istream &is, unsigned m = 1, + unsigned M = 10) { + auto ic = is.peek(); + if (!Traits::eq_int_type(ic, Traits::eof())) { + auto c = static_cast(Traits::to_char_type(ic)); + if (('0' <= c && c <= '9') || c == '-' || c == '+') { + if (c == '-' || c == '+') + (void)is.get(); + auto x = static_cast(read_unsigned(is, std::max(m, 1u), M)); + if (!is.fail()) { + if (c == '-') + x = -x; + return x; + } + } + } + if (m > 0) + is.setstate(std::ios::failbit); + return 0; +} + +template +long double read_long_double(std::basic_istream &is, + unsigned m = 1, unsigned M = 10) { + using namespace std; + unsigned count = 0; + auto decimal_point = Traits::to_int_type( + use_facet>(is.getloc()).decimal_point()); + string buf; + while (true) { + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) + break; + if (Traits::eq_int_type(ic, decimal_point)) { + buf += '.'; + decimal_point = Traits::eof(); + is.get(); + } else { + auto c = static_cast(Traits::to_char_type(ic)); + if (!('0' <= c && c <= '9')) + break; + buf += c; + (void)is.get(); + ++count; + } + if (count == M) + break; + } + if (count < m) { + is.setstate(std::ios::failbit); + return 0; + } + return std::stold(buf); +} + +struct rs { + int &i; + unsigned m; + unsigned M; +}; + +struct ru { + int &i; + unsigned m; + unsigned M; +}; + +struct rld { + long double &i; + unsigned m; + unsigned M; +}; + +template +void read(std::basic_istream &) {} + +template +void read(std::basic_istream &is, CharT a0, Args &&... args); + +template +void read(std::basic_istream &is, rs a0, Args &&... args); + +template +void read(std::basic_istream &is, ru a0, Args &&... args); + +template +void read(std::basic_istream &is, int a0, Args &&... args); + +template +void read(std::basic_istream &is, rld a0, Args &&... args); + +template +void read(std::basic_istream &is, CharT a0, Args &&... args) { + // No-op if a0 == CharT{} + if (a0 != CharT{}) { + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) { + is.setstate(std::ios::failbit | std::ios::eofbit); + return; + } + if (!Traits::eq(Traits::to_char_type(ic), a0)) { + is.setstate(std::ios::failbit); + return; + } + (void)is.get(); + } + read(is, std::forward(args)...); +} + +template +void read(std::basic_istream &is, rs a0, Args &&... args) { + auto x = read_signed(is, a0.m, a0.M); + if (is.fail()) + return; + a0.i = x; + read(is, std::forward(args)...); +} + +template +void read(std::basic_istream &is, ru a0, Args &&... args) { + auto x = read_unsigned(is, a0.m, a0.M); + if (is.fail()) + return; + a0.i = static_cast(x); + read(is, std::forward(args)...); +} + +template +void read(std::basic_istream &is, int a0, Args &&... args) { + if (a0 != -1) { + auto u = static_cast(a0); + CharT buf[std::numeric_limits::digits10 + 2] = {}; + auto e = buf; + do { + *e++ = static_cast((u % 10) + '0'); + u /= 10; + } while (u > 0); + std::reverse(buf, e); + for (auto p = buf; p != e && is.rdstate() == std::ios::goodbit; ++p) + read(is, *p); + } + if (is.rdstate() == std::ios::goodbit) + read(is, std::forward(args)...); +} + +template +void read(std::basic_istream &is, rld a0, Args &&... args) { + auto x = read_long_double(is, a0.m, a0.M); + if (is.fail()) + return; + a0.i = x; + read(is, std::forward(args)...); +} + +} // namespace detail + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, + fields &fds, + std::basic_string *abbrev, + std::chrono::minutes *offset) { + using namespace std; + using namespace std::chrono; + typename basic_istream::sentry ok{is, true}; + if (ok) { +#if !ONLY_C_LOCALE + auto &f = use_facet>(is.getloc()); + std::tm tm{}; +#endif + std::basic_string temp_abbrev; + minutes temp_offset{}; + const CharT *command = nullptr; + auto modified = CharT{}; + auto width = -1; + CONSTDATA int not_a_year = numeric_limits::min(); + int Y = not_a_year; + CONSTDATA int not_a_century = not_a_year / 100; + int C = not_a_century; + CONSTDATA int not_a_2digit_year = 100; + int y = not_a_2digit_year; + int m{}; + int d{}; + int j{}; + CONSTDATA int not_a_weekday = 7; + int wd = not_a_weekday; + CONSTDATA int not_a_hour_12_value = 0; + int I = not_a_hour_12_value; + hours h{}; + minutes min{}; + Duration s{}; + int g = not_a_2digit_year; + int G = not_a_year; + CONSTDATA int not_a_week_num = 100; + int V = not_a_week_num; + int U = not_a_week_num; + int W = not_a_week_num; + using detail::read; + using detail::rld; + using detail::rs; + using detail::ru; + for (; *fmt && is.rdstate() == std::ios::goodbit; ++fmt) { + switch (*fmt) { + case 'a': + case 'A': + if (command) { +#if !ONLY_C_LOCALE + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) + wd = tm.tm_wday; + is.setstate(err); +#else + auto nm = detail::weekday_names(); + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + if (!is.fail()) + wd = static_cast(i % 7); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'b': + case 'B': + case 'h': + if (command) { +#if !ONLY_C_LOCALE + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) + m = tm.tm_mon + 1; + is.setstate(err); +#else + auto nm = detail::month_names(); + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + if (!is.fail()) + m = static_cast(i % 12 + 1); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'c': + if (command) { +#if !ONLY_C_LOCALE + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) { + Y = tm.tm_year + 1900; + m = tm.tm_mon + 1; + d = tm.tm_mday; + h = hours{tm.tm_hour}; + min = minutes{tm.tm_min}; + s = duration_cast(seconds{tm.tm_sec}); + } + is.setstate(err); +#else + auto nm = detail::weekday_names(); + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + if (is.fail()) + goto broken; + wd = static_cast(i % 7); + ws(is); + nm = detail::month_names(); + i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + if (is.fail()) + goto broken; + m = static_cast(i % 12 + 1); + ws(is); + read(is, rs{d, 1, 2}); + if (is.fail()) + goto broken; + ws(is); + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + int H; + int M; + long double S; + read(is, ru{H, 1, 2}, CharT{':'}, ru{M, 1, 2}, CharT{':'}, + rld{S, 1, w}); + if (is.fail()) + goto broken; + h = hours{H}; + min = minutes{M}; + s = round(duration{S}); + ws(is); + read(is, rs{Y, 1, 4u}); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'x': + if (command) { +#if !ONLY_C_LOCALE + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) { + Y = tm.tm_year + 1900; + m = tm.tm_mon + 1; + d = tm.tm_mday; + } + is.setstate(err); +#else + read(is, ru{m, 1, 2}, CharT{'/'}, ru{d, 1, 2}, CharT{'/'}, + rs{y, 1, 2}); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'X': + if (command) { +#if !ONLY_C_LOCALE + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) { + h = hours{tm.tm_hour}; + min = minutes{tm.tm_min}; + s = duration_cast(seconds{tm.tm_sec}); + } + is.setstate(err); +#else + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + int H; + int M; + long double S; + read(is, ru{H, 1, 2}, CharT{':'}, ru{M, 1, 2}, CharT{':'}, + rld{S, 1, w}); + if (!is.fail()) { + h = hours{H}; + min = minutes{M}; + s = round(duration{S}); + } +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'C': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + read(is, rs{C, 1, width == -1 ? 2u : static_cast(width)}); +#if !ONLY_C_LOCALE + } else { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) { + auto tY = tm.tm_year + 1900; + C = (tY >= 0 ? tY : tY - 99) / 100; + } + is.setstate(err); + } +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'D': + if (command) { + if (modified == CharT{}) + read(is, ru{m, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, + ru{d, 1, 2}, CharT{'\0'}, CharT{'/'}, CharT{'\0'}, + rs{y, 1, 2}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'F': + if (command) { + if (modified == CharT{}) + read(is, rs{Y, 1, width == -1 ? 4u : static_cast(width)}, + CharT{'-'}, ru{m, 1, 2}, CharT{'-'}, ru{d, 1, 2}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'd': + case 'e': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + read(is, rs{d, 1, width == -1 ? 2u : static_cast(width)}); +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + command = nullptr; + width = -1; + modified = CharT{}; + if ((err & ios::failbit) == 0) + d = tm.tm_mday; + is.setstate(err); + } else + read(is, CharT{'%'}, width, modified, *fmt); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'H': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + int H; + read(is, ru{H, 1, width == -1 ? 2u : static_cast(width)}); + if (!is.fail()) + h = hours{H}; +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) + h = hours{tm.tm_hour}; + is.setstate(err); + } else + read(is, CharT{'%'}, width, modified, *fmt); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'I': + if (command) { + if (modified == CharT{}) { + // reads in an hour into I, but most be in [1, 12] + read(is, rs{I, 1, width == -1 ? 2u : static_cast(width)}); + if (I != not_a_hour_12_value) { + if (!(1 <= I && I <= 12)) { + I = not_a_hour_12_value; + goto broken; + } + } + } else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'j': + if (command) { + if (modified == CharT{}) + read(is, ru{j, 1, width == -1 ? 3u : static_cast(width)}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'M': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + int M; + read(is, ru{M, 1, width == -1 ? 2u : static_cast(width)}); + if (!is.fail()) + min = minutes{M}; +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) + min = minutes{tm.tm_min}; + is.setstate(err); + } else + read(is, CharT{'%'}, width, modified, *fmt); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'm': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + read(is, rs{m, 1, width == -1 ? 2u : static_cast(width)}); +#if !ONLY_C_LOCALE + else if (modified == CharT{'O'}) { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) + m = tm.tm_mon + 1; + is.setstate(err); + } else + read(is, CharT{'%'}, width, modified, *fmt); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'n': + case 't': + if (command) { + // %n matches a single white space character + // %t matches 0 or 1 white space characters + auto ic = is.peek(); + if (Traits::eq_int_type(ic, Traits::eof())) { + ios_base::iostate err = ios_base::eofbit; + if (*fmt == 'n') + err |= ios_base::failbit; + is.setstate(err); + break; + } + if (isspace(ic)) { + (void)is.get(); + } else if (*fmt == 'n') + is.setstate(ios_base::failbit); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'p': + // Error if haven't yet seen %I + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) { + if (I == not_a_hour_12_value) + goto broken; + tm = std::tm{}; + tm.tm_hour = I; + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if (err & ios::failbit) + goto broken; + h = hours{tm.tm_hour}; + I = not_a_hour_12_value; + } else + read(is, CharT{'%'}, width, modified, *fmt); +#else + if (I == not_a_hour_12_value) + goto broken; + auto nm = detail::ampm_names(); + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + if (is.fail()) + goto broken; + h = hours{I}; + if (i == 1) { + if (h != hours{12}) + h += hours{12}; + } else if (h == hours{12}) + h = hours{0}; + I = not_a_hour_12_value; +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + + break; + case 'r': + if (command) { +#if !ONLY_C_LOCALE + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) { + h = hours{tm.tm_hour}; + min = minutes{tm.tm_min}; + s = duration_cast(seconds{tm.tm_sec}); + } + is.setstate(err); +#else + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + int H; + int M; + long double S; + read(is, ru{H, 1, 2}, CharT{':'}, ru{M, 1, 2}, CharT{':'}, + rld{S, 1, w}); + if (is.fail() || !(1 <= H && H <= 12)) + goto broken; + ws(is); + auto nm = detail::ampm_names(); + auto i = detail::scan_keyword(is, nm.first, nm.second) - nm.first; + if (is.fail()) + goto broken; + h = hours{H}; + if (i == 1) { + if (h != hours{12}) + h += hours{12}; + } else if (h == hours{12}) + h = hours{0}; + min = minutes{M}; + s = round(duration{S}); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'R': + if (command) { + if (modified == CharT{}) { + int H, M; + read(is, ru{H, 1, 2}, CharT{'\0'}, CharT{':'}, CharT{'\0'}, + ru{M, 1, 2}, CharT{'\0'}); + if (!is.fail()) { + h = hours{H}; + min = minutes{M}; + } + } else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'S': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + long double S; + read(is, rld{S, 1, width == -1 ? w : static_cast(width)}); + if (!is.fail()) + s = round(duration{S}); +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) + s = duration_cast(seconds{tm.tm_sec}); + is.setstate(err); + } else + read(is, CharT{'%'}, width, modified, *fmt); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'T': + if (command) { + if (modified == CharT{}) { + using dfs = detail::decimal_format_seconds; + CONSTDATA auto w = Duration::period::den == 1 ? 2 : 3 + dfs::width; + int H; + int M; + long double S; + read(is, ru{H, 1, 2}, CharT{':'}, ru{M, 1, 2}, CharT{':'}, + rld{S, 1, w}); + if (!is.fail()) { + h = hours{H}; + min = minutes{M}; + s = round(duration{S}); + } + } else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'Y': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + read(is, rs{Y, 1, width == -1 ? 4u : static_cast(width)}); +#if !ONLY_C_LOCALE + else if (modified == CharT{'E'}) { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) + Y = tm.tm_year + 1900; + is.setstate(err); + } else + read(is, CharT{'%'}, width, modified, *fmt); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'y': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) +#endif + read(is, ru{y, 1, width == -1 ? 2u : static_cast(width)}); +#if !ONLY_C_LOCALE + else { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) + Y = tm.tm_year + 1900; + is.setstate(err); + } +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'g': + if (command) { + if (modified == CharT{}) + read(is, ru{g, 1, width == -1 ? 2u : static_cast(width)}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'G': + if (command) { + if (modified == CharT{}) + read(is, rs{G, 1, width == -1 ? 4u : static_cast(width)}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'U': + if (command) { + if (modified == CharT{}) + read(is, ru{U, 1, width == -1 ? 2u : static_cast(width)}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'V': + if (command) { + if (modified == CharT{}) + read(is, ru{V, 1, width == -1 ? 2u : static_cast(width)}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'W': + if (command) { + if (modified == CharT{}) + read(is, ru{W, 1, width == -1 ? 2u : static_cast(width)}); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'u': + case 'w': + if (command) { +#if !ONLY_C_LOCALE + if (modified == CharT{}) { +#endif + read(is, + ru{wd, 1, width == -1 ? 1u : static_cast(width)}); + if (!is.fail() && *fmt == 'u') { + if (wd == 7) + wd = 0; + else if (wd == 0) + wd = 7; + } +#if !ONLY_C_LOCALE + } else if (modified == CharT{'O'}) { + ios_base::iostate err = ios_base::goodbit; + f.get(is, 0, is, err, &tm, command, fmt + 1); + if ((err & ios::failbit) == 0) + wd = tm.tm_wday; + is.setstate(err); + } else + read(is, CharT{'%'}, width, modified, *fmt); +#endif + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'E': + case 'O': + if (command) { + if (modified == CharT{}) { + modified = *fmt; + } else { + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + } else + read(is, *fmt); + break; + case '%': + if (command) { + if (modified == CharT{}) + read(is, *fmt); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + command = fmt; + break; + case 'z': + if (command) { + int H, M; + if (modified == CharT{}) + read(is, rs{H, 2, 2}, ru{M, 2, 2}); + else + read(is, rs{H, 1, 2}, CharT{':'}, ru{M, 2, 2}); + if (!is.fail()) + temp_offset = hours{H} + minutes{M}; + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + case 'Z': + if (command) { + if (modified == CharT{}) { + if (!temp_abbrev.empty()) + is.setstate(ios::failbit); + else { + while (is.rdstate() == std::ios::goodbit) { + auto i = is.rdbuf()->sgetc(); + if (Traits::eq_int_type(i, Traits::eof())) { + is.setstate(ios::eofbit); + break; + } + auto wc = Traits::to_char_type(i); + auto c = static_cast(wc); + // is c a valid time zone name or abbreviation character? + if (!(CharT{1} < wc && wc < CharT{127}) || + !(isalnum(c) || c == '_' || c == '/' || c == '-' || + c == '+')) + break; + temp_abbrev.push_back(c); + is.rdbuf()->sbumpc(); + } + if (temp_abbrev.empty()) + is.setstate(ios::failbit); + } + } else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } else + read(is, *fmt); + break; + default: + if (command) { + if (width == -1 && modified == CharT{} && '0' <= *fmt && + *fmt <= '9') { + width = static_cast(*fmt) - '0'; + while ('0' <= fmt[1] && fmt[1] <= '9') + width = 10 * width + static_cast(*++fmt) - '0'; + } else { + if (modified == CharT{}) + read(is, CharT{'%'}, width, *fmt); + else + read(is, CharT{'%'}, width, modified, *fmt); + command = nullptr; + width = -1; + modified = CharT{}; + } + } else // !command + { + if (isspace(*fmt)) + ws(is); // space matches 0 or more white space characters + else + read(is, *fmt); + } + break; + } + } + // is.rdstate() != ios::goodbit || *fmt == CharT{} + if (is.rdstate() == ios::goodbit && command) { + if (modified == CharT{}) + read(is, CharT{'%'}, width); + else + read(is, CharT{'%'}, width, modified); + } + if (is.rdstate() != ios::goodbit && *fmt != CharT{} && !is.fail()) + is.setstate(ios::failbit); + if (!is.fail()) { + if (y != not_a_2digit_year) { + // Convert y and an optional C to Y + if (!(0 <= y && y <= 99)) + goto broken; + if (C == not_a_century) { + if (Y == not_a_year) { + if (y >= 69) + C = 19; + else + C = 20; + } else { + C = (Y >= 0 ? Y : Y - 100) / 100; + } + } + int tY; + if (C >= 0) + tY = 100 * C + y; + else + tY = 100 * (C + 1) - (y == 0 ? 100 : y); + if (Y != not_a_year && Y != tY) + goto broken; + Y = tY; + } + if (g != not_a_2digit_year) { + // Convert g and an optional C to G + if (!(0 <= g && g <= 99)) + goto broken; + if (C == not_a_century) { + if (G == not_a_year) { + if (g >= 69) + C = 19; + else + C = 20; + } else { + C = (G >= 0 ? G : G - 100) / 100; + } + } + int tG; + if (C >= 0) + tG = 100 * C + g; + else + tG = 100 * (C + 1) - (g == 0 ? 100 : g); + if (G != not_a_year && G != tG) + goto broken; + G = tG; + } + if (G != not_a_year) { + // Convert G, V and wd to Y, m and d + if (V == not_a_week_num || wd == not_a_weekday) + goto broken; + auto ymd = year_month_day{local_days(year{G - 1} / dec / thu[last]) + + (mon - thu) + weeks{V - 1} + + (weekday{static_cast(wd)} - mon)}; + if (Y == not_a_year) + Y = static_cast(ymd.year()); + else if (year{Y} != ymd.year()) + goto broken; + if (m == 0) + m = static_cast(static_cast(ymd.month())); + else if (month(static_cast(m)) != ymd.month()) + goto broken; + if (d == 0) + d = static_cast(static_cast(ymd.day())); + else if (day(static_cast(d)) != ymd.day()) + goto broken; + } + if (j != 0 && Y != not_a_year) { + auto ymd = year_month_day{local_days(year{Y} / 1 / 1) + days{j - 1}}; + if (m == 0) + m = static_cast(static_cast(ymd.month())); + else if (month(static_cast(m)) != ymd.month()) + goto broken; + if (d == 0) + d = static_cast(static_cast(ymd.day())); + else if (day(static_cast(d)) != ymd.day()) + goto broken; + } + if (U != not_a_week_num && Y != not_a_year) { + if (wd == not_a_weekday) + goto broken; + sys_days sd; + if (U == 0) + sd = year{Y - 1} / dec / weekday{static_cast(wd)}[last]; + else + sd = sys_days(year{Y} / jan / sun[1]) + weeks{U - 1} + + (weekday{static_cast(wd)} - sun); + year_month_day ymd = sd; + if (year{Y} != ymd.year()) + goto broken; + if (m == 0) + m = static_cast(static_cast(ymd.month())); + else if (month(static_cast(m)) != ymd.month()) + goto broken; + if (d == 0) + d = static_cast(static_cast(ymd.day())); + else if (day(static_cast(d)) != ymd.day()) + goto broken; + } + if (W != not_a_week_num && Y != not_a_year) { + if (wd == not_a_weekday) + goto broken; + sys_days sd; + if (W == 0) + sd = year{Y - 1} / dec / weekday{static_cast(wd)}[last]; + else + sd = sys_days(year{Y} / jan / mon[1]) + weeks{W - 1} + + (weekday{static_cast(wd)} - mon); + year_month_day ymd = sd; + if (year{Y} != ymd.year()) + goto broken; + if (m == 0) + m = static_cast(static_cast(ymd.month())); + else if (month(static_cast(m)) != ymd.month()) + goto broken; + if (d == 0) + d = static_cast(static_cast(ymd.day())); + else if (day(static_cast(d)) != ymd.day()) + goto broken; + } + auto ymd = year{Y} / m / d; + if (wd != not_a_weekday && ymd.ok()) { + if (weekday{static_cast(wd)} != weekday(ymd)) + goto broken; + } + fds.ymd = ymd; + fds.tod = time_of_day(hours{h} + minutes{min}); + fds.tod.s_ = detail::decimal_format_seconds{s}; + if (wd != not_a_weekday) + fds.wd = weekday{static_cast(wd)}; + if (abbrev != nullptr) + *abbrev = std::move(temp_abbrev); + if (offset != nullptr) + *offset = temp_offset; + } + return is; + } +broken: + is.setstate(ios_base::failbit); + return is; +} + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, year &y, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) { + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.year().ok()) + is.setstate(ios::failbit); + if (!is.fail()) + y = fds.ymd.year(); + return is; +} + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, month &m, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) { + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.month().ok()) + is.setstate(ios::failbit); + if (!is.fail()) + m = fds.ymd.month(); + return is; +} + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, day &d, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) { + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.day().ok()) + is.setstate(ios::failbit); + if (!is.fail()) + d = fds.ymd.day(); + return is; +} + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, + weekday &wd, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) { + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.wd.ok()) + is.setstate(ios::failbit); + if (!is.fail()) + wd = fds.wd; + return is; +} + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, + year_month &ym, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) { + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.month().ok()) + is.setstate(ios::failbit); + if (!is.fail()) + ym = fds.ymd.year() / fds.ymd.month(); + return is; +} + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, + month_day &md, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) { + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.month().ok() || !fds.ymd.day().ok()) + is.setstate(ios::failbit); + if (!is.fail()) + md = fds.ymd.month() / fds.ymd.day(); + return is; +} + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, + year_month_day &ymd, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) { + using namespace std; + using namespace std::chrono; + using CT = seconds; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.ok()) + is.setstate(ios::failbit); + if (!is.fail()) + ymd = fds.ymd; + return is; +} + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, + sys_time &tp, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) { + using namespace std; + using namespace std::chrono; + using CT = typename common_type::type; + minutes offset_local{}; + auto offptr = offset ? offset : &offset_local; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offptr); + if (!fds.ymd.ok() || !fds.tod.in_conventional_range()) + is.setstate(ios::failbit); + if (!is.fail()) + tp = round(sys_days(fds.ymd) + fds.tod.to_duration() - *offptr); + return is; +} + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, + local_time &tp, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) { + using namespace std; + using namespace std::chrono; + using CT = typename common_type::type; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!fds.ymd.ok() || !fds.tod.in_conventional_range()) + is.setstate(ios::failbit); + if (!is.fail()) + tp = round(local_days(fds.ymd) + fds.tod.to_duration()); + return is; +} + +template > +std::basic_istream & +from_stream(std::basic_istream &is, const CharT *fmt, + std::chrono::duration &d, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) { + using namespace std; + using namespace std::chrono; + using Duration = std::chrono::duration; + using CT = typename common_type::type; + fields fds{}; + from_stream(is, fmt, fds, abbrev, offset); + if (!is.fail()) + d = duration_cast(fds.tod.to_duration()); + return is; +} + +template , + class Alloc = std::allocator> +struct parse_manip { + const std::basic_string format_; + Parsable &tp_; + std::basic_string *abbrev_; + std::chrono::minutes *offset_; + +public: + parse_manip(std::basic_string format, Parsable &tp, + std::basic_string *abbrev = nullptr, + std::chrono::minutes *offset = nullptr) + : format_(std::move(format)), tp_(tp), abbrev_(abbrev), offset_(offset) {} +}; + +template +std::basic_istream & +operator>>(std::basic_istream &is, + const parse_manip &x) { + return from_stream(is, x.format_.c_str(), x.tp_, x.abbrev_, x.offset_); +} + +template +inline auto parse(const std::basic_string &format, + Parsable &tp) + -> decltype(from_stream(std::declval &>(), + format.c_str(), tp), + parse_manip{format, tp}) { + return {format, tp}; +} + +template +inline auto parse(const std::basic_string &format, + Parsable &tp, std::basic_string &abbrev) + -> decltype(from_stream(std::declval &>(), + format.c_str(), tp, &abbrev), + parse_manip{format, tp, + &abbrev}) { + return {format, tp, &abbrev}; +} + +template +inline auto parse(const std::basic_string &format, + Parsable &tp, std::chrono::minutes &offset) + -> decltype(from_stream(std::declval &>(), + format.c_str(), tp, nullptr, &offset), + parse_manip{format, tp, nullptr, + &offset}) { + return {format, tp, nullptr, &offset}; +} + +template +inline auto parse(const std::basic_string &format, + Parsable &tp, std::basic_string &abbrev, + std::chrono::minutes &offset) + -> decltype(from_stream(std::declval &>(), + format.c_str(), tp, &abbrev, &offset), + parse_manip{format, tp, &abbrev, + &offset}) { + return {format, tp, &abbrev, &offset}; +} + +// const CharT* formats + +template +inline auto parse(const CharT *format, Parsable &tp) + -> decltype(from_stream(std::declval &>(), format, + tp), + parse_manip{format, tp}) { + return {format, tp}; +} + +template +inline auto parse(const CharT *format, Parsable &tp, + std::basic_string &abbrev) + -> decltype(from_stream(std::declval &>(), + format, tp, &abbrev), + parse_manip{format, tp, + &abbrev}) { + return {format, tp, &abbrev}; +} + +template +inline auto parse(const CharT *format, Parsable &tp, + std::chrono::minutes &offset) + -> decltype(from_stream(std::declval &>(), format, + tp, nullptr, &offset), + parse_manip{format, tp, nullptr, &offset}) { + return {format, tp, nullptr, &offset}; +} + +template +inline auto parse(const CharT *format, Parsable &tp, + std::basic_string &abbrev, + std::chrono::minutes &offset) + -> decltype(from_stream(std::declval &>(), + format, tp, &abbrev, &offset), + parse_manip{format, tp, &abbrev, + &offset}) { + return {format, tp, &abbrev, &offset}; +} + +// duration streaming + +namespace detail { + +#if __cplusplus >= 201402 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ > 411) && \ + (!defined(__SUNPRO_CC) || __SUNPRO_CC > 0x5150) + +template class string_literal { + CharT p_[N]; + +public: + using const_iterator = const CharT *; + + string_literal(string_literal const &) = default; + string_literal &operator=(string_literal const &) = delete; + + template > + CONSTCD14 string_literal(CharT c) NOEXCEPT : p_{c} {} + + CONSTCD14 string_literal(const CharT (&a)[N]) NOEXCEPT : p_{} { + for (std::size_t i = 0; i < N; ++i) + p_[i] = a[i]; + } + + template > + CONSTCD14 string_literal(const char (&a)[N]) NOEXCEPT : p_{} { + for (std::size_t i = 0; i < N; ++i) + p_[i] = a[i]; + } + + template {}>> + CONSTCD14 string_literal(string_literal const &a) NOEXCEPT : p_{} { + for (std::size_t i = 0; i < N; ++i) + p_[i] = a[i]; + } + + template > + CONSTCD14 string_literal(const string_literal &x, + const string_literal &y) NOEXCEPT : p_{} { + std::size_t i = 0; + for (; i < N1 - 1; ++i) + p_[i] = x[i]; + for (std::size_t j = 0; j < N2; ++j, ++i) + p_[i] = y[j]; + } + + CONSTCD14 const CharT *data() const NOEXCEPT { return p_; } + CONSTCD14 std::size_t size() const NOEXCEPT { return N - 1; } + + CONSTCD14 const_iterator begin() const NOEXCEPT { return p_; } + CONSTCD14 const_iterator end() const NOEXCEPT { return p_ + N - 1; } + + CONSTCD14 CharT const &operator[](std::size_t n) const NOEXCEPT { + return p_[n]; + } + + template + friend std::basic_ostream & + operator<<(std::basic_ostream &os, const string_literal &s) { + return os << s.p_; + } +}; + +template +CONSTCD14 inline string_literal< + std::conditional_t, + N1 + N2 - 1> +operator+(const string_literal &x, + const string_literal &y) NOEXCEPT { + using CharT = + std::conditional_t; + return string_literal{string_literal{x}, + string_literal{y}}; +} + +template +inline std::basic_string +operator+(std::basic_string x, + const string_literal &y) NOEXCEPT { + x.append(y.data(), y.size()); + return x; +} + +template +CONSTCD14 inline string_literal msl(const CharT (&a)[N]) NOEXCEPT { + return string_literal{a}; +} + +template < + class CharT, + class = std::enable_if_t< + std::is_same{} || std::is_same{} || + std::is_same{} || std::is_same{}>> +CONSTCD14 inline string_literal msl(CharT c) NOEXCEPT { + return string_literal{c}; +} + +CONSTCD14 +std::size_t to_string_len(std::intmax_t i) { + std::size_t r = 0; + do { + i /= 10; + ++r; + } while (i > 0); + return r; +} + +template + CONSTCD14 inline std::enable_if_t < + N<10, string_literal> msl() NOEXCEPT { + return msl(char(N % 10 + '0')); +} + +template +CONSTCD14 inline std::enable_if_t<10 <= N, + string_literal> +msl() NOEXCEPT { + return msl() + msl(char(N % 10 + '0')); +} + +template +CONSTCD14 inline std::enable_if_t< + std::ratio::type::den != 1, + string_literal::type::num) + + to_string_len(std::ratio::type::den) + 4>> +msl(std::ratio) NOEXCEPT { + using R = typename std::ratio::type; + return msl(CharT{'['}) + msl() + msl(CharT{'/'}) + msl() + + msl(CharT{']'}); +} + +template +CONSTCD14 inline std::enable_if_t< + std::ratio::type::den == 1, + string_literal::type::num) + 3>> +msl(std::ratio) NOEXCEPT { + using R = typename std::ratio::type; + return msl(CharT{'['}) + msl() + msl(CharT{']'}); +} + +template CONSTCD14 inline auto msl(std::atto) NOEXCEPT { + return msl(CharT{'a'}); +} + +template CONSTCD14 inline auto msl(std::femto) NOEXCEPT { + return msl(CharT{'f'}); +} + +template CONSTCD14 inline auto msl(std::pico) NOEXCEPT { + return msl(CharT{'p'}); +} + +template CONSTCD14 inline auto msl(std::nano) NOEXCEPT { + return msl(CharT{'n'}); +} + +template +CONSTCD14 inline std::enable_if_t{}, + string_literal> +msl(std::micro) NOEXCEPT { + return string_literal{"\xC2\xB5"}; +} + +template +CONSTCD14 inline std::enable_if_t{}, + string_literal> +msl(std::micro) NOEXCEPT { + return string_literal{CharT{static_cast('\xB5')}}; +} + +template CONSTCD14 inline auto msl(std::milli) NOEXCEPT { + return msl(CharT{'m'}); +} + +template CONSTCD14 inline auto msl(std::centi) NOEXCEPT { + return msl(CharT{'c'}); +} + +template CONSTCD14 inline auto msl(std::deci) NOEXCEPT { + return msl(CharT{'d'}); +} + +template CONSTCD14 inline auto msl(std::deca) NOEXCEPT { + return string_literal{"da"}; +} + +template CONSTCD14 inline auto msl(std::hecto) NOEXCEPT { + return msl(CharT{'h'}); +} + +template CONSTCD14 inline auto msl(std::kilo) NOEXCEPT { + return msl(CharT{'k'}); +} + +template CONSTCD14 inline auto msl(std::mega) NOEXCEPT { + return msl(CharT{'M'}); +} + +template CONSTCD14 inline auto msl(std::giga) NOEXCEPT { + return msl(CharT{'G'}); +} + +template CONSTCD14 inline auto msl(std::tera) NOEXCEPT { + return msl(CharT{'T'}); +} + +template CONSTCD14 inline auto msl(std::peta) NOEXCEPT { + return msl(CharT{'P'}); +} + +template CONSTCD14 inline auto msl(std::exa) NOEXCEPT { + return msl(CharT{'E'}); +} + +template CONSTCD14 auto get_units(Period p) { + return msl(p) + string_literal{"s"}; +} + +template CONSTCD14 auto get_units(std::ratio<1>) { + return string_literal{"s"}; +} + +template CONSTCD14 auto get_units(std::ratio<60>) { + return string_literal{"min"}; +} + +template CONSTCD14 auto get_units(std::ratio<3600>) { + return string_literal{"h"}; +} + +#else // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ <= + // 411) + +inline std::string to_string(std::uint64_t x) { return std::to_string(x); } + +template std::basic_string to_string(std::uint64_t x) { + auto y = std::to_string(x); + return std::basic_string(y.begin(), y.end()); +} + +template +inline typename std::enable_if::type::den != 1, + std::basic_string>::type +msl(std::ratio) { + using R = typename std::ratio::type; + return std::basic_string(1, '[') + to_string(R::num) + + CharT{'/'} + to_string(R::den) + CharT{']'}; +} + +template +inline typename std::enable_if::type::den == 1, + std::basic_string>::type +msl(std::ratio) { + using R = typename std::ratio::type; + return std::basic_string(1, '[') + to_string(R::num) + + CharT{']'}; +} + +template inline std::basic_string msl(std::atto) { + return {'a'}; +} + +template inline std::basic_string msl(std::femto) { + return {'f'}; +} + +template inline std::basic_string msl(std::pico) { + return {'p'}; +} + +template inline std::basic_string msl(std::nano) { + return {'n'}; +} + +template +inline + typename std::enable_if::value, std::string>::type + msl(std::micro) { + return "\xC2\xB5"; +} + +template +inline typename std::enable_if::value, + std::basic_string>::type +msl(std::micro) { + return {CharT(static_cast('\xB5'))}; +} + +template inline std::basic_string msl(std::milli) { + return {'m'}; +} + +template inline std::basic_string msl(std::centi) { + return {'c'}; +} + +template inline std::basic_string msl(std::deci) { + return {'d'}; +} + +template inline std::basic_string msl(std::deca) { + return {'d', 'a'}; +} + +template inline std::basic_string msl(std::hecto) { + return {'h'}; +} + +template inline std::basic_string msl(std::kilo) { + return {'k'}; +} + +template inline std::basic_string msl(std::mega) { + return {'M'}; +} + +template inline std::basic_string msl(std::giga) { + return {'G'}; +} + +template inline std::basic_string msl(std::tera) { + return {'T'}; +} + +template inline std::basic_string msl(std::peta) { + return {'P'}; +} + +template inline std::basic_string msl(std::exa) { + return {'E'}; +} + +template +std::basic_string get_units(Period p) { + return msl(p) + CharT{'s'}; +} + +template std::basic_string get_units(std::ratio<1>) { + return {'s'}; +} + +template std::basic_string get_units(std::ratio<60>) { + return {'m', 'i', 'n'}; +} + +template std::basic_string get_units(std::ratio<3600>) { + return {'h'}; +} + +#endif // __cplusplus < 201402 || (defined(__EDG_VERSION__) && __EDG_VERSION__ + // <= 411) + +template > +struct make_string; + +template <> struct make_string { + template static std::string from(Rep n) { + return std::to_string(n); + } +}; + +template struct make_string { + template static std::basic_string from(Rep n) { + auto s = std::to_string(n); + return std::basic_string(s.begin(), s.end()); + } +}; + +template <> struct make_string { + template static std::wstring from(Rep n) { + return std::to_wstring(n); + } +}; + +template struct make_string { + template static std::basic_string from(Rep n) { + auto s = std::to_wstring(n); + return std::basic_string(s.begin(), s.end()); + } +}; + +} // namespace detail + +template +inline std::basic_ostream & +operator<<(std::basic_ostream &os, + const std::chrono::duration &d) { + using namespace detail; + return os << make_string::from(d.count()) + + get_units(typename Period::type{}); +} + +} // namespace date + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + +#endif // DATE_H diff --git a/projects/frontend/server-webapi/pistache/include/pistache/description.h b/projects/frontend/server-webapi/pistache/include/pistache/description.h new file mode 100755 index 0000000000000000000000000000000000000000..775b3d831a521b6b93f9b800c29b1249f2a75f12 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/description.h @@ -0,0 +1,466 @@ +/* + Mathieu Stefani, 24 février 2016 + + An API description (reflection) mechanism that is based on Swagger +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Pistache { +namespace Rest { +namespace Type { + +// Data Types + +#define DATA_TYPE \ + TYPE(Integer, std::int32_t, "integer", "int32") \ + TYPE(Long, std::int64_t, "integer", "int64") \ + TYPE(Float, float, "number", "float") \ + TYPE(Double, double, "number", "double") \ + TYPE(String, std::string, "string", "") \ + TYPE(Byte, char, "string", "byte") \ + TYPE(Binary, std::vector, "string", "binary") \ + TYPE(Bool, bool, "boolean", "") \ + COMPLEX_TYPE(Date, "string", "date") \ + COMPLEX_TYPE(Datetime, "string", "date-time") \ + COMPLEX_TYPE(Password, "string", "password") \ + COMPLEX_TYPE(Array, "array", "array") + +#define TYPE(rest, cpp, _, __) typedef cpp rest; +#define COMPLEX_TYPE(rest, _, __) \ + struct rest {}; +DATA_TYPE +#undef TYPE +#undef COMPLEX_TYPE + +} // namespace Type + +#define SCHEMES \ + SCHEME(Http, "http") \ + SCHEME(Https, "https") \ + SCHEME(Ws, "ws") \ + SCHEME(Wss, "wss") + +enum class Scheme { +#define SCHEME(e, _) e, + SCHEMES +#undef SCHEME +}; + +const char *schemeString(Scheme scheme); + +namespace Schema { + +namespace Traits { + +template struct IsDataType : public std::false_type {}; + +#define TYPE(rest, _, __, ___) \ + template <> struct IsDataType : public std::true_type {}; +#define COMPLEX_TYPE(rest, _, __) \ + template <> struct IsDataType : public std::true_type {}; +DATA_TYPE +#undef TYPE +#undef COMPLEX_TYPE + +template struct DataTypeInfo; + +#define TYPE(rest, _, typeStr, formatStr) \ + template <> struct DataTypeInfo { \ + static const char *typeName() { return typeStr; } \ + static const char *format() { return formatStr; } \ + }; +#define COMPLEX_TYPE(rest, typeStr, formatStr) \ + template <> struct DataTypeInfo { \ + static const char *typeName() { return typeStr; } \ + static const char *format() { return formatStr; } \ + }; +DATA_TYPE +#undef TYPE +#undef COMPLEX_TYPE + +template struct DataTypeValidation { + static bool validate(const std::string &) { return true; } +}; + +} // namespace Traits + +struct ProduceConsume { + ProduceConsume() : produce(), consume() {} + + std::vector produce; + std::vector consume; +}; + +struct Contact { + Contact(std::string name, std::string url, std::string email); + + std::string name; + std::string url; + std::string email; +}; + +struct License { + License(std::string name, std::string url); + + std::string name; + std::string url; +}; + +struct Info { + Info(std::string title, std::string version, std::string description = ""); + + std::string title; + std::string version; + std::string description; + std::string termsOfService; + + Optional contact; + Optional license; +}; + +struct InfoBuilder { + explicit InfoBuilder(Info *info); + + InfoBuilder &termsOfService(std::string value); + InfoBuilder &contact(std::string name, std::string url, std::string email); + InfoBuilder &license(std::string name, std::string url); + +private: + Info *info_; +}; + +struct DataType { + virtual const char *typeName() const = 0; + virtual const char *format() const = 0; + + virtual bool validate(const std::string &input) const = 0; + + virtual ~DataType() {} +}; + +template struct DataTypeT : public DataType { + const char *typeName() const override { + return Traits::DataTypeInfo::typeName(); + } + const char *format() const override { + return Traits::DataTypeInfo::format(); + } + + bool validate(const std::string &input) const override { + return Traits::DataTypeValidation::validate(input); + } + + virtual ~DataTypeT() {} +}; + +template std::unique_ptr makeDataType() { + static_assert(Traits::IsDataType::value, "Unknown Data Type"); + return std::unique_ptr(new DataTypeT()); +} + +struct Parameter { + Parameter(std::string name, std::string description); + + template + static Parameter create(Args &&... args) { + Parameter p(std::forward(args)...); + p.type = makeDataType(); + return p; + } + + std::string name; + std::string description; + bool required; + std::shared_ptr type; +}; + +struct Response { + Response(Http::Code statusCode, std::string description); + + Http::Code statusCode; + std::string description; +}; + +struct ResponseBuilder { + ResponseBuilder(Http::Code statusCode, std::string description); + + operator Response() const { return response_; } + +private: + Response response_; +}; + +struct PathDecl { + PathDecl(std::string value, Http::Method method); + + std::string value; + Http::Method method; +}; + +struct Path { + Path(std::string value, Http::Method method, std::string description); + + std::string value; + Http::Method method; + std::string description; + bool hidden; + + ProduceConsume pc; + std::vector parameters; + std::vector responses; + + Route::Handler handler; + + static std::string swaggerFormat(const std::string &path); + + bool isBound() const { return handler != nullptr; } +}; + +class PathGroup { +public: + struct Group : public std::vector { + bool isHidden() const { + if (empty()) + return false; + + return std::all_of(begin(), end(), + [](const Path &path) { return path.hidden; }); + } + }; + + typedef std::unordered_map Map; + typedef Map::iterator iterator; + typedef Map::const_iterator const_iterator; + + typedef std::vector::iterator group_iterator; + + typedef FlatMapIteratorAdapter flat_iterator; + + enum class Format { Default, Swagger }; + + bool hasPath(const std::string &name, Http::Method method) const; + bool hasPath(const Path &path) const; + + PathGroup() : groups_() {} + + Group paths(const std::string &name) const; + Optional path(const std::string &name, Http::Method method) const; + + group_iterator add(Path path); + + template group_iterator emplace(Args &&... args) { + return add(Path(std::forward(args)...)); + } + + const_iterator begin() const; + const_iterator end() const; + + flat_iterator flatBegin() const; + flat_iterator flatEnd() const; + + Map groups() const { return groups_; } + +private: + Map groups_; +}; + +struct PathBuilder { + explicit PathBuilder(Path *path); + + template PathBuilder &produces(Mimes... mimes) { + Http::Mime::MediaType m[sizeof...(Mimes)] = {mimes...}; + std::copy(std::begin(m), std::end(m), + std::back_inserter(path_->pc.produce)); + return *this; + } + + template PathBuilder &consumes(Mimes... mimes) { + Http::Mime::MediaType m[sizeof...(Mimes)] = {mimes...}; + std::copy(std::begin(m), std::end(m), + std::back_inserter(path_->pc.consume)); + return *this; + } + + template + PathBuilder ¶meter(std::string name, std::string description) { + path_->parameters.push_back( + Parameter::create(std::move(name), std::move(description))); + return *this; + } + + PathBuilder &response(Http::Code statusCode, std::string description) { + path_->responses.push_back(Response(statusCode, std::move(description))); + return *this; + } + + PathBuilder &response(Response response) { + path_->responses.push_back(std::move(response)); + return *this; + } + + /* @CodeDup: should re-use Routes::bind */ + template + PathBuilder &bind(Result (Cls::*func)(Args...), Obj obj) { + +#define CALL_MEMBER_FN(obj, pmf) ((obj)->*(pmf)) + + path_->handler = [=](const Rest::Request &request, + Http::ResponseWriter response) { + CALL_MEMBER_FN(obj, func)(request, std::move(response)); + + return Route::Result::Ok; + }; + +#undef CALL_MEMBER_FN + + return *this; + } + + template + PathBuilder &bind(Result (*func)(Args...)) { + + path_->handler = [=](const Rest::Request &request, + Http::ResponseWriter response) { + func(request, std::move(response)); + + return Route::Result::Ok; + }; + + return *this; + } + + PathBuilder &hide(bool value = true) { + path_->hidden = value; + return *this; + } + +private: + Path *path_; +}; + +struct SubPath { + SubPath(std::string prefix, PathGroup *paths); + + PathBuilder route(const std::string &name, Http::Method method, + std::string description = ""); + PathBuilder route(PathDecl fragment, std::string description = ""); + + SubPath path(const std::string &prefix); + + template + void parameter(std::string name, std::string description) { + parameters.push_back( + Parameter::create(std::move(name), std::move(description))); + } + + std::string prefix; + std::vector parameters; + PathGroup *paths; +}; + +} // namespace Schema + +class Description { +public: + Description(std::string title, std::string version, + std::string description = ""); + + Schema::InfoBuilder info(); + + Description &host(std::string value); + Description &basePath(std::string value); + + template Description &schemes(Schemes... _schemes) { + Scheme s[sizeof...(Schemes)] = {_schemes...}; + std::copy(std::begin(s), std::end(s), std::back_inserter(schemes_)); + return *this; + } + + template Description &produces(Mimes... mimes) { + Http::Mime::MediaType m[sizeof...(Mimes)] = {mimes...}; + std::copy(std::begin(m), std::end(m), std::back_inserter(pc.produce)); + return *this; + } + + template Description &consumes(Mimes... mimes) { + Http::Mime::MediaType m[sizeof...(Mimes)] = {mimes...}; + std::copy(std::begin(m), std::end(m), std::back_inserter(pc.consume)); + return *this; + } + + Schema::PathDecl options(std::string name); + Schema::PathDecl get(std::string name); + Schema::PathDecl post(std::string name); + Schema::PathDecl head(std::string name); + Schema::PathDecl put(std::string name); + Schema::PathDecl patch(std::string name); + Schema::PathDecl del(std::string name); + Schema::PathDecl trace(std::string name); + Schema::PathDecl connect(std::string name); + + Schema::SubPath path(std::string name); + + Schema::PathBuilder route(std::string name, Http::Method method, + std::string description = ""); + Schema::PathBuilder route(Schema::PathDecl fragment, + std::string description = ""); + + Schema::ResponseBuilder response(Http::Code statusCode, + std::string description); + + Schema::Info rawInfo() const { return info_; } + std::string rawHost() const { return host_; } + std::string rawBasePath() const { return basePath_; } + std::vector rawSchemes() const { return schemes_; } + Schema::ProduceConsume rawPC() const { return pc; } + const Schema::PathGroup &rawPaths() const { return paths_; } + +private: + Schema::Info info_; + std::string host_; + std::string basePath_; + std::vector schemes_; + Schema::ProduceConsume pc; + + Schema::PathGroup paths_; +}; + +class Swagger { +public: + explicit Swagger(const Description &description) + : description_(description), uiPath_(), uiDirectory_(), apiPath_(), + serializer_() {} + + typedef std::function Serializer; + + Swagger &uiPath(std::string path); + Swagger &uiDirectory(std::string dir); + Swagger &apiPath(std::string path); + Swagger &serializer(Serializer serialize); + + void install(Rest::Router &router); + +private: + Description description_; + std::string uiPath_; + std::string uiDirectory_; + std::string apiPath_; + Serializer serializer_; +}; + +} // namespace Rest +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/endpoint.h b/projects/frontend/server-webapi/pistache/include/pistache/endpoint.h new file mode 100755 index 0000000000000000000000000000000000000000..2c19f819bcd4d95e9ed48382e35c84e12ff5fb30 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/endpoint.h @@ -0,0 +1,166 @@ +/* + Mathieu Stefani, 22 janvier 2016 + + An Http endpoint +*/ + +#pragma once + +#include +#include +#include + +namespace Pistache { +namespace Http { + +class Endpoint { +public: + struct Options { + friend class Endpoint; + + Options &threads(int val); + Options &threadsName(const std::string &val); + Options &flags(Flags flags); + Options &flags(Tcp::Options tcp_opts) { + flags(Flags(tcp_opts)); + return *this; + } + Options &backlog(int val); + Options &maxRequestSize(size_t val); + Options &maxResponseSize(size_t val); + + [[deprecated("Replaced by maxRequestSize(val)")]] Options & + maxPayload(size_t val); + + private: + int threads_; + std::string threadsName_; + Flags flags_; + int backlog_; + size_t maxRequestSize_; + size_t maxResponseSize_; + Options(); + }; + Endpoint(); + explicit Endpoint(const Address &addr); + + template void initArgs(Args &&... args) { + listener.init(std::forward(args)...); + } + + void init(const Options &options = Options()); + void setHandler(const std::shared_ptr &handler); + + void bind(); + void bind(const Address &addr); + + void serve(); + void serveThreaded(); + + void shutdown(); + + /*! + * \brief Use SSL on this endpoint + * + * \param[in] cert Server certificate path + * \param[in] key Server key path + * \param[in] use_compression Wether or not use compression on the encryption + * + * Setup the SSL configuration for an endpoint. In order to do that, this + * function will init OpenSSL constants and load *all* algorithms. It will + * then load the server certificate and key, in order to use it later. + * *If the private key does not match the certificate, an exception will + * be thrown* + * + * \note use_compression is false by default to mitigate BREACH[1] and + * CRIME[2] vulnerabilities + * \note This function will throw an exception if pistache has not been + * compiled with PISTACHE_USE_SSL + * + * [1] https://en.wikipedia.org/wiki/BREACH + * [2] https://en.wikipedia.org/wiki/CRIME + */ + void useSSL(const std::string &cert, const std::string &key, bool use_compression = false); + + /*! + * \brief Use SSL certificate authentication on this endpoint + * + * \param[in] ca_file Certificate Authority file + * \param[in] ca_path Certificate Authority path + * \param[in] cb OpenSSL verify callback[1] + * + * Change the SSL configuration in order to only accept verified client + * certificates. The function 'useSSL' *should* be called before this + * function. + * Due to the way we actually doesn't expose any OpenSSL internal types, the + * callback function is Cpp generic. The 'real' callback will be: + * + * int callback(int preverify_ok, X509_STORE_CTX *x509_ctx) + * + * It is up to the caller to cast the second argument to an appropriate + * pointer: + * + * int store_callback(int preverify_ok, void *ctx) { + * X509_STORE_CTX *x509_ctx = (X509_STORE_CTX *)ctx; + * + * [...] + * + * if (all_good) + * return 1; + * return 0; + * } + * + * [...] + * + * endpoint->useSSLAuth(ca_file, ca_path, &store_callback); + * + * See the documentation[1] for more information about this callback. + * + * \sa useSSL + * \note This function will throw an exception if pistache has not been + * compiled with PISTACHE_USE_SSL + * + * [1] https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_verify.html + */ + void useSSLAuth(std::string ca_file, std::string ca_path = "", + int (*cb)(int, void *) = NULL); + + bool isBound() const { return listener.isBound(); } + + Port getPort() const { return listener.getPort(); } + + Async::Promise + requestLoad(const Tcp::Listener::Load &old); + + static Options options(); + +private: + template void serveImpl(Method method) { +#define CALL_MEMBER_FN(obj, pmf) ((obj).*(pmf)) + if (!handler_) + throw std::runtime_error("Must call setHandler() prior to serve()"); + + listener.setHandler(handler_); + listener.bind(); + + CALL_MEMBER_FN(listener, method)(); +#undef CALL_MEMBER_FN + } + + std::shared_ptr handler_; + Tcp::Listener listener; + size_t maxRequestSize_ = Const::DefaultMaxRequestSize; + size_t maxResponseSize_ = Const::DefaultMaxResponseSize; +}; + +template +void listenAndServe(Address addr, + const Endpoint::Options &options = Endpoint::options()) { + Endpoint endpoint(addr); + endpoint.init(options); + endpoint.setHandler(make_handler()); + endpoint.serve(); +} + +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/errors.h b/projects/frontend/server-webapi/pistache/include/pistache/errors.h new file mode 100755 index 0000000000000000000000000000000000000000..0a42536391300648457ea9bec7a9901c41e53eac --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/errors.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +namespace Pistache { +namespace Tcp { + +class SocketError : public std::runtime_error { +public: + explicit SocketError(const char *what_arg) : std::runtime_error(what_arg) {} +}; + +class ServerError : public std::runtime_error { +public: + explicit ServerError(const char *what_arg) : std::runtime_error(what_arg) {} +}; + +} // namespace Tcp +} // namespace Pistache \ No newline at end of file diff --git a/projects/frontend/server-webapi/pistache/include/pistache/flags.h b/projects/frontend/server-webapi/pistache/include/pistache/flags.h new file mode 100755 index 0000000000000000000000000000000000000000..25387730d23d773adfd345000c11f3f179253b31 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/flags.h @@ -0,0 +1,126 @@ +/* flags.h + Mathieu Stefani, 18 August 2015 + + Make it easy to have bitwise operators for scoped or unscoped enumerations +*/ + +#pragma once + +#include +#include +#include + +namespace Pistache { + +// Looks like gcc 4.6 does not implement std::underlying_type +namespace detail { +template struct TypeStorage; + +template <> struct TypeStorage { typedef uint8_t Type; }; +template <> struct TypeStorage { typedef uint16_t Type; }; +template <> struct TypeStorage { typedef uint32_t Type; }; +template <> struct TypeStorage { typedef uint64_t Type; }; + +template struct UnderlyingType { + typedef typename TypeStorage::Type Type; +}; + +template struct HasNone { + template + static auto test(U *) -> decltype(U::None, std::true_type()); + + template static auto test(...) -> std::false_type; + + static constexpr bool value = + std::is_same(0)), std::true_type>::value; +}; +} // namespace detail + +template class Flags { +public: + typedef typename detail::UnderlyingType::Type Type; + + static_assert(std::is_enum::value, "Flags only works with enumerations"); + static_assert(detail::HasNone::value, "The enumartion needs a None value"); + static_assert(static_cast(T::None) == 0, "None should be 0"); + + Flags() : val(T::None) {} + + explicit Flags(T _val) : val(_val) {} + +#define DEFINE_BITWISE_OP_CONST(Op) \ + Flags operator Op(T rhs) const { \ + return Flags( \ + static_cast(static_cast(val) Op static_cast(rhs))); \ + } \ + \ + Flags operator Op(Flags rhs) const { \ + return Flags( \ + static_cast(static_cast(val) Op static_cast(rhs.val))); \ + } + + DEFINE_BITWISE_OP_CONST(|) + DEFINE_BITWISE_OP_CONST(&) + DEFINE_BITWISE_OP_CONST(^) + +#undef DEFINE_BITWISE_OP_CONST + +#define DEFINE_BITWISE_OP(Op) \ + Flags &operator Op##=(T rhs) { \ + val = static_cast(static_cast(val) Op static_cast(rhs)); \ + return *this; \ + } \ + \ + Flags &operator Op##=(Flags rhs) { \ + val = \ + static_cast(static_cast(val) Op static_cast(rhs.val)); \ + return *this; \ + } + + DEFINE_BITWISE_OP(|) + DEFINE_BITWISE_OP(&) + DEFINE_BITWISE_OP(^) + +#undef DEFINE_BITWISE_OP + + bool hasFlag(T flag) const { + return static_cast(val) & static_cast(flag); + } + + Flags &setFlag(T flag) { + *this |= flag; + return *this; + } + + Flags &toggleFlag(T flag) { return *this ^= flag; } + + operator T() const { return val; } + +private: + T val; +}; + +} // namespace Pistache + +#define DEFINE_BITWISE_OP(Op, T) \ + inline T operator Op(T lhs, T rhs) { \ + typedef Pistache::detail::UnderlyingType::Type UnderlyingType; \ + return static_cast(static_cast(lhs) \ + Op static_cast(rhs)); \ + } + +#define DECLARE_FLAGS_OPERATORS(T) \ + DEFINE_BITWISE_OP(&, T) \ + DEFINE_BITWISE_OP(|, T) + +template +std::ostream &operator<<(std::ostream &os, Pistache::Flags flags) { + typedef typename Pistache::detail::UnderlyingType::Type UnderlyingType; + + auto val = static_cast(static_cast(flags)); + for (ssize_t i = (sizeof(UnderlyingType) * CHAR_BIT) - 1; i >= 0; --i) { + os << ((val >> i) & 0x1); + } + + return os; +} diff --git a/projects/frontend/server-webapi/pistache/include/pistache/http.h b/projects/frontend/server-webapi/pistache/include/pistache/http.h new file mode 100755 index 0000000000000000000000000000000000000000..66564f19f05eb29dd97663e9c80ee44046838811 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/http.h @@ -0,0 +1,605 @@ +/* http.h + Mathieu Stefani, 13 August 2015 + + Http Layer +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Pistache { +namespace Tcp { +class Peer; +} +namespace Http { + +namespace details { +struct prototype_tag {}; + +template struct IsHttpPrototype { + template static auto test(U *) -> decltype(typename U::tag()); + template static auto test(...) -> std::false_type; + + static constexpr bool value = + std::is_same(nullptr)), prototype_tag>::value; +}; +} // namespace details + +#define HTTP_PROTOTYPE(Class) \ + PROTOTYPE_OF(Pistache::Tcp::Handler, Class) \ + typedef Pistache::Http::details::prototype_tag tag; + +namespace Private { +class RequestLineStep; +class ResponseLineStep; +class HeadersStep; +class BodyStep; +} // namespace Private + +template +std::basic_ostream &crlf(std::basic_ostream &os) { + static constexpr char CRLF[] = {0xD, 0xA}; + os.write(CRLF, 2); + + return os; +} + +// 4. HTTP Message +class Message { +public: + friend class Private::HeadersStep; + friend class Private::BodyStep; + friend class ResponseWriter; + + Message() = default; + explicit Message(Version version); + + Message(const Message &other) = default; + Message &operator=(const Message &other) = default; + + Message(Message &&other) = default; + Message &operator=(Message &&other) = default; + + Version version() const; + Code code() const; + + const std::string& body() const; + + const CookieJar &cookies() const; + CookieJar &cookies(); + const Header::Collection &headers() const; + Header::Collection &headers(); + +protected: + Version version_ = Version::Http11; + Code code_; + + std::string body_; + + CookieJar cookies_; + Header::Collection headers_; +}; + +namespace Uri { + +class Query { +public: + Query(); + explicit Query( + std::initializer_list> params); + + void add(std::string name, std::string value); + Optional get(const std::string &name) const; + bool has(const std::string &name) const; + // Return empty string or "?key1=value1&key2=value2" if query exist + std::string as_str() const; + + void clear() { params.clear(); } + + // \brief Return iterator to the beginning of the parameters map + std::unordered_map::const_iterator + parameters_begin() const { + return params.begin(); + } + + // \brief Return iterator to the end of the parameters map + std::unordered_map::const_iterator + parameters_end() const { + return params.end(); + } + + // \brief returns all parameters given in the query + std::vector parameters() const { + std::vector keys; + std::transform( + params.begin(), params.end(), std::back_inserter(keys), + [](const std::unordered_map::value_type + &pair) { return pair.first; }); + return keys; + } + +private: + // first is key second is value + std::unordered_map params; +}; +} // namespace Uri + +// 5. Request +class Request : public Message { +public: + friend class Private::RequestLineStep; + + friend class RequestBuilder; + + Request() = default; + + Request(const Request &other) = default; + Request &operator=(const Request &other) = default; + + Request(Request &&other) = default; + Request &operator=(Request &&other) = default; + + Method method() const; + const std::string &resource() const; + + const Uri::Query &query() const; + +/* @Investigate: this is disabled because of a lock in the shared_ptr / + weak_ptr implementation of libstdc++. Under contention, we experience a + performance drop of 5x with that lock + + If this turns out to be a problem, we might be able to replace the + weak_ptr trick to detect peer disconnection by a plain old "observer" + pointer to a tcp connection with a "stale" state +*/ +#ifdef LIBSTDCPP_SMARTPTR_LOCK_FIXME + std::shared_ptr peer() const; +#endif + + const Address &address() const; + + void copyAddress(const Address &address) { address_ = address; } + + std::chrono::milliseconds timeout() const; + +private: +#ifdef LIBSTDCPP_SMARTPTR_LOCK_FIXME + void associatePeer(const std::shared_ptr &peer) { + if (peer_.use_count() > 0) + throw std::runtime_error("A peer was already associated to the response"); + + peer_ = peer; + } +#endif + + Method method_; + std::string resource_; + Uri::Query query_; + +#ifdef LIBSTDCPP_SMARTPTR_LOCK_FIXME + std::weak_ptr peer_; +#endif + Address address_; + std::chrono::milliseconds timeout_ = std::chrono::milliseconds(0); +}; + +class Handler; +class ResponseWriter; + +class Timeout { +public: + friend class ResponseWriter; + + explicit Timeout(Timeout &&other) + : handler(other.handler), request(std::move(other.request)), + transport(other.transport), armed(other.armed), timerFd(other.timerFd), + peer(std::move(other.peer)) { + // cppcheck-suppress useInitializationList + other.timerFd = -1; + } + + Timeout &operator=(Timeout &&other) { + handler = other.handler; + transport = other.transport; + request = std::move(other.request); + armed = other.armed; + timerFd = other.timerFd; + other.timerFd = -1; + peer = std::move(other.peer); + return *this; + } + + ~Timeout(); + + template void arm(Duration duration) { + Async::Promise p([=](Async::Deferred deferred) { + timerFd = TRY_RET(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK)); + transport->armTimer(timerFd, duration, std::move(deferred)); + }); + + p.then( + [=](uint64_t numWakeup) { + this->armed = false; + this->onTimeout(numWakeup); + close(timerFd); + }, + [=](std::exception_ptr exc) { std::rethrow_exception(exc); }); + + armed = true; + } + + void disarm(); + + bool isArmed() const; + +private: + Timeout(const Timeout &other) = default; + + Timeout(Tcp::Transport *transport_, Handler *handler_, Request request_, + std::weak_ptr peer_); + + void onTimeout(uint64_t numWakeup); + + Handler *handler; + Request request; + Tcp::Transport *transport; + bool armed; + Fd timerFd; + std::weak_ptr peer; +}; + +class ResponseStream final { +public: + friend class ResponseWriter; + + ResponseStream(ResponseStream &&other); + + ResponseStream &operator=(ResponseStream &&other); + + template + friend ResponseStream &operator<<(ResponseStream &stream, const T &val); + + std::streamsize write(const char *data, std::streamsize sz); + + void flush(); + void ends(); + +private: + ResponseStream(Message &&other, std::weak_ptr peer, + Tcp::Transport *transport, Timeout timeout, size_t streamSize, + size_t maxResponseSize); + + std::shared_ptr peer() const; + + Message response_; + std::weak_ptr peer_; + DynamicStreamBuf buf_; + Tcp::Transport *transport_; + Timeout timeout_; +}; + +inline ResponseStream &ends(ResponseStream &stream) { + stream.ends(); + return stream; +} + +inline ResponseStream &flush(ResponseStream &stream) { + stream.flush(); + return stream; +} + +template +ResponseStream &operator<<(ResponseStream &stream, const T &val) { + Size size; + + std::ostream os(&stream.buf_); + os << std::hex << size(val) << crlf; + os << val << crlf; + + return stream; +} + +inline ResponseStream &operator<<(ResponseStream &stream, + ResponseStream &(*func)(ResponseStream &)) { + return (*func)(stream); +} + +// 6. Response +// @Investigate public inheritence +class Response : public Message { +public: + friend class Private::ResponseLineStep; + + Response() = default; + explicit Response(Version version); + + Response(const Response &other) = default; + Response &operator=(const Response &other) = default; + Response(Response &&other) = default; + Response &operator=(Response &&other) = default; +}; + +class ResponseWriter final { +public: + static constexpr size_t DefaultStreamSize = 512; + + friend Async::Promise + serveFile(ResponseWriter &, const std::string &, const Mime::MediaType &); + + friend class Handler; + friend class Timeout; + + ResponseWriter &operator=(const ResponseWriter &other) = delete; + + friend class Private::ResponseLineStep; + + // + // C++11: std::weak_ptr move constructor is C++14 only so the default + // version of move constructor / assignement operator does not work and we + // have to define it ourself + ResponseWriter(ResponseWriter &&other); + + ResponseWriter &operator=(ResponseWriter &&other) = default; + + void setMime(const Mime::MediaType &mime); + + /* @Feature: add helper functions for common http return code: + * - halt() -> 404 + * - movedPermantly -> 301 + * - moved() -> 302 + */ + Async::Promise + sendMethodNotAllowed(const std::vector &supportedMethods); + + Async::Promise send(Code code, const std::string &body = "", + const Mime::MediaType &mime = Mime::MediaType()); + + template + Async::Promise + send(Code code, const char (&arr)[N], + const Mime::MediaType &mime = Mime::MediaType()) { + return sendImpl(code, arr, N - 1, mime); + } + + Async::Promise send(Code code, const char *data, const size_t size, + const Mime::MediaType &mime = Mime::MediaType()); + + ResponseStream stream(Code code, size_t streamSize = DefaultStreamSize); + + template void timeoutAfter(Duration duration) { + timeout_.arm(duration); + } + + const CookieJar &cookies() const; + CookieJar &cookies(); + + const Header::Collection &headers() const; + Header::Collection &headers(); + + Timeout &timeout(); + + std::shared_ptr peer() const; + + // Returns total count of HTTP bytes (headers, cookies, body) written when + // sending the response. Result valid AFTER ResponseWriter.send() is called. + ssize_t getResponseSize() const { return sent_bytes_; } + + // Returns HTTP result code that was sent with the response. + Code getResponseCode() const { return response_.code(); } + + // Unsafe API + + DynamicStreamBuf *rdbuf(); + + DynamicStreamBuf *rdbuf(DynamicStreamBuf *other); + + ResponseWriter clone() const; + +private: + ResponseWriter(Tcp::Transport *transport, Request request, Handler *handler, + std::weak_ptr peer); + + ResponseWriter(const ResponseWriter &other); + + Async::Promise sendImpl(Code code, const char *data, + const size_t size, + const Mime::MediaType &mime); + + Async::Promise putOnWire(const char *data, size_t len); + + Response response_; + std::weak_ptr peer_; + DynamicStreamBuf buf_; + Tcp::Transport *transport_; + Timeout timeout_; + ssize_t sent_bytes_; +}; + +Async::Promise +serveFile(ResponseWriter &writer, const std::string &fileName, + const Mime::MediaType &contentType = Mime::MediaType()); + +namespace Private { + +enum class State { Again, Next, Done }; + +struct Step { + explicit Step(Message *request); + + virtual ~Step() = default; + + virtual State apply(StreamCursor &cursor) = 0; + + static void raise(const char *msg, Code code = Code::Bad_Request); + +protected: + Message *message; +}; + +class RequestLineStep : public Step { +public: + explicit RequestLineStep(Request *request) : Step(request) {} + + State apply(StreamCursor &cursor) override; +}; + +class ResponseLineStep : public Step { +public: + explicit ResponseLineStep(Response *response) : Step(response) {} + + State apply(StreamCursor &cursor) override; +}; + +class HeadersStep : public Step { +public: + explicit HeadersStep(Message *request) : Step(request) {} + + State apply(StreamCursor &cursor) override; +}; + +class BodyStep : public Step { +public: + explicit BodyStep(Message *message_) + : Step(message_), chunk(message_), bytesRead(0) {} + + State apply(StreamCursor &cursor) override; + +private: + struct Chunk { + enum Result { Complete, Incomplete, Final }; + + explicit Chunk(Message *message_) + : message(message_), bytesRead(0), size(-1) {} + + Result parse(StreamCursor &cursor); + + void reset() { + bytesRead = 0; + size = -1; + } + + private: + Message *message; + size_t bytesRead; + ssize_t size; + }; + + State parseContentLength(StreamCursor &cursor, + const std::shared_ptr &cl); + State + parseTransferEncoding(StreamCursor &cursor, + const std::shared_ptr &te); + + Chunk chunk; + size_t bytesRead; +}; + +class ParserBase { +public: + explicit ParserBase(size_t maxDataSize); + + ParserBase(const ParserBase &) = delete; + ParserBase &operator=(const ParserBase &) = delete; + ParserBase(ParserBase &&) = default; + ParserBase &operator=(ParserBase &&) = default; + + virtual ~ParserBase() = default; + + bool feed(const char *data, size_t len); + virtual void reset(); + State parse(); + +protected: + static constexpr size_t StepsCount = 3; + + std::array, StepsCount> allSteps; + size_t currentStep = 0; + +private: + ArrayStreamBuf buffer; + StreamCursor cursor; +}; + +template class ParserImpl; + +template <> class ParserImpl : public ParserBase { + +public: + explicit ParserImpl(size_t maxDataSize); + + void reset() override; + + Request request; +}; + +template <> class ParserImpl : public ParserBase { +public: + explicit ParserImpl(size_t maxDataSize); + + Response response; +}; + +} // namespace Private + +using Parser = Private::ParserBase; +using RequestParser = Private::ParserImpl; +using ResponseParser = Private::ParserImpl; + +class Handler : public Tcp::Handler { +public: + virtual void onRequest(const Request &request, ResponseWriter response) = 0; + + virtual void onTimeout(const Request &request, ResponseWriter response); + + void setMaxRequestSize(size_t value); + size_t getMaxRequestSize() const; + void setMaxResponseSize(size_t value); + size_t getMaxResponseSize() const; + + virtual ~Handler() override {} + +private: + void onConnection(const std::shared_ptr &peer) override; + void onDisconnection(const std::shared_ptr &peer) override; + void onInput(const char *buffer, size_t len, + const std::shared_ptr &peer) override; + RequestParser &getParser(const std::shared_ptr &peer) const; + +private: + size_t maxRequestSize_ = Const::DefaultMaxRequestSize; + size_t maxResponseSize_ = Const::DefaultMaxResponseSize; +}; + +template +std::shared_ptr make_handler(Args &&... args) { + static_assert(std::is_base_of::value, + "An http handler must inherit from the Http::Handler class"); + static_assert(details::IsHttpPrototype::value, + "An http handler must be an http prototype, did you forget the " + "HTTP_PROTOTYPE macro ?"); + + return std::make_shared(std::forward(args)...); +} + +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/http_defs.h b/projects/frontend/server-webapi/pistache/include/pistache/http_defs.h new file mode 100755 index 0000000000000000000000000000000000000000..9a86aedf3b7c53545d0c223ee6678f89bf2be8e9 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/http_defs.h @@ -0,0 +1,239 @@ +/* http_defs.h + Mathieu Stefani, 01 September 2015 + + Various http definitions +*/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Pistache { +namespace Http { + +#define HTTP_METHODS \ + METHOD(Options, "OPTIONS") \ + METHOD(Get, "GET") \ + METHOD(Post, "POST") \ + METHOD(Head, "HEAD") \ + METHOD(Put, "PUT") \ + METHOD(Patch, "PATCH") \ + METHOD(Delete, "DELETE") \ + METHOD(Trace, "TRACE") \ + METHOD(Connect, "CONNECT") + +// 10. Status Code Definitions +#define STATUS_CODES \ + CODE(100, Continue, "Continue") \ + CODE(101, Switching_Protocols, "Switching Protocols") \ + CODE(102, Processing, "Processing") \ + CODE(103, Early_Hints, "Early Hints") \ + CODE(200, Ok, "OK") \ + CODE(201, Created, "Created") \ + CODE(202, Accepted, "Accepted") \ + CODE(203, NonAuthoritative_Information, "Non-Authoritative Information") \ + CODE(204, No_Content, "No Content") \ + CODE(205, Reset_Content, "Reset Content") \ + CODE(206, Partial_Content, "Partial Content") \ + CODE(207, MultiStatus, "Multi-Status") \ + CODE(208, Already_Reported, "Already Reported") \ + CODE(226, IM_Used, "IM Used") \ + CODE(300, Multiple_Choices, "Multiple Choices") \ + CODE(301, Moved_Permanently, "Moved Permanently") \ + CODE(302, Found, "Found") \ + CODE(303, See_Other, "See Other") \ + CODE(304, Not_Modified, "Not Modified") \ + CODE(305, Use_Proxy, "Use Proxy") \ + CODE(307, Temporary_Redirect, "Temporary Redirect") \ + CODE(308, Permanent_Redirect, "Permanent Redirect") \ + CODE(400, Bad_Request, "Bad Request") \ + CODE(401, Unauthorized, "Unauthorized") \ + CODE(402, Payment_Required, "Payment Required") \ + CODE(403, Forbidden, "Forbidden") \ + CODE(404, Not_Found, "Not Found") \ + CODE(405, Method_Not_Allowed, "Method Not Allowed") \ + CODE(406, Not_Acceptable, "Not Acceptable") \ + CODE(407, Proxy_Authentication_Required, "Proxy Authentication Required") \ + CODE(408, Request_Timeout, "Request Timeout") \ + CODE(409, Conflict, "Conflict") \ + CODE(410, Gone, "Gone") \ + CODE(411, Length_Required, "Length Required") \ + CODE(412, Precondition_Failed, "Precondition Failed") \ + CODE(413, Request_Entity_Too_Large, "Request Entity Too Large") \ + CODE(414, RequestURI_Too_Long, "Request-URI Too Long") \ + CODE(415, Unsupported_Media_Type, "Unsupported Media Type") \ + CODE(416, Requested_Range_Not_Satisfiable, \ + "Requested Range Not Satisfiable") \ + CODE(417, Expectation_Failed, "Expectation Failed") \ + CODE(418, I_m_a_teapot, "I'm a teapot") \ + CODE(421, Misdirected_Request, "Misdirected Request") \ + CODE(422, Unprocessable_Entity, "Unprocessable Entity") \ + CODE(423, Locked, "Locked") \ + CODE(424, Failed_Dependency, "Failed Dependency") \ + CODE(426, Upgrade_Required, "Upgrade Required") \ + CODE(428, Precondition_Required, "Precondition Required") \ + CODE(429, Too_Many_Requests, "Too Many Requests") \ + CODE(431, Request_Header_Fields_Too_Large, \ + "Request Header Fields Too Large") \ + CODE(444, Connection_Closed_Without_Response, \ + "Connection Closed Without Response") \ + CODE(451, Unavailable_For_Legal_Reasons, "Unavailable For Legal Reasons") \ + CODE(499, Client_Closed_Request, "Client Closed Request") \ + CODE(500, Internal_Server_Error, "Internal Server Error") \ + CODE(501, Not_Implemented, "Not Implemented") \ + CODE(502, Bad_Gateway, "Bad Gateway") \ + CODE(503, Service_Unavailable, "Service Unavailable") \ + CODE(504, Gateway_Timeout, "Gateway Timeout") \ + CODE(505, HTTP_Version_Not_Supported, "HTTP Version Not Supported") \ + CODE(506, Variant_Also_Negotiates, "Variant Also Negotiates") \ + CODE(507, Insufficient_Storage, "Insufficient Storage") \ + CODE(508, Loop_Detected, "Loop Detected") \ + CODE(510, Not_Extended, "Not Extended") \ + CODE(511, Network_Authentication_Required, \ + "Network Authentication Required") \ + CODE(599, Network_Connect_Timeout_Error, "Network Connect Timeout Error") + +// 3.4. Character Sets +// See http://tools.ietf.org/html/rfc2978 and +// http://www.iana.org/assignments/character-sets/character-sets.xhtml +#define CHARSETS \ + CHARSET(UsAscii, "us-ascii") \ + CHARSET(Iso - 8859 - 1, "iso-8859-1") \ + CHARSET(Iso - 8859 - 2, "iso-8859-2") \ + CHARSET(Iso - 8859 - 3, "iso-8859-3") \ + CHARSET(Iso - 8859 - 4, "iso-8859-4") \ + CHARSET(Iso - 8859 - 5, "iso-8859-5") \ + CHARSET(Iso - 8859 - 6, "iso-8859-6") \ + CHARSET(Iso - 8859 - 7, "iso-8859-7") \ + CHARSET(Iso - 8859 - 8, "iso-8859-8") \ + CHARSET(Iso - 8859 - 9, "iso-8859-9") \ + CHARSET(Iso - 8859 - 10, "iso-8859-10") \ + CHARSET(Shift - JIS, "shift_jis") \ + CHARSET(Utf7, "utf-7") \ + CHARSET(Utf8, "utf-8") \ + CHARSET(Utf16, "utf-16") \ + CHARSET(Utf16 - BE, "utf-16be") \ + CHARSET(Utf16 - LE, "utf-16le") \ + CHARSET(Utf32, "utf-32") \ + CHARSET(Utf32 - BE, "utf-32be") \ + CHARSET(Utf32 - LE, "utf-32le") \ + CHARSET(Unicode - 11, "unicode-1-1") + +enum class Method { +#define METHOD(m, _) m, + HTTP_METHODS +#undef METHOD +}; + +enum class Code { +#define CODE(value, name, _) name = value, + STATUS_CODES +#undef CODE +}; + +enum class Version { + Http10, // HTTP/1.0 + Http11 // HTTP/1.1 +}; + +enum class ConnectionControl { Close, KeepAlive, Ext }; + +enum class Expectation { Continue, Ext }; + +class CacheDirective { +public: + enum Directive { + NoCache, + NoStore, + MaxAge, + MaxStale, + MinFresh, + NoTransform, + OnlyIfCached, + Public, + Private, + MustRevalidate, + ProxyRevalidate, + SMaxAge, + Ext + }; + + CacheDirective() : directive_(), data() {} + + explicit CacheDirective(Directive directive); + CacheDirective(Directive directive, std::chrono::seconds delta); + + Directive directive() const { return directive_; } + std::chrono::seconds delta() const; + +private: + void init(Directive directive, std::chrono::seconds delta); + Directive directive_; + // Poor way of representing tagged unions in C++ + union { + uint64_t maxAge; + uint64_t sMaxAge; + uint64_t maxStale; + uint64_t minFresh; + } data; +}; + +// 3.3.1 Full Date +class FullDate { +public: + using time_point = std::chrono::system_clock::time_point; + FullDate() : date_() {} + + enum class Type { RFC1123, RFC850, AscTime }; + + explicit FullDate(time_point date) : date_(date) {} + + time_point date() const { return date_; } + void write(std::ostream &os, Type type = Type::RFC1123) const; + + static FullDate fromString(const std::string &str); + +private: + time_point date_; +}; + +const char *methodString(Method method); +const char *versionString(Version version); +const char *codeString(Code code); + +std::ostream &operator<<(std::ostream &os, Version version); +std::ostream &operator<<(std::ostream &os, Method method); +std::ostream &operator<<(std::ostream &os, Code code); + +struct HttpError : public std::exception { + HttpError(Code code, std::string reason); + HttpError(int code, std::string reason); + + ~HttpError() noexcept {} + + const char *what() const noexcept override { return reason_.c_str(); } + + int code() const { return code_; } + std::string reason() const { return reason_; } + +private: + int code_; + std::string reason_; +}; + +} // namespace Http +} // namespace Pistache + +namespace std { + +template <> struct hash { + size_t operator()(Pistache::Http::Method method) const { + return std::hash()(static_cast(method)); + } +}; + +} // namespace std diff --git a/projects/frontend/server-webapi/pistache/include/pistache/http_header.h b/projects/frontend/server-webapi/pistache/include/pistache/http_header.h new file mode 100755 index 0000000000000000000000000000000000000000..a4cade598de4e1bbf70504792f3aeaab54d1c288 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/http_header.h @@ -0,0 +1,542 @@ +/* http_header.h + Mathieu Stefani, 19 August 2015 + + Declaration of common http headers +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include + +#define SAFE_HEADER_CAST + +namespace Pistache { +namespace Http { +namespace Header { + +#ifdef SAFE_HEADER_CAST +namespace detail { + +// compile-time FNV-1a hashing algorithm +static constexpr uint64_t basis = 14695981039346656037ULL; +static constexpr uint64_t prime = 1099511628211ULL; + +constexpr uint64_t hash_one(char c, const char *remain, + unsigned long long value) { + return c == 0 ? value : hash_one(remain[0], remain + 1, (value ^ c) * prime); +} + +constexpr uint64_t hash(const char *str) { + return hash_one(str[0], str + 1, basis); +} + +} // namespace detail +#endif + +#ifdef SAFE_HEADER_CAST +#define NAME(header_name) \ + static constexpr uint64_t Hash = \ + Pistache::Http::Header::detail::hash(header_name); \ + uint64_t hash() const override { return Hash; } \ + static constexpr const char *Name = header_name; \ + const char *name() const override { return Name; } +#else +#define NAME(header_name) \ + static constexpr const char *Name = header_name; \ + const char *name() const override { return Name; } +#endif + +// 3.5 Content Codings +// 3.6 Transfer Codings +enum class Encoding { Gzip, Compress, Deflate, Identity, Chunked, Unknown }; + +const char *encodingString(Encoding encoding); + +class Header { +public: + virtual ~Header() {} + virtual const char *name() const = 0; + + virtual void parse(const std::string &data); + virtual void parseRaw(const char *str, size_t len); + + virtual void write(std::ostream &stream) const = 0; + +#ifdef SAFE_HEADER_CAST + virtual uint64_t hash() const = 0; +#endif +}; + +template struct IsHeader { + + template static std::true_type test(decltype(T::Name) *); + + template static std::false_type test(...); + + static constexpr bool value = + std::is_base_of::value && + std::is_same(nullptr)), std::true_type>::value; +}; + +#ifdef SAFE_HEADER_CAST +template +typename std::enable_if::value, std::shared_ptr>::type +header_cast(const std::shared_ptr
&from) { + return static_cast(0)->Hash == from->hash() + ? std::static_pointer_cast(from) + : nullptr; +} + +template +typename std::enable_if::value, std::shared_ptr>::type +header_cast(const std::shared_ptr &from) { + return static_cast(0)->Hash == from->hash() + ? std::static_pointer_cast(from) + : nullptr; +} +#endif + +class Allow : public Header { +public: + NAME("Allow"); + + Allow() : methods_() {} + + explicit Allow(const std::vector &methods) + : methods_(methods) {} + explicit Allow(std::initializer_list methods) + : methods_(methods) {} + + explicit Allow(Http::Method method) : methods_() { + methods_.push_back(method); + } + + void parseRaw(const char *str, size_t len) override; + void write(std::ostream &os) const override; + + void addMethod(Http::Method method); + void addMethods(std::initializer_list methods); + void addMethods(const std::vector &methods); + + std::vector methods() const { return methods_; } + +private: + std::vector methods_; +}; + +class Accept : public Header { +public: + NAME("Accept") + + Accept() : mediaRange_() {} + + void parseRaw(const char *str, size_t len) override; + void write(std::ostream &os) const override; + + const std::vector media() const { return mediaRange_; } + +private: + std::vector mediaRange_; +}; + +class AccessControlAllowOrigin : public Header { +public: + NAME("Access-Control-Allow-Origin") + + AccessControlAllowOrigin() : uri_() {} + + explicit AccessControlAllowOrigin(const char *uri) : uri_(uri) {} + explicit AccessControlAllowOrigin(const std::string &uri) : uri_(uri) {} + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + void setUri(std::string uri) { uri_ = std::move(uri); } + + std::string uri() const { return uri_; } + +private: + std::string uri_; +}; + +class AccessControlAllowHeaders : public Header { +public: + NAME("Access-Control-Allow-Headers") + + AccessControlAllowHeaders() : val_() {} + + explicit AccessControlAllowHeaders(const char *val) : val_(val) {} + explicit AccessControlAllowHeaders(const std::string &val) : val_(val) {} + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + void setUri(std::string val) { val_ = std::move(val); } + + std::string val() const { return val_; } + +private: + std::string val_; +}; + +class AccessControlExposeHeaders : public Header { +public: + NAME("Access-Control-Expose-Headers") + + AccessControlExposeHeaders() : val_() {} + + explicit AccessControlExposeHeaders(const char *val) : val_(val) {} + explicit AccessControlExposeHeaders(const std::string &val) : val_(val) {} + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + void setUri(std::string val) { val_ = std::move(val); } + + std::string val() const { return val_; } + +private: + std::string val_; +}; + +class AccessControlAllowMethods : public Header { +public: + NAME("Access-Control-Allow-Methods") + + AccessControlAllowMethods() : val_() {} + + explicit AccessControlAllowMethods(const char *val) : val_(val) {} + explicit AccessControlAllowMethods(const std::string &val) : val_(val) {} + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + void setUri(std::string val) { val_ = std::move(val); } + + std::string val() const { return val_; } + +private: + std::string val_; +}; + +class CacheControl : public Header { +public: + NAME("Cache-Control") + + CacheControl() : directives_() {} + + explicit CacheControl(const std::vector &directives) + : directives_(directives) {} + explicit CacheControl(Http::CacheDirective directive); + explicit CacheControl(Http::CacheDirective::Directive directive) + : CacheControl(Http::CacheDirective(directive)) {} + + void parseRaw(const char *str, size_t len) override; + void write(std::ostream &os) const override; + + std::vector directives() const { return directives_; } + + void addDirective(Http::CacheDirective directive); + void addDirective(Http::CacheDirective::Directive directive) { + addDirective(Http::CacheDirective(directive)); + } + void addDirectives(const std::vector &directives); + +private: + std::vector directives_; +}; + +class Connection : public Header { +public: + NAME("Connection") + + Connection() : control_(ConnectionControl::KeepAlive) {} + + explicit Connection(ConnectionControl control) : control_(control) {} + + void parseRaw(const char *str, size_t len) override; + void write(std::ostream &os) const override; + + ConnectionControl control() const { return control_; } + +private: + ConnectionControl control_; +}; + +class EncodingHeader : public Header { +public: + EncodingHeader() : encoding_() {} + + void parseRaw(const char *str, size_t len) override; + void write(std::ostream &os) const override; + + Encoding encoding() const { return encoding_; } + +protected: + explicit EncodingHeader(Encoding encoding) : encoding_(encoding) {} + +private: + Encoding encoding_; +}; + +class ContentEncoding : public EncodingHeader { +public: + NAME("Content-Encoding") + + ContentEncoding() : EncodingHeader(Encoding::Identity) {} + + explicit ContentEncoding(Encoding encoding) : EncodingHeader(encoding) {} +}; + +class TransferEncoding : public EncodingHeader { +public: + NAME("Transfer-Encoding") + + TransferEncoding() : EncodingHeader(Encoding::Identity) {} + + explicit TransferEncoding(Encoding encoding) : EncodingHeader(encoding) {} +}; + +class ContentLength : public Header { +public: + NAME("Content-Length"); + + ContentLength() : value_(0) {} + + explicit ContentLength(uint64_t val) : value_(val) {} + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + uint64_t value() const { return value_; } + +private: + uint64_t value_; +}; + +class Authorization : public Header { +public: + NAME("Authorization"); + + enum class Method { Basic, Bearer, Unknown }; + + Authorization() : value_("NONE") {} + + explicit Authorization(std::string &&val) : value_(std::move(val)) {} + explicit Authorization(const std::string &val) : value_(val) {} + + // What type of authorization method was used? + Method getMethod() const noexcept; + + // Check if a particular authorization method was used... + template bool hasMethod() const noexcept { return hasMethod(); } + + // Get decoded user ID and password if basic method was used... + std::string getBasicUser() const; + std::string getBasicPassword() const; + + // Set encoded user ID and password for basic method... + void setBasicUserPassword(const std::string &User, + const std::string &Password); + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + std::string value() const { return value_; } + +private: + std::string value_; +}; + +template <> +bool Authorization::hasMethod() const noexcept; + +template <> +bool Authorization::hasMethod() const noexcept; + +class ContentType : public Header { +public: + NAME("Content-Type") + + ContentType() : mime_() {} + + explicit ContentType(const Mime::MediaType &mime) : mime_(mime) {} + explicit ContentType(std::string &&raw_mime_str) + : ContentType(Mime::MediaType(std::move(raw_mime_str))) {} + explicit ContentType(const std::string &raw_mime_str) + : ContentType(Mime::MediaType(raw_mime_str)) {} + + void parseRaw(const char *str, size_t len) override; + void write(std::ostream &os) const override; + + Mime::MediaType mime() const { return mime_; } + void setMime(const Mime::MediaType &mime) { mime_ = mime; } + +private: + Mime::MediaType mime_; +}; + +class Date : public Header { +public: + NAME("Date") + + Date() : fullDate_() {} + + explicit Date(const FullDate &date) : fullDate_(date) {} + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + FullDate fullDate() const { return fullDate_; } + +private: + FullDate fullDate_; +}; + +class Expect : public Header { +public: + NAME("Expect") + + Expect() : expectation_() {} + + explicit Expect(Http::Expectation expectation) : expectation_(expectation) {} + + void parseRaw(const char *str, size_t len) override; + void write(std::ostream &os) const override; + + Http::Expectation expectation() const { return expectation_; } + +private: + Expectation expectation_; +}; + +class Host : public Header { +public: + NAME("Host"); + + Host() : host_(), port_(0) {} + + explicit Host(const std::string &host); + explicit Host(const std::string &host, Port port) + : host_(host), port_(port) {} + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + std::string host() const { return host_; } + Port port() const { return port_; } + +private: + std::string host_; + Port port_; +}; + +class Location : public Header { +public: + NAME("Location") + + Location() : location_() {} + + explicit Location(const std::string &location); + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + std::string location() const { return location_; } + +private: + std::string location_; +}; + +class Server : public Header { +public: + NAME("Server") + + Server() : tokens_() {} + + explicit Server(const std::vector &tokens); + explicit Server(const std::string &token); + explicit Server(const char *token); + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + std::vector tokens() const { return tokens_; } + +private: + std::vector tokens_; +}; + +class UserAgent : public Header { +public: + NAME("User-Agent") + + UserAgent() : ua_() {} + + explicit UserAgent(const char *ua) : ua_(ua) {} + + explicit UserAgent(const std::string &ua) : ua_(ua) {} + + void parse(const std::string &data) override; + void write(std::ostream &os) const override; + + void setAgent(std::string ua) { ua_ = std::move(ua); } + + std::string agent() const { return ua_; } + +private: + std::string ua_; +}; + +#define CUSTOM_HEADER(header_name) \ + class header_name : public Pistache::Http::Header::Header { \ + public: \ + NAME(#header_name) \ + \ + header_name() = default; \ + \ + explicit header_name(const char *value) : value_{value} {} \ + \ + explicit header_name(std::string value) : value_(std::move(value)) {} \ + \ + void parseRaw(const char *str, size_t len) final { value_ = {str, len}; } \ + \ + void write(std::ostream &os) const final { os << value_; }; \ + \ + std::string val() const { return value_; }; \ + \ + private: \ + std::string value_; \ + }; + +class Raw { +public: + Raw(); + Raw(std::string name, std::string value) + : name_(std::move(name)), value_(std::move(value)) {} + + Raw(const Raw &other) = default; + Raw &operator=(const Raw &other) = default; + + Raw(Raw &&other) = default; + Raw &operator=(Raw &&other) = default; + + std::string name() const { return name_; } + std::string value() const { return value_; } + +private: + std::string name_; + std::string value_; +}; + +} // namespace Header +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/http_headers.h b/projects/frontend/server-webapi/pistache/include/pistache/http_headers.h new file mode 100755 index 0000000000000000000000000000000000000000..302fdc6c3d02f3e5926b496f977b0c8ed19d3b95 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/http_headers.h @@ -0,0 +1,167 @@ +/* http_headers.h + Mathieu Stefani, 19 August 2015 + + A list of HTTP headers +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace Pistache { +namespace Http { +namespace Header { + +std::string toLowercase(std::string str); + +struct LowercaseHash { + size_t operator()(const std::string &key) const { + return std::hash{}(toLowercase(key)); + } +}; + +bool LowercaseEqualStatic(const std::string &dynamic, + const std::string &statik); + +struct LowercaseEqual { + bool operator()(const std::string &left, const std::string &right) const { + return std::equal(left.begin(), left.end(), right.begin(), right.end(), + [](const char &a, const char &b) { + return std::tolower(a) == std::tolower(b); + }); + }; +}; + +class Collection { +public: + Collection() : headers(), rawHeaders() {} + + template + typename std::enable_if::value, std::shared_ptr>::type + get() const { + return std::static_pointer_cast(get(H::Name)); + } + template + typename std::enable_if::value, std::shared_ptr>::type get() { + return std::static_pointer_cast(get(H::Name)); + } + + template + typename std::enable_if::value, std::shared_ptr>::type + tryGet() const { + return std::static_pointer_cast(tryGet(H::Name)); + } + template + typename std::enable_if::value, std::shared_ptr>::type + tryGet() { + return std::static_pointer_cast(tryGet(H::Name)); + } + + Collection &add(const std::shared_ptr
&header); + Collection &addRaw(const Raw &raw); + + template + typename std::enable_if::value, Collection &>::type + add(Args &&... args) { + return add(std::make_shared(std::forward(args)...)); + } + + template + typename std::enable_if::value, bool>::type remove() { + return remove(H::Name); + } + + std::shared_ptr get(const std::string &name) const; + std::shared_ptr
get(const std::string &name); + Raw getRaw(const std::string &name) const; + + std::shared_ptr tryGet(const std::string &name) const; + std::shared_ptr
tryGet(const std::string &name); + Optional tryGetRaw(const std::string &name) const; + + template + typename std::enable_if::value, bool>::type has() const { + return has(H::Name); + } + bool has(const std::string &name) const; + + std::vector> list() const; + + const std::unordered_map & + rawList() const { + return rawHeaders; + } + + bool remove(const std::string &name); + + void clear(); + +private: + std::pair> + getImpl(const std::string &name) const; + + std::unordered_map, LowercaseHash, + LowercaseEqual> + headers; + std::unordered_map + rawHeaders; +}; + +class Registry { + +public: + Registry(const Registry &) = delete; + Registry &operator=(const Registry &) = delete; + static Registry &instance(); + + template ::value)> void registerHeader() { + registerHeader(H::Name, []() -> std::unique_ptr
{ + return std::unique_ptr
(new H()); + }); + } + + std::vector headersList(); + + std::unique_ptr
makeHeader(const std::string &name); + bool isRegistered(const std::string &name); + +private: + Registry(); + ~Registry(); + + using RegistryFunc = std::function()>; + using RegistryStorageType = std::unordered_map; + + void registerHeader(const std::string &name, RegistryFunc func); + + RegistryStorageType registry; +}; + +template struct Registrar { + static_assert(IsHeader::value, "Registrar only works with header types"); + + Registrar() { Registry::instance().registerHeader(); } +}; + +/* Crazy macro machinery to generate a unique variable name + * Don't touch it ! + */ +#define CAT(a, b) CAT_I(a, b) +#define CAT_I(a, b) a##b + +#define UNIQUE_NAME(base) CAT(base, __LINE__) + +#define RegisterHeader(Header) \ + Registrar
UNIQUE_NAME(CAT(CAT_I(__, Header), __)) + +} // namespace Header +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/iterator_adapter.h b/projects/frontend/server-webapi/pistache/include/pistache/iterator_adapter.h new file mode 100755 index 0000000000000000000000000000000000000000..d8f7c86c2ea0fdb649f6a6e0f83941e29c086ef9 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/iterator_adapter.h @@ -0,0 +1,39 @@ +/* + Mathieu Stefani, 28 février 2016 + + A collection of sample iterator adapters +*/ + +#pragma once + +namespace Pistache { + +template struct FlatMapIteratorAdapter { + typedef typename Map::key_type Key; + typedef typename Map::mapped_type Value; + typedef typename Map::const_iterator const_iterator; + + explicit FlatMapIteratorAdapter(const_iterator _it) : it(_it) {} + + FlatMapIteratorAdapter &operator++() { + ++it; + return *this; + } + + const Value &operator*() { return it->second; } + + bool operator==(FlatMapIteratorAdapter other) { return other.it == it; } + + bool operator!=(FlatMapIteratorAdapter other) { return !(*this == other); } + +private: + const_iterator it; +}; + +template +FlatMapIteratorAdapter +makeFlatMapIterator(const Map &, typename Map::const_iterator it) { + return FlatMapIteratorAdapter(it); +} + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/listener.h b/projects/frontend/server-webapi/pistache/include/pistache/listener.h new file mode 100755 index 0000000000000000000000000000000000000000..3a2f72dad85231fe0faedbe0ac3629f6930e6582 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/listener.h @@ -0,0 +1,106 @@ +/* listener.h + Mathieu Stefani, 12 August 2015 + + A TCP Listener +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#ifdef PISTACHE_USE_SSL +#include +#endif /* PISTACHE_USE_SSL */ + +namespace Pistache { +namespace Tcp { + +class Peer; +class Transport; + +void setSocketOptions(Fd fd, Flags options); + +class Listener { +public: + struct Load { + using TimePoint = std::chrono::system_clock::time_point; + double global; + std::vector workers; + + std::vector raw; + TimePoint tick; + }; + + Listener(); + ~Listener(); + + explicit Listener(const Address &address); + void init(size_t workers, + Flags options = Flags(Options::None), + const std::string &workersName = "", + int backlog = Const::MaxBacklog); + void setHandler(const std::shared_ptr &handler); + + void bind(); + void bind(const Address &address); + + bool isBound() const; + Port getPort() const; + + void run(); + void runThreaded(); + + void shutdown(); + + Async::Promise requestLoad(const Load &old); + + Options options() const; + Address address() const; + + void pinWorker(size_t worker, const CpuSet &set); + + void setupSSL(const std::string &cert_path, const std::string &key_path, + bool use_compression); + void setupSSLAuth(const std::string &ca_file, const std::string &ca_path, + int (*cb)(int, void *)); + +private: + Address addr_; + int listen_fd; + int backlog_; + NotifyFd shutdownFd; + Polling::Epoll poller; + + Flags options_; + std::thread acceptThread; + + size_t workers_; + std::string workersName_; + std::shared_ptr handler_; + + Aio::Reactor reactor_; + Aio::Reactor::Key transportKey; + + void handleNewConnection(); + int acceptConnection(struct sockaddr_in &peer_addr) const; + void dispatchPeer(const std::shared_ptr &peer); + + bool useSSL_ = false; + ssl::SSLCtxPtr ssl_ctx_ = nullptr; +}; + +} // namespace Tcp +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/mailbox.h b/projects/frontend/server-webapi/pistache/include/pistache/mailbox.h new file mode 100755 index 0000000000000000000000000000000000000000..af6e796cf4d227b855b9ea01d39b53241352407d --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/mailbox.h @@ -0,0 +1,403 @@ +/* mailbox.h + Mathieu Stefani, 12 August 2015 + Copyright (c) 2014 Datacratic. All rights reserved. + + A simple lock-free Mailbox implementation +*/ + +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include + +namespace Pistache { + +static constexpr size_t CachelineSize = 64; +typedef char cacheline_pad_t[CachelineSize]; + +template class Mailbox { +public: + Mailbox() { data.store(nullptr); } + + virtual ~Mailbox() {} + + const T *get() const { + if (isEmpty()) { + throw std::runtime_error("Can not retrieve mail from empty mailbox"); + } + + return data.load(); + } + + virtual T *post(T *newData) { + T *old = data.load(); + while (!data.compare_exchange_weak(old, newData)) { + } + + return old; + } + + virtual T *clear() { return data.exchange(nullptr); } + + bool isEmpty() const { return data == nullptr; } + +private: + std::atomic data; +}; + +template class PollableMailbox : public Mailbox { +public: + PollableMailbox() : event_fd(-1) {} + + ~PollableMailbox() { + if (event_fd != -1) + close(event_fd); + } + + bool isBound() const { return event_fd != -1; } + + Polling::Tag bind(Polling::Epoll &poller) { + using namespace Polling; + + if (isBound()) { + throw std::runtime_error("The mailbox has already been bound"); + } + + event_fd = TRY_RET(eventfd(0, EFD_NONBLOCK)); + Tag tag_(event_fd); + poller.addFd(event_fd, Flags(NotifyOn::Read), tag_); + + return tag_; + } + + T *post(T *newData) { + auto *_ret = Mailbox::post(newData); + + if (isBound()) { + uint64_t val = 1; + TRY(write(event_fd, &val, sizeof val)); + } + + return _ret; + } + + T *clear() { + auto ret = Mailbox::clear(); + + if (isBound()) { + uint64_t val; + for (;;) { + ssize_t bytes = read(event_fd, &val, sizeof val); + if (bytes == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + else { + // TODO + } + } + } + } + + return ret; + } + + Polling::Tag tag() const { + if (!isBound()) + throw std::runtime_error("Can not retrieve tag of an unbound mailbox"); + + return Polling::Tag(event_fd); + } + + void unbind(Polling::Epoll &poller) { + if (event_fd == -1) { + throw std::runtime_error("The mailbox is not bound"); + } + + poller.removeFd(event_fd); + close(event_fd), event_fd = -1; + } + +private: + int event_fd; +}; + +/* + * An unbounded MPSC lock-free queue. Usefull for efficient cross-thread message + passing. + + * push() and pop() are wait-free. + * Might replace the Mailbox implementation below + + * Design comes from + http://www.1024cores.net/home/lock-free-algorithms/queues/non-intrusive-mpsc-node-based-queue +*/ +template class Queue { +public: + struct Entry { + friend class Queue; + + Entry() : storage(), next(nullptr) {} + + template explicit Entry(U &&u) : storage(), next(nullptr) { + new (&storage) T(std::forward(u)); + } + + const T &data() const { return *reinterpret_cast(&storage); } + + T &data() { return *reinterpret_cast(&storage); } + + private: + typedef typename std::aligned_storage::type Storage; + Storage storage; + std::atomic next; + }; + + Queue() : head(), tail(nullptr) { + auto *sentinel = new Entry; + sentinel->next = nullptr; + head.store(sentinel, std::memory_order_relaxed); + tail = sentinel; + } + + virtual ~Queue() { + while (!empty()) { + Entry *e = pop(); + e->data().~T(); + delete e; + } + delete tail; + } + + template void push(U &&u) { + Entry *entry = new Entry(std::forward(u)); + // @Note: we're using SC atomics here (exchange will issue a full fence), + // but I don't think we should bother relaxing them for now + auto *prev = head.exchange(entry); + prev->next = entry; + } + + virtual Entry *pop() { + auto *res = tail; + auto *next = res->next.load(std::memory_order_acquire); + if (next) { + // Since it's Single-Consumer, the store does not need to be atomic + tail = next; + new (&res->storage) T(std::move(next->data())); + return res; + } + return nullptr; + } + + bool empty() { return head == tail; } + + std::unique_ptr popSafe() { + std::unique_ptr object; + + std::unique_ptr entry(pop()); + if (entry) { + object.reset(new T(std::move(entry->data()))); + entry->data().~T(); + } + + return object; + } + +private: + std::atomic head; + Entry *tail; +}; + +template class PollableQueue : public Queue { +public: + typedef typename Queue::Entry Entry; + + PollableQueue() : event_fd(-1) {} + + ~PollableQueue() { + if (event_fd != -1) + close(event_fd); + } + + bool isBound() const { return event_fd != -1; } + + Polling::Tag bind(Polling::Epoll &poller) { + using namespace Polling; + + if (isBound()) { + throw std::runtime_error("The queue has already been bound"); + } + + event_fd = TRY_RET(eventfd(0, EFD_NONBLOCK)); + Tag tag_(event_fd); + poller.addFd(event_fd, Flags(NotifyOn::Read), tag_); + + return tag_; + } + + template void push(U &&u) { + Queue::push(std::forward(u)); + + if (isBound()) { + uint64_t val = 1; + TRY(write(event_fd, &val, sizeof val)); + } + } + + Entry *pop() override { + auto ret = Queue::pop(); + + if (isBound()) { + uint64_t val; + for (;;) { + ssize_t bytes = read(event_fd, &val, sizeof val); + if (bytes == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + break; + else { + // TODO + } + } + } + } + + return ret; + } + + Polling::Tag tag() const { + if (!isBound()) + throw std::runtime_error("Can not retrieve tag of an unbound mailbox"); + + return Polling::Tag(event_fd); + } + + void unbind(Polling::Epoll &poller) { + if (event_fd == -1) { + throw std::runtime_error("The mailbox is not bound"); + } + + poller.removeFd(event_fd); + close(event_fd), event_fd = -1; + } + +private: + int event_fd; +}; + +// A Multi-Producer Multi-Consumer bounded queue +// taken from +// http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue +template class MPMCQueue { + + static_assert(Size >= 2 && ((Size & (Size - 1)) == 0), + "The size must be a power of 2"); + static constexpr size_t Mask = Size - 1; + +public: + MPMCQueue(const MPMCQueue &other) = delete; + MPMCQueue &operator=(const MPMCQueue &other) = delete; + + /* + * Note that you should not move a queue. This is somehow needed for gcc 4.7, + * otherwise the client won't compile + * @Investigate why + */ + MPMCQueue(MPMCQueue &&other) { *this = std::move(other); } + + MPMCQueue &operator=(MPMCQueue &&other) { + for (size_t i = 0; i < Size; ++i) { + cells_[i].sequence.store( + other.cells_[i].sequence.load(std::memory_order_relaxed), + std::memory_order_relaxed); + cells_[i].data = std::move(other.cells_[i].data); + } + + enqueueIndex.store(other.enqueueIndex.load(), std::memory_order_relaxed); + dequeueIndex.store(other.enqueueIndex.load(), std::memory_order_relaxed); + return *this; + } + + MPMCQueue() : cells_(), enqueueIndex(), dequeueIndex() { + for (size_t i = 0; i < Size; ++i) { + cells_[i].sequence.store(i, std::memory_order_relaxed); + } + + enqueueIndex.store(0, std::memory_order_relaxed); + dequeueIndex.store(0, std::memory_order_relaxed); + } + + template bool enqueue(U &&data) { + Cell *target; + size_t index = enqueueIndex.load(std::memory_order_relaxed); + for (;;) { + target = cell(index); + size_t seq = target->sequence.load(std::memory_order_acquire); + auto diff = + static_cast(seq) - static_cast(index); + if (diff == 0) { + if (enqueueIndex.compare_exchange_weak(index, index + 1, + std::memory_order_relaxed)) + break; + } + + else if (diff < 0) + return false; + else { + index = enqueueIndex.load(std::memory_order_relaxed); + } + } + target->data = std::forward(data); + target->sequence.store(index + 1, std::memory_order_release); + return true; + } + + bool dequeue(T &data) { + Cell *target; + size_t index = dequeueIndex.load(std::memory_order_relaxed); + for (;;) { + target = cell(index); + size_t seq = target->sequence.load(std::memory_order_acquire); + auto diff = static_cast(seq) - + static_cast(index + 1); + if (diff == 0) { + if (dequeueIndex.compare_exchange_weak(index, index + 1, + std::memory_order_relaxed)) + break; + } else if (diff < 0) + return false; + else { + index = dequeueIndex.load(std::memory_order_relaxed); + } + } + data = target->data; + target->sequence.store(index + Mask + 1, std::memory_order_release); + return true; + } + +private: + struct Cell { + Cell() : sequence(), data() {} + std::atomic sequence; + T data; + }; + + size_t cellIndex(size_t index) const { return index & Mask; } + + Cell *cell(size_t index) { return &cells_[cellIndex(index)]; } + + std::array cells_; + + cacheline_pad_t pad0; + std::atomic enqueueIndex; + + cacheline_pad_t pad1; + std::atomic dequeueIndex; +}; + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/mime.h b/projects/frontend/server-webapi/pistache/include/pistache/mime.h new file mode 100755 index 0000000000000000000000000000000000000000..2daaa4f01c02be2bb9d6e7d0ae1229b9f228d910 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/mime.h @@ -0,0 +1,230 @@ +/* mime.h + Mathieu Stefani, 29 August 2015 + + Type safe representation of a MIME Type (RFC 1590) +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace Pistache { +namespace Http { +namespace Mime { + +#define MIME_TYPES \ + TYPE(Star, "*") \ + TYPE(Text, "text") \ + TYPE(Font, "font") \ + TYPE(Image, "image") \ + TYPE(Audio, "audio") \ + TYPE(Video, "video") \ + TYPE(Application, "application") \ + TYPE(Message, "message") \ + TYPE(Multipart, "multipart") + +#define MIME_SUBTYPES \ + SUB_TYPE(Star, "*") \ + SUB_TYPE(Plain, "plain") \ + SUB_TYPE(Html, "html") \ + SUB_TYPE(Xhtml, "xhtml") \ + SUB_TYPE(Xml, "xml") \ + SUB_TYPE(Javascript, "javascript") \ + SUB_TYPE(Css, "css") \ + \ + SUB_TYPE(OctetStream, "octet-stream") \ + SUB_TYPE(Json, "json") \ + SUB_TYPE(JsonSchema, "schema+json") \ + SUB_TYPE(JsonSchemaInstance, "schema-instance+json") \ + SUB_TYPE(FormUrlEncoded, "x-www-form-urlencoded") \ + SUB_TYPE(FormData, "form-data") \ + \ + SUB_TYPE(Ttf, "ttf") \ + SUB_TYPE(Woff, "woff") \ + SUB_TYPE(Woff2, "woff2") \ + \ + SUB_TYPE(Png, "png") \ + SUB_TYPE(Gif, "gif") \ + SUB_TYPE(Bmp, "bmp") \ + SUB_TYPE(Jpeg, "jpeg") \ + SUB_TYPE(XIcon, "x-icon") \ + SUB_TYPE(Svg, "svg+xml") \ + \ + SUB_TYPE(Mkv, "x-matroska") \ + SUB_TYPE(Mp4, "mp4") \ + SUB_TYPE(Avi, "x-msvideo") \ + \ + SUB_TYPE(Pdf, "pdf") \ + +#define MIME_SUFFIXES \ + SUFFIX(Json, "json", "JavaScript Object Notation") \ + SUFFIX(Ber, "ber", "Basic Encoding Rules") \ + SUFFIX(Der, "der", "Distinguished Encoding Rules") \ + SUFFIX(Fastinfoset, "fastinfoset", "Fast Infoset") \ + SUFFIX(Wbxml, "wbxml", "WAP Binary XML") \ + SUFFIX(Zip, "zip", "ZIP file storage") \ + SUFFIX(Xml, "xml", "Extensible Markup Language") + +enum class Type { +#define TYPE(val, _) val, + MIME_TYPES +#undef TYPE + None +}; + +enum class Subtype { +#define SUB_TYPE(val, _) val, + MIME_SUBTYPES +#undef SUB_TYPE + Vendor, + Ext, + None +}; + +enum class Suffix { +#define SUFFIX(val, _, __) val, + MIME_SUFFIXES +#undef SUFFIX + None, + Ext +}; + +// 3.9 Quality Values +class Q { +public: + // typedef uint8_t Type; + + typedef uint16_t Type; + + explicit Q(Type val) : val_() { + if (val > 100) { + throw std::runtime_error( + "Invalid quality value, must be in the [0; 100] range"); + } + + val_ = val; + } + + static Q fromFloat(double f) { + return Q(static_cast(round(f * 100.0))); + } + + Type value() const { return val_; } + operator Type() const { return val_; } + + std::string toString() const; + +private: + Type val_; +}; + +inline bool operator==(Q lhs, Q rhs) { return lhs.value() == rhs.value(); } + +// 3.7 Media Types +class MediaType { +public: + enum Parse { DoParse, DontParse }; + + MediaType() + : top_(Type::None), sub_(Subtype::None), suffix_(Suffix::None), raw_(), + rawSubIndex(), rawSuffixIndex(), params(), q_() {} + + explicit MediaType(std::string raw, Parse parse = DontParse) + : top_(Type::None), sub_(Subtype::None), suffix_(Suffix::None), raw_(), + rawSubIndex(), rawSuffixIndex(), params(), q_() { + if (parse == DoParse) { + parseRaw(raw.c_str(), raw.length()); + } else { + raw_ = std::move(raw); + } + } + + MediaType(Mime::Type top, Mime::Subtype sub) + : top_(top), sub_(sub), suffix_(Suffix::None), raw_(), rawSubIndex(), + rawSuffixIndex(), params(), q_() {} + + MediaType(Mime::Type top, Mime::Subtype sub, Mime::Suffix suffix) + : top_(top), sub_(sub), suffix_(suffix), raw_(), rawSubIndex(), + rawSuffixIndex(), params(), q_() {} + + void parseRaw(const char *str, size_t len); + static MediaType fromRaw(const char *str, size_t len); + + static MediaType fromString(const std::string &str); + static MediaType fromString(std::string &&str); + + static MediaType fromFile(const char *fileName); + + Mime::Type top() const { return top_; } + Mime::Subtype sub() const { return sub_; } + Mime::Suffix suffix() const { return suffix_; } + + std::string rawSub() const { return rawSubIndex.splice(raw_); } + + std::string raw() const { return raw_; } + + const Optional &q() const { return q_; } + void setQuality(Q quality); + + Optional getParam(const std::string &name) const; + void setParam(const std::string &name, std::string value); + + std::string toString() const; + bool isValid() const; + +private: + Mime::Type top_; + Mime::Subtype sub_; + Mime::Suffix suffix_; + + /* Let's save some extra memory allocations by only storing the + raw MediaType along with indexes of the relevant parts + Note: experimental for now as it might not be a good idea + */ + std::string raw_; + + struct Index { + size_t beg; + size_t end; + + std::string splice(const std::string &str) const { + assert(end >= beg); + return str.substr(beg, end - beg + 1); + } + }; + + Index rawSubIndex; + Index rawSuffixIndex; + + std::unordered_map params; + + Optional q_; +}; + +inline bool operator==(const MediaType &lhs, const MediaType &rhs) { + return lhs.top() == rhs.top() && lhs.sub() == rhs.sub() && + lhs.suffix() == rhs.suffix(); +} + +inline bool operator!=(const MediaType &lhs, const MediaType &rhs) { + return !operator==(lhs, rhs); +} + +} // namespace Mime +} // namespace Http +} // namespace Pistache + +#define MIME(top, sub) \ + Pistache::Http::Mime::MediaType(Pistache::Http::Mime::Type::top, \ + Pistache::Http::Mime::Subtype::sub) + +#define MIME3(top, sub, suffix) \ + Pistache::Http::Mime::MediaType(Pistache::Http::Mime::Type::top, \ + Pistache::Http::Mime::Subtype::sub, \ + Pistache::Http::Mime::Suffix::suffix) diff --git a/projects/frontend/server-webapi/pistache/include/pistache/net.h b/projects/frontend/server-webapi/pistache/include/pistache/net.h new file mode 100755 index 0000000000000000000000000000000000000000..e110c45cfba871c024bd4118eb24deff52ed97cf --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/net.h @@ -0,0 +1,220 @@ +/* net.h + Mathieu Stefani, 12 August 2015 + + Network utility classes +*/ + +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#ifndef _KERNEL_FASTOPEN +#define _KERNEL_FASTOPEN + +/* conditional define for TCP_FASTOPEN */ +#ifndef TCP_FASTOPEN +#define TCP_FASTOPEN 23 +#endif +#endif + +namespace Pistache { + +// Wrapper around 'getaddrinfo()' that handles cleanup on destruction. +class AddrInfo { +public: + // Disable copy and assign. + AddrInfo(const AddrInfo &) = delete; + AddrInfo &operator=(const AddrInfo &) = delete; + + // Default construction: do nothing. + AddrInfo() : addrs(nullptr) {} + + ~AddrInfo() { + if (addrs) { + ::freeaddrinfo(addrs); + } + } + + // Call "::getaddrinfo()", but stash result locally. Takes the same args + // as the first 3 args to "::getaddrinfo()" and returns the same result. + int invoke(const char *node, const char *service, + const struct addrinfo *hints) { + if (addrs) { + ::freeaddrinfo(addrs); + addrs = nullptr; + } + + return ::getaddrinfo(node, service, hints, &addrs); + } + + const struct addrinfo *get_info_ptr() const { return addrs; } + +private: + struct addrinfo *addrs; +}; + +class Port { +public: + Port(uint16_t port = 0); + explicit Port(const std::string &data); + + operator uint16_t() const { return port; } + + bool isReserved() const; + bool isUsed() const; + std::string toString() const; + + static constexpr uint16_t min() { + return std::numeric_limits::min(); + } + static constexpr uint16_t max() { + return std::numeric_limits::max(); + } + +private: + uint16_t port; +}; + +class IP { +private: + int port; + int family; + union { + struct sockaddr_in addr; + struct sockaddr_in6 addr6; + }; + +public: + IP(); + IP(uint8_t a, uint8_t b, uint8_t c, uint8_t d); + IP(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e, uint16_t f, + uint16_t g, uint16_t h); + explicit IP(struct sockaddr *); + static IP any(); + static IP loopback(); + static IP any(bool ipv6); + static IP loopback(bool ipv6); + int getFamily() const; + int getPort() const; + std::string toString() const; + void toNetwork(in_addr_t *) const; + void toNetwork(struct in6_addr *) const; + // Returns 'true' if the system has IPV6 support, false if not. + static bool supported(); +}; +using Ipv4 = IP; +using Ipv6 = IP; + +class AddressParser { +public: + explicit AddressParser(const std::string &data); + const std::string &rawHost() const; + const std::string &rawPort() const; + bool hasColon() const; + int family() const; + +private: + std::string host_; + std::string port_; + bool hasColon_ = false; + int family_ = 0; +}; + +class Address { +public: + Address(); + Address(std::string host, Port port); + explicit Address(std::string addr); + explicit Address(const char *addr); + Address(IP ip, Port port); + + Address(const Address &other) = default; + Address(Address &&other) = default; + + Address &operator=(const Address &other) = default; + Address &operator=(Address &&other) = default; + + static Address fromUnix(struct sockaddr *addr); + static Address fromUnix(struct sockaddr_in *addr); + + std::string host() const; + Port port() const; + int family() const; + +private: + void init(const std::string &addr); + IP ip_; + Port port_; +}; + +namespace helpers { +inline Address httpAddr(const StringView &view) { + auto const str = view.toString(); + return Address(str); +} +} // namespace helpers + +class Error : public std::runtime_error { +public: + explicit Error(const char *message); + explicit Error(std::string message); + static Error system(const char *message); +}; + +template struct Size {}; + +template size_t digitsCount(T val) { + size_t digits = 0; + while (val % 10) { + ++digits; + + val /= 10; + } + + return digits; +} + +template <> struct Size { + size_t operator()(const char *s) const { return std::strlen(s); } +}; + +template struct Size { + constexpr size_t operator()(const char (&)[N]) const { + + // We omit the \0 + return N - 1; + } +}; + +#define DEFINE_INTEGRAL_SIZE(Int) \ + template <> struct Size { \ + size_t operator()(Int val) const { return digitsCount(val); } \ + } + +DEFINE_INTEGRAL_SIZE(uint8_t); +DEFINE_INTEGRAL_SIZE(int8_t); +DEFINE_INTEGRAL_SIZE(uint16_t); +DEFINE_INTEGRAL_SIZE(int16_t); +DEFINE_INTEGRAL_SIZE(uint32_t); +DEFINE_INTEGRAL_SIZE(int32_t); +DEFINE_INTEGRAL_SIZE(uint64_t); +DEFINE_INTEGRAL_SIZE(int64_t); + +template <> struct Size { + constexpr size_t operator()(bool) const { return 1; } +}; + +template <> struct Size { + constexpr size_t operator()(char) const { return 1; } +}; + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/optional.h b/projects/frontend/server-webapi/pistache/include/pistache/optional.h new file mode 100755 index 0000000000000000000000000000000000000000..1a67091b5b229c1b043305986a4345f9387cb8c5 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/optional.h @@ -0,0 +1,374 @@ +/* optional.h + Mathieu Stefani, 27 August 2015 + + An algebraic data type that can either represent Some Value or None. + This type is the equivalent of the Haskell's Maybe type +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Pistache { + +template class Optional; + +namespace types { +template class Some { +public: + template friend class Pistache::Optional; + + explicit Some(const T &val) : val_(val) {} + explicit Some(T &&val) : val_(std::move(val)) {} + +private: + T val_; +}; + +class None {}; + +namespace impl { +template +struct callable_trait : public callable_trait {}; + +template +struct callable_trait { + static constexpr size_t Arity = sizeof...(Args); + + typedef std::tuple ArgsType; + typedef Ret ReturnType; + + template struct Arg { + static_assert(Index < Arity, "Invalid index"); + typedef typename std::tuple_element::type Type; + }; +}; +} // namespace impl + +template ::value> +struct callable_trait; + +/* std::bind returns an unspecified type which contains several overloads + * of operator(). Thus, decltype(&operator()) does not compile since operator() + * is overloaded. To bypass that, we only define the ReturnType for bind + * expressions + */ +template struct callable_trait { + typedef typename Func::result_type ReturnType; +}; + +template +struct callable_trait : public impl::callable_trait {}; + +template +struct is_nothrow_move_constructible + : std::is_nothrow_constructible< + T, typename std::add_rvalue_reference::type> {}; + +template +struct is_move_constructible + : std::is_constructible::type> {}; + +// Workaround for C++14 defect (CWG 1558) +template struct make_void { typedef void type; }; +template using void_t = typename make_void::type; + +// cppcheck 1.88 (and earlier?) are unable to handle the following template. +// cppcheck reports: +// (error) Syntax Error: AST broken, binary operator '>' doesn't have two +// operands. +// In order to run cppcheck on the rest of this file, we need to cause cppcheck +// to skip the next few lines of code (the code is valid and compiles just +// fine). +#ifndef CPPCHECK +template > +struct has_equalto_operator : std::false_type {}; +#endif + +template +struct has_equalto_operator< + T, void_t() == std::declval())>> + : std::true_type {}; +} // namespace types + +inline types::None None() { return types::None(); } + +template ::type> +inline types::Some Some(T &&value) { + return types::Some(std::forward(value)); +} + +template class Optional { +public: + Optional() { none_flag = NoneMarker; } + + // TODO: SFINAE-out if T is not trivially_copyable + Optional(const Optional &other) { + if (!other.isEmpty()) { + ::new (data()) T(*other.data()); + none_flag = ValueMarker; + } else { + none_flag = NoneMarker; + } + } + + Optional(Optional &&other) noexcept( + types::is_nothrow_move_constructible::value) { + *this = std::move(other); + } + + template + Optional(types::Some some) : none_flag(NoneMarker) { + static_assert(std::is_same::value || std::is_convertible::value, + "Types mismatch"); + from_some_helper(std::move(some), types::is_move_constructible()); + } + Optional(types::None) { none_flag = NoneMarker; } + + template Optional &operator=(types::Some some) { + static_assert(std::is_same::value || std::is_convertible::value, + "Types mismatch"); + if (none_flag != NoneMarker) { + data()->~T(); + } + from_some_helper(std::move(some), types::is_move_constructible()); + return *this; + } + + Optional &operator=(types::None) { + if (none_flag != NoneMarker) { + data()->~T(); + } + none_flag = NoneMarker; + return *this; + } + + // TODO: SFINAE-out if T is not trivially_copyable + Optional &operator=(const Optional &other) { + if (!other.isEmpty()) { + if (none_flag != NoneMarker) { + data()->~T(); + } + ::new (data()) T(*other.data()); + none_flag = ValueMarker; + } else { + if (none_flag != NoneMarker) { + data()->~T(); + } + none_flag = NoneMarker; + } + + return *this; + } + + Optional &operator=(Optional &&other) noexcept( + types::is_nothrow_move_constructible::value) { + if (!other.isEmpty()) { + move_helper(std::move(other), types::is_move_constructible()); + other.none_flag = NoneMarker; + } else { + none_flag = NoneMarker; + } + + return *this; + } + + bool isEmpty() const { return none_flag == NoneMarker; } + + T getOrElse(const T &defaultValue) { + if (none_flag != NoneMarker) { + return *constData(); + } + + return defaultValue; + } + + const T &getOrElse(const T &defaultValue) const { + if (none_flag != NoneMarker) { + return *constData(); + } + + return defaultValue; + } + + template void orElse(Func func) const { + if (isEmpty()) { + func(); + } + } + + T get() { return *constData(); } + + const T &get() const { return *constData(); } + + T &unsafeGet() const { return *data(); } + + ~Optional() { + if (!isEmpty()) { + data()->~T(); + } + } + + bool operator==(const Optional &other) const { + static_assert( + types::has_equalto_operator::value, + "optional requires T to be comparable by equal to operator"); + return (isEmpty() && other.isEmpty()) || + (!isEmpty() && !other.isEmpty() && get() == other.get()); + } + + bool operator!=(const Optional &other) const { + static_assert( + types::has_equalto_operator::value, + "optional requires T to be comparable by equal to operator"); + return !(*this == other); + } + +private: + T *constData() const { + return const_cast(reinterpret_cast(bytes.data())); + } + + T *data() const { + return const_cast(reinterpret_cast(bytes.data())); + } + + void move_helper(Optional &&other, std::true_type) { + ::new (data()) T(std::move(*other.data())); + none_flag = ValueMarker; + } + + void move_helper(Optional &&other, std::false_type) { + ::new (data()) T(*other.data()); + none_flag = ValueMarker; + } + + template + void from_some_helper(types::Some some, std::true_type) { + ::new (data()) T(std::move(some.val_)); + none_flag = ValueMarker; + } + + template + void from_some_helper(types::Some some, std::false_type) { + ::new (data()) T(some.val_); + none_flag = ValueMarker; + } + + typedef uint8_t none_flag_t; + static constexpr none_flag_t NoneMarker = 1; + static constexpr none_flag_t ValueMarker = 0; + + alignas(T) std::array bytes; + none_flag_t none_flag; +}; + +#define PistacheCheckSize(Type) \ + static_assert(sizeof(Optional) == sizeof(Type) + alignof(Type), \ + "Size differs") + +PistacheCheckSize(uint8_t); +PistacheCheckSize(uint16_t); +PistacheCheckSize(int); +PistacheCheckSize(void *); +PistacheCheckSize(std::string); + +namespace details { +template struct RemoveOptional { typedef T Type; }; + +template struct RemoveOptional> { typedef T Type; }; + +template void do_static_checks(std::false_type) { + static_assert(types::callable_trait::Arity == 1, + "The function must take exactly 1 argument"); + + typedef typename types::callable_trait::template Arg<0>::Type ArgType; + typedef typename std::remove_cv< + typename std::remove_reference::type>::type CleanArgType; + + static_assert(std::is_same::value || + std::is_convertible::value, + "Function parameter type mismatch"); +} + +template void do_static_checks(std::true_type) {} + +template void static_checks() { + do_static_checks(std::is_bind_expression()); +} + +template +struct IsArgMovable : public IsArgMovable {}; + +template +struct IsArgMovable + : public std::is_rvalue_reference {}; + +template +typename std::conditional::value, Arg &&, const Arg &>::type +tryMove(Arg &arg) { + return std::move(arg); +} +} // namespace details + +template +const Optional &optionally_do(const Optional &option, Func func) { + details::static_checks(); + static_assert(std::is_same::ReturnType, + void>::value, + "Use optionally_map if you want to return a value"); + if (!option.isEmpty()) { + func(details::tryMove(option.unsafeGet())); + } + + return option; +} + +template +auto optionally_map(const Optional &option, Func func) + -> Optional::ReturnType> { + details::static_checks(); + if (!option.isEmpty()) { + return Some(func(details::tryMove(option.unsafeGet()))); + } + + return None(); +} + +template +auto optionally_fmap(const Optional &option, Func func) + -> Optional::ReturnType>::Type> { + details::static_checks(); + if (!option.isEmpty()) { + const auto &ret = func(details::tryMove(option.unsafeGet())); + if (!ret.isEmpty()) { + return Some(ret.get()); + } + } + + return None(); +} + +template +Optional optionally_filter(const Optional &option, Func func) { + details::static_checks(); + typedef typename types::callable_trait::ReturnType ReturnType; + static_assert(std::is_same::value || + std::is_convertible::value, + "The predicate must return a boolean value"); + if (!option.isEmpty() && func(option.get())) { + return Some(option.get()); + } + + return None(); +} + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/os.h b/projects/frontend/server-webapi/pistache/include/pistache/os.h new file mode 100755 index 0000000000000000000000000000000000000000..7b35f9a2a050f3c2ca9a32a75544a13850e92734 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/os.h @@ -0,0 +1,136 @@ +/* os.h + Mathieu Stefani, 13 August 2015 + + Operating system specific functions +*/ + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace Pistache { + +using Fd = int; + +uint hardware_concurrency(); +bool make_non_blocking(int fd); + +class CpuSet { +public: + static constexpr size_t Size = 1024; + + CpuSet(); + explicit CpuSet(std::initializer_list cpus); + + void clear(); + CpuSet &set(size_t cpu); + CpuSet &unset(size_t cpu); + + CpuSet &set(std::initializer_list cpus); + CpuSet &unset(std::initializer_list cpus); + + CpuSet &setRange(size_t begin, size_t end); + CpuSet &unsetRange(size_t begin, size_t end); + + bool isSet(size_t cpu) const; + size_t count() const; + + cpu_set_t toPosix() const; + +private: + std::bitset bits; +}; + +namespace Polling { + +enum class Mode { Level, Edge }; + +enum class NotifyOn { + None = 0, + + Read = 1, + Write = Read << 1, + Hangup = Read << 2, + Shutdown = Read << 3 +}; + +DECLARE_FLAGS_OPERATORS(NotifyOn) + +struct Tag { + friend class Epoll; + + explicit constexpr Tag(uint64_t value) : value_(value) {} + + constexpr uint64_t value() const { return value_; } + + friend constexpr bool operator==(Tag lhs, Tag rhs); + +private: + uint64_t value_; +}; + +inline constexpr bool operator==(Tag lhs, Tag rhs) { + return lhs.value_ == rhs.value_; +} + +struct Event { + explicit Event(Tag _tag); + + Flags flags; + Fd fd; + Tag tag; +}; + +class Epoll { +public: + Epoll(); + ~Epoll(); + + void addFd(Fd fd, Flags interest, Tag tag, Mode mode = Mode::Level); + void addFdOneShot(Fd fd, Flags interest, Tag tag, + Mode mode = Mode::Level); + + void removeFd(Fd fd); + void rearmFd(Fd fd, Flags interest, Tag tag, + Mode mode = Mode::Level); + + int poll(std::vector &events, const std::chrono::milliseconds timeout = + std::chrono::milliseconds(-1)) const; + +private: + static int toEpollEvents(const Flags &interest); + static Flags toNotifyOn(int events); + Fd epoll_fd; +}; + +} // namespace Polling + +class NotifyFd { +public: + NotifyFd(); + + Polling::Tag bind(Polling::Epoll &poller); + + bool isBound() const; + + Polling::Tag tag() const; + + void notify() const; + + void read() const; + bool tryRead() const; + +private: + Fd event_fd; +}; + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/peer.h b/projects/frontend/server-webapi/pistache/include/pistache/peer.h new file mode 100755 index 0000000000000000000000000000000000000000..12de8239cda84c743d0a04f1ba2715d01167519d --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/peer.h @@ -0,0 +1,72 @@ +/* peer.h + Mathieu Stefani, 12 August 2015 + + A class representing a TCP Peer +*/ + +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef PISTACHE_USE_SSL + +#include + +#endif /* PISTACHE_USE_SSL */ + +namespace Pistache { +namespace Tcp { + +class Transport; + +class Peer { +public: + friend class Transport; + + ~Peer(); + + static std::shared_ptr Create(Fd fd, const Address &addr); + static std::shared_ptr CreateSSL(Fd fd, const Address &addr, void *ssl); + + const Address &address() const; + const std::string &hostname(); + Fd fd() const; + + void *ssl() const; + + void putData(std::string name, std::shared_ptr data); + std::shared_ptr getData(std::string name) const; + std::shared_ptr tryGetData(std::string name) const; + + Async::Promise send(const RawBuffer &buffer, int flags = 0); + +protected: + Peer(Fd fd, const Address &addr, void *ssl); + +private: + void associateTransport(Transport *transport); + Transport *transport() const; + + Transport *transport_ = nullptr; + Fd fd_ = -1; + Address addr; + + std::string hostname_; + std::unordered_map> data_; + + void *ssl_ = nullptr; +}; + +std::ostream &operator<<(std::ostream &os, Peer &peer); + +} // namespace Tcp +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/prototype.h b/projects/frontend/server-webapi/pistache/include/pistache/prototype.h new file mode 100755 index 0000000000000000000000000000000000000000..9573c194257e931c6b77a543242d81e2081c8314 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/prototype.h @@ -0,0 +1,28 @@ +/* + Mathieu Stefani, 28 janvier 2016 + + Simple Prototype design pattern implement +*/ + +#pragma once + +#include +#include + +namespace Pistache { + +/* In a sense, a Prototype is just a class that provides a clone() method */ +template struct Prototype { + virtual ~Prototype() {} + virtual std::shared_ptr clone() const = 0; +}; + +} // namespace Pistache + +#define PROTOTYPE_OF(Base, Class) \ +private: \ + std::shared_ptr clone() const override { \ + return std::make_shared(*this); \ + } \ + \ +public: diff --git a/projects/frontend/server-webapi/pistache/include/pistache/reactor.h b/projects/frontend/server-webapi/pistache/include/pistache/reactor.h new file mode 100755 index 0000000000000000000000000000000000000000..01f2d93f5b5b41b2f928f564309e9d8ad9f27206 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/reactor.h @@ -0,0 +1,198 @@ +/* + Mathieu Stefani, 15 juin 2016 + + A lightweight implementation of the Reactor design-pattern. + + The main goal of this component is to provide an solid abstraction + that can be used internally and by client code to dispatch I/O events + to callbacks and handlers, in an efficient way. +*/ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace Pistache { +namespace Aio { + +// A set of fds that are ready +class FdSet { +public: + FdSet() = delete; + + explicit FdSet(std::vector &&events) : events_() { + events_.reserve(events.size()); + events_.insert(events_.end(), std::make_move_iterator(events.begin()), + std::make_move_iterator(events.end())); + } + + struct Entry : private Polling::Event { + Entry(Polling::Event &&event) : Polling::Event(std::move(event)) {} + + bool isReadable() const { return flags.hasFlag(Polling::NotifyOn::Read); } + bool isWritable() const { return flags.hasFlag(Polling::NotifyOn::Write); } + bool isHangup() const { return flags.hasFlag(Polling::NotifyOn::Hangup); } + + Fd getFd() const { return this->fd; } + Polling::Tag getTag() const { return this->tag; } + }; + + using iterator = std::vector::iterator; + using const_iterator = std::vector::const_iterator; + + size_t size() const { return events_.size(); } + + const Entry &at(size_t index) const { return events_.at(index); } + + const Entry &operator[](size_t index) const { return events_.at(index); } + + iterator begin() { return events_.begin(); } + + iterator end() { return events_.end(); } + + const_iterator begin() const { return events_.begin(); } + + const_iterator end() const { return events_.end(); } + +private: + std::vector events_; +}; + +class Handler; +class ExecutionContext; + +class Reactor : public std::enable_shared_from_this { +public: + class Impl; + + Reactor(); + ~Reactor(); + + struct Key { + + Key(); + + friend class Reactor; + friend class Impl; + friend class SyncImpl; + friend class AsyncImpl; + + uint64_t data() const { return data_; } + + private: + explicit Key(uint64_t data); + uint64_t data_; + }; + + static std::shared_ptr create(); + + void init(); + void init(const ExecutionContext &context); + + Key addHandler(const std::shared_ptr &handler); + + std::vector> handlers(const Key &key); + + void registerFd(const Key &key, Fd fd, Polling::NotifyOn interest, + Polling::Tag tag, Polling::Mode mode = Polling::Mode::Level); + void registerFdOneShot(const Key &key, Fd fd, Polling::NotifyOn interest, + Polling::Tag tag, + Polling::Mode mode = Polling::Mode::Level); + + void registerFd(const Key &key, Fd fd, Polling::NotifyOn interest, + Polling::Mode mode = Polling::Mode::Level); + void registerFdOneShot(const Key &key, Fd fd, Polling::NotifyOn interest, + Polling::Mode mode = Polling::Mode::Level); + + void modifyFd(const Key &key, Fd fd, Polling::NotifyOn interest, + Polling::Mode mode = Polling::Mode::Level); + + void modifyFd(const Key &key, Fd fd, Polling::NotifyOn interest, + Polling::Tag tag, Polling::Mode mode = Polling::Mode::Level); + + void runOnce(); + void run(); + + void shutdown(); + +private: + Impl *impl() const; + std::unique_ptr impl_; +}; + +class ExecutionContext { +public: + virtual ~ExecutionContext() {} + virtual Reactor::Impl *makeImpl(Reactor *reactor) const = 0; +}; + +class SyncContext : public ExecutionContext { +public: + virtual ~SyncContext() {} + Reactor::Impl *makeImpl(Reactor *reactor) const override; +}; + +class AsyncContext : public ExecutionContext { +public: + explicit AsyncContext(size_t threads, const std::string &threadsName = "") + : threads_(threads), threadsName_(threadsName) {} + + virtual ~AsyncContext() {} + + Reactor::Impl *makeImpl(Reactor *reactor) const override; + + static AsyncContext singleThreaded(); + +private: + size_t threads_; + std::string threadsName_; +}; + +class Handler : public Prototype { +public: + friend class Reactor; + friend class SyncImpl; + friend class AsyncImpl; + + Handler() : reactor_(nullptr), context_(), key_() {} + + struct Context { + friend class SyncImpl; + + Context() : tid() {} + + std::thread::id thread() const { return tid; } + + private: + std::thread::id tid; + }; + + virtual void onReady(const FdSet &fds) = 0; + virtual void registerPoller(Polling::Epoll &poller) = 0; + + Reactor *reactor() const { return reactor_; } + + Context context() const { return context_; } + + Reactor::Key key() const { return key_; }; + + virtual ~Handler() {} + +private: + Reactor *reactor_; + Context context_; + Reactor::Key key_; +}; + +} // namespace Aio +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/route_bind.h b/projects/frontend/server-webapi/pistache/include/pistache/route_bind.h new file mode 100755 index 0000000000000000000000000000000000000000..ba9742986227bad2ca09e3a5d92e3667d706ae06 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/route_bind.h @@ -0,0 +1,68 @@ +/* + Mathieu Stefani, 27 février 2016 + + A special bind() method for REST routes +*/ + +#pragma once + +namespace Pistache { +namespace Rest { +namespace Route { + +void Get(Router &router, std::string resource, Route::Handler handler); +void Post(Router &router, std::string resource, Route::Handler handler); +void Put(Router &router, std::string resource, Route::Handler handler); +void Delete(Router &router, std::string resource, Route::Handler handler); + +namespace details { +template struct TypeList { + template struct At { + static_assert(N < sizeof...(Args), "Invalid index"); + typedef typename std::tuple_element>::type Type; + }; +}; + +template void static_checks() { + static_assert(sizeof...(Args) == 2, "Function should take 2 parameters"); + typedef details::TypeList Arguments; + // Disabled now as it + // 1/ does not compile + // 2/ might not be relevant +#if 0 + static_assert(std::is_same::Type, const Rest::Request&>::value, "First argument should be a const Rest::Request&"); + static_assert(std::is_same::Type, Http::Response>::value, "Second argument should be a Http::Response"); +#endif +} +} // namespace details + +template +Route::Handler bind(Result (Cls::*func)(Args...), Obj obj) { + details::static_checks(); + + return [=](const Rest::Request &request, Http::ResponseWriter response) { + (obj->*func)(request, std::move(response)); + }; +} + +template +Route::Handler bind(Result (Cls::*func)(Args...), std::shared_ptr objPtr) { + details::static_checks(); + + return [=](const Rest::Request &request, Http::ResponseWriter response) { + (objPtr.get()->*func)(request, std::move(response)); + }; +} + +template +Route::Handler bind(Result (*func)(Args...)) { + details::static_checks(); + + return [=](const Rest::Request &request, Http::ResponseWriter response) { + func(request, std::move(response)); + }; +} + +} // namespace Route +} // namespace Rest +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/router.h b/projects/frontend/server-webapi/pistache/include/pistache/router.h new file mode 100755 index 0000000000000000000000000000000000000000..878599761fe3beb1ab5a47fccf471daa28261641 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/router.h @@ -0,0 +1,359 @@ +/* router.h + Mathieu Stefani, 05 janvier 2016 + + Simple HTTP Rest Router +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "pistache/string_view.h" + +namespace Pistache { +namespace Rest { + +class Description; + +namespace details { +template struct LexicalCast { + static T cast(const std::string &value) { + std::istringstream iss(value); + T out; + if (!(iss >> out)) + throw std::runtime_error("Bad lexical cast"); + return out; + } +}; + +template <> struct LexicalCast { + static std::string cast(const std::string &value) { return value; } +}; +} // namespace details + +class TypedParam { +public: + TypedParam(std::string name, std::string value) + : name_(std::move(name)), value_(std::move(value)) {} + + template T as() const { + return details::LexicalCast::cast(value_); + } + + const std::string &name() const { return name_; } + +private: + const std::string name_; + const std::string value_; +}; + +class Request; + +struct Route { + enum class Result { Ok, Failure }; + + enum class Status { Match, NotFound, NotAllowed }; + + typedef std::function Handler; + + explicit Route(Route::Handler handler) : handler_(std::move(handler)) {} + + template void invokeHandler(Args &&... args) const { + handler_(std::forward(args)...); + } + + Handler handler_; +}; + +namespace Private { +class RouterHandler; +} + +/** + * A request URI is made of various path segments. + * Since all routes handled by a router are naturally + * represented as a tree, this class provides support for it. + * It is possible to perform tree-based routing search instead + * of linear one. + * This class holds all data for a given path segment, meaning + * that it holds the associated route handler (if any) and all + * next child routes (by means of fixed routes, parametric, + * optional parametric and splats). + * Each child is in turn a SegmentTreeNode. + */ +class SegmentTreeNode { +private: + enum class SegmentType { Fixed, Param, Optional, Splat }; + + /** + * string_view are very efficient when working with the + * substring function (massively used for routing) but are + * non-owning. To let the content survive after it is firstly + * created, their content is stored in this reference pointer + * that is in charge of managing their lifecycle (create on add, + * reset on remove). + */ + std::shared_ptr resource_ref_; + + std::unordered_map> fixed_; + std::unordered_map> param_; + std::unordered_map> + optional_; + std::shared_ptr splat_; + std::shared_ptr route_; + + /** + * Common web servers (nginx, httpd, IIS) collapse multiple + * forward slashes to a single one. This regex is used to + * obtain the same result. + */ + static std::regex multiple_slash; + + static SegmentType getSegmentType(const std::string_view &fragment); + + /** + * Fetches the route associated to a given path. + * \param[in] path Requested resource path. Must have no leading slash + * and no multiple slashes: + * eg: + * - auth/login is valid + * - /auth/login is invalid + * - auth//login is invalid + * \param[in,out] params Contains all the parameters parsed so far. + * \param[in,out] splats Contains all the splats parsed so far. + * \returns Tuple containing the route, the list of all parsed parameters + * and the list of all parsed splats. + * \throws std::runtime_error An empty path was given + */ + std::tuple, std::vector, + std::vector> + findRoute(const std::string_view &path, std::vector ¶ms, + std::vector &splats) const; + +public: + SegmentTreeNode(); + explicit SegmentTreeNode(const std::shared_ptr &resourceReference); + + /** + * Sanitizes a resource URL by removing any duplicate slash, leading + * slash and trailing slash. + * @param path URL to sanitize. + * @return Sanitized URL. + */ + static std::string sanitizeResource(const std::string &path); + + /** + * Associates a route handler to a given path. + * \param[in] path Requested resource path. Must have no leading and trailing + * slashes and no multiple slashes: + * eg: + * - auth/login is valid + * - /auth/login is invalid + * - auth/login/ is invalid + * - auth//login is invalid + * \param[in] handler Handler to associate to path. + * \param[in] resource_reference \see SegmentTreeNode::resource_ref_ + * \throws std::runtime_error An empty path was given + */ + void addRoute(const std::string_view &path, const Route::Handler &handler, + const std::shared_ptr &resource_reference); + + /** + * Removes the route handler associated to a given path. + * \param[in] path Requested resource path. Must have no leading slash + * and no multiple slashes: + * eg: + * - auth/login is valid + * - /auth/login is invalid + * - auth//login is invalid + * \throws std::runtime_error An empty path was given + */ + bool removeRoute(const std::string_view &path); + + /** + * Finds the correct route for the given path. + * \param[in] path Requested resource path. Must have no leading slash + * and no multiple slashes: + * eg: + * - auth/login is valid + * - /auth/login is invalid + * - auth//login is invalid + * \throws std::runtime_error An empty path was given + * \return Found route with its resolved parameters and splats (if no route + * is found, first element of the tuple is a null pointer). + */ + std::tuple, std::vector, + std::vector> + findRoute(const std::string_view &path) const; +}; + +class Router { +public: + static Router fromDescription(const Rest::Description &desc); + + std::shared_ptr handler() const; + static std::shared_ptr + handler(std::shared_ptr router); + + void initFromDescription(const Rest::Description &desc); + + void get(const std::string &resource, Route::Handler handler); + void post(const std::string &resource, Route::Handler handler); + void put(const std::string &resource, Route::Handler handler); + void patch(const std::string &resource, Route::Handler handler); + void del(const std::string &resource, Route::Handler handler); + void options(const std::string &resource, Route::Handler handler); + void addRoute(Http::Method method, const std::string &resource, + Route::Handler handler); + void removeRoute(Http::Method method, const std::string &resource); + void head(const std::string &resource, Route::Handler handler); + + void addCustomHandler(Route::Handler handler); + + void addNotFoundHandler(Route::Handler handler); + inline bool hasNotFoundHandler() { return notFoundHandler != nullptr; } + void invokeNotFoundHandler(const Http::Request &req, + Http::ResponseWriter resp) const; + + Route::Status route(const Http::Request &request, + Http::ResponseWriter response); + + Router() : routes(), customHandlers(), notFoundHandler() {} + +private: + std::unordered_map routes; + + std::vector customHandlers; + + Route::Handler notFoundHandler; +}; + +namespace Private { + +class RouterHandler : public Http::Handler { +public: + + HTTP_PROTOTYPE(RouterHandler) + + /** + * Used for immutable router. Useful if all the routes are + * defined at compile time (and for backward compatibility) + * \param[in] router Immutable router. + */ + explicit RouterHandler(const Rest::Router &router); + + /** + * Used for mutable router. Useful if it is required to + * add/remove routes at runtime. + * \param[in] router Pointer to a (mutable) router. + */ + explicit RouterHandler(std::shared_ptr router); + + void onRequest(const Http::Request &req, + Http::ResponseWriter response) override; + +private: + std::shared_ptr router; +}; +} // namespace Private + +class Request : public Http::Request { +public: + friend class Router; + + bool hasParam(const std::string &name) const; + TypedParam param(const std::string &name) const; + + TypedParam splatAt(size_t index) const; + std::vector splat() const; + +private: + explicit Request(const Http::Request &request, + std::vector &¶ms, + std::vector &&splats); + + std::vector params_; + std::vector splats_; +}; + +namespace Routes { + +void Get(Router &router, const std::string &resource, Route::Handler handler); +void Post(Router &router, const std::string &resource, Route::Handler handler); +void Put(Router &router, const std::string &resource, Route::Handler handler); +void Patch(Router &router, const std::string &resource, Route::Handler handler); +void Delete(Router &router, const std::string &resource, + Route::Handler handler); +void Options(Router &router, const std::string &resource, + Route::Handler handler); +void Remove(Router &router, Http::Method method, const std::string &resource); +void Head(Router &router, const std::string &resource, Route::Handler handler); + +void NotFound(Router &router, Route::Handler handler); + +namespace details { +template struct TypeList { + template struct At { + static_assert(N < sizeof...(Args), "Invalid index"); + typedef typename std::tuple_element>::type Type; + }; +}; + +template void static_checks() { + static_assert(sizeof...(Args) == 2, "Function should take 2 parameters"); +// typedef details::TypeList Arguments; +// Disabled now as it +// 1/ does not compile +// 2/ might not be relevant +#if 0 + static_assert(std::is_same::Type, const Rest::Request&>::value, "First argument should be a const Rest::Request&"); + static_assert(std::is_same::Type, Http::Response>::value, "Second argument should be a Http::Response"); +#endif +} +} // namespace details + +template +Route::Handler bind(Result (Cls::*func)(Args...), Obj obj) { + details::static_checks(); + + return [=](const Rest::Request &request, Http::ResponseWriter response) { + (obj->*func)(request, std::move(response)); + + return Route::Result::Ok; + }; +} + +template +Route::Handler bind(Result (Cls::*func)(Args...), std::shared_ptr objPtr) { + details::static_checks(); + + return [=](const Rest::Request &request, Http::ResponseWriter response) { + (objPtr.get()->*func)(request, std::move(response)); + + return Route::Result::Ok; + }; +} + +template +Route::Handler bind(Result (*func)(Args...)) { + details::static_checks(); + + return [=](const Rest::Request &request, Http::ResponseWriter response) { + func(request, std::move(response)); + + return Route::Result::Ok; + }; +} + +} // namespace Routes +} // namespace Rest +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/serializer/rapidjson.h b/projects/frontend/server-webapi/pistache/include/pistache/serializer/rapidjson.h new file mode 100755 index 0000000000000000000000000000000000000000..02bfbd4e29fc84bc8c4d54bf310e2fd0d19ce202 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/serializer/rapidjson.h @@ -0,0 +1,219 @@ +/* + Mathieu Stefani, 14 mai 2016 + + Swagger serializer for RapidJSON +*/ + +#pragma once + +#include + +#include +#include +#include + +namespace Pistache { +namespace Rest { +namespace Serializer { + +template +void serializeInfo(Writer &writer, const Schema::Info &info) { + writer.String("swagger"); + writer.String("2.0"); + writer.String("info"); + writer.StartObject(); + { + writer.String("title"); + writer.String(info.title.c_str()); + writer.String("version"); + writer.String(info.version.c_str()); + if (!info.description.empty()) { + writer.String("description"); + writer.String(info.description.c_str()); + } + if (!info.termsOfService.empty()) { + writer.String("termsOfService"); + writer.String(info.termsOfService.c_str()); + } + } + writer.EndObject(); +} + +template +void serializePC(Writer &writer, const Schema::ProduceConsume &pc) { + auto serializeMimes = [&](const char *name, + const std::vector &mimes) { + if (!mimes.empty()) { + writer.String(name); + writer.StartArray(); + { + for (const auto &mime : mimes) { + auto str = mime.toString(); + writer.String(str.c_str()); + } + } + writer.EndArray(); + } + }; + + serializeMimes("consumes", pc.consume); + serializeMimes("produces", pc.produce); +} + +template +void serializeParameter(Writer &writer, const Schema::Parameter ¶meter) { + writer.StartObject(); + { + writer.String("name"); + writer.String(parameter.name.c_str()); + writer.String("in"); + // @Feature: support other types of parameters + writer.String("path"); + writer.String("description"); + writer.String(parameter.description.c_str()); + writer.String("required"); + writer.Bool(parameter.required); + writer.String("type"); + writer.String(parameter.type->typeName()); + } + writer.EndObject(); +} + +template +void serializeResponse(Writer &writer, const Schema::Response &response) { + auto code = std::to_string(static_cast(response.statusCode)); + writer.String(code.c_str()); + writer.StartObject(); + { + writer.String("description"); + writer.String(response.description.c_str()); + } + writer.EndObject(); +} + +template +void serializePath(Writer &writer, const Schema::Path &path) { + std::string methodStr(methodString(path.method)); + // So it looks like Swagger requires method to be in lowercase + std::transform(std::begin(methodStr), std::end(methodStr), + std::begin(methodStr), ::tolower); + + writer.String(methodStr.c_str()); + writer.StartObject(); + { + writer.String("description"); + writer.String(path.description.c_str()); + serializePC(writer, path.pc); + + const auto ¶meters = path.parameters; + if (!parameters.empty()) { + writer.String("parameters"); + writer.StartArray(); + { + for (const auto ¶meter : parameters) { + serializeParameter(writer, parameter); + } + } + writer.EndArray(); + } + + const auto &responses = path.responses; + if (!responses.empty()) { + writer.String("responses"); + writer.StartObject(); + { + for (const auto &response : responses) { + serializeResponse(writer, response); + } + } + writer.EndObject(); + } + } + writer.EndObject(); +} + +template +void serializePathGroups(Writer &writer, const std::string &prefix, + const Schema::PathGroup &paths, + Schema::PathGroup::Format format) { + writer.String("paths"); + writer.StartObject(); + { + auto groups = paths.groups(); + for (const auto &group : groups) { + if (group.second.isHidden()) + continue; + + std::string name(group.first); + if (!prefix.empty()) { + if (!name.compare(0, prefix.size(), prefix)) { + name = name.substr(prefix.size()); + } + } + + if (format == Schema::PathGroup::Format::Default) { + writer.String(name.c_str()); + } else { + auto swaggerPath = Schema::Path::swaggerFormat(name); + writer.String(swaggerPath.c_str()); + } + writer.StartObject(); + { + for (const auto &path : group.second) { + if (!path.hidden) + serializePath(writer, path); + } + } + writer.EndObject(); + } + } + writer.EndObject(); +} + +template +void serializeDescription(Writer &writer, const Description &desc) { + writer.StartObject(); + { + serializeInfo(writer, desc.rawInfo()); + auto host = desc.rawHost(); + auto basePath = desc.rawBasePath(); + auto schemes = desc.rawSchemes(); + auto pc = desc.rawPC(); + auto paths = desc.rawPaths(); + + if (!host.empty()) { + writer.String("host"); + writer.String(host.c_str()); + } + if (!basePath.empty()) { + writer.String("basePath"); + writer.String(basePath.c_str()); + } + if (!schemes.empty()) { + writer.String("schemes"); + writer.StartArray(); + { + for (const auto &scheme : schemes) { + writer.String(schemeString(scheme)); + } + } + writer.EndArray(); + } + serializePC(writer, pc); + serializePathGroups(writer, basePath, paths, + Schema::PathGroup::Format::Swagger); + } + writer.EndObject(); +} + +inline std::string rapidJson(const Description &desc) { + rapidjson::StringBuffer sb; + rapidjson::PrettyWriter writer(sb); + serializeDescription(writer, desc); + + return sb.GetString(); +} + +} // namespace Serializer +} // namespace Rest +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/ssl_wrappers.h b/projects/frontend/server-webapi/pistache/include/pistache/ssl_wrappers.h new file mode 100755 index 0000000000000000000000000000000000000000..28571530cb5d1d39b960fcbc6751712ea8fed613 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/ssl_wrappers.h @@ -0,0 +1,38 @@ +#pragma once + +#ifdef PISTACHE_USE_SSL +#include +#endif + +#include + +namespace Pistache { +namespace ssl { + +struct SSLCtxDeleter { + void operator()(void *ptr) { +#ifdef PISTACHE_USE_SSL + SSL_CTX_free(reinterpret_cast(ptr)); + + // EVP_cleanup call is not related to cleaning SSL_CTX, just global cleanup routine. + // TODO: Think about removing EVP_cleanup call at all + // It was deprecated in openssl 1.1.0 version (see + // https://www.openssl.org/news/changelog.txt): + // "Make various cleanup routines no-ops and mark them as deprecated." + EVP_cleanup(); +#else + (void)ptr; +#endif + } +}; + +using SSLCtxPtr = std::unique_ptr; + +#ifdef PISTACHE_USE_SSL +inline SSL_CTX* GetSSLContext(ssl::SSLCtxPtr &ctx) { + return reinterpret_cast(ctx.get()); +} +#endif + +} +} diff --git a/projects/frontend/server-webapi/pistache/include/pistache/stream.h b/projects/frontend/server-webapi/pistache/include/pistache/stream.h new file mode 100755 index 0000000000000000000000000000000000000000..77fe69b37801566cb2908d6451a0d10c2ba94fb7 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/stream.h @@ -0,0 +1,266 @@ +/* stream.h + Mathieu Stefani, 05 September 2015 + + A set of classes to control input over a sequence of bytes +*/ + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Pistache { + +static constexpr char CR = 0xD; +static constexpr char LF = 0xA; + +template +class StreamBuf : public std::basic_streambuf { +public: + typedef std::basic_streambuf Base; + typedef typename Base::traits_type traits_type; + + void setArea(char *begin, char *current, char *end) { + this->setg(begin, current, end); + } + + CharT *begptr() const { return this->eback(); } + + CharT *curptr() const { return this->gptr(); } + + CharT *endptr() const { return this->egptr(); } + + size_t position() const { return this->gptr() - this->eback(); } + + void reset() { this->setg(nullptr, nullptr, nullptr); } + + typename Base::int_type snext() const { + if (this->gptr() == this->egptr()) { + return traits_type::eof(); + } + + const CharT *gptr = this->gptr(); + return *(gptr + 1); + } +}; + +template class RawStreamBuf : public StreamBuf { +public: + typedef StreamBuf Base; + + RawStreamBuf(char *begin, char *end) { Base::setg(begin, begin, end); } + RawStreamBuf(char *begin, size_t len) { + Base::setg(begin, begin, begin + len); + } +}; + +// Make the buffer dynamic +template +class ArrayStreamBuf : public StreamBuf { +public: + using Base = StreamBuf; + + explicit ArrayStreamBuf(size_t maxSize) + : StreamBuf(), bytes(), maxSize(maxSize) { + bytes.clear(); + Base::setg(bytes.data(), bytes.data(), bytes.data() + bytes.size()); + } + + template explicit ArrayStreamBuf(char (&arr)[M]) { + bytes.clear(); + std::copy(arr, arr + M, std::back_inserter(bytes)); + Base::setg(bytes.data(), bytes.data(), bytes.data() + bytes.size()); + } + + bool feed(const char *data, size_t len) { + if (bytes.size() + len > maxSize) { + return false; + } + // persist current offset + size_t readOffset = static_cast(this->gptr() - this->eback()); + std::copy(data, data + len, std::back_inserter(bytes)); + Base::setg(bytes.data(), bytes.data() + readOffset, + bytes.data() + bytes.size()); + return true; + } + + void reset() { + std::vector nbytes; + bytes.swap(nbytes); + Base::setg(bytes.data(), bytes.data(), bytes.data() + bytes.size()); + } + +private: + std::vector bytes; + size_t maxSize = Const::MaxBuffer; +}; + +struct RawBuffer { + RawBuffer(); + RawBuffer(std::string data, size_t length, bool isDetached = false); + RawBuffer(const char *data, size_t length, bool isDetached = false); + + RawBuffer detach(size_t fromIndex); + const std::string &data() const; + size_t size() const; + bool isDetached() const; + +private: + std::string data_; + size_t length_; + bool isDetached_; +}; + +struct FileBuffer { + explicit FileBuffer(const std::string &fileName); + + Fd fd() const; + size_t size() const; + +private: + std::string fileName_; + Fd fd_; + size_t size_; +}; + +class DynamicStreamBuf : public StreamBuf { +public: + using Base = StreamBuf; + using traits_type = typename Base::traits_type; + using int_type = typename Base::int_type; + + DynamicStreamBuf(size_t size, size_t maxSize); + + DynamicStreamBuf(const DynamicStreamBuf &other) = delete; + DynamicStreamBuf &operator=(const DynamicStreamBuf &other) = delete; + + DynamicStreamBuf(DynamicStreamBuf &&other); + DynamicStreamBuf &operator=(DynamicStreamBuf &&other); + + RawBuffer buffer() const; + + void clear(); + + size_t maxSize() const; + +protected: + int_type overflow(int_type ch) override; + +private: + void reserve(size_t size); + + std::vector data_; + size_t maxSize_ = Const::MaxBuffer; +}; + +class StreamCursor { +public: + explicit StreamCursor(StreamBuf *_buf, size_t initialPos = 0) + : buf(_buf) { + advance(initialPos); + } + + static constexpr int Eof = -1; + + struct Token { + explicit Token(StreamCursor &_cursor) + : cursor(_cursor), position(cursor.buf->position()), + eback(cursor.buf->begptr()), gptr(cursor.buf->curptr()), + egptr(cursor.buf->endptr()) {} + + size_t start() const { return position; } + + size_t end() const { return cursor.buf->position(); } + + size_t size() const { return end() - start(); } + + std::string text() const { return std::string(gptr, size()); } + + const char *rawText() const { return gptr; } + + private: + StreamCursor &cursor; + size_t position; + char *eback; + char *gptr; + char *egptr; + }; + + struct Revert { + explicit Revert(StreamCursor &_cursor) + : cursor(_cursor), eback(cursor.buf->begptr()), + gptr(cursor.buf->curptr()), egptr(cursor.buf->endptr()), + active(true) {} + + Revert(const Revert &) = delete; + Revert &operator=(const Revert &) = delete; + + ~Revert() { + if (active) + revert(); + } + + void revert() { cursor.buf->setArea(eback, gptr, egptr); } + + void ignore() { active = false; } + + private: + StreamCursor &cursor; + char *eback; + char *gptr; + char *egptr; + bool active; + }; + + bool advance(size_t count); + operator size_t() const { return buf->position(); } + + bool eol() const; + bool eof() const; + int next() const; + char current() const; + + const char *offset() const; + const char *offset(size_t off) const; + + size_t diff(size_t other) const; + size_t diff(const StreamCursor &other) const; + + size_t remaining() const; + + void reset(); + +public: + StreamBuf *buf; +}; + +enum class CaseSensitivity { Sensitive, Insensitive }; + +bool match_raw(const void *buf, size_t len, StreamCursor &cursor); +bool match_string(const char *str, size_t len, StreamCursor &cursor, + CaseSensitivity cs = CaseSensitivity::Insensitive); +template +bool match_string(const char (&str)[N], StreamCursor &cursor, + CaseSensitivity cs = CaseSensitivity::Insensitive) { + return match_string(str, N - 1, cursor, cs); +} + +bool match_literal(char c, StreamCursor &cursor, + CaseSensitivity cs = CaseSensitivity::Insensitive); +bool match_until(char c, StreamCursor &cursor, + CaseSensitivity cs = CaseSensitivity::Insensitive); +bool match_until(std::initializer_list chars, StreamCursor &cursor, + CaseSensitivity cs = CaseSensitivity::Insensitive); +bool match_double(double *val, StreamCursor &cursor); + +void skip_whitespaces(StreamCursor &cursor); + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/string_view.h b/projects/frontend/server-webapi/pistache/include/pistache/string_view.h new file mode 100755 index 0000000000000000000000000000000000000000..0bb9c2337485db60c77374fe3a4df39da98fb2a9 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/string_view.h @@ -0,0 +1,243 @@ +#pragma once + +#define CUSTOM_STRING_VIEW 1 + +#if __cplusplus >= 201703L +#if defined(__has_include) +#if __has_include() +#undef CUSTOM_STRING_VIEW +#include +#elif __has_include() +#undef CUSTOM_STRING_VIEW +#include +namespace std { +using string_view = experimental::string_view; +} +#endif +#endif +#endif + +#ifdef CUSTOM_STRING_VIEW + +#include + +#include +#include +#include + +namespace std { + +class string_view { +public: + using size_type = std::size_t; + + static constexpr size_type npos = size_type(-1); + + constexpr string_view() noexcept : data_(nullptr), size_(0) {} + + constexpr string_view(const string_view &other) noexcept = default; + + constexpr string_view(const char *s, size_type count) + : data_(s), size_(count) {} + + explicit string_view(const char *s) : data_(s), size_(strlen(s)) {} + + string_view substr(size_type pos, size_type count = npos) const { + if (pos > size_) { + throw std::out_of_range("Out of range."); + } + size_type rcount = std::min(count, size_ - pos); + return {data_ + pos, rcount}; + } + + constexpr size_type size() const noexcept { return size_; } + + constexpr size_type length() const noexcept { return size_; } + + constexpr char operator[](size_type pos) const { return data_[pos]; } + + constexpr const char *data() const noexcept { return data_; } + + bool operator==(const string_view &rhs) const { + return (!std::lexicographical_compare(data_, data_ + size_, rhs.data_, + rhs.data_ + rhs.size_) && + !std::lexicographical_compare(rhs.data_, rhs.data_ + rhs.size_, + data_, data_ + size_)); + } + + string_view &operator=(const string_view &view) noexcept = default; + + size_type find(string_view v, size_type pos = 0) const noexcept { + if (size_ < pos) + return npos; + + if ((size_ - pos) < v.size_) + return npos; + + for (; pos <= (size_ - v.size_); ++pos) { + bool found = true; + for (size_type i = 0; i < v.size_; ++i) { + if (data_[pos + i] != v.data_[i]) { + found = false; + break; + } + } + if (found) { + return pos; + } + } + + return npos; + } + + size_type find(char ch, size_type pos = 0) const noexcept { + return find(string_view(&ch, 1), pos); + } + + size_type find(const char *s, size_type pos, size_type count) const { + return find(string_view(s, count), pos); + } + + size_type find(const char *s, size_type pos = 0) const { + return find(string_view(s), pos); + } + + size_type rfind(string_view v, size_type pos = npos) const noexcept { + if (v.size_ > size_) + return npos; + + if (pos > size_) + pos = size_; + size_t start = size_ - v.size_; + if (pos != npos) + start = pos; + for (size_t offset = 0; offset <= pos; ++offset, --start) { + bool found = true; + for (size_t j = 0; j < v.size_; ++j) { + if (data_[start + j] != v.data_[j]) { + found = false; + break; + } + } + if (found) { + return start; + } + } + + return npos; + } + + size_type rfind(char c, size_type pos = npos) const noexcept { + return rfind(string_view(&c, 1), pos); + } + + size_type rfind(const char *s, size_type pos, size_type count) const { + return rfind(string_view(s, count), pos); + } + + size_type rfind(const char *s, size_type pos = npos) const { + return rfind(string_view(s), pos); + } + + constexpr bool empty() const noexcept { return size_ == 0; } + +private: + const char *data_; + size_type size_; +}; + +template <> struct hash { + //----------------------------------------------------------------------------- + // MurmurHash3 was written by Austin Appleby, and is placed in the public + // domain. The author hereby disclaims copyright to this source code. +private: +#ifdef __GNUC__ +#define FORCE_INLINE __attribute__((always_inline)) inline +#else +#define FORCE_INLINE inline +#endif + + static FORCE_INLINE std::uint32_t Rotl32(const std::uint32_t x, + const std::int8_t r) { + return (x << r) | (x >> (32 - r)); + } + + static FORCE_INLINE std::uint32_t Getblock(const std::uint32_t *p, + const int i) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return p[i]; +#else + std::uint32_t temp_number = p[i]; + uint8_t(&number)[4] = *reinterpret_cast(&temp_number); + std::swap(number[0], number[3]); + std::swap(number[1], number[2]); + return temp_number; +#endif + } + + static FORCE_INLINE uint32_t fmix32(uint32_t h) { + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; + } + +public: + string_view::size_type operator()(const string_view &str) const { + const uint32_t len = static_cast(str.length()); + const uint8_t *data = reinterpret_cast(str.data()); + const int nblocks = static_cast(len / 4); + + uint32_t h1 = 0; // seed + + const uint32_t c1 = 0xcc9e2d51U; + const uint32_t c2 = 0x1b873593U; + + const uint32_t *blocks = + reinterpret_cast(data + nblocks * 4); + + for (auto i = -nblocks; i; ++i) { + uint32_t k1 = Getblock(blocks, i); + + k1 *= c1; + k1 = Rotl32(k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = Rotl32(h1, 13); + h1 = h1 * 5 + 0xe6546b64; + } + + const uint8_t *tail = data + nblocks * 4; + + uint32_t k1 = 0; + + switch (len & 3) { + case 3: + k1 ^= tail[2] << 16; + case 2: + k1 ^= tail[1] << 8; + case 1: + k1 ^= tail[0]; + k1 *= c1; + k1 = Rotl32(k1, 15); + k1 *= c2; + h1 ^= k1; + default: + break; + } + + h1 ^= len; + + h1 = fmix32(h1); + return hash()(h1); + } + +#undef FORCE_INLINE +}; +} // namespace std + +#endif diff --git a/projects/frontend/server-webapi/pistache/include/pistache/tcp.h b/projects/frontend/server-webapi/pistache/include/pistache/tcp.h new file mode 100755 index 0000000000000000000000000000000000000000..2d6419222e1f1411c92f688b7eeab359fd2f0421 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/tcp.h @@ -0,0 +1,60 @@ +/* tcp.h + Mathieu Stefani, 05 novembre 2015 + + TCP +*/ + +#pragma once + +#include +#include + +#include +#include +#include + +namespace Pistache { +namespace Tcp { + +class Peer; +class Transport; + +enum class Options : uint64_t { + None = 0, + NoDelay = 1, + Linger = NoDelay << 1, + FastOpen = Linger << 1, + QuickAck = FastOpen << 1, + ReuseAddr = QuickAck << 1, + ReusePort = ReuseAddr << 1, +}; + +DECLARE_FLAGS_OPERATORS(Options) + +class Handler : private Prototype { +public: + friend class Transport; + + Handler(); + virtual ~Handler(); + + virtual void onInput(const char *buffer, size_t len, + const std::shared_ptr &peer) = 0; + + virtual void onConnection(const std::shared_ptr &peer); + virtual void onDisconnection(const std::shared_ptr &peer); + +private: + void associateTransport(Transport *transport); + Transport *transport_; + +protected: + Transport *transport() { + if (!transport_) + throw std::logic_error("Orphaned handler"); + return transport_; + } +}; + +} // namespace Tcp +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/timer_pool.h b/projects/frontend/server-webapi/pistache/include/pistache/timer_pool.h new file mode 100755 index 0000000000000000000000000000000000000000..d756a284f996ad3c6411c2672fb9c9eb0c80b37d --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/timer_pool.h @@ -0,0 +1,66 @@ +/* timer_pool.h + Mathieu Stefani, 09 février 2016 + + A pool of timer fd to avoid creating fds everytime we need a timer and + thus reduce the total number of system calls. + + Most operations are lock-free except resize operations needed when the + pool is empty, in which case it's blocking but we expect it to be rare. +*/ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace Pistache { + +class TimerPool { +public: + explicit TimerPool(size_t initialSize = Const::DefaultTimerPoolSize); + + struct Entry { + + friend class TimerPool; + + Entry(); + ~Entry(); + + Fd fd() const; + + void initialize(); + + template void arm(Duration duration) { + assert(fd_ != -1 && "Entry is not initialized"); + + armMs(std::chrono::duration_cast(duration)); + } + + void disarm(); + + void registerReactor(const Aio::Reactor::Key &key, Aio::Reactor *reactor); + + private: + void armMs(std::chrono::milliseconds value); + enum class State : uint32_t { Idle, Used }; + std::atomic state; + Fd fd_; + bool registered; + }; + + std::shared_ptr pickTimer(); + static void releaseTimer(const std::shared_ptr &timer); + +private: + std::vector> timers; +}; + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/transport.h b/projects/frontend/server-webapi/pistache/include/pistache/transport.h new file mode 100755 index 0000000000000000000000000000000000000000..84b5c2c8b06f624a7fc76ed90cb777d98a4df1e3 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/transport.h @@ -0,0 +1,207 @@ +/* + Mathieu Stefani, 26 janvier 2016 + + Transport TCP layer +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Pistache { +namespace Tcp { + +class Peer; +class Handler; + +class Transport : public Aio::Handler { +public: + explicit Transport(const std::shared_ptr &handler); + Transport(const Transport &) = delete; + Transport &operator=(const Transport &) = delete; + + void init(const std::shared_ptr &handler); + + void registerPoller(Polling::Epoll &poller) override; + + void handleNewPeer(const std::shared_ptr &peer); + void onReady(const Aio::FdSet &fds) override; + + template + Async::Promise asyncWrite(Fd fd, const Buf &buffer, int flags = 0) { + // Always enqueue reponses for sending. Giving preference to consumer + // context means chunked responses could be sent out of order. + return Async::Promise( + [=](Async::Deferred deferred) mutable { + BufferHolder holder(buffer); + auto detached = holder.detach(); + WriteEntry write(std::move(deferred), detached, flags); + write.peerFd = fd; + writesQueue.push(std::move(write)); + }); + } + + Async::Promise load() { + return Async::Promise([=](Async::Deferred deferred) { + loadRequest_ = std::move(deferred); + notifier.notify(); + }); + } + + template + void armTimer(Fd fd, Duration timeout, Async::Deferred deferred) { + armTimerMs(fd, + std::chrono::duration_cast(timeout), + std::move(deferred)); + } + + void disarmTimer(Fd fd); + + std::shared_ptr clone() const override; + +private: + enum WriteStatus { FirstTry, Retry }; + + struct BufferHolder { + enum Type { Raw, File }; + + explicit BufferHolder(const RawBuffer &buffer, off_t offset = 0) + : _raw(buffer), size_(buffer.size()), offset_(offset), type(Raw) {} + + explicit BufferHolder(const FileBuffer &buffer, off_t offset = 0) + : _fd(buffer.fd()), size_(buffer.size()), offset_(offset), type(File) {} + + bool isFile() const { return type == File; } + bool isRaw() const { return type == Raw; } + size_t size() const { return size_; } + size_t offset() const { return offset_; } + + Fd fd() const { + if (!isFile()) + throw std::runtime_error("Tried to retrieve fd of a non-filebuffer"); + return _fd; + } + + RawBuffer raw() const { + if (!isRaw()) + throw std::runtime_error("Tried to retrieve raw data of a non-buffer"); + return _raw; + } + + BufferHolder detach(size_t offset = 0) { + if (!isRaw()) + return BufferHolder(_fd, size_, offset); + + if (_raw.isDetached()) + return BufferHolder(_raw, offset); + + auto detached = _raw.detach(offset); + return BufferHolder(detached); + } + + private: + BufferHolder(Fd fd, size_t size, off_t offset = 0) + : _fd(fd), size_(size), offset_(offset), type(File) {} + + RawBuffer _raw; + Fd _fd; + + size_t size_ = 0; + off_t offset_ = 0; + Type type; + }; + + struct WriteEntry { + WriteEntry(Async::Deferred deferred_, BufferHolder buffer_, + int flags_ = 0) + : deferred(std::move(deferred_)), buffer(std::move(buffer_)), + flags(flags_), peerFd(-1) {} + + Async::Deferred deferred; + BufferHolder buffer; + int flags; + Fd peerFd; + }; + + struct TimerEntry { + TimerEntry(Fd fd_, std::chrono::milliseconds value_, + Async::Deferred deferred_) + : fd(fd_), value(value_), deferred(std::move(deferred_)), active() { + active.store(true, std::memory_order_relaxed); + } + + TimerEntry(TimerEntry &&other) + : fd(other.fd), value(other.value), deferred(std::move(other.deferred)), + active(other.active.load()) {} + + void disable() { active.store(false, std::memory_order_relaxed); } + + bool isActive() { return active.load(std::memory_order_relaxed); } + + Fd fd; + std::chrono::milliseconds value; + Async::Deferred deferred; + std::atomic active; + }; + + struct PeerEntry { + explicit PeerEntry(std::shared_ptr peer_) : peer(std::move(peer_)) {} + + std::shared_ptr peer; + }; + using Lock = std::mutex; + using Guard = std::lock_guard; + + PollableQueue writesQueue; + std::unordered_map> toWrite; + Lock toWriteLock; + + PollableQueue timersQueue; + std::unordered_map timers; + + PollableQueue peersQueue; + std::unordered_map> peers; + + Async::Deferred loadRequest_; + NotifyFd notifier; + + std::shared_ptr handler_; + + bool isPeerFd(Fd fd) const; + bool isTimerFd(Fd fd) const; + bool isPeerFd(Polling::Tag tag) const; + bool isTimerFd(Polling::Tag tag) const; + + std::shared_ptr &getPeer(Fd fd); + std::shared_ptr &getPeer(Polling::Tag tag); + + void armTimerMs(Fd fd, std::chrono::milliseconds value, + Async::Deferred deferred); + + void armTimerMsImpl(TimerEntry entry); + + // This will attempt to drain the write queue for the fd + void asyncWriteImpl(Fd fd); + + void handlePeerDisconnection(const std::shared_ptr &peer); + void handleIncoming(const std::shared_ptr &peer); + void handleWriteQueue(); + void handleTimerQueue(); + void handlePeerQueue(); + void handleNotify(); + void handleTimer(TimerEntry entry); + void handlePeer(const std::shared_ptr &entry); +}; + +} // namespace Tcp +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/include/pistache/type_checkers.h b/projects/frontend/server-webapi/pistache/include/pistache/type_checkers.h new file mode 100755 index 0000000000000000000000000000000000000000..5563a52faebadaccb5d794f6d5731f74ff08f5b3 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/type_checkers.h @@ -0,0 +1,7 @@ +#pragma once + +namespace Pistache { + +#define REQUIRES(condition) typename std::enable_if<(condition), int>::type = 0 + +} // namespace Pistache \ No newline at end of file diff --git a/projects/frontend/server-webapi/pistache/include/pistache/typeid.h b/projects/frontend/server-webapi/pistache/include/pistache/typeid.h new file mode 100755 index 0000000000000000000000000000000000000000..18c3245f41cb0cf22be2fa82819f760595038e2e --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/typeid.h @@ -0,0 +1,61 @@ +/* typeid.h + Mathieu Stefani, 30 novembre 2015 + Copyright (c) 2015 Datacratic. All rights reserved. + + This header provides a TypeId type that holds an unique identifier + for a given type. Basically equivalent to std::type_info except that + it does not rely on RTTI. The identifier is determined at compile-time. + + Inspired by Rust's std::TypeId +*/ + +#pragma once + +#include + +namespace Pistache { + +class TypeId { +public: + template static TypeId of() { + static char const id_{}; + + return TypeId(&id_); + } + + operator size_t() const { return reinterpret_cast(id_); } + +private: + typedef void const *Id; + + explicit TypeId(Id id) : id_(id) {} + + Id id_; +}; + +#define APPLY_OP(lhs, rhs, op) \ + static_cast(lhs) op static_cast(rhs); + +inline bool operator==(const TypeId &lhs, const TypeId &rhs) { + return APPLY_OP(lhs, rhs, ==); +} + +inline bool operator!=(const TypeId &lhs, const TypeId &rhs) { + return APPLY_OP(lhs, rhs, !=); +} + +inline bool operator<(const TypeId &lhs, const TypeId &rhs) { + return APPLY_OP(lhs, rhs, <); +} + +#undef APPLY_OP + +} // namespace Pistache + +namespace std { +template <> struct hash { + size_t operator()(const Pistache::TypeId &id) { + return static_cast(id); + } +}; +} // namespace std diff --git a/projects/frontend/server-webapi/pistache/include/pistache/utils.h b/projects/frontend/server-webapi/pistache/include/pistache/utils.h new file mode 100755 index 0000000000000000000000000000000000000000..2baa69c55a88ebd90526aa351a59d1babe0ba2bc --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/utils.h @@ -0,0 +1,39 @@ +/* utils.h + Louis Solofrizzo 2019-10-17 + + Utilities for pistache +*/ + +#pragma once + +#ifdef PISTACHE_USE_SSL + +#include + +/*! + * \brief sendfile(2) like utility for OpenSSL contexts + * + * \param[out] out The SSL context + * \param[in] in File descriptor to stream + * \param[out] offset See below. + * \param[in] count Number of bytes to write + * + * Unlike the system call, this function will bufferise data in user-space, + * thus making it blocking and memory hungry. + * + * If offset is not NULL, then it points to a variable holding the file offset + * from which SSL_sendfile() will start reading data from in. When + * SSL_sendfile() returns, this variable will be set to the offset of the byte + * following the last byte that was read. + * + * \note This function exists in OpenSSL3[1]. It uses KTLS features which are + * far more superior that this function. We'll need to do the switch when + * OpenSSL3 becomes the SSL mainline. + * + * \return The number of bytes written to the SSL context + * + * [1] https://www.openssl.org/docs/manmaster/man3/SSL_sendfile.html + */ +ssize_t SSL_sendfile(SSL *out, int in, off_t *offset, size_t count); + +#endif /* PISTACHE_USE_SSL */ diff --git a/projects/frontend/server-webapi/pistache/include/pistache/version.h.in b/projects/frontend/server-webapi/pistache/include/pistache/version.h.in new file mode 100755 index 0000000000000000000000000000000000000000..89aa60984f897fcbd384d5cb10868296caf09930 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/version.h.in @@ -0,0 +1,18 @@ +/* version.h + Kip Warner, 25 May 2019 + + Version constants +*/ + +#pragma once + +namespace Pistache { +namespace Version { + + static constexpr int Major = @VERSION_MAJOR@; + static constexpr int Minor = @VERSION_MINOR@; + static constexpr int Patch = @VERSION_PATCH@; + static constexpr int Git = @VERSION_GIT_DATE@; +} // namespace Version +} // namespace Pistache + diff --git a/projects/frontend/server-webapi/pistache/include/pistache/view.h b/projects/frontend/server-webapi/pistache/include/pistache/view.h new file mode 100755 index 0000000000000000000000000000000000000000..6ada99472ac38f564f6493d33c0ccb73ab1bf4b6 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/include/pistache/view.h @@ -0,0 +1,204 @@ +/* view.h + Mathieu Stefani, 19 janvier 2016 + + A non-owning range of contiguous memory that is represented by + a pointer to the beginning of the memory and the size. +*/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Pistache { + +template class ViewBase { +public: + typedef T value_type; + typedef T &reference; + typedef T *pointer; + typedef const T *const_pointer; + typedef const T &const_reference; + + typedef const_pointer iterator; + typedef const_pointer const_iterator; + + explicit ViewBase(std::nullptr_t) : begin_(nullptr), size_(0) {} + + explicit ViewBase(std::nullptr_t, size_t) : begin_(nullptr), size_(0) {} + + explicit ViewBase(const T *begin, size_t size) : begin_(begin), size_(size) {} + + explicit ViewBase(const T *begin, const T *end) : begin_(begin), size_(0) { + if (begin > end) { + throw std::invalid_argument("begin > end"); + } + + size_ = std::distance(begin, end); + } + + size_t size() const { return size_; } + + const T &operator[](size_t index) { return begin_[index]; } + + const T &operator[](size_t index) const { return begin_[index]; } + + const T &at(size_t index) { + if (index >= size_) + throw std::invalid_argument("index > size"); + + return begin_[index]; + } + + const T &at(size_t index) const { + if (index >= size_) + throw std::invalid_argument("index > size"); + + return begin_[index]; + } + + const_iterator begin() const { return const_iterator(begin_); } + + const_iterator end() const { return const_iterator(begin_ + size_); } + + bool operator==(const ViewBase &other) const { + return size() == other.size() && std::equal(begin(), end(), other.begin()); + } + + bool operator!=(const ViewBase &other) const { return !(*this == other); } + + bool empty() const { return begin_ == nullptr || size_ == 0; } + + const T *data() const { return begin_; } + +private: + const T *begin_; + size_t size_; +}; + +template struct View : public ViewBase { + explicit View(std::nullptr_t) : ViewBase(nullptr) {} + + explicit View(std::nullptr_t, size_t) : ViewBase(nullptr, 0) {} + + explicit View(const T *begin, size_t size) : ViewBase(begin, size) {} + + explicit View(const T *begin, const T *end) : ViewBase(begin, end) {} +}; + +template <> struct View : public ViewBase { + + typedef ViewBase Base; + + explicit View(std::nullptr_t) : Base(nullptr) {} + + explicit View(const char *begin, size_t end) : Base(begin, end) {} + + explicit View(const char *begin, const char *end) : Base(begin, end) {} + + using Base::operator==; + + bool operator==(const char *str) const { + return equals(str, std::strlen(str)); + } + + bool operator==(const std::string &str) const { + return equals(str.c_str(), str.size()); + } + + template bool operator==(const char (&arr)[N]) const { + return equals(arr, N); + } + + std::string toString() const { return std::string(data(), size()); } + + operator std::string() const { return toString(); } + +private: + bool equals(const char *str, size_t length) const { + if (length != size()) + return false; + + return std::equal(begin(), end(), str); + } +}; + +typedef View StringView; + +namespace impl { +template struct ViewBuilder; + +template struct ViewBuilder> { + typedef T Type; + + static View build(const std::array &arr) { + return buildSized(arr, arr.size()); + } + + static View buildSized(const std::array &arr, size_t size) { + if (size > arr.size()) + throw std::invalid_argument("out of bounds size"); + + return View(arr.data(), size); + } +}; + +template struct ViewBuilder { + typedef T Type; + + static View build(T (&arr)[N]) { return buildSize(arr, N); } + + static View buildSize(T (&arr)[N], size_t size) { + if (size > N) + throw std::invalid_argument("out of bounds size"); + + return View(&arr[0], size); + } +}; + +template struct ViewBuilder> { + typedef T Type; + + static View build(const std::vector &vec) { + return buildSized(vec, vec.size()); + } + + static View buildSized(const std::vector &vec, size_t size) { + if (size > vec.size()) + throw std::invalid_argument("out of bounds size"); + + return View(vec.data(), size); + } +}; + +template <> struct ViewBuilder { + typedef std::string Type; + + static StringView build(const std::string &str) { + return buildSized(str, str.size()); + } + + static StringView buildSized(const std::string &str, size_t size) { + if (size > str.size()) + throw std::invalid_argument("out of bounds size"); + + return StringView(str.data(), size); + } +}; +} // namespace impl + +template +View::Type> make_view(const Cont &cont) { + return impl::ViewBuilder::build(cont); +} + +template +View::Type> make_view(const Cont &cont, + size_t size) { + return impl::ViewBuilder::buildSized(cont, size); +} + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/CMakeLists.txt b/projects/frontend/server-webapi/pistache/src/CMakeLists.txt new file mode 100755 index 0000000000000000000000000000000000000000..6801ac76f7447944bbdd40e83b96e1210431e8b3 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/CMakeLists.txt @@ -0,0 +1,80 @@ +file (GLOB COMMON_SOURCE_FILES "common/*.cc") +file (GLOB SERVER_SOURCE_FILES "server/*.cc") +file (GLOB CLIENT_SOURCE_FILES "client/*.cc") + +file (GLOB INCLUDE_FILES ${PROJECT_SOURCE_DIR}/include/pistache/*h) + +set(SOURCE_FILES + ${COMMON_SOURCE_FILES} + ${SERVER_SOURCE_FILES} + ${CLIENT_SOURCE_FILES} + ${INCLUDE_FILES} +) + +add_library(pistache OBJECT ${SOURCE_FILES}) +set_target_properties(pistache PROPERTIES POSITION_INDEPENDENT_CODE 1) +add_definitions(-DONLY_C_LOCALE=1) + +target_include_directories(pistache PUBLIC + $ +) + +set(include_install_dir ${CMAKE_INSTALL_INCLUDEDIR}) +set(lib_install_dir ${CMAKE_INSTALL_LIBDIR}) +set(bin_install_dir ${CMAKE_INSTALL_BINDIR}) + +add_library(pistache_shared SHARED $) +add_library(pistache_static STATIC $) + +target_link_libraries(pistache_shared Threads::Threads) +target_link_libraries(pistache_static Threads::Threads) + +set(Pistache_OUTPUT_NAME "pistache") +set_target_properties(pistache_shared PROPERTIES + OUTPUT_NAME ${Pistache_OUTPUT_NAME} + VERSION ${version} + SOVERSION ${VERSION_MAJOR} +) + +set_target_properties(pistache_static PROPERTIES + OUTPUT_NAME ${Pistache_OUTPUT_NAME} +) + +if (PISTACHE_INSTALL) + set(Pistache_CMAKE_INSTALL_PATH "${CMAKE_INSTALL_LIBDIR}/cmake/pistache") + set(Pistache_CONFIG_FILE "PistacheConfig.cmake") + + install( + TARGETS pistache_shared + EXPORT ${targets_export_name} + ARCHIVE DESTINATION ${lib_install_dir} + LIBRARY DESTINATION ${lib_install_dir} + RUNTIME DESTINATION ${bin_install_dir} + INCLUDES DESTINATION ${include_install_dir}) + + install( + DIRECTORY "${PROJECT_SOURCE_DIR}/include/pistache" + DESTINATION ${include_install_dir} + FILES_MATCHING PATTERN "*.*h") + install(TARGETS pistache_static + EXPORT PistacheTargets + DESTINATION ${CMAKE_INSTALL_LIBDIR}) + install(EXPORT PistacheTargets + DESTINATION ${Pistache_CMAKE_INSTALL_PATH} + EXPORT_LINK_INTERFACE_LIBRARIES + COMPONENT cmake-config + ) + + include(CMakePackageConfigHelpers) + configure_package_config_file( + "${Pistache_CONFIG_FILE}.in" + "${CMAKE_CURRENT_BINARY_DIR}/${Pistache_CONFIG_FILE}" + INSTALL_DESTINATION ${Pistache_CMAKE_INSTALL_PATH} + PATH_VARS include_install_dir lib_install_dir + ) + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/${Pistache_CONFIG_FILE}" + DESTINATION ${Pistache_CMAKE_INSTALL_PATH} + COMPONENT cmake-config + ) +endif() diff --git a/projects/frontend/server-webapi/pistache/src/PistacheConfig.cmake.in b/projects/frontend/server-webapi/pistache/src/PistacheConfig.cmake.in new file mode 100755 index 0000000000000000000000000000000000000000..973411c14539c2e687b38c53b76651ba01399e9a --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/PistacheConfig.cmake.in @@ -0,0 +1,9 @@ +@PACKAGE_INIT@ + +set_and_check ( Pistache_INCLUDE_DIRS "@PACKAGE_include_install_dir@") +include_directories(${Pistache_INCLUDE_DIRS}) + +set_and_check ( Pistache_LIBRARIES "@PACKAGE_lib_install_dir@") +link_directories(${Pistache_LIBRARIES}) + +include("${CMAKE_CURRENT_LIST_DIR}/PistacheTargets.cmake") diff --git a/projects/frontend/server-webapi/pistache/src/client/client.cc b/projects/frontend/server-webapi/pistache/src/client/client.cc new file mode 100755 index 0000000000000000000000000000000000000000..83fb79607cfd210abb528ed8ecf88306de86a69d --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/client/client.cc @@ -0,0 +1,920 @@ +/* + Mathieu Stefani, 29 janvier 2016 + + Implementation of the Http client +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Pistache { + +using namespace Polling; + +namespace Http { + +static constexpr const char *UA = "pistache/0.1"; + +namespace { +std::pair splitUrl(const std::string &url) { + RawStreamBuf buf(const_cast(url.data()), url.size()); + StreamCursor cursor(&buf); + + match_string("http://", cursor); + match_string("www", cursor); + match_literal('.', cursor); + + StreamCursor::Token hostToken(cursor); + match_until({'?', '/'}, cursor); + + StringView host(hostToken.rawText(), hostToken.size()); + StringView page(cursor.offset(), buf.endptr()); + + return std::make_pair(std::move(host), std::move(page)); +} +} // namespace + +namespace { +template +void writeHeader(std::stringstream &streamBuf, Args &&... args) { + using Http::crlf; + + H header(std::forward(args)...); + + streamBuf << H::Name << ": "; + header.write(streamBuf); + + streamBuf << crlf; +} + +void writeHeaders(std::stringstream &streamBuf, + const Http::Header::Collection &headers) { + using Http::crlf; + + for (const auto &header : headers.list()) { + streamBuf << header->name() << ": "; + header->write(streamBuf); + streamBuf << crlf; + } +} + +void writeCookies(std::stringstream &streamBuf, + const Http::CookieJar &cookies) { + using Http::crlf; + + streamBuf << "Cookie: "; + bool first = true; + for (const auto &cookie : cookies) { + if (!first) { + streamBuf << "; "; + } else { + first = false; + } + streamBuf << cookie.name << "=" << cookie.value; + } + + streamBuf << crlf; +} + +void writeRequest(std::stringstream &streamBuf, const Http::Request &request) { + using Http::crlf; + + auto res = request.resource(); + auto s = splitUrl(res); + auto body = request.body(); + auto query = request.query(); + + auto host = s.first; + auto path = s.second; + + auto pathStr = path.toString(); + + streamBuf << request.method() << " "; + if (pathStr[0] != '/') + streamBuf << '/'; + + streamBuf << pathStr << query.as_str(); + streamBuf << " HTTP/1.1" << crlf; + + writeCookies(streamBuf, request.cookies()); + writeHeaders(streamBuf, request.headers()); + + writeHeader(streamBuf, UA); + writeHeader(streamBuf, host.toString()); + if (!body.empty()) { + writeHeader(streamBuf, body.size()); + } + streamBuf << crlf; + + if (!body.empty()) { + streamBuf << body; + } +} +} // namespace + +class Transport : public Aio::Handler { +public: + PROTOTYPE_OF(Aio::Handler, Transport) + + Transport() = default; + Transport(const Transport &) + : requestsQueue(), connectionsQueue(), connections(), timeouts(), + timeoutsLock() {} + + void onReady(const Aio::FdSet &fds) override; + void registerPoller(Polling::Epoll &poller) override; + + Async::Promise asyncConnect(std::shared_ptr connection, + const struct sockaddr *address, + socklen_t addr_len); + + Async::Promise + asyncSendRequest(std::shared_ptr connection, + std::shared_ptr timer, std::string buffer); + +private: + enum WriteStatus { FirstTry, Retry }; + + struct ConnectionEntry { + ConnectionEntry(Async::Resolver resolve, Async::Rejection reject, + std::shared_ptr connection, + const struct sockaddr *_addr, socklen_t _addr_len) + : resolve(std::move(resolve)), reject(std::move(reject)), + connection(connection), addr_len(_addr_len) { + memcpy(&addr, _addr, addr_len); + } + + const sockaddr *getAddr() const { + return reinterpret_cast(&addr); + } + + Async::Resolver resolve; + Async::Rejection reject; + std::weak_ptr connection; + sockaddr_storage addr; + socklen_t addr_len; + }; + + struct RequestEntry { + RequestEntry(Async::Resolver resolve, Async::Rejection reject, + std::shared_ptr connection, + std::shared_ptr timer, std::string buf) + : resolve(std::move(resolve)), reject(std::move(reject)), + connection(connection), timer(timer), buffer(std::move(buf)) {} + + Async::Resolver resolve; + Async::Rejection reject; + std::weak_ptr connection; + std::shared_ptr timer; + std::string buffer; + }; + + PollableQueue requestsQueue; + PollableQueue connectionsQueue; + + std::unordered_map connections; + std::unordered_map> timeouts; + + using Lock = std::mutex; + using Guard = std::lock_guard; + Lock timeoutsLock; + +private: + void asyncSendRequestImpl(const RequestEntry &req, + WriteStatus status = FirstTry); + + void handleRequestsQueue(); + void handleConnectionQueue(); + void handleReadableEntry(const Aio::FdSet::Entry &entry); + void handleWritableEntry(const Aio::FdSet::Entry &entry); + void handleHangupEntry(const Aio::FdSet::Entry &entry); + void handleIncoming(std::shared_ptr connection); +}; + +void Transport::onReady(const Aio::FdSet &fds) { + for (const auto &entry : fds) { + if (entry.getTag() == connectionsQueue.tag()) { + handleConnectionQueue(); + } else if (entry.getTag() == requestsQueue.tag()) { + handleRequestsQueue(); + } else if (entry.isReadable()) { + handleReadableEntry(entry); + } else if (entry.isWritable()) { + handleWritableEntry(entry); + } else if (entry.isHangup()) { + handleHangupEntry(entry); + } else { + assert(false && "Unexpected event in entry"); + } + } +} + +void Transport::registerPoller(Polling::Epoll &poller) { + requestsQueue.bind(poller); + connectionsQueue.bind(poller); +} + +Async::Promise +Transport::asyncConnect(std::shared_ptr connection, + const struct sockaddr *address, socklen_t addr_len) { + return Async::Promise( + [=](Async::Resolver &resolve, Async::Rejection &reject) { + ConnectionEntry entry(std::move(resolve), std::move(reject), connection, + address, addr_len); + connectionsQueue.push(std::move(entry)); + }); +} + +Async::Promise +Transport::asyncSendRequest(std::shared_ptr connection, + std::shared_ptr timer, + std::string buffer) { + + return Async::Promise( + [&](Async::Resolver &resolve, Async::Rejection &reject) { + auto ctx = context(); + RequestEntry req(std::move(resolve), std::move(reject), connection, + timer, std::move(buffer)); + if (std::this_thread::get_id() != ctx.thread()) { + requestsQueue.push(std::move(req)); + } else { + asyncSendRequestImpl(req); + } + }); +} + +void Transport::asyncSendRequestImpl(const RequestEntry &req, + WriteStatus status) { + const auto &buffer = req.buffer; + auto conn = req.connection.lock(); + if (!conn) + throw std::runtime_error("Send request error"); + + auto fd = conn->fd(); + + ssize_t totalWritten = 0; + for (;;) { + const char *data = buffer.data() + totalWritten; + const ssize_t len = buffer.size() - totalWritten; + const ssize_t bytesWritten = ::send(fd, data, len, 0); + if (bytesWritten < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (status == FirstTry) { + throw std::runtime_error("Unimplemented, fix me!"); + } + reactor()->modifyFd(key(), fd, NotifyOn::Write, Polling::Mode::Edge); + } else { + conn->handleError("Could not send request"); + } + break; + } else { + totalWritten += bytesWritten; + if (totalWritten == len) { + if (req.timer) { + Guard guard(timeoutsLock); + timeouts.insert(std::make_pair(req.timer->fd(), conn)); + req.timer->registerReactor(key(), reactor()); + } + req.resolve(totalWritten); + break; + } + } + } +} + +void Transport::handleRequestsQueue() { + // Let's drain the queue + for (;;) { + auto req = requestsQueue.popSafe(); + if (!req) + break; + + asyncSendRequestImpl(*req); + } +} + +void Transport::handleConnectionQueue() { + for (;;) { + auto data = connectionsQueue.popSafe(); + if (!data) + break; + + auto conn = data->connection.lock(); + if (!conn) { + data->reject(Error::system("Failed to connect")); + continue; + } + + int res = ::connect(conn->fd(), data->getAddr(), data->addr_len); + if (res == -1) { + if (errno == EINPROGRESS) { + reactor()->registerFdOneShot(key(), conn->fd(), + NotifyOn::Write | NotifyOn::Hangup | + NotifyOn::Shutdown); + } else { + data->reject(Error::system("Failed to connect")); + continue; + } + } + connections.insert(std::make_pair(conn->fd(), std::move(*data))); + } +} + +void Transport::handleReadableEntry(const Aio::FdSet::Entry &entry) { + assert(entry.isReadable() && "Entry must be readable"); + + auto tag = entry.getTag(); + auto fd = static_cast(tag.value()); + auto connIt = connections.find(fd); + if (connIt != std::end(connections)) { + auto connection = connIt->second.connection.lock(); + if (connection) { + handleIncoming(connection); + } else { + throw std::runtime_error( + "Connection error: problem with reading data from server"); + } + } else { + Guard guard(timeoutsLock); + auto timerIt = timeouts.find(fd); + if (timerIt != std::end(timeouts)) { + auto connection = timerIt->second.lock(); + if (connection) { + connection->handleTimeout(); + timeouts.erase(fd); + } + } + } +} + +void Transport::handleWritableEntry(const Aio::FdSet::Entry &entry) { + assert(entry.isWritable() && "Entry must be writable"); + + auto tag = entry.getTag(); + auto fd = static_cast(tag.value()); + auto connIt = connections.find(fd); + if (connIt != std::end(connections)) { + auto &connectionEntry = connIt->second; + auto connection = connIt->second.connection.lock(); + if (connection) { + connectionEntry.resolve(); + // We are connected, we can start reading data now + reactor()->modifyFd(key(), connection->fd(), NotifyOn::Read); + } else { + connectionEntry.reject(Error::system("Connection lost")); + } + } else { + throw std::runtime_error("Unknown fd"); + } +} + +void Transport::handleHangupEntry(const Aio::FdSet::Entry &entry) { + assert(entry.isHangup() && "Entry must be hangup"); + + auto tag = entry.getTag(); + auto fd = static_cast(tag.value()); + auto connIt = connections.find(fd); + if (connIt != std::end(connections)) { + auto &connectionEntry = connIt->second; + connectionEntry.reject(Error::system("Could not connect")); + } else { + throw std::runtime_error("Unknown fd"); + } +} + +void Transport::handleIncoming(std::shared_ptr connection) { + ssize_t totalBytes = 0; + + for (;;) { + char buffer[Const::MaxBuffer] = { + 0, + }; + const ssize_t bytes = recv(connection->fd(), buffer, Const::MaxBuffer, 0); + if (bytes == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + connection->handleError(strerror(errno)); + } + break; + } else if (bytes == 0) { + if (totalBytes == 0) { + connection->handleError("Remote closed connection"); + } + connections.erase(connection->fd()); + connection->close(); + break; + } else { + totalBytes += bytes; + connection->handleResponsePacket(buffer, bytes); + } + } +} + +Connection::Connection(size_t maxResponseSize) + : fd_(-1), requestEntry(nullptr), parser(maxResponseSize) { + state_.store(static_cast(State::Idle)); + connectionState_.store(NotConnected); +} + +void Connection::connect(const Address &addr) { + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = addr.family(); + hints.ai_socktype = SOCK_STREAM; /* Stream socket */ + hints.ai_flags = 0; + hints.ai_protocol = 0; + + const auto &host = addr.host(); + const auto &port = addr.port().toString(); + + AddrInfo addressInfo; + + TRY(addressInfo.invoke(host.c_str(), port.c_str(), &hints)); + const addrinfo *addrs = addressInfo.get_info_ptr(); + + int sfd = -1; + + for (const addrinfo *addr = addrs; addr; addr = addr->ai_next) { + sfd = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (sfd < 0) + continue; + + make_non_blocking(sfd); + + connectionState_.store(Connecting); + fd_ = sfd; + + transport_ + ->asyncConnect(shared_from_this(), addr->ai_addr, addr->ai_addrlen) + .then( + [=]() { + socklen_t len = sizeof(saddr); + getsockname(sfd, (struct sockaddr *)&saddr, &len); + connectionState_.store(Connected); + processRequestQueue(); + }, + PrintException()); + break; + } + + if (sfd < 0) + throw std::runtime_error("Failed to connect"); +} + +std::string Connection::dump() const { + std::ostringstream oss; + oss << "Connection(fd = " << fd_ << ", src_port = "; + oss << ntohs(saddr.sin_port) << ")"; + return oss.str(); +} + +bool Connection::isIdle() const { + return static_cast(state_.load()) == + Connection::State::Idle; +} + +bool Connection::tryUse() { + auto curState = static_cast(Connection::State::Idle); + auto newState = static_cast(Connection::State::Used); + return state_.compare_exchange_strong(curState, newState); +} + +void Connection::setAsIdle() { + state_.store(static_cast(Connection::State::Idle)); +} + +bool Connection::isConnected() const { + return connectionState_.load() == Connected; +} + +void Connection::close() { + connectionState_.store(NotConnected); + ::close(fd_); +} + +void Connection::associateTransport( + const std::shared_ptr &transport) { + if (transport_) + throw std::runtime_error( + "A transport has already been associated to the connection"); + + transport_ = transport; +} + +bool Connection::hasTransport() const { return transport_ != nullptr; } + +Fd Connection::fd() const { + assert(fd_ != -1); + return fd_; +} + +void Connection::handleResponsePacket(const char *buffer, size_t totalBytes) { + try { + const bool result = parser.feed(buffer, totalBytes); + if (!result) { + handleError("Client: Too long packet"); + return; + } + if (parser.parse() == Private::State::Done) { + if (requestEntry) { + if (requestEntry->timer) { + requestEntry->timer->disarm(); + timerPool_.releaseTimer(requestEntry->timer); + } + + requestEntry->resolve(std::move(parser.response)); + parser.reset(); + + auto onDone = requestEntry->onDone; + + requestEntry.reset(nullptr); + + if (onDone) + onDone(); + } + } + } catch (const std::exception &ex) { + handleError(ex.what()); + } +} + +void Connection::handleError(const char *error) { + if (requestEntry) { + if (requestEntry->timer) { + requestEntry->timer->disarm(); + timerPool_.releaseTimer(requestEntry->timer); + } + + auto onDone = requestEntry->onDone; + + requestEntry->reject(Error(error)); + + requestEntry.reset(nullptr); + + if (onDone) + onDone(); + } +} + +void Connection::handleTimeout() { + if (requestEntry) { + requestEntry->timer->disarm(); + timerPool_.releaseTimer(requestEntry->timer); + + auto onDone = requestEntry->onDone; + + /* @API: create a TimeoutException */ + requestEntry->reject(std::runtime_error("Timeout")); + + requestEntry.reset(nullptr); + + if (onDone) + onDone(); + } +} + +Async::Promise Connection::perform(const Http::Request &request, + Connection::OnDone onDone) { + return Async::Promise( + [=](Async::Resolver &resolve, Async::Rejection &reject) { + performImpl(request, std::move(resolve), std::move(reject), + std::move(onDone)); + }); +} + +Async::Promise Connection::asyncPerform(const Http::Request &request, + Connection::OnDone onDone) { + return Async::Promise( + [=](Async::Resolver &resolve, Async::Rejection &reject) { + requestsQueue.push(RequestData(std::move(resolve), std::move(reject), + request, std::move(onDone))); + }); +} + +void Connection::performImpl(const Http::Request &request, + Async::Resolver resolve, Async::Rejection reject, + Connection::OnDone onDone) { + + std::stringstream streamBuf; + writeRequest(streamBuf, request); + if (!streamBuf) + reject(std::runtime_error("Could not write request")); + std::string buffer = streamBuf.str(); + + std::shared_ptr timer(nullptr); + auto timeout = request.timeout(); + if (timeout.count() > 0) { + timer = timerPool_.pickTimer(); + timer->arm(timeout); + } + + requestEntry.reset(new RequestEntry(std::move(resolve), std::move(reject), + timer, std::move(onDone))); + transport_->asyncSendRequest(shared_from_this(), timer, std::move(buffer)); +} + +void Connection::processRequestQueue() { + for (;;) { + auto req = requestsQueue.popSafe(); + if (!req) + break; + + performImpl(req->request, std::move(req->resolve), std::move(req->reject), + std::move(req->onDone)); + } +} + +void ConnectionPool::init(size_t maxConnectionsPerHost, + size_t maxResponseSize) { + this->maxConnectionsPerHost = maxConnectionsPerHost; + this->maxResponseSize = maxResponseSize; +} + +std::shared_ptr +ConnectionPool::pickConnection(const std::string &domain) { + Connections pool; + + { + Guard guard(connsLock); + auto poolIt = conns.find(domain); + if (poolIt == std::end(conns)) { + Connections connections; + for (size_t i = 0; i < maxConnectionsPerHost; ++i) { + connections.push_back(std::make_shared(maxResponseSize)); + } + + poolIt = + conns.insert(std::make_pair(domain, std::move(connections))).first; + } + pool = poolIt->second; + } + + for (auto &conn : pool) { + if (conn->tryUse()) { + return conn; + } + } + + return nullptr; +} + +void ConnectionPool::releaseConnection( + const std::shared_ptr &connection) { + connection->setAsIdle(); +} + +size_t ConnectionPool::usedConnections(const std::string &domain) const { + Connections pool; + { + Guard guard(connsLock); + auto it = conns.find(domain); + if (it == std::end(conns)) { + return 0; + } + pool = it->second; + } + + return std::count_if(pool.begin(), pool.end(), + [](const std::shared_ptr &conn) { + return conn->isConnected(); + }); +} + +size_t ConnectionPool::idleConnections(const std::string &domain) const { + Connections pool; + { + Guard guard(connsLock); + auto it = conns.find(domain); + if (it == std::end(conns)) { + return 0; + } + pool = it->second; + } + + return std::count_if( + pool.begin(), pool.end(), + [](const std::shared_ptr &conn) { return conn->isIdle(); }); +} + +size_t ConnectionPool::availableConnections(const std::string &domain) const { + UNUSED(domain) + return 0; +} + +void ConnectionPool::closeIdleConnections(const std::string &domain){ + UNUSED(domain)} + +RequestBuilder &RequestBuilder::method(Method method) { + request_.method_ = method; + return *this; +} + +RequestBuilder &RequestBuilder::resource(const std::string &val) { + request_.resource_ = val; + return *this; +} + +RequestBuilder &RequestBuilder::params(const Uri::Query &query) { + request_.query_ = query; + return *this; +} + +RequestBuilder & +RequestBuilder::header(const std::shared_ptr &header) { + request_.headers_.add(header); + return *this; +} + +RequestBuilder &RequestBuilder::cookie(const Cookie &cookie) { + request_.cookies_.add(cookie); + return *this; +} + +RequestBuilder &RequestBuilder::body(const std::string &val) { + request_.body_ = val; + return *this; +} + +RequestBuilder &RequestBuilder::body(std::string &&val) { + request_.body_ = std::move(val); + return *this; +} + +RequestBuilder &RequestBuilder::timeout(std::chrono::milliseconds val) { + request_.timeout_ = val; + return *this; +} + +Async::Promise RequestBuilder::send() { + return client_->doRequest(request_); +} + +Client::Options &Client::Options::threads(int val) { + threads_ = val; + return *this; +} + +Client::Options &Client::Options::keepAlive(bool val) { + keepAlive_ = val; + return *this; +} + +Client::Options &Client::Options::maxConnectionsPerHost(int val) { + maxConnectionsPerHost_ = val; + return *this; +} + +Client::Options &Client::Options::maxResponseSize(size_t val) { + maxResponseSize_ = val; + return *this; +} + +Client::Client() + : reactor_(Aio::Reactor::create()), pool(), transportKey(), ioIndex(0), + queuesLock(), requestsQueues(), stopProcessPequestsQueues(false) {} + +Client::~Client() { + assert(stopProcessPequestsQueues == true && + "You must explicitly call shutdown method of Client object"); +} + +Client::Options Client::options() { return Client::Options(); } + +void Client::init(const Client::Options &options) { + pool.init(options.maxConnectionsPerHost_, options.maxResponseSize_); + reactor_->init(Aio::AsyncContext(options.threads_)); + transportKey = reactor_->addHandler(std::make_shared()); + reactor_->run(); +} + +void Client::shutdown() { + reactor_->shutdown(); + Guard guard(queuesLock); + stopProcessPequestsQueues = true; +} + +RequestBuilder Client::get(const std::string &resource) { + return prepareRequest(resource, Http::Method::Get); +} + +RequestBuilder Client::post(const std::string &resource) { + return prepareRequest(resource, Http::Method::Post); +} + +RequestBuilder Client::put(const std::string &resource) { + return prepareRequest(resource, Http::Method::Put); +} + +RequestBuilder Client::patch(const std::string &resource) { + return prepareRequest(resource, Http::Method::Patch); +} + +RequestBuilder Client::del(const std::string &resource) { + return prepareRequest(resource, Http::Method::Delete); +} + +RequestBuilder Client::prepareRequest(const std::string &resource, + Http::Method method) { + RequestBuilder builder(this); + builder.resource(resource).method(method); + + return builder; +} + +Async::Promise Client::doRequest(Http::Request request) { + // request.headers_.add(ConnectionControl::KeepAlive); + request.headers().remove(); + auto resourceData = request.resource(); + + auto resource = splitUrl(resourceData); + auto conn = pool.pickConnection(resource.first); + + if (conn == nullptr) { + return Async::Promise([this, resource = std::move(resource), + request](Async::Resolver &resolve, + Async::Rejection &reject) { + Guard guard(queuesLock); + + auto data = std::make_shared( + std::move(resolve), std::move(reject), std::move(request), nullptr); + auto &queue = requestsQueues[resource.first]; + if (!queue.enqueue(data)) + data->reject(std::runtime_error("Queue is full")); + }); + } else { + + if (!conn->hasTransport()) { + auto transports = reactor_->handlers(transportKey); + auto index = ioIndex.fetch_add(1) % transports.size(); + + auto transport = std::static_pointer_cast(transports[index]); + conn->associateTransport(transport); + } + + if (!conn->isConnected()) { + std::weak_ptr weakConn = conn; + auto res = conn->asyncPerform(request, [this, weakConn]() { + auto conn = weakConn.lock(); + if (conn) { + pool.releaseConnection(conn); + processRequestQueue(); + } + }); + conn->connect(helpers::httpAddr(resource.first)); + return res; + } + + std::weak_ptr weakConn = conn; + return conn->perform(request, [this, weakConn]() { + auto conn = weakConn.lock(); + if (conn) { + pool.releaseConnection(conn); + processRequestQueue(); + } + }); + } +} + +void Client::processRequestQueue() { + Guard guard(queuesLock); + + if (stopProcessPequestsQueues) + return; + + for (auto &queues : requestsQueues) { + for (;;) { + const auto &domain = queues.first; + auto conn = pool.pickConnection(domain); + if (!conn) + break; + + auto &queue = queues.second; + std::shared_ptr data; + if (!queue.dequeue(data)) { + pool.releaseConnection(conn); + break; + } + + conn->performImpl(data->request, std::move(data->resolve), + std::move(data->reject), [this, conn]() { + pool.releaseConnection(conn); + processRequestQueue(); + }); + } + } +} + +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/base64.cc b/projects/frontend/server-webapi/pistache/src/common/base64.cc new file mode 100755 index 0000000000000000000000000000000000000000..c3f91c0e68659e347a1cd313818e8069ccaab001 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/base64.cc @@ -0,0 +1,287 @@ +/* + Copyright (C) 2019-2020, Kip Warner. + Released under the terms of Apache License 2.0 +*/ + +// Includes... + +// Our headers... +#include + +// Standard C++ / POSIX system headers... +#include +#include +#include +#include +#include + +// Using the standard namespace and Pistache... +using namespace std; + +// Calculate length of decoded raw bytes from that would be generated if the +// base 64 encoded input buffer was decoded. This is not a static method +// because we need to examine the string... +vector::size_type Base64Decoder::CalculateDecodedSize() const { + // If encoded size was zero, so is decoded size... + if (m_Base64EncodedString.size() == 0) + return 0; + + // If non-zero, should always be at least four characters... + if (m_Base64EncodedString.size() < 4) + throw runtime_error( + "Base64 encoded stream should always be at least four bytes."); + + // ...and always a multiple of four bytes because every three decoded bytes + // produce four encoded base 64 bytes, which may include padding... + if ((m_Base64EncodedString.size() % 4) != 0) + throw runtime_error("Base64 encoded stream length should always be evenly " + "divisible by four."); + + // Iterator to walk the encoded string from the beginning... + auto EndIterator = m_Base64EncodedString.begin(); + + // Keep walking along the input buffer trying to decode characters, but + // without storing them, until we hit the first character we cannot decode. + // This should be the first padding character or end of string... + while (DecodeCharacter(*EndIterator) < static_cast(64)) + ++EndIterator; + + // The length of the encoded string is the distance from the beginning to + // the first non-decodable character, such as padding... + const auto InputSize = distance(m_Base64EncodedString.begin(), EndIterator); + + // Calculate decoded size before account for any more decoded bytes within + // the trailing padding block... + const auto DecodedSize = InputSize / 4 * 3; + + // True decoded size depends on how much padding needed to be applied... + switch (InputSize % 4) { + case 2: + return DecodedSize + 1; + case 3: + return DecodedSize + 2; + default: + return DecodedSize; + } +} + +// Decode base 64 encoding into raw bytes... +const vector &Base64Decoder::Decode() { + // Calculate required size of output buffer... + const auto DecodedSize = CalculateDecodedSize(); + + // Allocate sufficient storage... + m_DecodedData = vector(DecodedSize, byte(0x00)); + m_DecodedData.shrink_to_fit(); + + // Initialize decode input and output iterators... + string::size_type InputOffset = 0; + string::size_type OutputOffset = 0; + + // While there is at least one set of three octets remaining to decode... + for (string::size_type Index = 2; Index < DecodedSize; Index += 3) { + // Construct octets from sextets... + m_DecodedData.at(OutputOffset + 0) = static_cast( + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 0)) << 2 | + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 1)) >> 4); + m_DecodedData.at(OutputOffset + 1) = static_cast( + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 1)) << 4 | + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 2)) >> 2); + m_DecodedData.at(OutputOffset + 2) = static_cast( + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 2)) << 6 | + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 3))); + + // Reseek i/o pointers... + InputOffset += 4; + OutputOffset += 3; + } + + // There's less than three octets remaining... + switch (DecodedSize % 3) { + // One octet left to construct... + case 1: + m_DecodedData.at(OutputOffset + 0) = static_cast( + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 0)) << 2 | + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 1)) >> 4); + break; + + // Two octets left to construct... + case 2: + m_DecodedData.at(OutputOffset + 0) = static_cast( + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 0)) << 2 | + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 1)) >> 4); + m_DecodedData.at(OutputOffset + 1) = static_cast( + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 1)) << 4 | + DecodeCharacter(m_Base64EncodedString.at(InputOffset + 2)) >> 2); + break; + } + + // All done. Return constant reference to buffer containing decoded data... + return m_DecodedData; +} + +// Convert an octet character to corresponding sextet, provided it can safely be +// represented as such. Otherwise return 0xff... +inline byte +Base64Decoder::DecodeCharacter(const unsigned char Character) const { + // Capital letter 'A' is ASCII 65 and zero in base 64... + if ('A' <= Character && Character <= 'Z') + return static_cast(Character - 'A'); + + // Lowercase letter 'a' is ASCII 97 and 26 in base 64... + if ('a' <= Character && Character <= 'z') + return static_cast(Character - (97 - 26)); + + // Numeric digit '0' is ASCII 48 and 52 in base 64... + if ('0' <= Character && Character <= '9') + return static_cast(Character - (48 - 52)); + + // '+' is ASCII 43 and 62 in base 64... + if (Character == '+') + return static_cast(62); + + // '/' is ASCII 47 and 63 in base 64... + if (Character == '/') + return static_cast(63); + + // Anything else that's not a 6-bit representation, signal to caller... + return static_cast(255); +} + +// Calculate length of base 64 string that would need to be generated for raw +// data of a given length... +string::size_type Base64Encoder::CalculateEncodedSize( + const vector::size_type DecodedSize) noexcept { + // First term calcualtes the unpadded length. The bitwise and rounds up to + // the nearest multiple of four to add padding... + return ((4 * DecodedSize / 3) + 3) & ~3; +} + +// Encode raw data input buffer to base 64... +const string &Base64Encoder::Encode() noexcept { + // Allocate precise storage for the output buffer... + m_Base64EncodedString = + string(CalculateEncodedSize(m_InputBuffer.size()), '!'); + m_Base64EncodedString.shrink_to_fit(); + + // Number of complete octet triplets... + const auto OctetTriplets = m_InputBuffer.size() / 3; + + // Initialize encode input and output offset registers... + string::size_type InputOffset = 0; + string::size_type OutputOffset = 0; + + // While there are still complete octet triplets remaining... + for (string::size_type Index = 0; Index < OctetTriplets; ++Index) { + // Encode first sextet from first octet... + m_Base64EncodedString.at(OutputOffset + 0) = + EncodeByte(static_cast(m_InputBuffer.at(InputOffset + 0) >> 2)); + + // Encode second sextet from first and second octet.... + m_Base64EncodedString.at(OutputOffset + 1) = EncodeByte(static_cast( + (m_InputBuffer.at(InputOffset + 0) & static_cast(0x03)) << 4 | + m_InputBuffer.at(InputOffset + 1) >> 4)); + + // Encode third sextet from second and third octet... + m_Base64EncodedString.at(OutputOffset + 2) = EncodeByte(static_cast( + (m_InputBuffer.at(InputOffset + 1) & static_cast(0x0F)) << 2 | + m_InputBuffer.at(InputOffset + 2) >> 6)); + + // Encode fourth sextet from third octet... + m_Base64EncodedString.at(OutputOffset + 3) = + EncodeByte(m_InputBuffer.at(InputOffset + 2) & static_cast(0x3F)); + + // Stride i/o pointers... + InputOffset += 3; + OutputOffset += 4; + } + + // Since the length of padded base 64 encoding must always be a multiple of + // four, after the last octet triplet, were there any additional octets in + // the input to encode that were less than three in number? + switch (m_InputBuffer.size() % 3) { + // Exactly one trailing octet followed... + case 1: + + // Encode first sextet from remaining octet... + m_Base64EncodedString.at(OutputOffset + 0) = + EncodeByte(static_cast(m_InputBuffer.at(InputOffset + 0) >> 2)); + + // Encode second sextet from remaining octet and empty second one... + m_Base64EncodedString.at(OutputOffset + 1) = EncodeByte( + (m_InputBuffer.at(InputOffset + 0) & static_cast(0x03)) << 4); + + // Padd the two sextets with two padding characters to ensure the + // total length is a multiple of four... + m_Base64EncodedString.at(OutputOffset + 2) = '='; + m_Base64EncodedString.at(OutputOffset + 3) = '='; + break; + + // Exactly two trailing octets followed... + case 2: + + // Encode first sextet from first octet... + m_Base64EncodedString.at(OutputOffset + 0) = + EncodeByte(static_cast(m_InputBuffer.at(InputOffset + 0) >> 2)); + + // Encode second sextet from first and second octet... + m_Base64EncodedString.at(OutputOffset + 1) = EncodeByte(static_cast( + (m_InputBuffer.at(InputOffset + 0) & static_cast(0x03)) << 4 | + m_InputBuffer.at(InputOffset + 1) >> 4)); + + // Encode third sextet from second and dummy third octet... + m_Base64EncodedString.at(OutputOffset + 2) = EncodeByte( + (m_InputBuffer.at(InputOffset + 1) & static_cast(0x0F)) << 2); + + // Padd three sextets with a single padding character to ensure the + // total length is a multiple of four... + m_Base64EncodedString.at(OutputOffset + 3) = '='; + break; + } + + // Return constant reference to encoded data to caller... + return m_Base64EncodedString; +} + +// Encode single binary byte to 6-bit base 64 character... +inline unsigned char Base64Encoder::EncodeByte(const byte Byte) const { + // Capital letter 'A' is ASCII 65 and zero in base 64... + auto ch = static_cast(Byte); + if ( ch < 26) + return static_cast(ch + 'A'); + + // Lowercase letter 'a' is ASCII 97 and 26 in base 64... + if (ch < 52) + return static_cast(ch + 71); + + // Numeric digit '0' is ASCII 48 and 52 in base 64... + if (ch < 62) + return static_cast(ch - 4); + + // '+' is ASCII 43 and 62 in base 64... + if (ch == 62) + return '+'; + + // '/' is ASCII 47 and 63 in base 64... + if (ch == 63) + return '/'; + + // And lastly anything that can't be represented in 6-bits we return 64... + return 64; +} + +// Encode a string into base 64 format... +string Base64Encoder::EncodeString(const string &StringInput) { + // Allocate storage for binary form of message... + vector BinaryInput(StringInput.size()); + + // Convert message to binary form... + transform(StringInput.begin(), StringInput.end(), BinaryInput.begin(), + [](const char Character) { return byte(Character); }); + + // Encode to base 64... + Base64Encoder Encoder(BinaryInput); + + // Return encoded string to caller by value... + return Encoder.Encode(); +} diff --git a/projects/frontend/server-webapi/pistache/src/common/cookie.cc b/projects/frontend/server-webapi/pistache/src/common/cookie.cc new file mode 100755 index 0000000000000000000000000000000000000000..e9c07002fe2bacae294ef8642c6ec649c04c093c --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/cookie.cc @@ -0,0 +1,267 @@ +/* + Mathieu Stefani, 16 janvier 2016 + + Cookie implementation +*/ + +#include +#include +#include + +#include +#include + +namespace Pistache { +namespace Http { + +namespace { + +StreamCursor::Token matchValue(StreamCursor &cursor) { + int c; + if ((c = cursor.current()) != StreamCursor::Eof && c != '=') + throw std::runtime_error("Invalid cookie"); + + if (!cursor.advance(1)) + throw std::runtime_error("Invalid cookie, early eof"); + + StreamCursor::Token token(cursor); + match_until(';', cursor); + + return token; +} + +template struct AttributeMatcher; + +template <> struct AttributeMatcher> { + static void match(StreamCursor &cursor, Cookie *obj, + Optional Cookie::*attr) { + auto token = matchValue(cursor); + obj->*attr = Some(token.text()); + } +}; + +template <> struct AttributeMatcher> { + static void match(StreamCursor &cursor, Cookie *obj, + Optional Cookie::*attr) { + auto token = matchValue(cursor); + + auto strntol = [](const char *str, size_t len) { + int ret = 0; + for (size_t i = 0; i < len; ++i) { + if (!isdigit(str[i])) + throw std::invalid_argument("Invalid conversion"); + + ret *= 10; + ret += str[i] - '0'; + }; + + return ret; + }; + + obj->*attr = Some(strntol(token.rawText(), token.size())); + } +}; + +template <> struct AttributeMatcher { + static void match(StreamCursor &cursor, Cookie *obj, bool Cookie::*attr) { + UNUSED(cursor) + obj->*attr = true; + } +}; + +template <> struct AttributeMatcher> { + static void match(StreamCursor &cursor, Cookie *obj, + Optional Cookie::*attr) { + auto token = matchValue(cursor); + obj->*attr = Some(FullDate::fromString(token.text())); + } +}; + +template +bool match_attribute(const char *name, size_t len, StreamCursor &cursor, + Cookie *obj, T Cookie::*attr) { + if (match_string(name, len, cursor)) { + AttributeMatcher::match(cursor, obj, attr); + cursor.advance(1); + + return true; + } + + return false; +} + +} // namespace + +Cookie::Cookie(std::string name, std::string value) + : name(std::move(name)), value(std::move(value)), path(), domain(), + expires(), maxAge(), secure(false), httpOnly(false), ext() {} + +Cookie Cookie::fromRaw(const char *str, size_t len) { + RawStreamBuf<> buf(const_cast(str), len); + StreamCursor cursor(&buf); + + StreamCursor::Token nameToken(cursor); + + if (!match_until('=', cursor)) + throw std::runtime_error("Invalid cookie, missing value"); + + auto name_ = nameToken.text(); + + if (!cursor.advance(1)) + throw std::runtime_error("Invalid cookie, missing value"); + + StreamCursor::Token valueToken(cursor); + + match_until(';', cursor); + auto value_ = valueToken.text(); + + Cookie cookie(std::move(name_), std::move(value_)); + if (cursor.eof()) { + return cookie; + } + + cursor.advance(1); + +#define STR(str) str, sizeof(str) - 1 + + do { + skip_whitespaces(cursor); + + if (match_attribute(STR("Path"), cursor, &cookie, &Cookie::path)) + ; + else if (match_attribute(STR("Domain"), cursor, &cookie, &Cookie::domain)) + ; + else if (match_attribute(STR("Secure"), cursor, &cookie, &Cookie::secure)) + ; + else if (match_attribute(STR("HttpOnly"), cursor, &cookie, + &Cookie::httpOnly)) + ; + else if (match_attribute(STR("Max-Age"), cursor, &cookie, &Cookie::maxAge)) + ; + else if (match_attribute(STR("Expires"), cursor, &cookie, &Cookie::expires)) + ; + // ext + else { + StreamCursor::Token nameToken_(cursor); + match_until('=', cursor); + + auto name = nameToken_.text(); + std::string value; + if (!cursor.eof()) { + auto token = matchValue(cursor); + value = token.text(); + } + cookie.ext.insert(std::make_pair(std::move(name), std::move(value))); + } + + } while (!cursor.eof()); + +#undef STR + + return cookie; +} + +Cookie Cookie::fromString(const std::string &str) { + return Cookie::fromRaw(str.c_str(), str.size()); +} + +void Cookie::write(std::ostream &os) const { + os << name << "=" << value; + optionally_do(path, [&](const std::string &value) { + os << "; "; + os << "Path=" << value; + }); + optionally_do(domain, [&](const std::string &value) { + os << "; "; + os << "Domain=" << value; + }); + optionally_do(maxAge, [&](int value) { + os << "; "; + os << "Max-Age=" << value; + }); + optionally_do(expires, [&](const FullDate &value) { + os << "; "; + os << "Expires="; + value.write(os); + }); + if (secure) + os << "; Secure"; + if (httpOnly) + os << "; HttpOnly"; + if (!ext.empty()) { + os << "; "; + for (auto it = std::begin(ext), end = std::end(ext); it != end; ++it) { + os << it->first << "=" << it->second; + if (std::distance(it, end) > 1) + os << "; "; + } + } +} + +std::ostream &operator<<(std::ostream &os, const Cookie &cookie) { + cookie.write(os); + return os; +} + +CookieJar::CookieJar() : cookies() {} + +void CookieJar::add(const Cookie &cookie) { + + std::string cookieName = cookie.name; + std::string cookieValue = cookie.value; + + Storage::iterator it = cookies.find(cookieName); + if (it == cookies.end()) { + HashMapCookies hashmapWithFirstCookie; + hashmapWithFirstCookie.insert(std::make_pair(cookieValue, cookie)); + cookies.insert(std::make_pair(cookieName, hashmapWithFirstCookie)); + } else { + it->second.insert(std::make_pair(cookieValue, cookie)); + } +} + +void CookieJar::removeAllCookies() { cookies.clear(); } + +void CookieJar::addFromRaw(const char *str, size_t len) { + RawStreamBuf<> buf(const_cast(str), len); + StreamCursor cursor(&buf); + + while (!cursor.eof()) { + StreamCursor::Token nameToken(cursor); + + if (!match_until('=', cursor)) + throw std::runtime_error("Invalid cookie, missing value"); + + auto name = nameToken.text(); + + if (!cursor.advance(1)) + throw std::runtime_error("Invalid cookie, missing value"); + + StreamCursor::Token valueToken(cursor); + + match_until(';', cursor); + auto value = valueToken.text(); + + Cookie cookie(std::move(name), std::move(value)); + add(cookie); + + cursor.advance(1); + skip_whitespaces(cursor); + } +} + +Cookie CookieJar::get(const std::string &name) const { + Storage::const_iterator it = cookies.find(name); + if (it != cookies.end()) { + return it->second.begin() + ->second; // it returns begin(), first element, could be changed. + } + throw std::runtime_error("Could not find requested cookie"); +} + +bool CookieJar::has(const std::string &name) const { + return cookies.find(name) != cookies.end(); +} + +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/description.cc b/projects/frontend/server-webapi/pistache/src/common/description.cc new file mode 100755 index 0000000000000000000000000000000000000000..fa2a251c5e613ca6b81fc63323491bc625f4526b --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/description.cc @@ -0,0 +1,414 @@ +/* description.cc + Mathieu Stefani, 24 février 2016 + + Implementation of the description system +*/ + +#include +#include +#include + +#include +#include + +namespace Pistache { +namespace Rest { + +const char *schemeString(Scheme scheme) { + switch (scheme) { +#define SCHEME(e, str) \ + case Scheme::e: \ + return str; + SCHEMES +#undef SCHEME + } + + return nullptr; +} + +namespace Schema { + +Contact::Contact(std::string name, std::string url, std::string email) + : name(std::move(name)), url(std::move(url)), email(std::move(email)) {} + +License::License(std::string name, std::string url) + : name(std::move(name)), url(std::move(url)) {} + +Info::Info(std::string title, std::string version, std::string description) + : title(std::move(title)), version(std::move(version)), + description(std::move(description)), termsOfService(), contact(), + license() {} + +PathDecl::PathDecl(std::string value, Http::Method method) + : value(std::move(value)), method(method) {} + +Path::Path(std::string value, Http::Method method, std::string description) + : value(std::move(value)), method(method), + description(std::move(description)), hidden(false), pc(), parameters(), + responses(), handler() {} + +std::string Path::swaggerFormat(const std::string &path) { + if (path.empty()) + return ""; + if (path[0] != '/') + throw std::invalid_argument("Invalid path, should start with a '/'"); + + /* @CodeDup: re-use the private Fragment class of Router */ + auto isPositional = [](const std::string &fragment) { + if (fragment[0] == ':') + return std::make_pair(true, fragment.substr(1)); + return std::make_pair(false, std::string()); + }; + + auto isOptional = [](const std::string &fragment) { + auto pos = fragment.find('?'); + // @Validation the '?' should be the last character + return std::make_pair(pos != std::string::npos, pos); + }; + + std::ostringstream oss; + + auto processFragment = [&](std::string fragment) { + auto optional = isOptional(fragment); + if (optional.first) + fragment.erase(optional.second, 1); + + auto positional = isPositional(fragment); + if (positional.first) { + oss << '{' << positional.second << '}'; + } else { + oss << fragment; + } + }; + + std::istringstream iss(path); + std::string fragment; + + std::vector fragments; + + while (std::getline(iss, fragment, '/')) { + fragments.push_back(std::move(fragment)); + } + + for (size_t i = 0; i < fragments.size() - 1; ++i) { + const auto &frag = fragments[i]; + + processFragment(frag); + oss << '/'; + } + + const auto &last = fragments.back(); + if (last.empty()) + oss << '/'; + else { + processFragment(last); + } + + return oss.str(); +} + +bool PathGroup::hasPath(const std::string &name, Http::Method method) const { + auto group = paths(name); + auto it = std::find_if(std::begin(group), std::end(group), + [&](const Path &p) { return p.method == method; }); + return it != std::end(group); +} + +bool PathGroup::hasPath(const Path &path) const { + return hasPath(path.value, path.method); +} + +PathGroup::Group PathGroup::paths(const std::string &name) const { + auto it = groups_.find(name); + if (it == std::end(groups_)) + return PathGroup::Group{}; + + return it->second; +} + +Optional PathGroup::path(const std::string &name, + Http::Method method) const { + auto group = paths(name); + auto it = std::find_if(std::begin(group), std::end(group), + [&](const Path &p) { return p.method == method; }); + + if (it != std::end(group)) { + return Optional(Some(*it)); + } + return Optional(None()); +} + +PathGroup::group_iterator PathGroup::add(Path path) { + if (hasPath(path)) + return PathGroup::group_iterator{}; + + auto &group = groups_[path.value]; + return group.insert(group.end(), std::move(path)); +} + +PathGroup::const_iterator PathGroup::begin() const { return groups_.begin(); } + +PathGroup::const_iterator PathGroup::end() const { return groups_.end(); } + +PathGroup::flat_iterator PathGroup::flatBegin() const { + return makeFlatMapIterator(groups_, begin()); +} + +PathGroup::flat_iterator PathGroup::flatEnd() const { + return makeFlatMapIterator(groups_, end()); +} + +PathBuilder::PathBuilder(Path *path) : path_(path) {} + +SubPath::SubPath(std::string prefix, PathGroup *paths) + : prefix(std::move(prefix)), parameters(), paths(paths) {} + +PathBuilder SubPath::route(const std::string &name, Http::Method method, + std::string description) { + auto fullPath = prefix + name; + Path path(std::move(fullPath), method, std::move(description)); + std::copy(std::begin(parameters), std::end(parameters), + std::back_inserter(path.parameters)); + + auto it = paths->add(std::move(path)); + + return PathBuilder(&*it); +} + +PathBuilder SubPath::route(PathDecl fragment, std::string description) { + return route(std::move(fragment.value), fragment.method, + std::move(description)); +} + +SubPath SubPath::path(const std::string &prefix) { + return SubPath(this->prefix + prefix, paths); +} + +Parameter::Parameter(std::string name, std::string description) + : name(std::move(name)), description(std::move(description)), + required(true), type() {} + +Response::Response(Http::Code statusCode, std::string description) + : statusCode(statusCode), description(std::move(description)) {} + +ResponseBuilder::ResponseBuilder(Http::Code statusCode, std::string description) + : response_(statusCode, std::move(description)) {} + +InfoBuilder::InfoBuilder(Info *info) : info_(info) {} + +InfoBuilder &InfoBuilder::termsOfService(std::string value) { + info_->termsOfService = std::move(value); + return *this; +} + +InfoBuilder &InfoBuilder::contact(std::string name, std::string url, + std::string email) { + info_->contact = + Some(Contact(std::move(name), std::move(url), std::move(email))); + return *this; +} + +InfoBuilder &InfoBuilder::license(std::string name, std::string url) { + info_->license = Some(License(std::move(name), std::move(url))); + return *this; +} + +} // namespace Schema + +Description::Description(std::string title, std::string version, + std::string description) + : info_(std::move(title), std::move(version), std::move(description)), + host_(), basePath_(), schemes_(), pc(), paths_() {} + +Schema::InfoBuilder Description::info() { + Schema::InfoBuilder builder(&info_); + return builder; +} + +Description &Description::host(std::string value) { + host_ = std::move(value); + return *this; +} + +Description &Description::basePath(std::string value) { + basePath_ = std::move(value); + return *this; +} + +Schema::PathDecl Description::options(std::string name) { + return Schema::PathDecl(std::move(name), Http::Method::Options); +} + +Schema::PathDecl Description::get(std::string name) { + return Schema::PathDecl(std::move(name), Http::Method::Get); +} + +Schema::PathDecl Description::post(std::string name) { + return Schema::PathDecl(std::move(name), Http::Method::Post); +} + +Schema::PathDecl Description::head(std::string name) { + return Schema::PathDecl(std::move(name), Http::Method::Head); +} + +Schema::PathDecl Description::put(std::string name) { + return Schema::PathDecl(std::move(name), Http::Method::Put); +} + +Schema::PathDecl Description::patch(std::string name) { + return Schema::PathDecl(std::move(name), Http::Method::Patch); +} + +Schema::PathDecl Description::del(std::string name) { + return Schema::PathDecl(std::move(name), Http::Method::Delete); +} + +Schema::PathDecl Description::trace(std::string name) { + return Schema::PathDecl(std::move(name), Http::Method::Trace); +} + +Schema::PathDecl Description::connect(std::string name) { + return Schema::PathDecl(std::move(name), Http::Method::Connect); +} + +Schema::SubPath Description::path(std::string name) { + return Schema::SubPath(std::move(name), &paths_); +} + +Schema::PathBuilder Description::route(std::string name, Http::Method method, + std::string description) { + auto it = paths_.emplace(std::move(name), method, std::move(description)); + return Schema::PathBuilder(&*it); +} + +Schema::PathBuilder Description::route(Schema::PathDecl fragment, + std::string description) { + return route(std::move(fragment.value), fragment.method, + std::move(description)); +} + +Schema::ResponseBuilder Description::response(Http::Code statusCode, + std::string description) { + Schema::ResponseBuilder builder(statusCode, std::move(description)); + return builder; +} + +Swagger &Swagger::uiPath(std::string path) { + uiPath_ = std::move(path); + return *this; +} + +Swagger &Swagger::uiDirectory(std::string dir) { + uiDirectory_ = std::move(dir); + return *this; +} + +Swagger &Swagger::apiPath(std::string path) { + apiPath_ = std::move(path); + return *this; +} + +Swagger &Swagger::serializer(Swagger::Serializer serialize) { + serializer_ = std::move(serialize); + return *this; +} + +void Swagger::install(Rest::Router &router) { + + Route::Handler uiHandler = [=](const Rest::Request &req, + Http::ResponseWriter response) { + auto res = req.resource(); + + /* + * @Export might be useful for routing also. Make it public or merge it with + * the Fragment class + */ + + struct Path { + explicit Path(const std::string &value) + : value(value), trailingSlashValue(value) { + if (trailingSlashValue.empty() || trailingSlashValue.back() != '/') + trailingSlashValue += '/'; + } + + static bool hasTrailingSlash(const Rest::Request &req) { + auto res_ = req.resource(); + return res_.back() == '/'; + } + + std::string stripPrefix(const Rest::Request &req) { + auto res_ = req.resource(); + if (!res_.compare(0, value.size(), value)) { + return res_.substr(value.size()); + } + return res_; + } + + bool matches(const Rest::Request &req) const { + auto res_ = req.resource(); + if (value == res_) + return true; + + if (res_ == trailingSlashValue) + return true; + + return false; + } + + bool isPrefix(const Rest::Request &req) { + auto res_ = req.resource(); + return !res_.compare(0, value.size(), value); + } + + const std::string &withTrailingSlash() const { + return trailingSlashValue; + } + + std::string join(const std::string &value) const { + std::string val; + if (value[0] == '/') + val = value.substr(1); + else + val = value; + return trailingSlashValue + val; + } + + private: + std::string value; + std::string trailingSlashValue; + }; + + Path ui(uiPath_); + Path uiDir(uiDirectory_); + + if (ui.matches(req)) { + if (!Path::hasTrailingSlash(req)) { + response.headers().add(uiPath_ + '/'); + + response.send(Http::Code::Moved_Permanently); + } else { + auto index = uiDir.join("index.html"); + Http::serveFile(response, index); + } + return Route::Result::Ok; + } else if (ui.isPrefix(req)) { + auto file = ui.stripPrefix(req); + auto path = uiDir.join(file); + Http::serveFile(response, path); + return Route::Result::Ok; + } + + else if (res == apiPath_) { + response.send(Http::Code::Ok, serializer_(description_), + MIME(Application, Json)); + return Route::Result::Ok; + } + + return Route::Result::Failure; + }; + + router.addCustomHandler(uiHandler); +} + +} // namespace Rest +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/http.cc b/projects/frontend/server-webapi/pistache/src/common/http.cc new file mode 100755 index 0000000000000000000000000000000000000000..ea4038a5ce60b76ea067b32b51f8dda6a8239376 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/http.cc @@ -0,0 +1,1008 @@ +/* http.cc + Mathieu Stefani, 13 August 2015 + + Http layer implementation +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Pistache { +namespace Http { + +template +typename std::enable_if::value, Stream &>::type +writeHeader(Stream &stream, Args &&... args) { + H header(std::forward(args)...); + + stream << H::Name << ": "; + header.write(stream); + + stream << crlf; + + return stream; +} + +namespace { +bool writeStatusLine(Version version, Code code, DynamicStreamBuf &buf) { +#define OUT(...) \ + do { \ + __VA_ARGS__; \ + if (!os) \ + return false; \ + } while (0) + + std::ostream os(&buf); + + OUT(os << version << " "); + OUT(os << static_cast(code)); + OUT(os << ' '); + OUT(os << code); + OUT(os << crlf); + + return true; + +#undef OUT +} + +bool writeHeaders(const Header::Collection &headers, DynamicStreamBuf &buf) { +#define OUT(...) \ + do { \ + __VA_ARGS__; \ + if (!os) \ + return false; \ + } while (0) + + std::ostream os(&buf); + + for (const auto &header : headers.list()) { + OUT(os << header->name() << ": "); + OUT(header->write(os)); + OUT(os << crlf); + } + + return true; + +#undef OUT +} + +bool writeCookies(const CookieJar &cookies, DynamicStreamBuf &buf) { +#define OUT(...) \ + do { \ + __VA_ARGS__; \ + if (!os) \ + return false; \ + } while (0) + + std::ostream os(&buf); + for (const auto &cookie : cookies) { + OUT(os << "Set-Cookie: "); + OUT(os << cookie); + OUT(os << crlf); + } + + return true; + +#undef OUT +} + +using HttpMethods = std::unordered_map; + +const HttpMethods httpMethods = { +#define METHOD(repr, str) {str, Method::repr}, + HTTP_METHODS +#undef METHOD +}; + +} // namespace + +static constexpr const char *ParserData = "__Parser"; + +namespace Private { + +Step::Step(Message *request) : message(request) {} + +void Step::raise(const char *msg, Code code /* = Code::Bad_Request */) { + throw HttpError(code, msg); +} + +State RequestLineStep::apply(StreamCursor &cursor) { + StreamCursor::Revert revert(cursor); + + auto request = static_cast(message); + + StreamCursor::Token methodToken(cursor); + if (!match_until(' ', cursor)) + return State::Again; + + auto it = httpMethods.find(methodToken.text()); + if (it != httpMethods.end()) { + request->method_ = it->second; + } else { + raise("Unknown HTTP request method"); + } + + int n; + + if (cursor.eof()) + return State::Again; + else if ((n = cursor.current()) != ' ') + raise("Malformed HTTP request after Method, expected SP"); + + if (!cursor.advance(1)) + return State::Again; + + StreamCursor::Token resToken(cursor); + while ((n = cursor.current()) != '?' && n != ' ') + if (!cursor.advance(1)) + return State::Again; + + request->resource_ = resToken.text(); + + // Query parameters of the Uri + if (n == '?') { + if (!cursor.advance(1)) + return State::Again; + + while ((n = cursor.current()) != ' ') { + StreamCursor::Token keyToken(cursor); + if (!match_until({'=', ' ', '&'}, cursor)) + return State::Again; + + std::string key = keyToken.text(); + + auto c = cursor.current(); + if (c == ' ') { + request->query_.add(std::move(key), ""); + } else if (c == '&') { + request->query_.add(std::move(key), ""); + if (!cursor.advance(1)) + return State::Again; + } else if (c == '=') { + if (!cursor.advance(1)) + return State::Again; + + StreamCursor::Token valueToken(cursor); + if (!match_until({' ', '&'}, cursor)) + return State::Again; + + std::string value = valueToken.text(); + request->query_.add(std::move(key), std::move(value)); + if (cursor.current() == '&') { + if (!cursor.advance(1)) + return State::Again; + } + } + } + } + + // @Todo: Fragment + + // SP + if (!cursor.advance(1)) + return State::Again; + + // HTTP-Version + StreamCursor::Token versionToken(cursor); + + while (!cursor.eol()) + if (!cursor.advance(1)) + return State::Again; + + const char *ver = versionToken.rawText(); + const size_t size = versionToken.size(); + if (strncmp(ver, "HTTP/1.0", size) == 0) { + request->version_ = Version::Http10; + } else if (strncmp(ver, "HTTP/1.1", size) == 0) { + request->version_ = Version::Http11; + } else { + raise("Encountered invalid HTTP version"); + } + + if (!cursor.advance(2)) + return State::Again; + + revert.ignore(); + return State::Next; +} + +State ResponseLineStep::apply(StreamCursor &cursor) { + StreamCursor::Revert revert(cursor); + + auto *response = static_cast(message); + + if (match_raw("HTTP/1.1", strlen("HTTP/1.1"), cursor)) { + // response->version = Version::Http11; + } else if (match_raw("HTTP/1.0", strlen("HTTP/1.0"), cursor)) { + } else { + raise("Encountered invalid HTTP version"); + } + + int n; + // SP + if ((n = cursor.current()) != StreamCursor::Eof && n != ' ') + raise("Expected SPACE after http version"); + if (!cursor.advance(1)) + return State::Again; + + StreamCursor::Token codeToken(cursor); + if (!match_until(' ', cursor)) + return State::Again; + + char *end; + auto code = strtol(codeToken.rawText(), &end, 10); + if (*end != ' ') + raise("Failed to parsed return code"); + response->code_ = static_cast(code); + + if (!cursor.advance(1)) + return State::Again; + + while (!cursor.eol() && !cursor.eof()) { + cursor.advance(1); + } + + if (!cursor.advance(2)) + return State::Again; + + revert.ignore(); + return State::Next; +} + +State HeadersStep::apply(StreamCursor &cursor) { + StreamCursor::Revert revert(cursor); + + while (!cursor.eol()) { + StreamCursor::Revert headerRevert(cursor); + + // Read the header name + size_t start = cursor; + + while (cursor.current() != ':') + if (!cursor.advance(1)) + return State::Again; + + // Skip the ':' + if (!cursor.advance(1)) + return State::Again; + + std::string name = + std::string(cursor.offset(start), cursor.diff(start) - 1); + + // Ignore spaces + while (cursor.current() == ' ') + if (!cursor.advance(1)) + return State::Again; + + // Read the header value + start = cursor; + while (!cursor.eol()) { + if (!cursor.advance(1)) + return State::Again; + } + + if (Header::LowercaseEqualStatic(name, "cookie")) { + message->cookies_.removeAllCookies(); // removing existing cookies before + // re-adding them. + message->cookies_.addFromRaw(cursor.offset(start), cursor.diff(start)); + } else if (Header::LowercaseEqualStatic(name, "set-cookie")) { + message->cookies_.add( + Cookie::fromRaw(cursor.offset(start), cursor.diff(start))); + } + + // If the header is registered with the Registry, add its strongly + // typed form to the headers list... + else if (Header::Registry::instance().isRegistered(name)) { + std::shared_ptr header = + Header::Registry::instance().makeHeader(name); + header->parseRaw(cursor.offset(start), cursor.diff(start)); + message->headers_.add(header); + } + + // But also preserve a raw header version too, regardless of whether + // its type was known to the Registry... + std::string value(cursor.offset(start), cursor.diff(start)); + message->headers_.addRaw(Header::Raw(std::move(name), std::move(value))); + + // CRLF + if (!cursor.advance(2)) + return State::Again; + + headerRevert.ignore(); + } + + if (!cursor.advance(2)) + return State::Again; + + revert.ignore(); + return State::Next; +} + +State BodyStep::apply(StreamCursor &cursor) { + auto cl = message->headers_.tryGet(); + auto te = message->headers_.tryGet(); + + if (cl && te) + raise("Got mutually exclusive ContentLength and TransferEncoding header"); + + if (cl) + return parseContentLength(cursor, cl); + + if (te) + return parseTransferEncoding(cursor, te); + + return State::Done; +} + +State BodyStep::parseContentLength( + StreamCursor &cursor, const std::shared_ptr &cl) { + auto contentLength = cl->value(); + + auto readBody = [&](size_t size) { + StreamCursor::Token token(cursor); + const size_t available = cursor.remaining(); + + // We have an incomplete body, read what we can + if (available < size) { + cursor.advance(available); + message->body_.append(token.rawText(), token.size()); + + bytesRead += available; + + return false; + } + + cursor.advance(size); + message->body_.append(token.rawText(), token.size()); + return true; + }; + + // We already started to read some bytes but we got an incomplete payload + if (bytesRead > 0) { + // How many bytes do we still need to read ? + const size_t remaining = contentLength - bytesRead; + if (!readBody(remaining)) + return State::Again; + } + // This is the first time we are reading the payload + else { + message->body_.reserve(contentLength); + if (!readBody(contentLength)) + return State::Again; + } + + bytesRead = 0; + return State::Done; +} + +BodyStep::Chunk::Result BodyStep::Chunk::parse(StreamCursor &cursor) { + if (size == -1) { + StreamCursor::Revert revert(cursor); + StreamCursor::Token chunkSize(cursor); + + while (!cursor.eol()) + if (!cursor.advance(1)) + return Incomplete; + + char *end; + const char *raw = chunkSize.rawText(); + auto sz = std::strtol(raw, &end, 16); + if (*end != '\r') + throw std::runtime_error("Invalid chunk size"); + + // CRLF + if (!cursor.advance(2)) + return Incomplete; + + revert.ignore(); + + size = sz; + } + + if (size == 0) + return Final; + + message->body_.reserve(size); + StreamCursor::Token chunkData(cursor); + const ssize_t available = cursor.remaining(); + + if (static_cast(available + message->body_.size()) < size) { + cursor.advance(available); + message->body_.append(chunkData.rawText(), available); + return Incomplete; + } + cursor.advance(size - message->body_.size()); + + if (!cursor.advance(2)) + return Incomplete; + + message->body_.append(chunkData.rawText(), size - message->body_.size()); + + return Complete; +} + +State BodyStep::parseTransferEncoding( + StreamCursor &cursor, const std::shared_ptr &te) { + auto encoding = te->encoding(); + if (encoding == Http::Header::Encoding::Chunked) { + Chunk::Result result; + try { + while ((result = chunk.parse(cursor)) != Chunk::Final) { + if (result == Chunk::Incomplete) + return State::Again; + + chunk.reset(); + if (cursor.eof()) + return State::Again; + } + chunk.reset(); + } catch (const std::exception &e) { + // reset chunk incase signal handled & chunk eventually reused + chunk.reset(); + raise(e.what()); + } + + return State::Done; + } else { + raise("Unsupported Transfer-Encoding", Code::Not_Implemented); + } + return State::Done; +} + +ParserBase::ParserBase(size_t maxDataSize) + : buffer(maxDataSize), cursor(&buffer) {} + +State ParserBase::parse() { + State state; + do { + Step *step = allSteps[currentStep].get(); + state = step->apply(cursor); + if (state == State::Next) { + ++currentStep; + } + } while (state == State::Next); + + // Should be either Again or Done + return state; +} + +bool ParserBase::feed(const char *data, size_t len) { + return buffer.feed(data, len); +} + +void ParserBase::reset() { + buffer.reset(); + cursor.reset(); + + currentStep = 0; +} + +} // namespace Private + +namespace Uri { + +Query::Query() : params() {} + +Query::Query( + std::initializer_list> params) + : params(params) {} + +void Query::add(std::string name, std::string value) { + params.insert(std::make_pair(std::move(name), std::move(value))); +} + +Optional Query::get(const std::string &name) const { + auto it = params.find(name); + if (it == std::end(params)) + return Optional(None()); + + return Optional(Some(it->second)); +} + +std::string Query::as_str() const { + std::string query_url; + for (const auto &e : params) { + query_url += "&" + e.first + "=" + e.second; + } + if (!query_url.empty()) { + query_url[0] = '?'; // replace first `&` with `?` + } + return query_url; +} + +bool Query::has(const std::string &name) const { + return params.find(name) != std::end(params); +} + +} // namespace Uri + +Message::Message(Version version) : version_(version) {} + +Version Message::version() const { return version_; } + +Code Message::code() const { return code_; } + +const std::string& Message::body() const { return body_; } + +const Header::Collection &Message::headers() const { return headers_; } + +Header::Collection &Message::headers() { return headers_; } + +const CookieJar &Message::cookies() const { return cookies_; } + +CookieJar &Message::cookies() { return cookies_; } + +Method Request::method() const { return method_; } + +const std::string &Request::resource() const { return resource_; } + +const Uri::Query &Request::query() const { return query_; } + +const Address &Request::address() const { return address_; } + +std::chrono::milliseconds Request::timeout() const { return timeout_; } + +Response::Response(Version version) : Message(version) {} + +#ifdef LIBSTDCPP_SMARTPTR_LOCK_FIXME +std::shared_ptr Request::peer() const { + auto p = peer_.lock(); + + if (!p) + throw std::runtime_error("Failed to retrieve peer: Broken pipe"); + + return p; +} +#endif + +ResponseStream::ResponseStream(ResponseStream &&other) + : response_(std::move(other.response_)), peer_(std::move(other.peer_)), + buf_(std::move(other.buf_)), transport_(other.transport_), + timeout_(std::move(other.timeout_)) {} + +ResponseStream::ResponseStream(Message &&other, std::weak_ptr peer, + Tcp::Transport *transport, Timeout timeout, + size_t streamSize, size_t maxResponseSize) + : response_(std::move(other)), peer_(std::move(peer)), + buf_(streamSize, maxResponseSize), transport_(transport), + timeout_(std::move(timeout)) { + if (!writeStatusLine(response_.version(), response_.code(), buf_)) + throw Error("Response exceeded buffer size"); + + if (!writeCookies(response_.cookies(), buf_)) { + throw Error("Response exceeded buffer size"); + } + + if (writeHeaders(response_.headers(), buf_)) { + std::ostream os(&buf_); + /* @Todo @Major: + * Correctly handle non-keep alive requests + * Do not put Keep-Alive if version == Http::11 and request.keepAlive == + * true + */ + // writeHeader(os, ConnectionControl::KeepAlive); + // if (!os) throw Error("Response exceeded buffer size"); + writeHeader(os, Header::Encoding::Chunked); + if (!os) + throw Error("Response exceeded buffer size"); + os << crlf; + } +} + +ResponseStream &ResponseStream::operator=(ResponseStream &&other) { + response_ = std::move(other.response_); + peer_ = std::move(other.peer_); + buf_ = std::move(other.buf_); + transport_ = other.transport_; + timeout_ = std::move(other.timeout_); + + return *this; +} + +std::streamsize ResponseStream::write(const char *data, std::streamsize sz) { + std::ostream os(&buf_); + os << std::hex << sz << crlf; + os.write(data, sz); + os << crlf; + return sz; +} + +std::shared_ptr ResponseStream::peer() const { + if (peer_.expired()) { + throw std::runtime_error("Write failed: Broken pipe"); + } + + return peer_.lock(); +} + +void ResponseStream::flush() { + timeout_.disarm(); + auto buf = buf_.buffer(); + + auto fd = peer()->fd(); + transport_->asyncWrite(fd, buf); + + buf_.clear(); +} + +void ResponseStream::ends() { + std::ostream os(&buf_); + os << "0" << crlf; + os << crlf; + + if (!os) { + throw Error("Response exceeded buffer size"); + } + + flush(); +} + +ResponseWriter::ResponseWriter(ResponseWriter &&other) + : response_(std::move(other.response_)), peer_(other.peer_), + buf_(std::move(other.buf_)), transport_(other.transport_), + timeout_(std::move(other.timeout_)), sent_bytes_(0) {} + +ResponseWriter::ResponseWriter(Tcp::Transport *transport, Request request, + Handler *handler, std::weak_ptr peer) + : response_(request.version()), peer_(peer), + buf_(DefaultStreamSize, handler->getMaxResponseSize()), + transport_(transport), + timeout_(transport, handler, std::move(request), peer), sent_bytes_(0) {} + +ResponseWriter::ResponseWriter(const ResponseWriter &other) + : response_(other.response_), peer_(other.peer_), + buf_(DefaultStreamSize, other.buf_.maxSize()), + transport_(other.transport_), timeout_(other.timeout_), sent_bytes_(0) {} + +void ResponseWriter::setMime(const Mime::MediaType &mime) { + auto ct = response_.headers().tryGet(); + if (ct) { + ct->setMime(mime); + } else { + response_.headers().add(std::make_shared(mime)); + } +} + +Async::Promise ResponseWriter::sendMethodNotAllowed( + const std::vector &supportedMethods) { + response_.code_ = Http::Code::Method_Not_Allowed; + response_.headers().add( + std::make_shared(supportedMethods)); + const std::string &body = + codeString(Pistache::Http::Code::Method_Not_Allowed); + return putOnWire(body.c_str(), body.size()); +} + +Async::Promise ResponseWriter::send(Code code, const std::string &body, + const Mime::MediaType &mime) { + return sendImpl(code, body.c_str(), body.size(), mime); +} + +Async::Promise ResponseWriter::send(Code code, const char *data, + const size_t size, + const Mime::MediaType &mime) { + return sendImpl(code, data, size, mime); +} + +Async::Promise ResponseWriter::sendImpl(Code code, const char *data, + const size_t size, + const Mime::MediaType &mime) { + response_.code_ = code; + + if (mime.isValid()) { + auto contentType = headers().tryGet(); + if (contentType) { + contentType->setMime(mime); + } else { + headers().add(std::make_shared(mime)); + } + } + + return putOnWire(data, size); +} + +ResponseStream ResponseWriter::stream(Code code, size_t streamSize) { + response_.code_ = code; + + return ResponseStream(std::move(response_), peer_, transport_, + std::move(timeout_), streamSize, buf_.maxSize()); +} + +const CookieJar &ResponseWriter::cookies() const { return response_.cookies(); } + +CookieJar &ResponseWriter::cookies() { return response_.cookies(); } + +const Header::Collection &ResponseWriter::headers() const { + return response_.headers(); +} + +Header::Collection &ResponseWriter::headers() { return response_.headers(); } + +Timeout &ResponseWriter::timeout() { return timeout_; } + +std::shared_ptr ResponseWriter::peer() const { + if (peer_.expired()) { + throw std::runtime_error("Write failed: Broken pipe"); + } + + return peer_.lock(); +} + +DynamicStreamBuf *ResponseWriter::rdbuf() { return &buf_; } + +DynamicStreamBuf *ResponseWriter::rdbuf(DynamicStreamBuf *other) { + UNUSED(other) + throw std::domain_error("Unimplemented"); +} + +ResponseWriter ResponseWriter::clone() const { return ResponseWriter(*this); } + +Async::Promise ResponseWriter::putOnWire(const char *data, + size_t len) { + try { + std::ostream os(&buf_); + +#define OUT(...) \ + do { \ + __VA_ARGS__; \ + if (!os) { \ + return Async::Promise::rejected( \ + Error("Response exceeded buffer size")); \ + } \ + } while (0); + + OUT(writeStatusLine(response_.version(), response_.code(), buf_)); + OUT(writeHeaders(response_.headers(), buf_)); + OUT(writeCookies(response_.cookies(), buf_)); + + /* @Todo @Major: + * Correctly handle non-keep alive requests + * Do not put Keep-Alive if version == Http::11 and request.keepAlive == + * true + */ + // OUT(writeHeader(os, ConnectionControl::KeepAlive)); + OUT(writeHeader(os, len)); + + OUT(os << crlf); + + if (len > 0) { + OUT(os.write(data, len)); + } + + auto buffer = buf_.buffer(); + sent_bytes_ += buffer.size(); + + timeout_.disarm(); + +#undef OUT + + auto fd = peer()->fd(); + + return transport_->asyncWrite(fd, buffer) + .then(ssize_t)>, + std::function>( + [=](int /*l*/) { + return Async::Promise([=]( + Async::Deferred /*deferred*/) mutable { return; }); + }, + + [=](std::exception_ptr &eptr) { + return Async::Promise::rejected(eptr); + }); + + } catch (const std::runtime_error &e) { + return Async::Promise::rejected(e); + } +} + +Async::Promise serveFile(ResponseWriter &writer, + const std::string &fileName, + const Mime::MediaType &contentType) { + struct stat sb; + + int fd = open(fileName.c_str(), O_RDONLY); + if (fd == -1) { + std::string str_error(strerror(errno)); + if (errno == ENOENT) { + throw HttpError(Http::Code::Not_Found, std::move(str_error)); + } + // eles if TODO + /* @Improvement: maybe could we check for errno here and emit a different + error message + */ + else { + throw HttpError(Http::Code::Internal_Server_Error, std::move(str_error)); + } + } + + int res = ::fstat(fd, &sb); + close(fd); // Done with fd, close before error can be thrown + if (res == -1) { + throw HttpError(Code::Internal_Server_Error, ""); + } + + auto *buf = writer.rdbuf(); + + std::ostream os(buf); + +#define OUT(...) \ + do { \ + __VA_ARGS__; \ + if (!os) { \ + return Async::Promise::rejected( \ + Error("Response exceeded buffer size")); \ + } \ + } while (0); + + auto setContentType = [&](const Mime::MediaType &contentType) { + auto &headers = writer.headers(); + auto ct = headers.tryGet(); + if (ct) + ct->setMime(contentType); + else + headers.add(contentType); + }; + + OUT(writeStatusLine(writer.response_.version(), Http::Code::Ok, *buf)); + if (contentType.isValid()) { + setContentType(contentType); + } else { + auto mime = Mime::MediaType::fromFile(fileName.c_str()); + if (mime.isValid()) + setContentType(mime); + } + + OUT(writeHeaders(writer.headers(), *buf)); + + const size_t len = sb.st_size; + + OUT(writeHeader(os, len)); + + OUT(os << crlf); + + auto *transport = writer.transport_; + auto peer = writer.peer(); + auto sockFd = peer->fd(); + + auto buffer = buf->buffer(); + return transport->asyncWrite(sockFd, buffer, MSG_MORE) + .then( + [=](ssize_t) { + return transport->asyncWrite(sockFd, FileBuffer(fileName)); + }, + Async::Throw); + +#undef OUT +} + +Private::ParserImpl::ParserImpl(size_t maxDataSize) + : ParserBase(maxDataSize), request() { + allSteps[0].reset(new RequestLineStep(&request)); + allSteps[1].reset(new HeadersStep(&request)); + allSteps[2].reset(new BodyStep(&request)); +} + +void Private::ParserImpl::reset() { + ParserBase::reset(); + + request = Request(); +} + +Private::ParserImpl::ParserImpl(size_t maxDataSize) + : ParserBase(maxDataSize), response() { + allSteps[0].reset(new ResponseLineStep(&response)); + allSteps[1].reset(new HeadersStep(&response)); + allSteps[2].reset(new BodyStep(&response)); +} + +void Handler::onInput(const char *buffer, size_t len, + const std::shared_ptr &peer) { + auto &parser = getParser(peer); + try { + if (!parser.feed(buffer, len)) { + parser.reset(); + throw HttpError(Code::Request_Entity_Too_Large, + "Request exceeded maximum buffer size"); + } + + auto state = parser.parse(); + + if (state == Private::State::Done) { + ResponseWriter response(transport(), parser.request, this, peer); + +#ifdef LIBSTDCPP_SMARTPTR_LOCK_FIXME + parser.request.associatePeer(peer); +#endif + + auto request = parser.request; + request.copyAddress(peer->address()); + + auto connection = request.headers().tryGet(); + + if (connection) { + response.headers().add(connection->control()); + } else { + response.headers().add(ConnectionControl::Close); + } + + onRequest(request, std::move(response)); + parser.reset(); + } + + } catch (const HttpError &err) { + ResponseWriter response(transport(), parser.request, this, peer); + response.send(static_cast(err.code()), err.reason()); + parser.reset(); + } + + catch (const std::exception &e) { + ResponseWriter response(transport(), parser.request, this, peer); + response.send(Code::Internal_Server_Error, e.what()); + parser.reset(); + } +} + +void Handler::onConnection(const std::shared_ptr &peer) { + peer->putData(ParserData, std::make_shared(maxRequestSize_)); +} + +void Handler::onDisconnection(const std::shared_ptr & /*peer*/) {} + +void Handler::onTimeout(const Request & /*request*/, + ResponseWriter /*response*/) {} + +Timeout::~Timeout() { disarm(); } + +void Timeout::disarm() { + if (transport && armed) { + transport->disarmTimer(timerFd); + } +} + +bool Timeout::isArmed() const { return armed; } + +Timeout::Timeout(Tcp::Transport *transport_, Handler *handler_, + Request request_, std::weak_ptr peer_) + : handler(handler_), request(std::move(request_)), transport(transport_), + armed(false), timerFd(-1), peer(peer_) {} + +void Timeout::onTimeout(uint64_t numWakeup) { + UNUSED(numWakeup) + if (!peer.lock()) + return; + + ResponseWriter response(transport, request, handler, peer); + + handler->onTimeout(request, std::move(response)); +} + +void Handler::setMaxRequestSize(size_t value) { maxRequestSize_ = value; } + +size_t Handler::getMaxRequestSize() const { return maxRequestSize_; } + +void Handler::setMaxResponseSize(size_t value) { maxResponseSize_ = value; } + +size_t Handler::getMaxResponseSize() const { return maxResponseSize_; } + +RequestParser & +Handler::getParser(const std::shared_ptr &peer) const { + return static_cast(*peer->getData(ParserData)); +} + +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/http_defs.cc b/projects/frontend/server-webapi/pistache/src/common/http_defs.cc new file mode 100755 index 0000000000000000000000000000000000000000..30019cd10a76cf4b4dffc26f9e035da1f9ea5f14 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/http_defs.cc @@ -0,0 +1,177 @@ +/* http_defs.cc + Mathieu Stefani, 01 September 2015 + + Implementation of http definitions +*/ + +#include +#include + +#include +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif +#include +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif +#include + +namespace Pistache { +namespace Http { + +namespace { +using time_point = FullDate::time_point; + +bool parse_RFC_1123(const std::string &s, time_point &tp) { + std::istringstream in{s}; + in >> date::parse("%a, %d %b %Y %T %Z", tp); + return !in.fail(); +} + +bool parse_RFC_850(const std::string &s, time_point &tp) { + std::istringstream in{s}; + in >> date::parse("%a, %d-%b-%y %T %Z", tp); + return !in.fail(); +} + +bool parse_asctime(const std::string &s, time_point &tp) { + std::istringstream in{s}; + in >> date::parse("%a %b %d %T %Y", tp); + return !in.fail(); +} + +} // anonymous namespace + +CacheDirective::CacheDirective(Directive directive) : directive_(), data() { + init(directive, std::chrono::seconds(0)); +} + +CacheDirective::CacheDirective(Directive directive, std::chrono::seconds delta) + : directive_(), data() { + init(directive, delta); +} + +std::chrono::seconds CacheDirective::delta() const { + switch (directive_) { + case MaxAge: + return std::chrono::seconds(data.maxAge); + case SMaxAge: + return std::chrono::seconds(data.sMaxAge); + case MaxStale: + return std::chrono::seconds(data.maxStale); + case MinFresh: + return std::chrono::seconds(data.minFresh); + default: + throw std::domain_error("Invalid operation on cache directive"); + } +} + +void CacheDirective::init(Directive directive, std::chrono::seconds delta) { + directive_ = directive; + switch (directive) { + case MaxAge: + data.maxAge = delta.count(); + break; + case SMaxAge: + data.sMaxAge = delta.count(); + break; + case MaxStale: + data.maxStale = delta.count(); + break; + case MinFresh: + data.minFresh = delta.count(); + break; + default: + break; + } +} + +FullDate FullDate::fromString(const std::string &str) { + + FullDate::time_point tp; + if (parse_RFC_1123(str, tp)) + return FullDate(tp); + else if (parse_RFC_850(str, tp)) + return FullDate(tp); + else if (parse_asctime(str, tp)) + return FullDate(tp); + + throw std::runtime_error("Invalid Date format"); +} + +void FullDate::write(std::ostream &os, Type type) const { + switch (type) { + case Type::RFC1123: + date::to_stream(os, "%a, %d %b %Y %T %Z", date_); + break; + case Type::RFC850: + date::to_stream(os, "%a, %d-%b-%y %T %Z", date_); + break; + case Type::AscTime: + date::to_stream(os, "%a %b %d %T %Y", date_); + break; + default: + std::runtime_error("Invalid use of FullDate::write"); + } +} + +const char *versionString(Version version) { + switch (version) { + case Version::Http10: + return "HTTP/1.0"; + case Version::Http11: + return "HTTP/1.1"; + } + + unreachable(); +} + +const char *methodString(Method method) { + switch (method) { +#define METHOD(name, str) \ + case Method::name: \ + return str; + HTTP_METHODS +#undef METHOD + } + + unreachable(); +} + +const char *codeString(Code code) { + switch (code) { +#define CODE(_, name, str) \ + case Code::name: \ + return str; + STATUS_CODES +#undef CODE + } + + return ""; +} + +std::ostream &operator<<(std::ostream &os, Version version) { + os << versionString(version); + return os; +} + +std::ostream &operator<<(std::ostream &os, Method method) { + os << methodString(method); + return os; +} + +std::ostream &operator<<(std::ostream &os, Code code) { + os << codeString(code); + return os; +} + +HttpError::HttpError(Code code, std::string reason) + : code_(static_cast(code)), reason_(std::move(reason)) {} + +HttpError::HttpError(int code, std::string reason) + : code_(code), reason_(std::move(reason)) {} + +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/http_header.cc b/projects/frontend/server-webapi/pistache/src/common/http_header.cc new file mode 100755 index 0000000000000000000000000000000000000000..879522b89027306f5909bd77671d5b09dcb4e63b --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/http_header.cc @@ -0,0 +1,564 @@ +/* http_header.cc + Mathieu Stefani, 19 August 2015 + + Implementation of common HTTP headers described by the RFC +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Pistache { +namespace Http { +namespace Header { + +void Header::parse(const std::string &str) { + parseRaw(str.c_str(), str.length()); +} + +void Header::parseRaw(const char *str, size_t len) { + parse(std::string(str, len)); +} + +void Allow::parseRaw(const char *str, size_t len) { + UNUSED(str) + UNUSED(len) +} + +const char *encodingString(Encoding encoding) { + switch (encoding) { + case Encoding::Gzip: + return "gzip"; + case Encoding::Compress: + return "compress"; + case Encoding::Deflate: + return "deflate"; + case Encoding::Identity: + return "identity"; + case Encoding::Chunked: + return "chunked"; + case Encoding::Unknown: + return "unknown"; + } + return "unknown"; +} + +void Allow::write(std::ostream &os) const { + /* This puts an extra ',' at the end :/ + std::copy(std::begin(methods_), std::end(methods_), + std::ostream_iterator(os, ", ")); + */ + + for (std::vector::size_type i = 0; i < methods_.size(); ++i) { + os << methods_[i]; + if (i < methods_.size() - 1) + os << ", "; + } +} + +void Allow::addMethod(Http::Method method) { methods_.push_back(method); } + +void Allow::addMethods(std::initializer_list methods) { + std::copy(std::begin(methods), std::end(methods), + std::back_inserter(methods_)); +} + +void Allow::addMethods(const std::vector &methods) { + std::copy(std::begin(methods), std::end(methods), + std::back_inserter(methods_)); +} + +CacheControl::CacheControl(Http::CacheDirective directive) { + directives_.push_back(directive); +} + +void CacheControl::parseRaw(const char *str, size_t len) { + using Http::CacheDirective; + + struct DirectiveValue { + const char *const str; + const size_t size; + CacheDirective::Directive repr; + }; + +#define VALUE(divStr, enumValue) \ + { divStr, sizeof(divStr) - 1, CacheDirective::enumValue } + + static constexpr DirectiveValue TrivialDirectives[] = { + VALUE("no-cache", NoCache), + VALUE("no-store", NoStore), + VALUE("no-transform", NoTransform), + VALUE("only-if-cached", OnlyIfCached), + VALUE("public", Public), + VALUE("private", Private), + VALUE("must-revalidate", MustRevalidate), + VALUE("proxy-revalidate", ProxyRevalidate)}; + + static constexpr DirectiveValue TimedDirectives[] = { + VALUE("max-age", MaxAge), VALUE("max-stale", MaxStale), + VALUE("min-fresh", MinFresh), VALUE("s-maxage", SMaxAge)}; + +#undef VALUE + + RawStreamBuf<> buf(const_cast(str), len); + StreamCursor cursor(&buf); + + do { + + bool found = false; + // First scan trivial directives + for (const auto &d : TrivialDirectives) { + if (match_raw(d.str, d.size, cursor)) { + directives_.push_back(CacheDirective(d.repr)); + found = true; + break; + } + } + + // Not found, let's try timed directives + if (!found) { + for (const auto &d : TimedDirectives) { + if (match_raw(d.str, d.size, cursor)) { + if (!cursor.advance(1)) { + throw std::runtime_error( + "Invalid caching directive, missing delta-seconds"); + } + + char *end; + const char *beg = cursor.offset(); + // @Security: if str is not \0 terminated, there might be a situation + // where strtol can overflow. Double-check that it's harmless and fix + // if not + auto secs = strtol(beg, &end, 10); + cursor.advance(end - beg); + if (!cursor.eof() && cursor.current() != ',') { + throw std::runtime_error( + "Invalid caching directive, malformated delta-seconds"); + } + directives_.push_back( + CacheDirective(d.repr, std::chrono::seconds(secs))); + break; + } + } + } + + if (!cursor.eof()) { + if (cursor.current() != ',') { + throw std::runtime_error("Invalid caching directive, expected a comma"); + } + + int c; + while ((c = cursor.current()) != StreamCursor::Eof && + (c == ',' || c == ' ')) + cursor.advance(1); + } + + } while (!cursor.eof()); +} + +void CacheControl::write(std::ostream &os) const { + using Http::CacheDirective; + + auto directiveString = [](CacheDirective directive) -> const char * { + switch (directive.directive()) { + case CacheDirective::NoCache: + return "no-cache"; + case CacheDirective::NoStore: + return "no-store"; + case CacheDirective::NoTransform: + return "no-transform"; + case CacheDirective::OnlyIfCached: + return "only-if-cached"; + case CacheDirective::Public: + return "public"; + case CacheDirective::Private: + return "private"; + case CacheDirective::MustRevalidate: + return "must-revalidate"; + case CacheDirective::ProxyRevalidate: + return "proxy-revalidate"; + case CacheDirective::MaxAge: + return "max-age"; + case CacheDirective::MaxStale: + return "max-stale"; + case CacheDirective::MinFresh: + return "min-fresh"; + case CacheDirective::SMaxAge: + return "s-maxage"; + case CacheDirective::Ext: + return ""; + default: + return ""; + } + return ""; + }; + + auto hasDelta = [](CacheDirective directive) { + switch (directive.directive()) { + case CacheDirective::MaxAge: + case CacheDirective::MaxStale: + case CacheDirective::MinFresh: + case CacheDirective::SMaxAge: + return true; + default: + return false; + } + }; + + for (std::vector::size_type i = 0; i < directives_.size(); + ++i) { + const auto &d = directives_[i]; + os << directiveString(d); + if (hasDelta(d)) { + auto delta = d.delta(); + if (delta.count() > 0) { + os << "=" << delta.count(); + } + } + + if (i < directives_.size() - 1) { + os << ", "; + } + } +} + +void CacheControl::addDirective(Http::CacheDirective directive) { + directives_.push_back(directive); +} + +void CacheControl::addDirectives( + const std::vector &directives) { + std::copy(std::begin(directives), std::end(directives), + std::back_inserter(directives_)); +} + +void Connection::parseRaw(const char *str, size_t len) { + char *p = const_cast(str); + RawStreamBuf<> buf(p, p + len); + StreamCursor cursor(&buf); + + if (match_string("close", cursor)) { + control_ = ConnectionControl::Close; + } else if (match_string("keep-alive", cursor)) { + control_ = ConnectionControl::KeepAlive; + } else { + control_ = ConnectionControl::Ext; + } +} + +void Connection::write(std::ostream &os) const { + switch (control_) { + case ConnectionControl::Close: + os << "Close"; + break; + case ConnectionControl::KeepAlive: + os << "Keep-Alive"; + break; + case ConnectionControl::Ext: + os << "Ext"; + break; + } +} + +void ContentLength::parse(const std::string &data) { + try { + size_t pos; + uint64_t val = std::stoull(data, &pos); + if (pos != 0) { + } + + value_ = val; + } catch (const std::invalid_argument &e) { + } +} + +void ContentLength::write(std::ostream &os) const { os << value_; } + +// What type of authorization method was used? +Authorization::Method Authorization::getMethod() const noexcept { + // Basic... + if (hasMethod()) + return Method::Basic; + + // Bearer... + else if (hasMethod()) + return Method::Bearer; + + // Unknown... + else + return Method::Unknown; +} + +// Authorization is basic method... +template <> +bool Authorization::hasMethod() const noexcept { + // Method should begin with "Basic: " + if (value().rfind("Basic ", 0) == std::string::npos) + return false; + + // Verify value is long enough to contain basic method's credentials... + if (value().length() <= std::string("Basic ").length()) + return false; + + // Looks good... + return true; +} + +// Authorization is bearer method... +template <> +bool Authorization::hasMethod() const noexcept { + // Method should begin with "Bearer: " + if (value().rfind("Bearer ", 0) == std::string::npos) + return false; + + // Verify value is long enough to contain basic method's credentials... + if (value().length() <= std::string("Bearer ").length()) + return false; + + // Looks good... + return true; +} + +// Get decoded user ID if basic method was used... +std::string Authorization::getBasicUser() const { + // Verify basic authorization method was used... + if (!hasMethod()) + throw std::runtime_error("Authorization header does not use Basic method."); + + // Extract encoded credentials... + const std::string EncodedCredentials( + value_.begin() + std::string("Basic ").length(), value_.end()); + + // Decode them... + Base64Decoder Decoder(EncodedCredentials); + const std::vector &BinaryDecodedCredentials = Decoder.Decode(); + + // Transform to string... + std::string DecodedCredentials; + for (std::byte CurrentByte : BinaryDecodedCredentials) + DecodedCredentials.push_back(static_cast(CurrentByte)); + + // Find user ID and password delimiter... + const auto Delimiter = DecodedCredentials.find_first_of(':'); + + // None detected. Assume this is a malformed header... + if (Delimiter == std::string::npos) + return std::string(); + + // Extract and return just the user ID... + return std::string(DecodedCredentials.begin(), + DecodedCredentials.begin() + Delimiter); +} + +// Get decoded password if basic method was used... +std::string Authorization::getBasicPassword() const { + // Verify basic authorization method was used... + if (!hasMethod()) + throw std::runtime_error("Authorization header does not use Basic method."); + + // Extract encoded credentials... + const std::string EncodedCredentials( + value_.begin() + std::string("Basic ").length(), value_.end()); + + // Decode them... + Base64Decoder Decoder(EncodedCredentials); + const std::vector &BinaryDecodedCredentials = Decoder.Decode(); + + // Transform to string... + std::string DecodedCredentials; + for (std::byte CurrentByte : BinaryDecodedCredentials) + DecodedCredentials.push_back(static_cast(CurrentByte)); + + // Find user ID and password delimiter... + const auto Delimiter = DecodedCredentials.find_first_of(':'); + + // None detected. Assume this is a malformed header... + if (Delimiter == std::string::npos) + return std::string(); + + // Extract and return just the password... + return std::string(DecodedCredentials.begin() + Delimiter + 1, + DecodedCredentials.end()); +} + +// Set encoded user ID and password for basic method... +void Authorization::setBasicUserPassword(const std::string &User, + const std::string &Password) { + // Verify user ID does not contain a colon... + if (User.find_first_of(':') != std::string::npos) + throw std::runtime_error("User ID cannot contain a colon."); + + // Format credentials string... + const std::string Credentials = User + std::string(":") + Password; + + // Encode credentials... + value_ = std::string("Basic ") + Base64Encoder::EncodeString(Credentials); +} + +void Authorization::parse(const std::string &data) { + value_ = data; +} + +void Authorization::write(std::ostream &os) const { os << value_; } + +void Date::parse(const std::string &str) { + fullDate_ = FullDate::fromString(str); +} + +void Date::write(std::ostream &os) const { fullDate_.write(os); } + +void Expect::parseRaw(const char *str, size_t /*len*/) { + if (std::strcmp(str, "100-continue") == 0) { + expectation_ = Expectation::Continue; + } else { + expectation_ = Expectation::Ext; + } +} + +void Expect::write(std::ostream &os) const { + if (expectation_ == Expectation::Continue) { + os << "100-continue"; + } +} + +Host::Host(const std::string &data) : host_(), port_(0) { parse(data); } + +void Host::parse(const std::string &data) { + AddressParser parser(data); + host_ = parser.rawHost(); + const std::string &port = parser.rawPort(); + if (port.empty()) { + port_ = Const::HTTP_STANDARD_PORT; + } else { + port_ = Port(port); + } +} + +void Host::write(std::ostream &os) const { + os << host_; + /* @Clarity @Robustness: maybe a found a literal different than zero + to represent a null port ? + */ + if (port_ != 0) { + os << ":" << port_; + } +} + +Location::Location(const std::string &location) : location_(location) {} + +void Location::parse(const std::string &data) { location_ = data; } + +void Location::write(std::ostream &os) const { os << location_; } + +void UserAgent::parse(const std::string &data) { ua_ = data; } + +void UserAgent::write(std::ostream &os) const { os << ua_; } + +void Accept::parseRaw(const char *str, size_t len) { + + RawStreamBuf buf(const_cast(str), len); + StreamCursor cursor(&buf); + + do { + int c; + size_t beg = cursor; + while ((c = cursor.next()) != StreamCursor::Eof && c != ',') + cursor.advance(1); + + cursor.advance(1); + + const size_t mimeLen = cursor.diff(beg); + + mediaRange_.push_back( + Mime::MediaType::fromRaw(cursor.offset(beg), mimeLen)); + + if (!cursor.eof()) { + if (!cursor.advance(1)) + throw std::runtime_error("Ill-formed Accept header"); + + if ((c = cursor.next()) == StreamCursor::Eof || c == ',' || c == 0) + throw std::runtime_error("Ill-formed Accept header"); + + while (!cursor.eof() && cursor.current() == ' ') + cursor.advance(1); + } + + } while (!cursor.eof()); +} + +void Accept::write(std::ostream &os) const { UNUSED(os) } + +void AccessControlAllowOrigin::parse(const std::string &data) { uri_ = data; } + +void AccessControlAllowOrigin::write(std::ostream &os) const { os << uri_; } + +void AccessControlAllowHeaders::parse(const std::string &data) { val_ = data; } + +void AccessControlAllowHeaders::write(std::ostream &os) const { os << val_; } + +void AccessControlExposeHeaders::parse(const std::string &data) { val_ = data; } + +void AccessControlExposeHeaders::write(std::ostream &os) const { os << val_; } + +void AccessControlAllowMethods::parse(const std::string &data) { val_ = data; } + +void AccessControlAllowMethods::write(std::ostream &os) const { os << val_; } + +void EncodingHeader::parseRaw(const char *str, size_t len) { + if (!strncasecmp(str, "gzip", len)) { + encoding_ = Encoding::Gzip; + } else if (!strncasecmp(str, "deflate", len)) { + encoding_ = Encoding::Deflate; + } else if (!strncasecmp(str, "compress", len)) { + encoding_ = Encoding::Compress; + } else if (!strncasecmp(str, "identity", len)) { + encoding_ = Encoding::Identity; + } else if (!strncasecmp(str, "chunked", len)) { + encoding_ = Encoding::Chunked; + } else { + encoding_ = Encoding::Unknown; + } +} + +void EncodingHeader::write(std::ostream &os) const { + os << encodingString(encoding_); +} + +Server::Server(const std::vector &tokens) : tokens_(tokens) {} + +Server::Server(const std::string &token) : tokens_() { + tokens_.push_back(token); +} + +Server::Server(const char *token) : tokens_() { tokens_.emplace_back(token); } + +void Server::parse(const std::string &token) { tokens_.push_back(token); } + +void Server::write(std::ostream &os) const { + for (size_t i = 0; i < tokens_.size(); i++) { + auto &token = tokens_[i]; + os << token; + if (i < tokens_.size() - 1) { + os << " "; + } + } +} + +void ContentType::parseRaw(const char *str, size_t len) { + mime_.parseRaw(str, len); +} + +void ContentType::write(std::ostream &os) const { os << mime_.toString(); } + +} // namespace Header +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/http_headers.cc b/projects/frontend/server-webapi/pistache/src/common/http_headers.cc new file mode 100755 index 0000000000000000000000000000000000000000..bda48c1382c40fbe0e22a58ecd748947a4993d7e --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/http_headers.cc @@ -0,0 +1,204 @@ +/* http_headers.cc + Mathieu Stefani, 19 August 2015 + + Headers registry +*/ + +#include + +#include +#include +#include +#include + +namespace Pistache { +namespace Http { +namespace Header { + +RegisterHeader(Accept); +RegisterHeader(AccessControlAllowOrigin); +RegisterHeader(AccessControlAllowHeaders); +RegisterHeader(AccessControlExposeHeaders); +RegisterHeader(AccessControlAllowMethods); +RegisterHeader(Allow); +RegisterHeader(CacheControl); +RegisterHeader(Connection); +RegisterHeader(ContentEncoding); +RegisterHeader(TransferEncoding); +RegisterHeader(ContentLength); +RegisterHeader(ContentType); +RegisterHeader(Authorization); +RegisterHeader(Date); +RegisterHeader(Expect); +RegisterHeader(Host); +RegisterHeader(Location); +RegisterHeader(Server); +RegisterHeader(UserAgent); + +std::string toLowercase(std::string str) { + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + return str; +} + +bool LowercaseEqualStatic(const std::string &dynamic, + const std::string &statik) { + return std::equal( + dynamic.begin(), dynamic.end(), statik.begin(), statik.end(), + [](const char &a, const char &b) { return std::tolower(a) == b; }); +} + +Registry &Registry::instance() { + static Registry instance; + + return instance; +} + +Registry::Registry() {} + +Registry::~Registry() {} + +void Registry::registerHeader(const std::string &name, + Registry::RegistryFunc func) { + auto it = registry.find(name); + if (it != std::end(registry)) { + throw std::runtime_error("Header already registered"); + } + + registry.insert(std::make_pair(name, std::move(func))); +} + +std::vector Registry::headersList() { + std::vector names; + names.reserve(registry.size()); + + for (const auto &header : registry) { + names.push_back(header.first); + } + + return names; +} + +std::unique_ptr
Registry::makeHeader(const std::string &name) { + auto it = registry.find(name); + if (it == std::end(registry)) { + throw std::runtime_error("Unknown header"); + } + + return it->second(); +} + +bool Registry::isRegistered(const std::string &name) { + auto it = registry.find(name); + return it != std::end(registry); +} + +Collection &Collection::add(const std::shared_ptr
&header) { + headers.insert(std::make_pair(header->name(), header)); + + return *this; +} + +Collection &Collection::addRaw(const Raw &raw) { + rawHeaders.insert(std::make_pair(raw.name(), raw)); + return *this; +} + +std::shared_ptr Collection::get(const std::string &name) const { + auto header = getImpl(name); + if (!header.first) { + throw std::runtime_error("Could not find header"); + } + + return header.second; +} + +std::shared_ptr
Collection::get(const std::string &name) { + auto header = getImpl(name); + if (!header.first) { + throw std::runtime_error("Could not find header"); + } + + return header.second; +} + +Raw Collection::getRaw(const std::string &name) const { + auto it = rawHeaders.find(name); + if (it == std::end(rawHeaders)) { + throw std::runtime_error("Could not find header"); + } + + return it->second; +} + +std::shared_ptr +Collection::tryGet(const std::string &name) const { + auto header = getImpl(name); + if (!header.first) + return nullptr; + + return header.second; +} + +std::shared_ptr
Collection::tryGet(const std::string &name) { + auto header = getImpl(name); + if (!header.first) + return nullptr; + + return header.second; +} + +Optional Collection::tryGetRaw(const std::string &name) const { + auto it = rawHeaders.find(name); + if (it == std::end(rawHeaders)) { + return Optional(None()); + } + + return Optional(Some(it->second)); +} + +bool Collection::has(const std::string &name) const { + return getImpl(name).first; +} + +std::vector> Collection::list() const { + std::vector> ret; + ret.reserve(headers.size()); + for (const auto &h : headers) { + ret.push_back(h.second); + } + + return ret; +} + +bool Collection::remove(const std::string &name) { + auto tit = headers.find(name); + if (tit == std::end(headers)) { + auto rit = rawHeaders.find(name); + if (rit == std::end(rawHeaders)) + return false; + + rawHeaders.erase(rit); + return true; + } + headers.erase(tit); + return true; +} + +void Collection::clear() { + headers.clear(); + rawHeaders.clear(); +} + +std::pair> +Collection::getImpl(const std::string &name) const { + auto it = headers.find(name); + if (it == std::end(headers)) { + return std::make_pair(false, nullptr); + } + + return std::make_pair(true, it->second); +} + +} // namespace Header +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/mime.cc b/projects/frontend/server-webapi/pistache/src/common/mime.cc new file mode 100755 index 0000000000000000000000000000000000000000..d82f00e4ca84a6df860207b625f58691a251649f --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/mime.cc @@ -0,0 +1,320 @@ +/* mime.cc + Mathieu Stefani, 29 August 2015 + + Implementation of MIME Type parsing +*/ + +#include + +#include +#include + +namespace Pistache { +namespace Http { +namespace Mime { + +std::string Q::toString() const { + if (val_ == 0) + return "q=0"; + else if (val_ == 100) + return "q=1"; + + char buff[sizeof("q=0.99")]; + memset(buff, 0, sizeof buff); + if (val_ % 10 == 0) + snprintf(buff, sizeof buff, "q=%.1f", val_ / 100.0); + else + snprintf(buff, sizeof buff, "q=%.2f", val_ / 100.0); + + return std::string(buff); +} + +MediaType MediaType::fromString(const std::string &str) { + return fromRaw(str.c_str(), str.size()); +} + +MediaType MediaType::fromString(std::string &&str) { + return fromRaw(str.c_str(), str.size()); +} + +MediaType MediaType::fromRaw(const char *str, size_t len) { + MediaType res; + + res.parseRaw(str, len); + return res; +} + +MediaType MediaType::fromFile(const char *fileName) { + const char *extensionOffset = nullptr; + const char *p = fileName; + while (*p) { + if (*p == '.') + extensionOffset = p; + ++p; + } + + if (!extensionOffset) + return MediaType(); + + ++extensionOffset; + + struct Extension { + const char *const raw; + Mime::Type top; + Mime::Subtype sub; + }; + + // @Data: maybe one day try to export + // http://www.iana.org/assignments/media-types/media-types.xhtml as an + // item-list + + static constexpr Extension KnownExtensions[] = { + {"jpg", Type::Image, Subtype::Jpeg}, + {"jpeg", Type::Image, Subtype::Jpeg}, + {"png", Type::Image, Subtype::Png}, + {"bmp", Type::Image, Subtype::Bmp}, + + {"txt", Type::Text, Subtype::Plain}, + {"md", Type::Text, Subtype::Plain}, + + {"bin", Type::Application, Subtype::OctetStream}, + }; + + for (const auto &ext : KnownExtensions) { + if (!strcmp(extensionOffset, ext.raw)) { + return MediaType(ext.top, ext.sub); + } + } + + return MediaType(); +} + +void MediaType::parseRaw(const char *str, size_t len) { + auto raise = [&](const char *str) { + // TODO: eventually, we should throw a more generic exception + // that could then be catched in lower stack frames to rethrow + // an HttpError + throw HttpError(Http::Code::Unsupported_Media_Type, str); + }; + + RawStreamBuf buf(const_cast(str), len); + StreamCursor cursor(&buf); + + raw_ = std::string(str, len); + + Mime::Type top = Type::None; + + // The reason we are using a do { } while (0); syntax construct here is to + // emulate if / else-if. Since we are using items-list macros to compare the + // strings, we want to avoid evaluating all the branches when one of them + // evaluates to true. + // + // Instead, we break the loop when a branch evaluates to true so that we do + // not evaluate all the subsequent ones. + // + // Watch out, this pattern is repeated throughout the function + do { +#define TYPE(val, s) \ + if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) { \ + top = Type::val; \ + break; \ + } + MIME_TYPES +#undef TYPE + raise("Unknown Media Type"); + } while (0); + + top_ = top; + + if (!match_literal('/', cursor)) + raise("Malformed Media Type, expected a '/' after the top type"); + + if (cursor.eof()) + raise("Malformed Media type, missing subtype"); + + // Parse subtype + Mime::Subtype sub; + + StreamCursor::Token subToken(cursor); + + if (match_raw("vnd.", 4, cursor)) { + sub = Subtype::Vendor; + } else { + do { +#define SUB_TYPE(val, s) \ + if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) { \ + sub = Subtype::val; \ + break; \ + } + MIME_SUBTYPES +#undef SUB_TYPE + sub = Subtype::Ext; + } while (0); + } + + if (sub == Subtype::Ext || sub == Subtype::Vendor) { + (void)match_until({';', '+'}, cursor); + rawSubIndex.beg = subToken.start(); + rawSubIndex.end = subToken.end() - 1; + } + + sub_ = sub; + + if (cursor.eof()) + return; + + // Parse suffix + Mime::Suffix suffix = Suffix::None; + if (match_literal('+', cursor)) { + + if (cursor.eof()) + raise("Malformed Media Type, expected suffix, got EOF"); + + StreamCursor::Token suffixToken(cursor); + + do { +#define SUFFIX(val, s, _) \ + if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) { \ + suffix = Suffix::val; \ + break; \ + } + MIME_SUFFIXES +#undef SUFFIX + suffix = Suffix::Ext; + } while (0); + + if (suffix == Suffix::Ext) { + (void)match_until({';', '+'}, cursor); + rawSuffixIndex.beg = suffixToken.start(); + rawSuffixIndex.end = suffixToken.end() - 1; + } + + suffix_ = suffix; + } + + // Parse parameters + while (!cursor.eof()) { + + if (cursor.current() == ';' || cursor.current() == ' ') { + int c; + if ((c = cursor.next()) == StreamCursor::Eof || c == 0) + raise("Malformed Media Type, expected parameter got EOF"); + cursor.advance(1); + } + + else if (match_literal('q', cursor)) { + + if (cursor.eof()) + raise("Invalid quality factor"); + + if (match_literal('=', cursor)) { + double val; + if (!match_double(&val, cursor)) + raise("Invalid quality factor"); + q_ = Some(Q::fromFloat(val)); + } else { + raise("Missing quality factor"); + } + } else { + StreamCursor::Token keyToken(cursor); + (void)match_until('=', cursor); + + int c; + if (cursor.eof() || (c = cursor.next()) == StreamCursor::Eof || c == 0) + raise("Unfinished Media Type parameter"); + + std::string key = keyToken.text(); + cursor.advance(1); + + StreamCursor::Token valueToken(cursor); + (void)match_until({' ', ';'}, cursor); + params.insert(std::make_pair(std::move(key), valueToken.text())); + } + } +} + +void MediaType::setQuality(Q quality) { q_ = Some(quality); } + +Optional MediaType::getParam(const std::string &name) const { + auto it = params.find(name); + if (it == std::end(params)) { + return Optional(None()); + } + + return Optional(Some(it->second)); +} + +void MediaType::setParam(const std::string &name, std::string value) { + params[name] = std::move(value); +} + +std::string MediaType::toString() const { + + if (!raw_.empty()) + return raw_; + + auto topString = [](Mime::Type top) -> const char * { + switch (top) { +#define TYPE(val, str) \ + case Mime::Type::val: \ + return str; + MIME_TYPES +#undef TYPE + default: + return ""; + } + }; + + auto subString = [](Mime::Subtype sub) -> const char * { + switch (sub) { +#define SUB_TYPE(val, str) \ + case Mime::Subtype::val: \ + return str; + MIME_SUBTYPES +#undef TYPE + default: + return ""; + } + }; + + auto suffixString = [](Mime::Suffix suffix) -> const char * { + switch (suffix) { +#define SUFFIX(val, str, _) \ + case Mime::Suffix::val: \ + return "+" str; + MIME_SUFFIXES +#undef SUFFIX + default: + return ""; + } + }; + + std::string res; + res.reserve(128); + res += topString(top_); + res += "/"; + res += subString(sub_); + if (suffix_ != Suffix::None) { + res += suffixString(suffix_); + } + + optionally_do(q_, [&res](Q quality) { + res += "; "; + res += quality.toString(); + }); + + for (const auto ¶m : params) { + res += "; "; + res += param.first + "=" + param.second; + } + + return res; +} + +bool MediaType::isValid() const { + return top_ != Type::None && sub_ != Subtype::None; +} + +} // namespace Mime +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/net.cc b/projects/frontend/server-webapi/pistache/src/common/net.cc new file mode 100755 index 0000000000000000000000000000000000000000..7f1523d7f3cf6760b1433615b0da7d1a58266c7b --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/net.cc @@ -0,0 +1,345 @@ +/* net.cc + Mathieu Stefani, 12 August 2015 + +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Pistache { + +namespace { +IP GetIPv4(const std::string &host) { + in_addr addr; + int res = inet_pton(AF_INET, host.c_str(), &addr); + if (res == 0) { + throw std::invalid_argument("Invalid IPv4 network address"); + } else if (res < 0) { + throw std::invalid_argument(strerror(errno)); + } else { + struct sockaddr_in s_addr = {0}; + s_addr.sin_family = AF_INET; + + static_assert(sizeof(s_addr.sin_addr.s_addr) >= sizeof(uint32_t), "Incompatible s_addr.sin_addr.s_addr size"); + memcpy(&(s_addr.sin_addr.s_addr), &addr.s_addr, sizeof(uint32_t)); + + return IP(reinterpret_cast(&s_addr)); + } +} + +IP GetIPv6(const std::string &host) { + in6_addr addr6; + int res = inet_pton(AF_INET6, host.c_str(), &(addr6.s6_addr16)); + if (res == 0) { + throw std::invalid_argument("Invalid IPv6 network address"); + } else if (res < 0) { + throw std::invalid_argument(strerror(errno)); + } else { + struct sockaddr_in6 s_addr = {0}; + s_addr.sin6_family = AF_INET6; + + static_assert(sizeof(s_addr.sin6_addr.s6_addr16) >= 8 * sizeof(uint16_t), "Incompatible s_addr.sin6_addr.s6_addr16 size"); + memcpy(&(s_addr.sin6_addr.s6_addr16), &addr6.s6_addr16, + 8 * sizeof(uint16_t)); + + return IP(reinterpret_cast(&s_addr)); + } +} +} // namespace + +Port::Port(uint16_t port) : port(port) {} + +Port::Port(const std::string &data) { + if (data.empty()) + throw std::invalid_argument("Invalid port: empty port"); + char *end = 0; + long port_num = strtol(data.c_str(), &end, 10); + if (*end != 0 || port_num < Port::min() || port_num > Port::max()) + throw std::invalid_argument("Invalid port: " + data); + port = static_cast(port_num); +} + +bool Port::isReserved() const { return port < 1024; } + +bool Port::isUsed() const { + throw std::runtime_error("Unimplemented"); + return false; +} + +std::string Port::toString() const { return std::to_string(port); } + +IP::IP() { + family = AF_INET; + addr = {0}; + addr.sin_family = AF_INET; + uint8_t buff[INET_ADDRSTRLEN + 1] = {0, 0, 0, 0}; + memcpy(&addr.sin_addr.s_addr, buff, INET_ADDRSTRLEN); +} + +IP::IP(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { + family = AF_INET; + addr = {0}; + addr.sin_family = AF_INET; + uint8_t buff[INET_ADDRSTRLEN + 1] = {a, b, c, d}; + memcpy(&addr.sin_addr.s_addr, buff, INET_ADDRSTRLEN); +} + +IP::IP(uint16_t a, uint16_t b, uint16_t c, uint16_t d, uint16_t e, uint16_t f, + uint16_t g, uint16_t h) { + family = AF_INET6; + addr6 = {0}; + addr6.sin6_family = AF_INET6; + uint16_t buff[9]{a, b, c, d, e, f, g, h, '\0'}; + uint16_t remap[9] = {0, 0, 0, 0, 0, 0, 0, 0, '\0'}; + if (htonl(1) != 1) { + for (int i = 0; i < 8; i++) { + uint16_t x = buff[i]; + uint16_t y = htons(x); + remap[i] = y; + } + } else { + memcpy(&remap, &buff, sizeof(remap)); + } + memcpy(&addr6.sin6_addr.s6_addr16, &remap, 8 * sizeof(uint16_t)); +} + +IP::IP(struct sockaddr *_addr) { + if (_addr->sa_family == AF_INET) { + struct sockaddr_in *in_addr = reinterpret_cast(_addr); + family = AF_INET; + port = in_addr->sin_port; + memcpy(&(addr.sin_addr.s_addr), &(in_addr->sin_addr.s_addr), + sizeof(in_addr_t)); + } else if (_addr->sa_family == AF_INET6) { + struct sockaddr_in6 *in_addr = + reinterpret_cast(_addr); + family = AF_INET6; + port = in_addr->sin6_port; + memcpy(&(addr6.sin6_addr.s6_addr16), &(in_addr->sin6_addr.s6_addr16), + 8 * sizeof(uint16_t)); + } +} + +IP IP::any() { return IP(0, 0, 0, 0); } + +IP IP::any(bool is_ipv6) { + if (is_ipv6) { + return IP(0, 0, 0, 0, 0, 0, 0, 0); + } else { + return IP(0, 0, 0, 0); + } +} + +IP IP::loopback() { return IP(127, 0, 0, 1); } + +IP IP::loopback(bool is_ipv6) { + if (is_ipv6) { + return IP(0, 0, 0, 0, 0, 0, 0, 1); + } else { + return IP(127, 0, 0, 1); + } +} + +int IP::getFamily() const { return family; } + +int IP::getPort() const { return port; } + +std::string IP::toString() const { + char buff[INET6_ADDRSTRLEN + 1]; + if (family == AF_INET) { + in_addr_t addr_; + toNetwork(&addr_); + inet_ntop(AF_INET, &addr_, buff, INET_ADDRSTRLEN); + } else if (family == AF_INET6) { + struct in6_addr addr_ = in6addr_any; + toNetwork(&addr_); + inet_ntop(AF_INET6, &addr_, buff, INET6_ADDRSTRLEN); + } + return std::string(buff); +} + +void IP::toNetwork(in_addr_t *_addr) const { + memcpy(_addr, &addr.sin_addr.s_addr, sizeof(uint32_t)); +} + +void IP::toNetwork(struct in6_addr *out) const { + memcpy(&out->s6_addr16, &(addr6.sin6_addr.s6_addr16), 8 * sizeof(uint16_t)); +} + +bool IP::supported() { + struct ifaddrs *ifaddr = nullptr; + struct ifaddrs *ifa = nullptr; + int addr_family, n; + bool supportsIpv6 = false; + + if (getifaddrs(&ifaddr) == -1) { + throw std::runtime_error("Call to getifaddrs() failed"); + } + + for (ifa = ifaddr, n = 0; ifa != nullptr; ifa = ifa->ifa_next, n++) { + if (ifa->ifa_addr == nullptr) { + continue; + } + + addr_family = ifa->ifa_addr->sa_family; + if (addr_family == AF_INET6) { + supportsIpv6 = true; + continue; + } + } + + freeifaddrs(ifaddr); + return supportsIpv6; +} + +AddressParser::AddressParser(const std::string &data) { + std::size_t end_pos = data.find(']'); + std::size_t start_pos = data.find('['); + if (start_pos != std::string::npos && end_pos != std::string::npos && + start_pos < end_pos) { + std::size_t colon_pos = data.find_first_of(':', end_pos); + if (colon_pos != std::string::npos) { + hasColon_ = true; + } + host_ = data.substr(start_pos, end_pos + 1); + family_ = AF_INET6; + ++end_pos; + } else { + std::size_t colon_pos = data.find(':'); + if (colon_pos != std::string::npos) { + hasColon_ = true; + } + end_pos = colon_pos; + host_ = data.substr(0, end_pos); + family_ = AF_INET; + } + + if (end_pos != std::string::npos && hasColon_) { + port_ = data.substr(end_pos + 1); + if (port_.empty()) + throw std::invalid_argument("Invalid port"); + } +} + +const std::string &AddressParser::rawHost() const { return host_; } + +const std::string &AddressParser::rawPort() const { return port_; } + +bool AddressParser::hasColon() const { return hasColon_; } + +int AddressParser::family() const { return family_; } + +Address::Address() : ip_{}, port_{0} {} + +Address::Address(std::string host, Port port) { + std::string addr = std::move(host); + addr.append(":"); + addr.append(port.toString()); + init(std::move(addr)); +} + +Address::Address(std::string addr) { init(std::move(addr)); } + +Address::Address(const char *addr) { init(std::string(addr)); } + +Address::Address(IP ip, Port port) : ip_(ip), port_(port) {} + +Address Address::fromUnix(struct sockaddr *addr) { + if ((addr->sa_family == AF_INET) or (addr->sa_family == AF_INET6)) { + IP ip = IP(addr); + Port port = Port(static_cast(ip.getPort())); + assert(addr); + return Address(ip, port); + } + throw Error("Not an IP socket"); +} + +Address Address::fromUnix(struct sockaddr_in *addr) { + return Address::fromUnix(reinterpret_cast(addr)); +} + +std::string Address::host() const { return ip_.toString(); } + +Port Address::port() const { return port_; } + +int Address::family() const { return ip_.getFamily(); } + +void Address::init(const std::string &addr) { + AddressParser parser(addr); + int family_ = parser.family(); + std::string host_; + if (family_ == AF_INET6) { + const std::string &raw_host = parser.rawHost(); + assert(raw_host.size() > 2); + host_ = addr.substr(1, raw_host.size() - 2); + + ip_ = GetIPv6(host_); + } else if (family_ == AF_INET) { + host_ = parser.rawHost(); + + if (host_ == "*") { + host_ = "0.0.0.0"; + } else if (host_ == "localhost") { + host_ = "127.0.0.1"; + } + + struct hostent *hp = ::gethostbyname(host_.c_str()); + + if (hp) { + char **addr = hp->h_addr_list; + while (*addr) { + host_ = + std::string(inet_ntoa(*reinterpret_cast(*addr))); + break; + } + } + + ip_ = GetIPv4(host_); + } else { + assert(false); + } + + const std::string &portPart = parser.rawPort(); + if (portPart.empty()) { + if (parser.hasColon()) { + // "www.example.com:" or "127.0.0.1:" cases + throw std::invalid_argument("Invalid port"); + } else { + // "www.example.com" or "127.0.0.1" cases + port_ = Const::HTTP_STANDARD_PORT; + } + } else { + char *end = 0; + long port = strtol(portPart.c_str(), &end, 10); + if (*end != 0 || port < Port::min() || port > Port::max()) + throw std::invalid_argument("Invalid port"); + port_ = Port(static_cast(port)); + } +} + +Error::Error(const char *message) : std::runtime_error(message) {} + +Error::Error(std::string message) : std::runtime_error(std::move(message)) {} + +Error Error::system(const char *message) { + const char *err = strerror(errno); + + std::string str(message); + str += ": "; + str += err; + + return Error(std::move(str)); +} + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/os.cc b/projects/frontend/server-webapi/pistache/src/common/os.cc new file mode 100755 index 0000000000000000000000000000000000000000..4e49981d6114397738f6a8a83c0f47140daf747d --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/os.cc @@ -0,0 +1,264 @@ +/* os.cc + Mathieu Stefani, 13 August 2015 + +*/ + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Pistache { + +uint hardware_concurrency() { return std::thread::hardware_concurrency(); } + +bool make_non_blocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) + return false; + + flags |= O_NONBLOCK; + int ret = fcntl(fd, F_SETFL, flags); + if (ret == -1) + return false; + + return true; +} + +CpuSet::CpuSet() { bits.reset(); } + +CpuSet::CpuSet(std::initializer_list cpus) { set(cpus); } + +void CpuSet::clear() { bits.reset(); } + +CpuSet &CpuSet::set(size_t cpu) { + if (cpu >= Size) { + throw std::invalid_argument("Trying to set invalid cpu number"); + } + + bits.set(cpu); + return *this; +} + +CpuSet &CpuSet::unset(size_t cpu) { + if (cpu >= Size) { + throw std::invalid_argument("Trying to unset invalid cpu number"); + } + + bits.set(cpu, false); + return *this; +} + +CpuSet &CpuSet::set(std::initializer_list cpus) { + for (auto cpu : cpus) + set(cpu); + return *this; +} + +CpuSet &CpuSet::unset(std::initializer_list cpus) { + for (auto cpu : cpus) + unset(cpu); + return *this; +} + +CpuSet &CpuSet::setRange(size_t begin, size_t end) { + if (begin > end) { + throw std::range_error("Invalid range, begin > end"); + } + + for (size_t cpu = begin; cpu < end; ++cpu) { + set(cpu); + } + + return *this; +} + +CpuSet &CpuSet::unsetRange(size_t begin, size_t end) { + if (begin > end) { + throw std::range_error("Invalid range, begin > end"); + } + + for (size_t cpu = begin; cpu < end; ++cpu) { + unset(cpu); + } + + return *this; +} + +bool CpuSet::isSet(size_t cpu) const { + if (cpu >= Size) { + throw std::invalid_argument("Trying to test invalid cpu number"); + } + + return bits.test(cpu); +} + +size_t CpuSet::count() const { return bits.count(); } + +cpu_set_t CpuSet::toPosix() const { + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + + for (size_t cpu = 0; cpu < Size; ++cpu) { + if (bits.test(cpu)) + CPU_SET(cpu, &cpu_set); + } + + return cpu_set; +} + +namespace Polling { + +Event::Event(Tag _tag) : flags(), fd(-1), tag(_tag) {} + +Epoll::Epoll() + : epoll_fd([&]() { return TRY_RET(epoll_create(Const::MaxEvents)); }()) {} + +Epoll::~Epoll() { + if (epoll_fd >= 0) { + close(epoll_fd); + } +} + +void Epoll::addFd(Fd fd, Flags interest, Tag tag, Mode mode) { + struct epoll_event ev; + ev.events = toEpollEvents(interest); + if (mode == Mode::Edge) + ev.events |= EPOLLET; + ev.data.u64 = tag.value_; + + TRY(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev)); +} + +void Epoll::addFdOneShot(Fd fd, Flags interest, Tag tag, Mode mode) { + struct epoll_event ev; + ev.events = toEpollEvents(interest); + ev.events |= EPOLLONESHOT; + if (mode == Mode::Edge) + ev.events |= EPOLLET; + ev.data.u64 = tag.value_; + + TRY(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev)); +} + +void Epoll::removeFd(Fd fd) { + struct epoll_event ev; + TRY(epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, &ev)); +} + +void Epoll::rearmFd(Fd fd, Flags interest, Tag tag, Mode mode) { + struct epoll_event ev; + ev.events = toEpollEvents(interest); + if (mode == Mode::Edge) + ev.events |= EPOLLET; + ev.data.u64 = tag.value_; + + TRY(epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev)); +} + +int Epoll::poll(std::vector &events, + const std::chrono::milliseconds timeout) const { + struct epoll_event evs[Const::MaxEvents]; + + int ready_fds = -1; + do { + ready_fds = ::epoll_wait(epoll_fd, evs, Const::MaxEvents, static_cast(timeout.count())); + } while (ready_fds < 0 && errno == EINTR); + + for (int i = 0; i < ready_fds; ++i) { + const struct epoll_event *ev = evs + i; + + const Tag tag(ev->data.u64); + + Event event(tag); + event.flags = toNotifyOn(ev->events); + events.push_back(event); + } + + return ready_fds; +} + +int Epoll::toEpollEvents(const Flags &interest) { + int events = 0; + + if (interest.hasFlag(NotifyOn::Read)) + events |= EPOLLIN; + if (interest.hasFlag(NotifyOn::Write)) + events |= EPOLLOUT; + if (interest.hasFlag(NotifyOn::Hangup)) + events |= EPOLLHUP; + if (interest.hasFlag(NotifyOn::Shutdown)) + events |= EPOLLRDHUP; + + return events; +} + +Flags Epoll::toNotifyOn(int events) { + Flags flags; + + if (events & EPOLLIN) + flags.setFlag(NotifyOn::Read); + if (events & EPOLLOUT) + flags.setFlag(NotifyOn::Write); + if (events & EPOLLHUP) + flags.setFlag(NotifyOn::Hangup); + if (events & EPOLLRDHUP) { + flags.setFlag(NotifyOn::Shutdown); + } + + return flags; +} + +} // namespace Polling + +NotifyFd::NotifyFd() : event_fd(-1) {} + +Polling::Tag NotifyFd::bind(Polling::Epoll &poller) { + event_fd = TRY_RET(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + Polling::Tag tag(event_fd); + + poller.addFd(event_fd, Flags(Polling::NotifyOn::Read), tag, + Polling::Mode::Edge); + return tag; +} + +bool NotifyFd::isBound() const { return event_fd != -1; } + +Polling::Tag NotifyFd::tag() const { return Polling::Tag(event_fd); } + +void NotifyFd::notify() const { + if (!isBound()) + throw std::runtime_error("Can not notify an unbound fd"); + eventfd_t val = 1; + TRY(eventfd_write(event_fd, val)); +} + +void NotifyFd::read() const { + if (!isBound()) + throw std::runtime_error("Can not read an unbound fd"); + eventfd_t val; + TRY(eventfd_read(event_fd, &val)); +} + +bool NotifyFd::tryRead() const { + eventfd_t val; + int res = eventfd_read(event_fd, &val); + if (res == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return false; + throw std::runtime_error("Failed to read eventfd"); + } + + return true; +} + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/peer.cc b/projects/frontend/server-webapi/pistache/src/common/peer.cc new file mode 100755 index 0000000000000000000000000000000000000000..2cab9ffa5051b99c1f1e7a4b9b4c06fa10d081b2 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/peer.cc @@ -0,0 +1,125 @@ +/* peer.cc + Mathieu Stefani, 12 August 2015 + +*/ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace Pistache { +namespace Tcp { + +namespace { +struct ConcretePeer : Peer { + ConcretePeer() = default; + ConcretePeer(Fd fd, const Address &addr, void *ssl) : Peer(fd, addr, ssl) {} +}; +} // namespace + +Peer::Peer(Fd fd, const Address &addr, void *ssl) + : fd_(fd), addr(addr), ssl_(ssl) {} + +Peer::~Peer() { +#ifdef PISTACHE_USE_SSL + if (ssl_) + SSL_free(static_cast(ssl_)); +#endif /* PISTACHE_USE_SSL */ +} + +std::shared_ptr Peer::Create(Fd fd, const Address &addr) { + return std::make_shared(fd, addr, nullptr); +} + +std::shared_ptr Peer::CreateSSL(Fd fd, const Address &addr, void *ssl) { + return std::make_shared(fd, addr, ssl); +} + +const Address &Peer::address() const { return addr; } + +const std::string &Peer::hostname() { + if (hostname_.empty()) { + char host[NI_MAXHOST]; + struct sockaddr_in sa; + sa.sin_family = AF_INET; + if (inet_pton(AF_INET, addr.host().c_str(), &sa.sin_addr) == 0) { + hostname_ = addr.host(); + } else { + if (!getnameinfo((struct sockaddr *)&sa, sizeof(sa), host, sizeof(host), + NULL, 0 // Service info + , + NI_NAMEREQD // Raise an error if name resolution failed + )) { + hostname_.assign((char *)host); + } + } + } + return hostname_; +} + +void *Peer::ssl() const { return ssl_; } + +int Peer::fd() const { + if (fd_ == -1) { + throw std::runtime_error("The peer has no associated fd"); + } + + return fd_; +} + +void Peer::putData(std::string name, std::shared_ptr data) { + auto it = data_.find(name); + if (it != std::end(data_)) { + throw std::runtime_error("The data already exists"); + } + + data_.insert(std::make_pair(std::move(name), std::move(data))); +} + +std::shared_ptr Peer::getData(std::string name) const { + auto data = tryGetData(std::move(name)); + if (data == nullptr) { + throw std::runtime_error("The data does not exist"); + } + + return data; +} + +std::shared_ptr Peer::tryGetData(std::string(name)) const { + auto it = data_.find(name); + if (it == std::end(data_)) + return nullptr; + + return it->second; +} + +Async::Promise Peer::send(const RawBuffer &buffer, int flags) { + return transport()->asyncWrite(fd_, buffer, flags); +} + +std::ostream &operator<<(std::ostream &os, Peer &peer) { + const auto &addr = peer.address(); + os << "(" << addr.host() << ", " << addr.port() << ") [" << peer.hostname() + << "]"; + return os; +} + +void Peer::associateTransport(Transport *transport) { transport_ = transport; } + +Transport *Peer::transport() const { + if (!transport_) + throw std::logic_error("Orphaned peer"); + + return transport_; +} + +} // namespace Tcp +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/reactor.cc b/projects/frontend/server-webapi/pistache/src/common/reactor.cc new file mode 100755 index 0000000000000000000000000000000000000000..d091bc63cdc37bb29b0d30e17ae3f2ffe826ac88 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/reactor.cc @@ -0,0 +1,542 @@ +/* + Mathieu Stefani, 15 juin 2016 + + Implementation of the Reactor +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; + +namespace Pistache { +namespace Aio { + +class Reactor::Impl { +public: + Impl(Reactor *reactor) : reactor_(reactor) {} + + virtual ~Impl() = default; + + virtual Reactor::Key addHandler(const std::shared_ptr &handler, + bool setKey) = 0; + + virtual std::vector> + handlers(const Reactor::Key &key) const = 0; + + virtual void registerFd(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, Polling::Tag tag, + Polling::Mode mode = Polling::Mode::Level) = 0; + + virtual void registerFdOneShot(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, Polling::Tag tag, + Polling::Mode mode = Polling::Mode::Level) = 0; + + virtual void modifyFd(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, Polling::Tag tag, + Polling::Mode mode = Polling::Mode::Level) = 0; + + virtual void runOnce() = 0; + virtual void run() = 0; + + virtual void shutdown() = 0; + + Reactor *reactor_; +}; + +/* Synchronous implementation of the reactor that polls in the context + * of the same thread + */ +class SyncImpl : public Reactor::Impl { +public: + explicit SyncImpl(Reactor *reactor) + : Reactor::Impl(reactor), handlers_(), shutdown_(), shutdownFd(), + poller() { + shutdownFd.bind(poller); + } + + Reactor::Key addHandler(const std::shared_ptr &handler, + bool setKey = true) override { + + handler->registerPoller(poller); + + handler->reactor_ = reactor_; + + auto key = handlers_.add(handler); + if (setKey) + handler->key_ = key; + + return key; + } + + std::shared_ptr handler(const Reactor::Key &key) const { + return handlers_.at(key.data()); + } + + std::vector> + handlers(const Reactor::Key &key) const override { + std::vector> res; + + res.push_back(handler(key)); + return res; + } + + void registerFd(const Reactor::Key &key, Fd fd, Polling::NotifyOn interest, + Polling::Tag tag, + Polling::Mode mode = Polling::Mode::Level) override { + + auto pollTag = encodeTag(key, tag); + poller.addFd(fd, Flags(interest), pollTag, mode); + } + + void registerFdOneShot(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, Polling::Tag tag, + Polling::Mode mode = Polling::Mode::Level) override { + + auto pollTag = encodeTag(key, tag); + poller.addFdOneShot(fd, Flags(interest), pollTag, mode); + } + + void modifyFd(const Reactor::Key &key, Fd fd, Polling::NotifyOn interest, + Polling::Tag tag, + Polling::Mode mode = Polling::Mode::Level) override { + + auto pollTag = encodeTag(key, tag); + poller.rearmFd(fd, Flags(interest), pollTag, mode); + } + + void runOnce() override { + if (handlers_.empty()) + throw std::runtime_error("You need to set at least one handler"); + + for (;;) { + std::vector events; + int ready_fds = poller.poll(events); + + switch (ready_fds) { + case -1: + break; + case 0: + break; + default: + if (shutdown_) + return; + + handleFds(std::move(events)); + } + } + } + + void run() override { + handlers_.forEachHandler([](const std::shared_ptr handler) { + handler->context_.tid = std::this_thread::get_id(); + }); + + while (!shutdown_) + runOnce(); + } + + void shutdown() override { + shutdown_.store(true); + shutdownFd.notify(); + } + + static constexpr size_t MaxHandlers() { return HandlerList::MaxHandlers; } + +private: + static Polling::Tag encodeTag(const Reactor::Key &key, Polling::Tag tag) { + uint64_t value = tag.value(); + return HandlerList::encodeTag(key, value); + } + + static std::pair decodeTag(const Polling::Tag &tag) { + return HandlerList::decodeTag(tag); + } + + void handleFds(std::vector events) const { + // Fast-path: if we only have one handler, do not bother scanning the fds to + // find the right handlers + if (handlers_.size() == 1) + handlers_.at(0)->onReady(FdSet(std::move(events))); + else { + std::unordered_map, std::vector> + fdHandlers; + + for (auto &event : events) { + size_t index; + uint64_t value; + + std::tie(index, value) = decodeTag(event.tag); + auto handler_ = handlers_.at(index); + auto &evs = fdHandlers.at(handler_); + evs.push_back(std::move(event)); + } + + for (auto &data : fdHandlers) { + data.first->onReady(FdSet(std::move(data.second))); + } + } + } + + struct HandlerList { + + // We are using the highest 8 bits of the fd to encode the index of the + // handler, which gives us a maximum of 2**8 - 1 handler, 255 + static constexpr size_t HandlerBits = 8; + static constexpr size_t HandlerShift = sizeof(uint64_t) - HandlerBits; + static constexpr uint64_t DataMask = uint64_t(-1) >> HandlerBits; + + static constexpr size_t MaxHandlers = (1 << HandlerBits) - 1; + + HandlerList() : handlers(), index_() { + std::fill(std::begin(handlers), std::end(handlers), nullptr); + } + + HandlerList(const HandlerList &other) = delete; + HandlerList &operator=(const HandlerList &other) = delete; + + HandlerList(HandlerList &&other) = default; + HandlerList &operator=(HandlerList &&other) = default; + + HandlerList clone() const { + HandlerList list; + + for (size_t i = 0; i < index_; ++i) { + list.handlers.at(i) = handlers.at(i)->clone(); + } + list.index_ = index_; + + return list; + } + + Reactor::Key add(const std::shared_ptr &handler) { + if (index_ == MaxHandlers) + throw std::runtime_error("Maximum handlers reached"); + + Reactor::Key key(index_); + handlers.at(index_++) = handler; + + return key; + } + + std::shared_ptr operator[](size_t index) const { + return handlers.at(index); + } + + std::shared_ptr at(size_t index) const { + if (index >= index_) + throw std::runtime_error("Attempting to retrieve invalid handler"); + + return handlers.at(index); + } + + bool empty() const { return index_ == 0; } + + size_t size() const { return index_; } + + static Polling::Tag encodeTag(const Reactor::Key &key, uint64_t value) { + auto index = key.data(); + // The reason why we are using the most significant bits to encode + // the index of the handler is that in the fast path, we won't need + // to shift the value to retrieve the fd if there is only one handler as + // all the bits will already be set to 0. + auto encodedValue = (index << HandlerShift) | value; + return Polling::Tag(encodedValue); + } + + static std::pair decodeTag(const Polling::Tag &tag) { + auto value = tag.value(); + size_t index = value >> HandlerShift; + uint64_t fd = value & DataMask; + + return std::make_pair(index, fd); + } + + template void forEachHandler(Func func) const { + for (size_t i = 0; i < index_; ++i) + func(handlers.at(i)); + } + + private: + std::array, MaxHandlers> handlers; + size_t index_; + }; + + HandlerList handlers_; + + std::atomic shutdown_; + NotifyFd shutdownFd; + + Polling::Epoll poller; +}; + +/* Asynchronous implementation of the reactor that spawns a number N of threads + * and creates a polling fd per thread + * + * Implementation detail: + * + * Here is how it works: the implementation simply starts a synchronous variant + * of the implementation in its own std::thread. When adding an handler, it + * will add a clone() of the handler to every worker (thread), and assign its + * own key to the handler. Here is where things start to get interesting. Here + * is how the key encoding works for every handler: + * + * [ handler idx ] [ worker idx ] + * ------------------------ ---------------------------- + * ^ 32 bits ^ 32 bits + * ----------------------------------------------------- + * ^ 64 bits + * + * Since we have up to 64 bits of data for every key, we encode the index of the + * handler that has been assigned by the SyncImpl in the upper 32 bits, and + * encode the index of the worker thread in the lowest 32 bits. + * + * When registering a fd for a given key, the AsyncImpl then knows which worker + * to use by looking at the lowest 32 bits of the Key's data. The SyncImpl will + * then use the highest 32 bits to retrieve the index of the handler. + */ + +class AsyncImpl : public Reactor::Impl { +public: + static constexpr uint32_t KeyMarker = 0xBADB0B; + + AsyncImpl(Reactor *reactor, size_t threads, const std::string &threadsName) + : Reactor::Impl(reactor) { + + if (threads > SyncImpl::MaxHandlers()) + throw std::runtime_error("Too many worker threads requested (max "s + + std::to_string(SyncImpl::MaxHandlers()) + ")."s); + + for (size_t i = 0; i < threads; ++i) + workers_.emplace_back(std::make_unique(reactor, threadsName)); + } + + Reactor::Key addHandler(const std::shared_ptr &handler, + bool) override { + + std::array keys; + + for (size_t i = 0; i < workers_.size(); ++i) { + auto &wrk = workers_.at(i); + + auto cl = handler->clone(); + auto key = wrk->sync->addHandler(cl, false /* setKey */); + auto newKey = encodeKey(key, static_cast(i)); + cl->key_ = newKey; + + keys.at(i) = key; + } + + auto data = keys.at(0).data() << 32 | KeyMarker; + + return Reactor::Key(data); + } + + std::vector> + handlers(const Reactor::Key &key) const override { + + const std::pair idx_marker = decodeKey(key); + if (idx_marker.second != KeyMarker) + throw std::runtime_error("Invalid key"); + + Reactor::Key originalKey(idx_marker.first); + + std::vector> res; + res.reserve(workers_.size()); + for (auto &wrk : workers_) { + res.push_back(wrk->sync->handler(originalKey)); + } + + return res; + } + + void registerFd(const Reactor::Key &key, Fd fd, Polling::NotifyOn interest, + Polling::Tag tag, + Polling::Mode mode = Polling::Mode::Level) override { + dispatchCall(key, &SyncImpl::registerFd, fd, interest, tag, mode); + } + + void registerFdOneShot(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, Polling::Tag tag, + Polling::Mode mode = Polling::Mode::Level) override { + dispatchCall(key, &SyncImpl::registerFdOneShot, fd, interest, tag, mode); + } + + void modifyFd(const Reactor::Key &key, Fd fd, Polling::NotifyOn interest, + Polling::Tag tag, + Polling::Mode mode = Polling::Mode::Level) override { + dispatchCall(key, &SyncImpl::modifyFd, fd, interest, tag, mode); + } + + void runOnce() override {} + + void run() override { + for (auto &wrk : workers_) + wrk->run(); + } + + void shutdown() override { + for (auto &wrk : workers_) + wrk->shutdown(); + } + +private: + static Reactor::Key encodeKey(const Reactor::Key &originalKey, + uint32_t value) { + auto data = originalKey.data(); + auto newValue = data << 32 | value; + return Reactor::Key(newValue); + } + + static std::pair + decodeKey(const Reactor::Key &encodedKey) { + auto data = encodedKey.data(); + auto hi = static_cast(data >> 32); + auto lo = static_cast(data & 0xFFFFFFFF); + return std::make_pair(hi, lo); + } + +#define CALL_MEMBER_FN(obj, pmf) (obj->*(pmf)) + + template + void dispatchCall(const Reactor::Key &key, Func func, Args &&... args) const { + auto decoded = decodeKey(key); + auto &wrk = workers_.at(decoded.second); + + Reactor::Key originalKey(decoded.first); + CALL_MEMBER_FN(wrk->sync.get(), func) + (originalKey, std::forward(args)...); + } + +#undef CALL_MEMBER_FN + + struct Worker { + + explicit Worker(Reactor *reactor, const std::string &threadsName) + : thread(), sync(new SyncImpl(reactor)), threadsName_(threadsName) {} + + ~Worker() { + if (thread.joinable()) + thread.join(); + } + + void run() { + thread = std::thread([=]() { + if (threadsName_.size() > 0) { + pthread_setname_np(pthread_self(), + threadsName_.substr(0, 15).c_str()); + } + sync->run(); + }); + } + + void shutdown() { sync->shutdown(); } + + std::thread thread; + std::unique_ptr sync; + std::string threadsName_; + }; + + std::vector> workers_; +}; + +Reactor::Key::Key() : data_(0) {} + +Reactor::Key::Key(uint64_t data) : data_(data) {} + +Reactor::Reactor() = default; + +Reactor::~Reactor() = default; + +std::shared_ptr Reactor::create() { + return std::make_shared(); +} + +void Reactor::init() { + SyncContext context; + init(context); +} + +void Reactor::init(const ExecutionContext &context) { + impl_.reset(context.makeImpl(this)); +} + +Reactor::Key Reactor::addHandler(const std::shared_ptr &handler) { + return impl()->addHandler(handler, true); +} + +std::vector> +Reactor::handlers(const Reactor::Key &key) { + return impl()->handlers(key); +} + +void Reactor::registerFd(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, Polling::Tag tag, + Polling::Mode mode) { + impl()->registerFd(key, fd, interest, tag, mode); +} + +void Reactor::registerFdOneShot(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, Polling::Tag tag, + Polling::Mode mode) { + impl()->registerFdOneShot(key, fd, interest, tag, mode); +} + +void Reactor::registerFd(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, Polling::Mode mode) { + impl()->registerFd(key, fd, interest, Polling::Tag(fd), mode); +} + +void Reactor::registerFdOneShot(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, + Polling::Mode mode) { + impl()->registerFdOneShot(key, fd, interest, Polling::Tag(fd), mode); +} + +void Reactor::modifyFd(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, Polling::Tag tag, + Polling::Mode mode) { + impl()->modifyFd(key, fd, interest, tag, mode); +} + +void Reactor::modifyFd(const Reactor::Key &key, Fd fd, + Polling::NotifyOn interest, Polling::Mode mode) { + impl()->modifyFd(key, fd, interest, Polling::Tag(fd), mode); +} + +void Reactor::run() { impl()->run(); } + +void Reactor::shutdown() { + if (impl_) + impl()->shutdown(); +} + +void Reactor::runOnce() { impl()->runOnce(); } + +Reactor::Impl *Reactor::impl() const { + if (!impl_) + throw std::runtime_error( + "Invalid object state, you should call init() before."); + + return impl_.get(); +} + +Reactor::Impl *SyncContext::makeImpl(Reactor *reactor) const { + return new SyncImpl(reactor); +} + +Reactor::Impl *AsyncContext::makeImpl(Reactor *reactor) const { + return new AsyncImpl(reactor, threads_, threadsName_); +} + +AsyncContext AsyncContext::singleThreaded() { return AsyncContext(1); } + +} // namespace Aio +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/stream.cc b/projects/frontend/server-webapi/pistache/src/common/stream.cc new file mode 100755 index 0000000000000000000000000000000000000000..ac325953dcbb8a107077d5b62f7f022c633c00df --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/stream.cc @@ -0,0 +1,287 @@ +/* stream.cc + Mathieu Stefani, 05 September 2015 + +*/ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Pistache { + +RawBuffer::RawBuffer() : data_(), length_(0), isDetached_(false) {} + +RawBuffer::RawBuffer(std::string data, size_t length, bool isDetached) + : data_(std::move(data)), length_(length), isDetached_(isDetached) {} + +RawBuffer::RawBuffer(const char *data, size_t length, bool isDetached) + : data_(), length_(length), isDetached_(isDetached) { + // input may come not from a ZTS - copy only length_ characters. + data_.assign(data, length_); +} + +RawBuffer RawBuffer::detach(size_t fromIndex) { + if (data_.empty()) + return RawBuffer(); + + if (length_ < fromIndex) + throw std::range_error( + "Trying to detach buffer from an index bigger than lengthght."); + + auto newDatalength = length_ - fromIndex; + std::string newData = data_.substr(fromIndex, newDatalength); + + return RawBuffer(std::move(newData), newDatalength, true); +} + +const std::string &RawBuffer::data() const { return data_; } + +size_t RawBuffer::size() const { return length_; } + +bool RawBuffer::isDetached() const { return isDetached_; } + +FileBuffer::FileBuffer(const std::string &fileName) + : fileName_(fileName), fd_(-1), size_(0) { + if (fileName.empty()) { + throw std::runtime_error("Empty fileName"); + } + + int fd = open(fileName.c_str(), O_RDONLY); + if (fd == -1) { + throw std::runtime_error("Could not open file"); + } + + struct stat sb; + int res = ::fstat(fd, &sb); + if (res == -1) { + close(fd); + throw std::runtime_error("Could not get file stats"); + } + + fd_ = fd; + size_ = sb.st_size; +} + +Fd FileBuffer::fd() const { return fd_; } + +size_t FileBuffer::size() const { return size_; } + +DynamicStreamBuf::DynamicStreamBuf(size_t size, size_t maxSize) + : data_(), maxSize_(maxSize) { + assert(size <= maxSize); + + reserve(size); +} + +DynamicStreamBuf::DynamicStreamBuf(DynamicStreamBuf &&other) + : data_(std::move(other.data_)), maxSize_(std::move(other.maxSize_)) { + setp(other.pptr(), other.epptr()); + other.setp(nullptr, nullptr); +} + +DynamicStreamBuf &DynamicStreamBuf::operator=(DynamicStreamBuf &&other) { + if (&other != this) { + data_ = std::move(other.data_); + maxSize_ = std::move(other.maxSize_); + setp(other.pptr(), other.epptr()); + other.setp(nullptr, nullptr); + } + + return *this; +} + +RawBuffer DynamicStreamBuf::buffer() const { + return RawBuffer(data_.data(), pptr() - data_.data()); +} + +size_t DynamicStreamBuf::maxSize() const { return maxSize_; } + +void DynamicStreamBuf::clear() { + // reset stream buffer to the whole backing storage. + this->setp(data_.data(), data_.data() + data_.size()); +} + +DynamicStreamBuf::int_type +DynamicStreamBuf::overflow(DynamicStreamBuf::int_type ch) { + if (!traits_type::eq_int_type(ch, traits_type::eof())) { + const auto size = data_.size(); + if (size < maxSize_) { + reserve((size ? size : 1u) * 2); + *pptr() = static_cast(ch); + pbump(1); + return traits_type::not_eof(ch); + } + } + + return traits_type::eof(); +} + +void DynamicStreamBuf::reserve(size_t size) { + if (size > maxSize_) { + size = maxSize_; + } + + const size_t oldSize = data_.size(); + data_.resize(size); + this->setp(data_.data() + oldSize, data_.data() + size); +} + +bool StreamCursor::advance(size_t count) { + if (static_cast(count) > buf->in_avail()) + return false; + + for (size_t i = 0; i < count; ++i) { + buf->sbumpc(); + } + + return true; +} + +bool StreamCursor::eol() const { return buf->sgetc() == CR && next() == LF; } + +bool StreamCursor::eof() const { return remaining() == 0; } + +int StreamCursor::next() const { + if (buf->in_avail() < 1) + return Eof; + + return buf->snext(); +} + +char StreamCursor::current() const { return static_cast(buf->sgetc()); } + +const char *StreamCursor::offset() const { return buf->curptr(); } + +const char *StreamCursor::offset(size_t off) const { + return buf->begptr() + off; +} + +size_t StreamCursor::diff(size_t other) const { + return buf->position() - other; +} + +size_t StreamCursor::diff(const StreamCursor &other) const { + return other.buf->position() - buf->position(); +} + +size_t StreamCursor::remaining() const { return buf->in_avail(); } + +void StreamCursor::reset() { buf->reset(); } + +bool match_raw(const void *buf, size_t len, StreamCursor &cursor) { + if (cursor.remaining() < len) + return false; + + if (memcmp(cursor.offset(), buf, len) == 0) { + cursor.advance(len); + return true; + } + + return false; +} + +bool match_string(const char *str, size_t len, StreamCursor &cursor, + CaseSensitivity cs) { + if (cursor.remaining() < len) + return false; + + if (cs == CaseSensitivity::Sensitive) { + if (strncmp(cursor.offset(), str, len) == 0) { + cursor.advance(len); + return true; + } + } else { + const char *off = cursor.offset(); + for (size_t i = 0; i < len; ++i) { + const char lhs = static_cast(std::tolower(str[i])); + const char rhs = static_cast(std::tolower(off[i])); + if (lhs != rhs) + return false; + } + + cursor.advance(len); + return true; + } + + return false; +} + +bool match_literal(char c, StreamCursor &cursor, CaseSensitivity cs) { + if (cursor.eof()) + return false; + + char lhs = (cs == CaseSensitivity::Sensitive ? c : static_cast(std::tolower(c))); + char rhs = + (cs == CaseSensitivity::Sensitive ? cursor.current() + : static_cast(std::tolower(cursor.current()))); + + if (lhs == rhs) { + cursor.advance(1); + return true; + } + + return false; +} + +bool match_until(char c, StreamCursor &cursor, CaseSensitivity cs) { + return match_until({c}, cursor, cs); +} + +bool match_until(std::initializer_list chars, StreamCursor &cursor, + CaseSensitivity cs) { + if (cursor.eof()) + return false; + + auto find = [&](char val) { + for (auto c : chars) { + char lhs = cs == CaseSensitivity::Sensitive ? c : static_cast(std::tolower(c)); + char rhs = cs == CaseSensitivity::Insensitive ? val : static_cast(std::tolower(val)); + + if (lhs == rhs) + return true; + } + + return false; + }; + + while (!cursor.eof()) { + const char c = cursor.current(); + if (find(c)) + return true; + cursor.advance(1); + } + + return false; +} + +bool match_double(double *val, StreamCursor &cursor) { + // @Todo: strtod does not support a length argument + char *end; + *val = strtod(cursor.offset(), &end); + if (end == cursor.offset()) + return false; + + cursor.advance(static_cast(end - cursor.offset())); + return true; +} + +void skip_whitespaces(StreamCursor &cursor) { + if (cursor.eof()) + return; + + int c; + while ((c = cursor.current()) != StreamCursor::Eof && + (c == ' ' || c == '\t')) { + cursor.advance(1); + } +} + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/tcp.cc b/projects/frontend/server-webapi/pistache/src/common/tcp.cc new file mode 100755 index 0000000000000000000000000000000000000000..12e0e63a766de23cccaf90de43ee842bc8aff116 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/tcp.cc @@ -0,0 +1,30 @@ +/* tcp.cc + Mathieu Stefani, 05 novembre 2015 + + TCP +*/ + +#include +#include + +namespace Pistache { +namespace Tcp { + +Handler::Handler() : transport_(nullptr) {} + +Handler::~Handler() {} + +void Handler::associateTransport(Transport *transport) { + transport_ = transport; +} + +void Handler::onConnection(const std::shared_ptr &peer) { + UNUSED(peer) +} + +void Handler::onDisconnection(const std::shared_ptr &peer) { + UNUSED(peer) +} + +} // namespace Tcp +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/timer_pool.cc b/projects/frontend/server-webapi/pistache/src/common/timer_pool.cc new file mode 100755 index 0000000000000000000000000000000000000000..abf2a2d39d21b57486cbd35f86d97745dd5bf514 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/timer_pool.cc @@ -0,0 +1,98 @@ +/* timer_pool.cc + Mathieu Stefani, 09 février 2016 + + Implementation of the timer pool +*/ + +#include +#include + +#include + +#include + +namespace Pistache { + +TimerPool::Entry::Entry() : fd_(-1), registered(false) { + state.store(static_cast(State::Idle)); +} + +TimerPool::Entry::~Entry() { + if (fd_ != -1) + close(fd_); +} + +Fd TimerPool::Entry::fd() const { + assert(fd_ != -1); + + return fd_; +} + +void TimerPool::Entry::initialize() { + if (fd_ == -1) { + fd_ = TRY_RET(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK)); + } +} + +void TimerPool::Entry::disarm() { + assert(fd_ != -1); + + itimerspec spec; + spec.it_interval.tv_sec = 0; + spec.it_interval.tv_nsec = 0; + + spec.it_value.tv_sec = 0; + spec.it_value.tv_nsec = 0; + + TRY(timerfd_settime(fd_, 0, &spec, 0)); +} + +void TimerPool::Entry::registerReactor(const Aio::Reactor::Key &key, + Aio::Reactor *reactor) { + if (!registered) { + reactor->registerFd(key, fd_, Polling::NotifyOn::Read); + registered = true; + } +} + +void TimerPool::Entry::armMs(std::chrono::milliseconds value) { + itimerspec spec; + spec.it_interval.tv_sec = 0; + spec.it_interval.tv_nsec = 0; + + if (value.count() < 1000) { + spec.it_value.tv_sec = 0; + spec.it_value.tv_nsec = + std::chrono::duration_cast(value).count(); + } else { + spec.it_value.tv_sec = + std::chrono::duration_cast(value).count(); + spec.it_value.tv_nsec = 0; + } + TRY(timerfd_settime(fd_, 0, &spec, 0)); +} + +TimerPool::TimerPool(size_t initialSize) { + for (size_t i = 0; i < initialSize; ++i) { + timers.push_back(std::make_shared()); + } +} + +std::shared_ptr TimerPool::pickTimer() { + for (auto &entry : timers) { + auto curState = static_cast(TimerPool::Entry::State::Idle); + auto newState = static_cast(TimerPool::Entry::State::Used); + if (entry->state.compare_exchange_strong(curState, newState)) { + entry->initialize(); + return entry; + } + } + + return nullptr; +} + +void TimerPool::releaseTimer(const std::shared_ptr &timer) { + timer->state.store(static_cast(TimerPool::Entry::State::Idle)); +} + +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/transport.cc b/projects/frontend/server-webapi/pistache/src/common/transport.cc new file mode 100755 index 0000000000000000000000000000000000000000..fd40e5f1e17aa22af34bca633e8e82e13a3887e1 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/transport.cc @@ -0,0 +1,468 @@ +/* traqnsport.cc + Mathieu Stefani, 02 July 2017 + + TCP transport handling + +*/ + +#include +#include + +#include +#include +#include +#include +#include + +namespace Pistache { + +using namespace Polling; + +namespace Tcp { + +Transport::Transport(const std::shared_ptr &handler) { + init(handler); +} + +void Transport::init(const std::shared_ptr &handler) { + handler_ = handler; + handler_->associateTransport(this); +} + +std::shared_ptr Transport::clone() const { + return std::make_shared(handler_->clone()); +} + +void Transport::registerPoller(Polling::Epoll &poller) { + writesQueue.bind(poller); + timersQueue.bind(poller); + peersQueue.bind(poller); + notifier.bind(poller); +} + +void Transport::handleNewPeer(const std::shared_ptr &peer) { + auto ctx = context(); + const bool isInRightThread = std::this_thread::get_id() == ctx.thread(); + if (!isInRightThread) { + PeerEntry entry(peer); + peersQueue.push(std::move(entry)); + } else { + handlePeer(peer); + } + int fd = peer->fd(); + { + Guard guard(toWriteLock); + toWrite.emplace(fd, std::deque{}); + } +} + +void Transport::onReady(const Aio::FdSet &fds) { + for (const auto &entry : fds) { + if (entry.getTag() == writesQueue.tag()) { + handleWriteQueue(); + } else if (entry.getTag() == timersQueue.tag()) { + handleTimerQueue(); + } else if (entry.getTag() == peersQueue.tag()) { + handlePeerQueue(); + } else if (entry.getTag() == notifier.tag()) { + handleNotify(); + } + + else if (entry.isReadable()) { + auto tag = entry.getTag(); + if (isPeerFd(tag)) { + auto &peer = getPeer(tag); + handleIncoming(peer); + } else if (isTimerFd(tag)) { + auto it = timers.find(static_cast(tag.value())); + auto &entry_ = it->second; + handleTimer(std::move(entry_)); + timers.erase(it->first); + } else { + throw std::runtime_error("Unknown fd"); + } + + } else if (entry.isWritable()) { + auto tag = entry.getTag(); + auto fd = static_cast(tag.value()); + + { + Guard guard(toWriteLock); + auto it = toWrite.find(fd); + if (it == std::end(toWrite)) { + throw std::runtime_error( + "Assertion Error: could not find write data"); + } + } + + reactor()->modifyFd(key(), fd, NotifyOn::Read, Polling::Mode::Edge); + + // Try to drain the queue + asyncWriteImpl(fd); + } + } +} + +void Transport::disarmTimer(Fd fd) { + auto it = timers.find(fd); + if (it == std::end(timers)) + throw std::runtime_error("Timer has not been armed"); + + auto &entry = it->second; + entry.disable(); +} + +void Transport::handleIncoming(const std::shared_ptr &peer) { + char buffer[Const::MaxBuffer] = {0}; + + ssize_t totalBytes = 0; + int fd = peer->fd(); + + for (;;) { + + ssize_t bytes; + +#ifdef PISTACHE_USE_SSL + if (peer->ssl() != NULL) { + bytes = SSL_read((SSL *)peer->ssl(), buffer + totalBytes, + static_cast(Const::MaxBuffer - totalBytes)); + } else { +#endif /* PISTACHE_USE_SSL */ + bytes = recv(fd, buffer + totalBytes, Const::MaxBuffer - totalBytes, 0); +#ifdef PISTACHE_USE_SSL + } +#endif /* PISTACHE_USE_SSL */ + + if (bytes == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (totalBytes > 0) { + handler_->onInput(buffer, totalBytes, peer); + } + } else { + if (errno == ECONNRESET) { + handlePeerDisconnection(peer); + } else { + throw std::runtime_error(strerror(errno)); + } + } + break; + } else if (bytes == 0) { + handlePeerDisconnection(peer); + break; + } + + else { + handler_->onInput(buffer, bytes, peer); + } + } +} + +void Transport::handlePeerDisconnection(const std::shared_ptr &peer) { + handler_->onDisconnection(peer); + + int fd = peer->fd(); + auto it = peers.find(fd); + if (it == std::end(peers)) + throw std::runtime_error("Could not find peer to erase"); + + peers.erase(it->first); + + { + // Clean up buffers + Guard guard(toWriteLock); + auto &wq = toWrite[fd]; + while (wq.size() > 0) { + wq.pop_front(); + } + toWrite.erase(fd); + } + + close(fd); +} + +void Transport::asyncWriteImpl(Fd fd) { + bool stop = false; + while (!stop) { + Guard guard(toWriteLock); + + auto it = toWrite.find(fd); + + // cleanup will have been handled by handlePeerDisconnection + if (it == std::end(toWrite)) { + return; + } + auto &wq = it->second; + if (wq.size() == 0) { + break; + } + + auto &entry = wq.front(); + int flags = entry.flags; + BufferHolder &buffer = entry.buffer; + Async::Deferred deferred = std::move(entry.deferred); + + auto cleanUp = [&]() { + wq.pop_front(); + if (wq.size() == 0) { + toWrite.erase(fd); + reactor()->modifyFd(key(), fd, NotifyOn::Read, Polling::Mode::Edge); + stop = true; + } + }; + + size_t totalWritten = buffer.offset(); + for (;;) { + ssize_t bytesWritten = 0; + auto len = buffer.size() - totalWritten; + + if (buffer.isRaw()) { + auto raw = buffer.raw(); + auto ptr = raw.data().c_str() + totalWritten; + +#ifdef PISTACHE_USE_SSL + auto it_ = peers.find(fd); + + if (it_ == std::end(peers)) + throw std::runtime_error("No peer found for fd: " + + std::to_string(fd)); + + if (it_->second->ssl() != NULL) { + auto ssl_ = static_cast(it_->second->ssl()); + bytesWritten = SSL_write(ssl_, ptr, static_cast(len)); + } else { +#endif /* PISTACHE_USE_SSL */ + bytesWritten = ::send(fd, ptr, len, flags); +#ifdef PISTACHE_USE_SSL + } +#endif /* PISTACHE_USE_SSL */ + } else { + auto file = buffer.fd(); + off_t offset = totalWritten; + +#ifdef PISTACHE_USE_SSL + auto it_ = peers.find(fd); + + if (it_ == std::end(peers)) + throw std::runtime_error("No peer found for fd: " + + std::to_string(fd)); + + if (it_->second->ssl() != NULL) { + auto ssl_ = static_cast(it_->second->ssl()); + bytesWritten = SSL_sendfile(ssl_, file, &offset, len); + } else { +#endif /* PISTACHE_USE_SSL */ + bytesWritten = ::sendfile(fd, file, &offset, len); +#ifdef PISTACHE_USE_SSL + } +#endif /* PISTACHE_USE_SSL */ + } + if (bytesWritten < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + + auto bufferHolder = buffer.detach(totalWritten); + + // pop_front kills buffer - so we cannot continue loop or use buffer + // after this point + wq.pop_front(); + wq.push_front(WriteEntry(std::move(deferred), bufferHolder, flags)); + reactor()->modifyFd(key(), fd, NotifyOn::Read | NotifyOn::Write, + Polling::Mode::Edge); + } + // EBADF can happen when the HTTP parser, in the case of + // an error, closes fd before the entire request is processed. + // https://github.com/oktal/pistache/issues/501 + else if (errno == EBADF || errno == EPIPE || errno == ECONNRESET) { + wq.pop_front(); + toWrite.erase(fd); + stop = true; + } else { + cleanUp(); + deferred.reject(Pistache::Error::system("Could not write data")); + } + break; + } else { + totalWritten += bytesWritten; + if (totalWritten >= buffer.size()) { + if (buffer.isFile()) { + // done with the file buffer, nothing else knows whether to + // close it with the way the code is written. + ::close(buffer.fd()); + } + + cleanUp(); + + // Cast to match the type of defered template + // to avoid a BadType exception + deferred.resolve(static_cast(totalWritten)); + break; + } + } + } + } +} + +void Transport::armTimerMs(Fd fd, std::chrono::milliseconds value, + Async::Deferred deferred) { + + auto ctx = context(); + const bool isInRightThread = std::this_thread::get_id() == ctx.thread(); + TimerEntry entry(fd, value, std::move(deferred)); + + if (!isInRightThread) { + timersQueue.push(std::move(entry)); + } else { + armTimerMsImpl(std::move(entry)); + } +} + +void Transport::armTimerMsImpl(TimerEntry entry) { + + auto it = timers.find(entry.fd); + if (it != std::end(timers)) { + entry.deferred.reject(std::runtime_error("Timer is already armed")); + return; + } + + itimerspec spec; + spec.it_interval.tv_sec = 0; + spec.it_interval.tv_nsec = 0; + + if (entry.value.count() < 1000) { + spec.it_value.tv_sec = 0; + spec.it_value.tv_nsec = + std::chrono::duration_cast(entry.value) + .count(); + } else { + spec.it_value.tv_sec = + std::chrono::duration_cast(entry.value).count(); + spec.it_value.tv_nsec = 0; + } + + int res = timerfd_settime(entry.fd, 0, &spec, 0); + if (res == -1) { + entry.deferred.reject(Pistache::Error::system("Could not set timer time")); + return; + } + + reactor()->registerFdOneShot(key(), entry.fd, NotifyOn::Read, + Polling::Mode::Edge); + timers.insert(std::make_pair(entry.fd, std::move(entry))); +} + +void Transport::handleWriteQueue() { + // Let's drain the queue + for (;;) { + auto write = writesQueue.popSafe(); + if (!write) + break; + + auto fd = write->peerFd; + if (!isPeerFd(fd)) + continue; + + { + Guard guard(toWriteLock); + toWrite[fd].push_back(std::move(*write)); + } + + reactor()->modifyFd(key(), fd, NotifyOn::Read | NotifyOn::Write, + Polling::Mode::Edge); + } +} + +void Transport::handleTimerQueue() { + for (;;) { + auto timer = timersQueue.popSafe(); + if (!timer) + break; + + armTimerMsImpl(std::move(*timer)); + } +} + +void Transport::handlePeerQueue() { + for (;;) { + auto data = peersQueue.popSafe(); + if (!data) + break; + + handlePeer(data->peer); + } +} + +void Transport::handlePeer(const std::shared_ptr &peer) { + int fd = peer->fd(); + peers.insert(std::make_pair(fd, peer)); + + peer->associateTransport(this); + + handler_->onConnection(peer); + reactor()->registerFd(key(), fd, NotifyOn::Read | NotifyOn::Shutdown, + Polling::Mode::Edge); +} + +void Transport::handleNotify() { + while (this->notifier.tryRead()) + ; + + rusage now; + + auto res = getrusage(RUSAGE_THREAD, &now); + if (res == -1) + loadRequest_.reject(std::runtime_error("Could not compute usage")); + + loadRequest_.resolve(now); + loadRequest_.clear(); +} + +void Transport::handleTimer(TimerEntry entry) { + if (entry.isActive()) { + uint64_t numWakeups; + auto res = ::read(entry.fd, &numWakeups, sizeof numWakeups); + if (res == -1) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return; + else + entry.deferred.reject( + Pistache::Error::system("Could not read timerfd")); + } else { + if (res != sizeof(numWakeups)) { + entry.deferred.reject( + Pistache::Error("Read invalid number of bytes for timer fd: " + + std::to_string(entry.fd))); + } else { + entry.deferred.resolve(numWakeups); + } + } + } +} + +bool Transport::isPeerFd(Fd fd) const { + return peers.find(fd) != std::end(peers); +} + +bool Transport::isTimerFd(Fd fd) const { + return timers.find(fd) != std::end(timers); +} + +bool Transport::isPeerFd(Polling::Tag tag) const { + return isPeerFd(static_cast(tag.value())); +} +bool Transport::isTimerFd(Polling::Tag tag) const { + return isTimerFd(static_cast(tag.value())); +} + +std::shared_ptr &Transport::getPeer(Fd fd) { + auto it = peers.find(fd); + if (it == std::end(peers)) { + throw std::runtime_error("No peer found for fd: " + std::to_string(fd)); + } + return it->second; +} + +std::shared_ptr &Transport::getPeer(Polling::Tag tag) { + return getPeer(static_cast(tag.value())); +} + +} // namespace Tcp +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/common/utils.cc b/projects/frontend/server-webapi/pistache/src/common/utils.cc new file mode 100755 index 0000000000000000000000000000000000000000..ce1311b805d259ef43a9675768e8ab3436735a07 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/common/utils.cc @@ -0,0 +1,38 @@ +/* utils.cc + Louis Solofrizzo 2019-10-17 + + Utilities for pistache +*/ + +#include +#include + +#ifdef PISTACHE_USE_SSL + +ssize_t SSL_sendfile(SSL *out, int in, off_t *offset, size_t count) { + unsigned char buffer[4096] = {0}; + ssize_t ret; + ssize_t written; + size_t to_read; + + if (in == -1) + return -1; + + to_read = sizeof(buffer) > count ? count : sizeof(buffer); + + if (offset != NULL) + ret = pread(in, buffer, to_read, *offset); + else + ret = read(in, buffer, to_read); + + if (ret == -1) + return -1; + + written = SSL_write(out, buffer, static_cast(ret)); + if (offset != NULL) + *offset += written; + + return written; +} + +#endif /* PISTACHE_USE_SSL */ diff --git a/projects/frontend/server-webapi/pistache/src/server/endpoint.cc b/projects/frontend/server-webapi/pistache/src/server/endpoint.cc new file mode 100755 index 0000000000000000000000000000000000000000..8dc6b87767eeed01a64f44cb42ab5d03faa20e6f --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/server/endpoint.cc @@ -0,0 +1,111 @@ +/* endpoint.cc + Mathieu Stefani, 22 janvier 2016 + + Implementation of the http endpoint +*/ + +#include +#include +#include +#include + +namespace Pistache { +namespace Http { + +Endpoint::Options::Options() + : threads_(1), flags_(), backlog_(Const::MaxBacklog), + maxRequestSize_(Const::DefaultMaxRequestSize), + maxResponseSize_(Const::DefaultMaxResponseSize) {} + +Endpoint::Options &Endpoint::Options::threads(int val) { + threads_ = val; + return *this; +} + +Endpoint::Options &Endpoint::Options::threadsName(const std::string &val) { + threadsName_ = val; + return *this; +} + +Endpoint::Options &Endpoint::Options::flags(Flags flags) { + flags_ = flags; + return *this; +} + +Endpoint::Options &Endpoint::Options::backlog(int val) { + backlog_ = val; + return *this; +} + +Endpoint::Options &Endpoint::Options::maxRequestSize(size_t val) { + maxRequestSize_ = val; + return *this; +} + +Endpoint::Options &Endpoint::Options::maxPayload(size_t val) { + return maxRequestSize(val); +} + +Endpoint::Options &Endpoint::Options::maxResponseSize(size_t val) { + maxResponseSize_ = val; + return *this; +} + +Endpoint::Endpoint() {} + +Endpoint::Endpoint(const Address &addr) : listener(addr) {} + +void Endpoint::init(const Endpoint::Options &options) { + listener.init(options.threads_, options.flags_, options.threadsName_); + maxRequestSize_ = options.maxRequestSize_; + maxResponseSize_ = options.maxResponseSize_; +} + +void Endpoint::setHandler(const std::shared_ptr &handler) { + handler_ = handler; + handler_->setMaxRequestSize(maxRequestSize_); + handler_->setMaxResponseSize(maxResponseSize_); +} + +void Endpoint::bind() { listener.bind(); } + +void Endpoint::bind(const Address &addr) { listener.bind(addr); } + +void Endpoint::serve() { serveImpl(&Tcp::Listener::run); } + +void Endpoint::serveThreaded() { serveImpl(&Tcp::Listener::runThreaded); } + +void Endpoint::shutdown() { listener.shutdown(); } + +void Endpoint::useSSL(const std::string &cert, const std::string &key, bool use_compression) { +#ifndef PISTACHE_USE_SSL + (void)cert; + (void)key; + (void)use_compression; + throw std::runtime_error("Pistache is not compiled with SSL support."); +#else + listener.setupSSL(cert, key, use_compression); +#endif /* PISTACHE_USE_SSL */ +} + +void Endpoint::useSSLAuth(std::string ca_file, std::string ca_path, + int (*cb)(int, void *)) { +#ifndef PISTACHE_USE_SSL + (void)ca_file; + (void)ca_path; + (void)cb; + throw std::runtime_error("Pistache is not compiled with SSL support."); +#else + listener.setupSSLAuth(ca_file, ca_path, cb); +#endif /* PISTACHE_USE_SSL */ +} + +Async::Promise +Endpoint::requestLoad(const Tcp::Listener::Load &old) { + return listener.requestLoad(old); +} + +Endpoint::Options Endpoint::options() { return Options(); } + +} // namespace Http +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/server/listener.cc b/projects/frontend/server-webapi/pistache/src/server/listener.cc new file mode 100755 index 0000000000000000000000000000000000000000..dac459e91ad8eb75d6354dbe20c97182d4a78feb --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/server/listener.cc @@ -0,0 +1,461 @@ +/* listener.cc + Mathieu Stefani, 12 August 2015 + +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#ifdef PISTACHE_USE_SSL + +#include +#include + +#endif /* PISTACHE_USE_SSL */ + +namespace Pistache { +namespace Tcp { + +void setSocketOptions(Fd fd, Flags options) { + if (options.hasFlag(Options::ReuseAddr)) { + int one = 1; + TRY(::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))); + } + + if (options.hasFlag(Options::ReusePort)) { + int one = 1; + TRY(::setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one))); + } + + if (options.hasFlag(Options::Linger)) { + struct linger opt; + opt.l_onoff = 1; + opt.l_linger = 1; + TRY(::setsockopt(fd, SOL_SOCKET, SO_LINGER, &opt, sizeof(opt))); + } + + if (options.hasFlag(Options::FastOpen)) { + int hint = 5; + TRY(::setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &hint, sizeof(hint))); + } + if (options.hasFlag(Options::NoDelay)) { + int one = 1; + TRY(::setsockopt(fd, SOL_TCP, TCP_NODELAY, &one, sizeof(one))); + } +} + +Listener::Listener() + : addr_(), listen_fd(-1), backlog_(Const::MaxBacklog), shutdownFd(), + poller(), options_(), workers_(Const::DefaultWorkers), workersName_(), + reactor_(), transportKey() {} + +Listener::Listener(const Address &address) + : addr_(address), listen_fd(-1), backlog_(Const::MaxBacklog), shutdownFd(), + poller(), options_(), workers_(Const::DefaultWorkers), workersName_(), + reactor_(), transportKey() {} + +Listener::~Listener() { + if (isBound()) + shutdown(); + if (acceptThread.joinable()) + acceptThread.join(); + + if (listen_fd >= 0) { + close(listen_fd); + listen_fd = -1; + } +} + +void Listener::init(size_t workers, Flags options, + const std::string &workersName, int backlog) { + if (workers > hardware_concurrency()) { + // Log::warning() << "More workers than available cores" + } + + options_ = options; + backlog_ = backlog; + useSSL_ = false; + workers_ = workers; + workersName_ = workersName; +} + +void Listener::setHandler(const std::shared_ptr &handler) { + handler_ = handler; +} + +void Listener::pinWorker(size_t worker, const CpuSet &set) { + UNUSED(worker) + UNUSED(set) +#if 0 + if (ioGroup.empty()) { + throw std::domain_error("Invalid operation, did you call init() before ?"); + } + if (worker > ioGroup.size()) { + throw std::invalid_argument("Trying to pin invalid worker"); + } + + auto &wrk = ioGroup[worker]; + wrk->pin(set); +#endif +} + +void Listener::bind() { bind(addr_); } + +void Listener::bind(const Address &address) { + if (!handler_) + throw std::runtime_error("Call setHandler before calling bind()"); + addr_ = address; + + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = address.family(); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + + const auto &host = addr_.host(); + const auto &port = addr_.port().toString(); + AddrInfo addr_info; + + TRY(addr_info.invoke(host.c_str(), port.c_str(), &hints)); + + int fd = -1; + + const addrinfo *addr = nullptr; + for (addr = addr_info.get_info_ptr(); addr; addr = addr->ai_next) { + fd = ::socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (fd < 0) + continue; + + setSocketOptions(fd, options_); + + if (::bind(fd, addr->ai_addr, addr->ai_addrlen) < 0) { + close(fd); + continue; + } + + TRY(::listen(fd, backlog_)); + break; + } + + // At this point, it is still possible that we couldn't bind any socket. If it + // is the case, the previous loop would have exited naturally and addr will be + // null. + if (addr == nullptr) { + throw std::runtime_error(strerror(errno)); + } + + make_non_blocking(fd); + poller.addFd(fd, Flags(Polling::NotifyOn::Read), + Polling::Tag(fd)); + listen_fd = fd; + + auto transport = std::make_shared(handler_); + + reactor_.init(Aio::AsyncContext(workers_, workersName_)); + transportKey = reactor_.addHandler(transport); +} + +bool Listener::isBound() const { return listen_fd != -1; } + +// Return actual TCP port Listener is on, or 0 on error / no port. +// Notes: +// 1) Default constructor for 'Port()' sets value to 0. +// 2) Socket is created inside 'Listener::run()', which is called from +// 'Endpoint::serve()' and 'Endpoint::serveThreaded()'. So getting the +// port is only useful if you attempt to do so from a _different_ thread +// than the one running 'Listener::run()'. So for a traditional single- +// threaded program this method is of little value. +Port Listener::getPort() const { + if (listen_fd == -1) { + return Port(); + } + + struct sockaddr_in sock_addr = {0}; + socklen_t addrlen = sizeof(sock_addr); + auto sock_addr_alias = reinterpret_cast(&sock_addr); + + if (-1 == getsockname(listen_fd, sock_addr_alias, &addrlen)) { + return Port(); + } + + return Port(ntohs(sock_addr.sin_port)); +} + +void Listener::run() { + if (!shutdownFd.isBound()) + shutdownFd.bind(poller); + reactor_.run(); + + for (;;) { + std::vector events; + int ready_fds = poller.poll(events); + + if (ready_fds == -1) { + throw Error::system("Polling"); + } + for (const auto &event : events) { + if (event.tag == shutdownFd.tag()) + return; + + if (event.flags.hasFlag(Polling::NotifyOn::Read)) { + auto fd = event.tag.value(); + if (static_cast(fd) == listen_fd) { + try { + handleNewConnection(); + } catch (SocketError &ex) { + std::cerr << "Server: " << ex.what() << std::endl; + } catch (ServerError &ex) { + std::cerr << "Server: " << ex.what() << std::endl; + throw; + } + } + } + } + } +} + +void Listener::runThreaded() { + shutdownFd.bind(poller); + acceptThread = std::thread([=]() { this->run(); }); +} + +void Listener::shutdown() { + if (shutdownFd.isBound()) + shutdownFd.notify(); + reactor_.shutdown(); +} + +Async::Promise +Listener::requestLoad(const Listener::Load &old) { + auto handlers = reactor_.handlers(transportKey); + + std::vector> loads; + for (const auto &handler : handlers) { + auto transport = std::static_pointer_cast(handler); + loads.push_back(transport->load()); + } + + return Async::whenAll(std::begin(loads), std::end(loads)) + .then( + [=](const std::vector &usages) { + Load res; + res.raw = usages; + + if (old.raw.empty()) { + res.global = 0.0; + for (size_t i = 0; i < handlers.size(); ++i) + res.workers.push_back(0.0); + } else { + + auto totalElapsed = [](rusage usage) { + return static_cast((usage.ru_stime.tv_sec * 1000000 + usage.ru_stime.tv_usec) + + (usage.ru_utime.tv_sec * 1000000 + usage.ru_utime.tv_usec)); + }; + + auto now = std::chrono::system_clock::now(); + auto diff = now - old.tick; + auto tick = + std::chrono::duration_cast(diff); + res.tick = now; + + for (size_t i = 0; i < usages.size(); ++i) { + auto last = old.raw[i]; + const auto &usage = usages[i]; + + auto nowElapsed = totalElapsed(usage); + auto timeElapsed = nowElapsed - totalElapsed(last); + + auto loadPct = (timeElapsed * 100.0) / static_cast(tick.count()); + res.workers.push_back(loadPct); + res.global += loadPct; + } + + res.global /= static_cast(usages.size()); + } + + return res; + }, + Async::Throw); +} + +Address Listener::address() const { return addr_; } + +Options Listener::options() const { return options_; } + +void Listener::handleNewConnection() { + struct sockaddr_in peer_addr; + int client_fd = acceptConnection(peer_addr); + + void *ssl = nullptr; + +#ifdef PISTACHE_USE_SSL + if (this->useSSL_) { + + SSL *ssl_data = SSL_new(GetSSLContext(ssl_ctx_)); + if (ssl_data == nullptr) { + close(client_fd); + throw std::runtime_error("Cannot create SSL connection"); + } + + SSL_set_fd(ssl_data, client_fd); + SSL_set_accept_state(ssl_data); + + if (SSL_accept(ssl_data) <= 0) { + ERR_print_errors_fp(stderr); + SSL_free(ssl_data); + close(client_fd); + return; + } + ssl = static_cast(ssl_data); + } +#endif /* PISTACHE_USE_SSL */ + + make_non_blocking(client_fd); + + std::shared_ptr peer; + if (this->useSSL_) { + peer = Peer::CreateSSL(client_fd, Address::fromUnix(&peer_addr), ssl); + } else { + peer = Peer::Create(client_fd, Address::fromUnix(&peer_addr)); + } + + dispatchPeer(peer); +} + +int Listener::acceptConnection(struct sockaddr_in &peer_addr) const { + socklen_t peer_addr_len = sizeof(peer_addr); + int client_fd = + ::accept(listen_fd, (struct sockaddr *)&peer_addr, &peer_addr_len); + if (client_fd < 0) { + if (errno == EBADF || errno == ENOTSOCK) + throw ServerError(strerror(errno)); + else + throw SocketError(strerror(errno)); + } + return client_fd; +} + +void Listener::dispatchPeer(const std::shared_ptr &peer) { + auto handlers = reactor_.handlers(transportKey); + auto idx = peer->fd() % handlers.size(); + auto transport = std::static_pointer_cast(handlers[idx]); + + transport->handleNewPeer(peer); +} + +#ifdef PISTACHE_USE_SSL + +namespace { + +ssl::SSLCtxPtr ssl_create_context(const std::string &cert, + const std::string &key, + bool use_compression) { + const SSL_METHOD* method = SSLv23_server_method(); + + ssl::SSLCtxPtr ctx{SSL_CTX_new(method)}; + if (ctx == nullptr) { + ERR_print_errors_fp(stderr); + throw std::runtime_error("Cannot setup SSL context"); + } + + if (!use_compression) { + /* Disable compression to prevent BREACH and CRIME vulnerabilities. */ + if (!SSL_CTX_set_options(GetSSLContext(ctx), SSL_OP_NO_COMPRESSION)) { + ERR_print_errors_fp(stderr); + throw std::runtime_error("Cannot disable compression"); + } + } + +/* Function introduced in 1.0.2 */ +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + SSL_CTX_set_ecdh_auto(GetSSLContext(ctx), 1); +#endif /* OPENSSL_VERSION_NUMBER */ + + if (SSL_CTX_use_certificate_file(GetSSLContext(ctx), cert.c_str(), SSL_FILETYPE_PEM) <= 0) { + ERR_print_errors_fp(stderr); + throw std::runtime_error("Cannot load SSL certificate"); + } + + if (SSL_CTX_use_PrivateKey_file(GetSSLContext(ctx), key.c_str(), SSL_FILETYPE_PEM) <= 0) { + ERR_print_errors_fp(stderr); + throw std::runtime_error("Cannot load SSL private key"); + } + + if (!SSL_CTX_check_private_key(GetSSLContext(ctx))) { + ERR_print_errors_fp(stderr); + throw std::runtime_error( + "Private key does not match public key in the certificate"); + } + + return ctx; +} + +} + +void Listener::setupSSLAuth(const std::string &ca_file, + const std::string &ca_path, + int (*cb)(int, void *) = NULL) { + const char *__ca_file = NULL; + const char *__ca_path = NULL; + + if (ssl_ctx_ == nullptr) + throw std::runtime_error("SSL Context is not initialized"); + + if (!ca_file.empty()) + __ca_file = ca_file.c_str(); + if (!ca_path.empty()) + __ca_path = ca_path.c_str(); + + if (SSL_CTX_load_verify_locations(GetSSLContext(ssl_ctx_), __ca_file, + __ca_path) <= 0) { + ERR_print_errors_fp(stderr); + throw std::runtime_error("Cannot verify SSL locations"); + } + + SSL_CTX_set_verify(GetSSLContext(ssl_ctx_), + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | + SSL_VERIFY_CLIENT_ONCE, +/* Callback type did change in 1.0.1 */ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + (int (*)(int, X509_STORE_CTX *))cb +#else + (SSL_verify_cb)cb +#endif /* OPENSSL_VERSION_NUMBER */ + ); +} + +void Listener::setupSSL(const std::string &cert_path, + const std::string &key_path, bool use_compression) { + SSL_load_error_strings(); + OpenSSL_add_ssl_algorithms(); + + ssl_ctx_ = ssl_create_context(cert_path, key_path, use_compression); + useSSL_ = true; +} + +#endif /* PISTACHE_USE_SSL */ + +} // namespace Tcp +} // namespace Pistache diff --git a/projects/frontend/server-webapi/pistache/src/server/router.cc b/projects/frontend/server-webapi/pistache/src/server/router.cc new file mode 100755 index 0000000000000000000000000000000000000000..3e968e0a1a09b96210048e9ef0fd6de6d4184098 --- /dev/null +++ b/projects/frontend/server-webapi/pistache/src/server/router.cc @@ -0,0 +1,512 @@ +/* router.cc + Mathieu Stefani, 05 janvier 2016 + + Rest routing implementation +*/ + +#include + +#include +#include + +namespace Pistache { +namespace Rest { + +Request::Request(const Http::Request &request, std::vector &¶ms, + std::vector &&splats) + : Http::Request(request), params_(std::move(params)), + splats_(std::move(splats)) {} + +bool Request::hasParam(const std::string &name) const { + auto it = std::find_if( + params_.begin(), params_.end(), + [&](const TypedParam ¶m) { return param.name() == name; }); + + return it != std::end(params_); +} + +TypedParam Request::param(const std::string &name) const { + auto it = std::find_if( + params_.begin(), params_.end(), + [&](const TypedParam ¶m) { return param.name() == name; }); + + if (it == std::end(params_)) { + throw std::runtime_error("Unknown parameter"); + } + + return *it; +} + +TypedParam Request::splatAt(size_t index) const { + if (index >= splats_.size()) { + throw std::out_of_range("Request splat index out of range"); + } + return splats_[index]; +} + +std::vector Request::splat() const { return splats_; } + +std::regex SegmentTreeNode::multiple_slash = + std::regex("//+", std::regex_constants::optimize); + +SegmentTreeNode::SegmentTreeNode() + : resource_ref_(), fixed_(), param_(), optional_(), splat_(nullptr), + route_(nullptr) { + std::shared_ptr ptr(new char[0], std::default_delete()); + resource_ref_.swap(ptr); +} + +SegmentTreeNode::SegmentTreeNode(const std::shared_ptr &resourceReference) + : resource_ref_(resourceReference), fixed_(), param_(), optional_(), + splat_(nullptr), route_(nullptr) {} + +SegmentTreeNode::SegmentType +SegmentTreeNode::getSegmentType(const std::string_view &fragment) { + auto optpos = fragment.find('?'); + if (fragment[0] == ':') { + if (optpos != std::string_view::npos) { + if (optpos != fragment.length() - 1) { + throw std::runtime_error("? should be at the end of the string"); + } + return SegmentType::Optional; + } + return SegmentType::Param; + } else if (fragment[0] == '*') { + if (fragment.length() > 1) { + throw std::runtime_error("Invalid splat parameter"); + } + return SegmentType::Splat; + } + + if (optpos != std::string_view::npos) { + throw std::runtime_error( + "Only optional parameters are currently supported"); + } + + return SegmentType::Fixed; +} + +std::string SegmentTreeNode::sanitizeResource(const std::string &path) { + const auto &dup = std::regex_replace(path, SegmentTreeNode::multiple_slash, + std::string("/")); + if (dup[dup.length() - 1] == '/') { + return dup.substr(1, dup.length() - 2); + } + return dup.substr(1); +} + +void SegmentTreeNode::addRoute( + const std::string_view &path, const Route::Handler &handler, + const std::shared_ptr &resource_reference) { + // recursion to correct path segment + if (!path.empty()) { + const auto segment_delimiter = path.find('/'); + // current segment value + auto current_segment = path.substr(0, segment_delimiter); + // complete child path (path without this segment) + // if no '/' was found, it means that it is a leaf resource + const auto lower_path = (segment_delimiter == std::string_view::npos) + ? std::string_view{nullptr, 0} + : path.substr(segment_delimiter + 1); + + std::unordered_map> + *collection = nullptr; + const auto fragmentType = getSegmentType(current_segment); + switch (fragmentType) { + case SegmentType::Fixed: + collection = &fixed_; + break; + case SegmentType::Param: + collection = ¶m_; + break; + case SegmentType::Optional: + // remove the trailing question mark + current_segment = current_segment.substr(0, current_segment.length() - 1); + collection = &optional_; + break; + case SegmentType::Splat: + if (splat_ == nullptr) { + splat_ = std::make_shared(resource_reference); + } + splat_->addRoute(lower_path, handler, resource_reference); + return; + } + + // if the segment tree nodes for the lower path does not exist + if (collection->count(current_segment) == 0) { + // first create it + collection->insert(std::make_pair( + current_segment, + std::make_shared(resource_reference))); + } + collection->at(current_segment) + ->addRoute(lower_path, handler, resource_reference); + } else { // current path segment requested + if (route_ != nullptr) + throw std::runtime_error("Requested route already exist."); + route_ = std::make_shared(handler); + } +} + +bool Pistache::Rest::SegmentTreeNode::removeRoute( + const std::string_view &path) { + // recursion to correct path segment + if (!path.empty()) { + const auto segment_delimiter = path.find('/'); + // current segment value + auto current_segment = path.substr(0, segment_delimiter); + // complete child path (path without this segment) + // if no '/' was found, it means that it is a leaf resource + const auto lower_path = (segment_delimiter == std::string_view::npos) + ? std::string_view{nullptr, 0} + : path.substr(segment_delimiter + 1); + + std::unordered_map> + *collection = nullptr; + auto fragmentType = getSegmentType(current_segment); + switch (fragmentType) { + case SegmentType::Fixed: + collection = &fixed_; + break; + case SegmentType::Param: + collection = ¶m_; + break; + case SegmentType::Optional: + // remove the trailing question mark + current_segment = current_segment.substr(0, current_segment.length() - 1); + collection = &optional_; + break; + case SegmentType::Splat: + return splat_->removeRoute(lower_path); + } + + try { + const bool removable = + collection->at(current_segment)->removeRoute(lower_path); + if (removable) { + collection->erase(current_segment); + } + } catch (const std::out_of_range &) { + throw std::runtime_error("Requested does not exist."); + } + } else { // current leaf requested + route_.reset(); + } + return fixed_.empty() && param_.empty() && optional_.empty() && + splat_ == nullptr && route_ == nullptr; +} + +std::tuple, std::vector, + std::vector> +Pistache::Rest::SegmentTreeNode::findRoute( + const std::string_view &path, std::vector ¶ms, + std::vector &splats) const { + // recursion to correct path segment + if (!path.empty()) { + const auto segment_delimiter = path.find('/'); + // current segment value + auto current_segment = path.substr(0, segment_delimiter); + // complete child path (path without this segment) + // if no '/' was found, it means that it is a leaf resource + const auto lower_path = (segment_delimiter == std::string_view::npos) + ? std::string_view{nullptr, 0} + : path.substr(segment_delimiter + 1); + + // Check if it is a fixed route + if (fixed_.count(current_segment) != 0) { + auto result = + fixed_.at(current_segment)->findRoute(lower_path, params, splats); + auto route = std::get<0>(result); + if (route != nullptr) + return result; + } + + // Check if it is a path param + for (const auto ¶m : param_) { + std::string para_name{param.first.data(), param.first.length()}; + std::string para_val{current_segment.data(), current_segment.length()}; + params.emplace_back(para_name, para_val); + auto result = param.second->findRoute(lower_path, params, splats); + auto route = std::get<0>(result); + if (route != nullptr) + return result; + params.pop_back(); + } + + // Check if it is an optional path param + for (const auto &optional : optional_) { + std::string opt_name{optional.first.data(), optional.first.length()}; + std::string opt_val{current_segment.data(), current_segment.length()}; + params.emplace_back(opt_name, opt_val); + auto result = optional.second->findRoute(lower_path, params, splats); + + auto route = std::get<0>(result); + if (route != nullptr) + return result; + params.pop_back(); + // try to find a route for lower path assuming that + // this optional path param is not present + result = optional.second->findRoute(lower_path, params, splats); + + route = std::get<0>(result); + if (route != nullptr) + return result; + } + + // Check if it is a splat + if (splat_ != nullptr) { + std::string splat{current_segment.data(), current_segment.length()}; + splats.emplace_back(splat, splat); + auto result = splat_->findRoute(lower_path, params, splats); + + auto route = std::get<0>(result); + if (route != nullptr) + return result; + splats.pop_back(); + } + // Requested route does not exists + return std::make_tuple(nullptr, std::vector(), + std::vector()); + } else { // current leaf requested, or empty final optional + if (!optional_.empty()) { + // in case of more than one optional at this point, as it is an + // ambiguity, it is resolved by using the first optional + auto optional = optional_.begin(); + // std::string opt {optional->first.data(), optional->first.length()}; + return optional->second->findRoute(path, params, splats); + } else if (route_ == nullptr) { + // if we are here but route is null, we reached this point + // trying to parse an optional, that was missing + return std::make_tuple(nullptr, std::vector(), + std::vector()); + } else { + return std::make_tuple(route_, std::move(params), std::move(splats)); + } + } +} + +std::tuple, std::vector, + std::vector> +Pistache::Rest::SegmentTreeNode::findRoute(const std::string_view &path) const { + std::vector params; + std::vector splats; + return findRoute(path, params, splats); +} + +namespace Private { + +RouterHandler::RouterHandler(const Rest::Router &router) + : router(std::make_shared(router)) {} + +RouterHandler::RouterHandler(std::shared_ptr router) + : router(std::move(router)) {} + +void RouterHandler::onRequest(const Http::Request &req, + Http::ResponseWriter response) { + auto resp = response.clone(); + router->route(req, std::move(resp)); +} + +} // namespace Private + +Router Router::fromDescription(const Rest::Description &desc) { + Router router; + router.initFromDescription(desc); + return router; +} + +std::shared_ptr Router::handler() const { + return std::make_shared(*this); +} + +std::shared_ptr +Router::handler(std::shared_ptr router) { + return std::make_shared(router); +} + +void Router::initFromDescription(const Rest::Description &desc) { + auto paths = desc.rawPaths(); + for (auto it = paths.flatBegin(), end = paths.flatEnd(); it != end; ++it) { + const auto &paths_ = *it; + for (const auto &path : paths_) { + if (!path.isBound()) { + std::ostringstream oss; + oss << "Path '" << path.value << "' is not bound"; + throw std::runtime_error(oss.str()); + } + + addRoute(path.method, path.value, path.handler); + } + } +} + +void Router::get(const std::string &resource, Route::Handler handler) { + addRoute(Http::Method::Get, resource, std::move(handler)); +} + +void Router::post(const std::string &resource, Route::Handler handler) { + addRoute(Http::Method::Post, resource, std::move(handler)); +} + +void Router::put(const std::string &resource, Route::Handler handler) { + addRoute(Http::Method::Put, resource, std::move(handler)); +} + +void Router::patch(const std::string &resource, Route::Handler handler) { + addRoute(Http::Method::Patch, resource, std::move(handler)); +} + +void Router::del(const std::string &resource, Route::Handler handler) { + addRoute(Http::Method::Delete, resource, std::move(handler)); +} + +void Router::options(const std::string &resource, Route::Handler handler) { + addRoute(Http::Method::Options, resource, std::move(handler)); +} + +void Router::removeRoute(Http::Method method, const std::string &resource) { + if (resource.empty()) + throw std::runtime_error("Invalid zero-length URL."); + auto &r = routes[method]; + const auto sanitized = SegmentTreeNode::sanitizeResource(resource); + const std::string_view path{sanitized.data(), sanitized.size()}; + r.removeRoute(path); +} + +void Router::head(const std::string &resource, Route::Handler handler) { + addRoute(Http::Method::Head, resource, std::move(handler)); +} + +void Router::addCustomHandler(Route::Handler handler) { + customHandlers.push_back(std::move(handler)); +} + +void Router::addNotFoundHandler(Route::Handler handler) { + notFoundHandler = std::move(handler); +} + +void Router::invokeNotFoundHandler(const Http::Request &req, + Http::ResponseWriter resp) const { + notFoundHandler(Rest::Request(std::move(req), std::vector(), + std::vector()), + std::move(resp)); +} + +Route::Status Router::route(const Http::Request &req, + Http::ResponseWriter response) { + const auto resource = req.resource(); + if (resource.empty()) + throw std::runtime_error("Invalid zero-length URL."); + + auto &r = routes[req.method()]; + const auto sanitized = SegmentTreeNode::sanitizeResource(resource); + const std::string_view path{sanitized.data(), sanitized.size()}; + auto result = r.findRoute(path); + + auto route = std::get<0>(result); + if (route != nullptr) { + auto params = std::get<1>(result); + auto splats = std::get<2>(result); + route->invokeHandler(Request(req, std::move(params), std::move(splats)), + std::move(response)); + return Route::Status::Match; + } + + for (const auto &handler : customHandlers) { + auto resp = response.clone(); + auto handler1 = handler( + Request(req, std::vector(), std::vector()), + std::move(resp)); + if (handler1 == Route::Result::Ok) + return Route::Status::Match; + } + + // No route or custom handler found. Let's walk through the + // list of other methods and see if any of them support + // this resource. + // This will allow server to send a + // HTTP 405 (method not allowed) response. + // RFC 7231 requires HTTP 405 responses to include a list of + // supported methods for the requested resource. + std::vector supportedMethods; + for (auto &methods : routes) { + if (methods.first == req.method()) + continue; + + auto res = methods.second.findRoute(path); + auto rte = std::get<0>(res); + if (rte != nullptr) { + supportedMethods.push_back(methods.first); + } + } + + if (!supportedMethods.empty()) { + response.sendMethodNotAllowed(supportedMethods); + return Route::Status::NotAllowed; + } + + if (hasNotFoundHandler()) { + invokeNotFoundHandler(req, std::move(response)); + } else { + response.send(Http::Code::Not_Found, "Could not find a matching route"); + } + return Route::Status::NotFound; +} + +void Router::addRoute(Http::Method method, const std::string &resource, + Route::Handler handler) { + if (resource.empty()) + throw std::runtime_error("Invalid zero-length URL."); + auto &r = routes[method]; + const auto sanitized = SegmentTreeNode::sanitizeResource(resource); + std::shared_ptr ptr(new char[sanitized.length()], + std::default_delete()); + memcpy(ptr.get(), sanitized.data(), sanitized.length()); + const std::string_view path{ptr.get(), sanitized.length()}; + r.addRoute(path, handler, ptr); +} + +namespace Routes { + +void Get(Router &router, const std::string &resource, Route::Handler handler) { + router.get(resource, std::move(handler)); +} + +void Post(Router &router, const std::string &resource, Route::Handler handler) { + router.post(resource, std::move(handler)); +} + +void Put(Router &router, const std::string &resource, Route::Handler handler) { + router.put(resource, std::move(handler)); +} + +void Patch(Router &router, const std::string &resource, + Route::Handler handler) { + router.patch(resource, std::move(handler)); +} + +void Delete(Router &router, const std::string &resource, + Route::Handler handler) { + router.del(resource, std::move(handler)); +} + +void Options(Router &router, const std::string &resource, + Route::Handler handler) { + router.options(resource, std::move(handler)); +} + +void Remove(Router &router, Http::Method method, const std::string &resource) { + router.removeRoute(method, resource); +} + +void NotFound(Router &router, Route::Handler handler) { + router.addNotFoundHandler(std::move(handler)); +} + +void Head(Router &router, const std::string &resource, Route::Handler handler) { + router.head(resource, std::move(handler)); +} + +} // namespace Routes +} // namespace Rest +} // namespace Pistache diff --git a/projects/frontend/server-webapi/server/IRouter.h b/projects/frontend/server-webapi/server/IRouter.h new file mode 100644 index 0000000000000000000000000000000000000000..b5ecc13842ac73753722e3fb184380d0886e576c --- /dev/null +++ b/projects/frontend/server-webapi/server/IRouter.h @@ -0,0 +1,254 @@ +// +// Created by bkg2k on 30/03/2020. +// +#pragma once + +#include +#include +#include + +using namespace Pistache; + +class IRouter +{ + private: + Rest::Router mRouter; + + protected: + /*! + * @brief Handle files + * @param request Request object + * @param response Response object + */ + virtual void FileServer(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /* + * Routes + */ + + /*! + * @brief Handle GET version + * @param request Request object + * @param response Response object + */ + virtual void Versions(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET cpu information + * @param request Request object + * @param response Response object + */ + virtual void SystemInfo(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET storage information + * @param request Request object + * @param response Response object + */ + virtual void StorageInfo(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET bios download + * @param request Request object + * @param response Response object + */ + virtual void BiosDownload(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET bios upload + * @param request Request object + * @param response Response object + */ + virtual void BiosUpload(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get array of all system's bios + * @param request Request object + * @param response Response object + */ + virtual void BiosGetAll(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get bios for the given system + * @param request Request object + * @param response Response object + */ + virtual void BiosGetSystem(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get all systems + * @param request Request object + * @param response Response object + */ + virtual void SystemsGetAll(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get all active systems + * @param request Request object + * @param response Response object + */ + virtual void SystemsGetActives(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get system console svg for the given system and region + * @param request Request object + * @param response Response object + */ + virtual void SystemsResourceGetConsole(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get system controler svg for the given system and region + * @param request Request object + * @param response Response object + */ + virtual void SystemsResourceGetControler(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get system controls svg for the given system and region + * @param request Request object + * @param response Response object + */ + virtual void SystemsResourceGetControls(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get system game support svg for the given system and region + * @param request Request object + * @param response Response object + */ + virtual void SystemsResourceGetGame(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get system logo svg for the given system and region + * @param request Request object + * @param response Response object + */ + virtual void SystemsResourceGetLogo(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get configuration keys + * @param request Request object + * @param response Response object + */ + virtual void ConfigurationGet(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle POST set configuration keys + * @param request Request object + * @param response Response object + */ + virtual void ConfigurationSet(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle OPTIONS get configuration keys, type and validators + * @param request Request object + * @param response Response object + */ + virtual void ConfigurationOptions(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle DELETE delete configuration keys + * @param request Request object + * @param response Response object + */ + virtual void ConfigurationDelete(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get system configuration keys + * @param request Request object + * @param response Response object + */ + virtual void SystemConfigurationGet(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle POST set system configuration keys + * @param request Request object + * @param response Response object + */ + virtual void SystemConfigurationSet(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle OPTIONS get system configuration keys, type and validators + * @param request Request object + * @param response Response object + */ + virtual void SystemConfigurationOptions(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle DELETE delete system configuration keys + * @param request Request object + * @param response Response object + */ + virtual void SystemConfigurationDelete(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET get media list + * @param request Request object + * @param response Response object + */ + virtual void MediaUserGetList(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle DELETE delete media + * @param request Request object + * @param response Response object + */ + virtual void MediaUserDelete(const Rest::Request& request, Http::ResponseWriter response) = 0; + + /*! + * @brief Handle GET to get game media + * @param request Request object + * @param response Response object + */ + virtual void MediaGet(const Rest::Request& request, Http::ResponseWriter response) = 0; + + public: + /*! + * @brief Constructor. Set all routes + */ + IRouter() + { + // Versions + Rest::Routes::Get(mRouter, "/api/versions", Rest::Routes::bind(&IRouter::Versions, this)); + // Monitoring + Rest::Routes::Get(mRouter, "/api/monitoring/systeminfo", Rest::Routes::bind(&IRouter::SystemInfo, this)); + Rest::Routes::Get(mRouter, "/api/monitoring/storageinfo", Rest::Routes::bind(&IRouter::StorageInfo, this)); + // Bios + Rest::Routes::Get(mRouter, "/api/bios/getall", Rest::Routes::bind(&IRouter::BiosGetAll, this)); + Rest::Routes::Get(mRouter, "/api/bios/get/*", Rest::Routes::bind(&IRouter::BiosGetSystem, this)); + Rest::Routes::Get(mRouter, "/api/bios/download", Rest::Routes::bind(&IRouter::BiosDownload, this)); + Rest::Routes::Post(mRouter, "/api/bios/upload/*", Rest::Routes::bind(&IRouter::BiosUpload, this)); + // Systems + Rest::Routes::Get(mRouter, "/api/systems/getall", Rest::Routes::bind(&IRouter::SystemsGetAll, this)); + Rest::Routes::Get(mRouter, "/api/systems/getactives", Rest::Routes::bind(&IRouter::SystemsGetActives, this)); + Rest::Routes::Get(mRouter, "/api/systems/*/resource/*/svg/console", Rest::Routes::bind(&IRouter::SystemsResourceGetConsole, this)); + Rest::Routes::Get(mRouter, "/api/systems/*/resource/*/svg/controler", Rest::Routes::bind(&IRouter::SystemsResourceGetControler, this)); + Rest::Routes::Get(mRouter, "/api/systems/*/resource/*/svg/controls", Rest::Routes::bind(&IRouter::SystemsResourceGetControls, this)); + Rest::Routes::Get(mRouter, "/api/systems/*/resource/*/svg/game", Rest::Routes::bind(&IRouter::SystemsResourceGetGame, this)); + Rest::Routes::Get(mRouter, "/api/systems/*/resource/*/svg/logo", Rest::Routes::bind(&IRouter::SystemsResourceGetLogo, this)); + // Configurations + Rest::Routes::Get(mRouter, "/api/configuration/*", Rest::Routes::bind(&IRouter::ConfigurationGet, this)); + Rest::Routes::Post(mRouter, "/api/configuration/*", Rest::Routes::bind(&IRouter::ConfigurationSet, this)); + Rest::Routes::Options(mRouter, "/api/configuration/*", Rest::Routes::bind(&IRouter::ConfigurationOptions, this)); + Rest::Routes::Delete(mRouter, "/api/configuration/*", Rest::Routes::bind(&IRouter::ConfigurationDelete, this)); + Rest::Routes::Get(mRouter, "/api/configuration/system/*", Rest::Routes::bind(&IRouter::SystemConfigurationGet, this)); + Rest::Routes::Post(mRouter, "/api/configuration/system/*", Rest::Routes::bind(&IRouter::SystemConfigurationSet, this)); + Rest::Routes::Options(mRouter, "/api/configuration/system/*", Rest::Routes::bind(&IRouter::SystemConfigurationOptions, this)); + Rest::Routes::Delete(mRouter, "/api/configuration/system/*", Rest::Routes::bind(&IRouter::SystemConfigurationDelete, this)); + // Screenshots/Videos + Rest::Routes::Get(mRouter, "/api/media/user/getall", Rest::Routes::bind(&IRouter::MediaUserGetList, this)); + Rest::Routes::Delete(mRouter, "/api/media/user", Rest::Routes::bind(&IRouter::MediaUserDelete, this)); + // All media - Full path - .png .jpg .pdf .gif .mp4 .avi .mkv + Rest::Routes::Get(mRouter, "/api/media/get/*", Rest::Routes::bind(&IRouter::MediaGet, this)); + + // Default file service + Rest::Routes::NotFound(mRouter, Rest::Routes::bind(&IRouter::FileServer, this)); + + LOG(LogInfo) << "Router initialized"; + } + + /*! + * @brief Get route handler + * @return Route handler + */ + std::shared_ptr Handler() const { return mRouter.handler(); } +}; diff --git a/projects/frontend/server-webapi/server/Server.cpp b/projects/frontend/server-webapi/server/Server.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e6a4d8e08fbad181dcde5447cfdea63fa858893e --- /dev/null +++ b/projects/frontend/server-webapi/server/Server.cpp @@ -0,0 +1,42 @@ +// +// Created by bkg2k on 30/03/2020. +// + +#include +#include "Server.h" + +Server::Server(const Parameters& param, IRouter* router) + : mAddress(param.IP(), Pistache::Port(param.Port())), + mServer(mAddress) +{ + assert(router != nullptr); + + auto opts = Http::Endpoint::options().threads(param.Threads()); + mServer.init(opts); + mServer.setHandler(router->Handler()); +} + +void Server::Run() +{ + for(;;) + try + { + LOG(LogInfo) << "Server up!"; + mServer.serve(); + break; + } + catch(std::exception& ex) + { + LOG(LogError) << "Error running server! Retrying in 5s... (Exception: " << ex.what() << ')'; + sleep(5); + } + + mServer.shutdown(); + LOG(LogInfo) << "Server down."; +} + +void Server::Cancel() +{ + LOG(LogInfo) << "Server interrupted!"; + mServer.shutdown(); +} diff --git a/projects/frontend/server-webapi/server/Server.h b/projects/frontend/server-webapi/server/Server.h new file mode 100644 index 0000000000000000000000000000000000000000..4e41e5327a08c2ad1184f1de9d7d5390d85e495c --- /dev/null +++ b/projects/frontend/server-webapi/server/Server.h @@ -0,0 +1,32 @@ +// +// Created by bkg2k on 30/03/2020. +// +#pragma once + +#include "IRouter.h" +#include "Parameters.h" +#include +#include + +class Server +{ + private: + Pistache::Address mAddress; + Http::Endpoint mServer; + + public: + /*! + * @brief Default Constructor + */ + Server(const Parameters& param, IRouter* router); + + /*! + * @brief Run the server + */ + void Run(); + + /*! + * @brief Stop the server + */ + void Cancel(); +}; diff --git a/projects/frontend/server-webapi/server/handlers/Mime.cpp b/projects/frontend/server-webapi/server/handlers/Mime.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e2165f90005300300ae6e14170f60a8b66256a6c --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/Mime.cpp @@ -0,0 +1,49 @@ +// +// Created by bkg2k on 03/04/2020. +// + +#include "Mime.h" + +Pistache::Http::Mime::MediaType Mime::Json(Pistache::Http::Mime::Type::Application, Pistache::Http::Mime::Subtype::Json); +Pistache::Http::Mime::MediaType Mime::BinaryFile(Pistache::Http::Mime::Type::Application, Pistache::Http::Mime::Subtype::OctetStream); +Pistache::Http::Mime::MediaType Mime::FilePdf(Pistache::Http::Mime::Type::Application, Pistache::Http::Mime::Subtype::Pdf); +Pistache::Http::Mime::MediaType Mime::PlainText(Pistache::Http::Mime::Type::Text, Pistache::Http::Mime::Subtype::Plain); +Pistache::Http::Mime::MediaType Mime::Html(Pistache::Http::Mime::Type::Text, Pistache::Http::Mime::Subtype::Html); +Pistache::Http::Mime::MediaType Mime::JavaScript(Pistache::Http::Mime::Type::Text, Pistache::Http::Mime::Subtype::Javascript); +Pistache::Http::Mime::MediaType Mime::Css(Pistache::Http::Mime::Type::Text, Pistache::Http::Mime::Subtype::Css); +Pistache::Http::Mime::MediaType Mime::Icon(Pistache::Http::Mime::Type::Image, Pistache::Http::Mime::Subtype::XIcon); +Pistache::Http::Mime::MediaType Mime::ImagePng(Pistache::Http::Mime::Type::Image, Pistache::Http::Mime::Subtype::Png); +Pistache::Http::Mime::MediaType Mime::ImageJpg(Pistache::Http::Mime::Type::Image, Pistache::Http::Mime::Subtype::Jpeg); +Pistache::Http::Mime::MediaType Mime::ImageGif(Pistache::Http::Mime::Type::Image, Pistache::Http::Mime::Subtype::Gif); +Pistache::Http::Mime::MediaType Mime::ImageSvg(Pistache::Http::Mime::Type::Image, Pistache::Http::Mime::Subtype::Svg); +Pistache::Http::Mime::MediaType Mime::VideoMkv(Pistache::Http::Mime::Type::Video, Pistache::Http::Mime::Subtype::Mkv); +Pistache::Http::Mime::MediaType Mime::VideoMp4(Pistache::Http::Mime::Type::Video, Pistache::Http::Mime::Subtype::Mp4); +Pistache::Http::Mime::MediaType Mime::VideoAvi(Pistache::Http::Mime::Type::Video, Pistache::Http::Mime::Subtype::Avi); +Pistache::Http::Mime::MediaType Mime::Zip(Pistache::Http::Mime::Type::Application, Pistache::Http::Mime::Subtype::OctetStream, Pistache::Http::Mime::Suffix::Zip); +Pistache::Http::Mime::MediaType Mime::FontTtf(Pistache::Http::Mime::Type::Font, Pistache::Http::Mime::Subtype::Ttf); +Pistache::Http::Mime::MediaType Mime::FontWoff(Pistache::Http::Mime::Type::Font, Pistache::Http::Mime::Subtype::Woff); +Pistache::Http::Mime::MediaType Mime::FontWoff2(Pistache::Http::Mime::Type::Font, Pistache::Http::Mime::Subtype::Woff2); + +HashMap Mime::ExtToMIME +({ + { ".html" , Mime::Html }, + { ".htm" , Mime::Html }, + { ".css" , Mime::Css }, + { ".json" , Mime::Json }, + { ".ico" , Mime::Icon }, + { ".js" , Mime::JavaScript }, + { ".png" , Mime::ImagePng }, + { ".gif" , Mime::ImageGif }, + { ".jpg" , Mime::ImageJpg }, + { ".jpeg" , Mime::ImageJpg }, + { ".svg" , Mime::ImageSvg }, + { ".zip" , Mime::Zip }, + { ".ttf" , Mime::FontTtf }, + { ".woff" , Mime::FontWoff }, + { ".woff2" , Mime::FontWoff2 }, + { ".pdf" , Mime::FilePdf }, + { ".mkv" , Mime::VideoMkv }, + { ".mp4" , Mime::VideoMp4 }, + { ".avi" , Mime::VideoAvi }, +}); + diff --git a/projects/frontend/server-webapi/server/handlers/Mime.h b/projects/frontend/server-webapi/server/handlers/Mime.h new file mode 100644 index 0000000000000000000000000000000000000000..a38557f3979857a79ad4fb784b4885cb46021f44 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/Mime.h @@ -0,0 +1,53 @@ +// +// Created by bkg2k on 03/04/2020. +// +#pragma once + +#include +#include + +class Mime +{ + public: + //! JSON MIME type + static Pistache::Http::Mime::MediaType Json; + //! Binary MIME type + static Pistache::Http::Mime::MediaType BinaryFile; + //! Plain text MIME type + static Pistache::Http::Mime::MediaType PlainText; + //! Html MIME Type + static Pistache::Http::Mime::MediaType Html; + //! css MIME Type + static Pistache::Http::Mime::MediaType Css; + //! JavaScript MIME Type + static Pistache::Http::Mime::MediaType JavaScript; + //! Icon MIME Type + static Pistache::Http::Mime::MediaType Icon; + //! Png MIME Type + static Pistache::Http::Mime::MediaType ImagePng; + //! Jpeg MIME Type + static Pistache::Http::Mime::MediaType ImageJpg; + //! Gif MIME Type + static Pistache::Http::Mime::MediaType ImageGif; + //! SVG MIME Type + static Pistache::Http::Mime::MediaType ImageSvg; + //! MKV MIME Type + static Pistache::Http::Mime::MediaType VideoMkv; + //! MP4 MIME Type + static Pistache::Http::Mime::MediaType VideoMp4; + //! AVI MIME Type + static Pistache::Http::Mime::MediaType VideoAvi; + //! PDF MIME Type + static Pistache::Http::Mime::MediaType FilePdf; + //! ttf MIME Type + static Pistache::Http::Mime::MediaType FontTtf; + //! zip MIME Type + static Pistache::Http::Mime::MediaType Zip; + //! woff MIME Type + static Pistache::Http::Mime::MediaType FontWoff; + //! woff2 MIME Type + static Pistache::Http::Mime::MediaType FontWoff2; + + //! Extension to MIME Type + static HashMap ExtToMIME; +}; \ No newline at end of file diff --git a/projects/frontend/server-webapi/server/handlers/MqttTopics.h b/projects/frontend/server-webapi/server/handlers/MqttTopics.h new file mode 100644 index 0000000000000000000000000000000000000000..59166f862a8ac9f43562424f83588ce4ce15e737 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/MqttTopics.h @@ -0,0 +1,17 @@ +// +// Created by bkg2k on 09/04/2020. +// +#pragma once + +class MqttTopics +{ + public: + //! MQTT Topic - Where to broadcast system info + static constexpr const char* sSystemInfoTopic = "Recalbox/WebAPI/SystemInfo"; + + //! MQTT Topic - Where to broardcast emulationstation event objects + static constexpr const char* sEventObjectTopic = "Recalbox/WebAPI/EmulationStation/Event"; + + //! MQTT Topic - From where to get EmulationStation events + static constexpr const char* sEventTopic = "Recalbox/EmulationStation/Event"; +}; diff --git a/projects/frontend/server-webapi/server/handlers/RequestHandler.cpp b/projects/frontend/server-webapi/server/handlers/RequestHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ac0a562918760ae6612fb595bc78450c828513f9 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/RequestHandler.cpp @@ -0,0 +1,490 @@ +// +// Created by bkg2k on 30/03/2020. +// + +#include +#include +#include +#include +#include "RequestHandler.h" +#include "Mime.h" +#include "RequestHandlerTools.h" + +using namespace Pistache; + +void RequestHandler::FileServer(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "FileServer"); + + Path path = mWWWRoot / (request.resource() != "/" ? request.resource() : mDefaultFile); + std::string ext = path.Extension(); + + bool knownMime = Mime::ExtToMIME.contains(ext); + const Pistache::Http::Mime::MediaType& mimeType = knownMime ? Mime::ExtToMIME[ext] : Mime::BinaryFile; + if (!knownMime) + LOG(LogWarning) << "Unknown MIME Type for file extension: " << ext; + RequestHandlerTools::SendResource(path, response, mimeType); +} + +void RequestHandler::Versions(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "Versions"); + + // Get libretro cores + std::map cores; + Strings::Vector coreLines = Strings::Split(Files::LoadFile(Path("/recalbox/share/system/configs/retroarch.corenames")), '\n'); + for(const std::string& coreLine : coreLines) + { + Strings::Vector coreItems = Strings::Split(coreLine, ';'); + if (coreItems.size() == 3) cores[coreItems[0]] = coreItems[2]; + } + std::string retroarchVersion = Strings::Extract(RequestHandlerTools::OutputOf("retroarch --version 2>&1"), "-- ", " --", 3, 3); + + // Get Recalbox & Linux version + std::string recalboxVersion = Files::LoadFile(Path("/recalbox/recalbox.version")); + std::string linuxVersion = Files::LoadFile(Path("/proc/version")); + int p = linuxVersion.find('('); if (p != (int)std::string::npos) linuxVersion = Strings::Trim(linuxVersion.substr(0, p)); + + // Build final version object + JSONBuilder json; + json.Open() + .Field("webapi", "2.0") + .Field("recalbox", recalboxVersion) + .Field("linux", linuxVersion) + .OpenObject("libretro") + .Field("retroarch", retroarchVersion) + .Field("cores", cores) + .CloseObject() + .Close(); + + RequestHandlerTools::Send(response, Http::Code::Ok, json, Mime::Json); +} + +void RequestHandler::SystemInfo(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "CpuInfo"); + + JSONBuilder sysInfo; + sysInfo.Open() + .Field("timestamp", DateTime().ToISO8601()) + .Field("platform", mSysInfos.BuildPlatformObject()) + .Field("cpus", mSysInfos.BuildCpuObject(true)) + .Field("memory", mSysInfos.BuildMemoryObject(true)) + .Field("temperature", mSysInfos.BuildTemperatureObject(true)) + .Close(); + + RequestHandlerTools::Send(response, Http::Code::Ok, sysInfo, Mime::Json); +} + +void RequestHandler::StorageInfo(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "StorageInfo"); + + Strings::Vector lines = RequestHandlerTools::OutputLinesOf("df -T"); + JSONBuilder result; + result.Open() + .OpenObject("storages"); + int id = 0; + for(int i=lines.size(); --i>0; ) + { + Strings::Vector parts = Strings::Split(lines[i], ' ', true); + if (parts.size() == 7) + { + RequestHandlerTools::DeviceInfo info(parts[6], parts[0], parts[1], parts[2], parts[3]); + RequestHandlerTools::GetDevicePropertiesOf(info); + if (info.Mount == "/") result.Field(Strings::ToString(id++).data(), RequestHandlerTools::BuildPartitionObject(info, "system")); + else if (info.Mount == "/boot") result.Field(Strings::ToString(id++).data(), RequestHandlerTools::BuildPartitionObject(info, "boot")); + else if (info.Mount == "/recalbox/share") result.Field(Strings::ToString(id++).data(), RequestHandlerTools::BuildPartitionObject(info, "share")); + else if (info.Mount == "/recalbox/share/bootvideos") ; // Filtered out + else if (Strings::StartsWith(info.Mount, "/recalbox/share") && + Strings::StartsWith(info.FileSystem, "//")) result.Field(Strings::ToString(id++).data(), RequestHandlerTools::BuildPartitionObject(info, "network")); + else result.Field(Strings::ToString(id++).data(), RequestHandlerTools::BuildPartitionObject(info, "unknown")); + } + else LOG(LogError) << "df -T unknown result : " << lines[i]; + } + result.CloseObject() + .Close(); + RequestHandlerTools::Send(response, Http::Code::Ok, result, Mime::Json); +} + +void RequestHandler::BiosDownload(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "BiosDownload"); + + Path path = RequestHandlerTools::GetCompressedBiosFolder(); + RequestHandlerTools::SendResource(path, response, Mime::Zip); +} + +void RequestHandler::BiosUpload(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "BiosUpload"); + + std::string fileName; + int start = 0; + int size = 0; + if (RequestHandlerTools::GetUploadedFile(request, fileName, start, size)) + { + const char* data = request.body().data() + start; + + // Get bios + std::string biosMd5 = md5(data, size); + + // Load and scan + if (mBiosManager.SystemCount() == 0) + mBiosManager.LoadFromFile(); + mBiosManager.Scan(nullptr, true); + + // Lookup + const Bios* bios = nullptr; + BiosManager::LookupResult result = mBiosManager.Lookup(request.splatAt(0).name(), biosMd5, bios); + + std::string extraResult = "Unknown"; + switch (result) + { + case BiosManager::AlreadyExists: + case BiosManager::Found: + { + bool ok = (result == BiosManager::Found) ? Files::SaveFile(bios->Filepath(), data, size) : true; + if (ok) + RequestHandlerTools::Send(response, Http::Code::Ok, RequestHandlerTools::SerializeBiosToJSON(*bios), Mime::Json); + else + RequestHandlerTools::Send(response, Http::Code::Insufficient_Storage); + break; + } + case BiosManager::NotFound: + default: RequestHandlerTools::Error404(response); + break; + } + + // Response + JSONBuilder jsonResult; + jsonResult.Open(); + jsonResult.CloseObject(); + + RequestHandlerTools::Send(response, Http::Code::Ok); + } +} + +void RequestHandler::BiosGetAll(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "BiosGetAll"); + + // Load and scan + if (mBiosManager.SystemCount() == 0) + mBiosManager.LoadFromFile(); + mBiosManager.Scan(nullptr, true); + + JSONBuilder output; + output.Open(); + + // Loop through systems + for(int sysIndex = 0; sysIndex < mBiosManager.SystemCount(); sysIndex++) + { + const BiosList& list = mBiosManager.SystemBios(sysIndex); + output.Field(list.Name().c_str(), RequestHandlerTools::SerializeBiosListToJSON(list)); + } + output.Close(); + + RequestHandlerTools::Send(response, Http::Code::Ok, output, Mime::Json); +} + +void RequestHandler::BiosGetSystem(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "BiosGetSystem"); + + // Load and scan + if (mBiosManager.SystemCount() == 0) + mBiosManager.LoadFromFile(); + mBiosManager.Scan(nullptr, true); + + std::string systemName = request.splatAt(0).name(); + const BiosList& list = mBiosManager.SystemBios(systemName); + // Empty system does not have a name => not found + if (list.Name() == systemName) + RequestHandlerTools::Send(response, Http::Code::Ok, RequestHandlerTools::SerializeBiosListToJSON(list), Mime::Json); + else + RequestHandlerTools::Error404(response); +} + +void RequestHandler::SystemsGetAll(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemsGetAll"); + + JSONBuilder systems; + systems.Open() + .Field("romPath", "/recalbox/share/roms") + .OpenObject("systemList"); + + SystemDeserializer deserializer; + deserializer.LoadSystems(); + for(int i = 0; i < deserializer.Count(); ++i) + { + SystemDescriptor descriptor; + deserializer.Deserialize(i, descriptor); + + JSONBuilder emulators = RequestHandlerTools::SerializeEmulatorsAndCoreToJson(descriptor.EmulatorTree()); + + JSONBuilder systemJson; + systemJson.Open() + .Field("name", descriptor.Name()) + .Field("fullname", descriptor.FullName()) + .Field("romFolder", descriptor.RomPath().Filename()) + .Field("themeFolder", descriptor.ThemeFolder()) + .Field("extensions", Strings::Split(descriptor.Extension(), ' ', true)) + .Field("command", descriptor.Command()) + .Field("emulators", emulators) + .Close(); + systems.Field(Strings::ToString(i).c_str(), systemJson); + } + + systems.CloseObject() + .Close(); + RequestHandlerTools::Send(response, Http::Code::Ok, systems, Mime::Json); +} + +void RequestHandler::SystemsGetActives(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemsGetActives"); + + RequestHandlerTools::Send(response, Http::Code::Method_Not_Allowed); +} + +void RequestHandler::SystemsResourceGetConsole(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemsResourceGetConsole"); + + Path first, second; + RequestHandlerTools::GetSystemResourcePath(first, second, request.splatAt(0).name(), request.splatAt(1).name(), "console.svg"); + RequestHandlerTools::SendResource(first, second, response, Mime::ImageSvg); +} + +void RequestHandler::SystemsResourceGetControler(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemsResourceGetControler"); + + Path first, second; + RequestHandlerTools::GetSystemResourcePath(first, second, request.splatAt(0).name(), request.splatAt(1).name(), "controler.svg"); + RequestHandlerTools::SendResource(first, second, response, Mime::ImageSvg); +} + +void RequestHandler::SystemsResourceGetControls(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemsResourceGetControls"); + + Path first, second; + RequestHandlerTools::GetSystemResourcePath(first, second, request.splatAt(0).name(), request.splatAt(1).name(), "controls.svg"); + RequestHandlerTools::SendResource(first, second, response, Mime::ImageSvg); +} + +void RequestHandler::SystemsResourceGetGame(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemsResourceGetGame"); + + Path first, second; + RequestHandlerTools::GetSystemResourcePath(first, second, request.splatAt(0).name(), request.splatAt(1).name(), "game.svg"); + RequestHandlerTools::SendResource(first, second, response, Mime::ImageSvg); +} + +void RequestHandler::SystemsResourceGetLogo(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemsResourceGetLogo"); + + Path first, second; + RequestHandlerTools::GetSystemResourcePath(first, second, request.splatAt(0).name(), request.splatAt(1).name(), "logo.svg"); + RequestHandlerTools::SendResource(first, second, response, Mime::ImageSvg); +} + +void RequestHandler::ConfigurationGet(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "ConfigurationGet"); + + std::string ns = request.splatAt(0).name(); + const HashMap& keys = RequestHandlerTools::SelectConfigurationKeySet(ns); + if (keys.empty()) + RequestHandlerTools::Error404(response); + + RequestHandlerTools::GetKeyValues(ns, keys, response); +} + +void RequestHandler::ConfigurationOptions(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "ConfigurationOptions"); + + std::string ns = request.splatAt(0).name(); + const HashMap& keys = RequestHandlerTools::SelectConfigurationKeySet(ns); + if (keys.empty()) + RequestHandlerTools::Error404(response); + + RequestHandlerTools::GetKeyValueOptions(keys, response); +} + +void RequestHandler::ConfigurationSet(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "ConfigurationSet"); + + std::string ns = request.splatAt(0).name(); + const HashMap& keys = RequestHandlerTools::SelectConfigurationKeySet(ns); + if (keys.empty()) + RequestHandlerTools::Error404(response); + + RequestHandlerTools::SetKeyValues(ns, keys, request.body(), response); +} + +void RequestHandler::ConfigurationDelete(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "ConfigurationDelete"); + + std::string ns = request.splatAt(0).name(); + const HashMap& keys = RequestHandlerTools::SelectConfigurationKeySet(ns); + if (keys.empty()) + RequestHandlerTools::Error404(response); + + RequestHandlerTools::DeleteKeyValues(ns, keys, request.body(), response); +} + +void RequestHandler::SystemConfigurationGet(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemConfigurationGet"); + + // Check system + std::string subSystem = request.splatAt(0).name(); + if (!RequestHandlerTools::IsValidSystem(subSystem)) + RequestHandlerTools::Error404(response); + + // Check data + const HashMap& keys = RequestHandlerTools::SelectConfigurationKeySet("specific"); + if (keys.empty()) + RequestHandlerTools::Error404(response); + + RequestHandlerTools::GetKeyValues(subSystem, keys, response); +} + +void RequestHandler::SystemConfigurationSet(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemConfigurationSet"); + + // Check system + std::string subSystem = request.splatAt(0).name(); + if (!RequestHandlerTools::IsValidSystem(subSystem)) + RequestHandlerTools::Error404(response); + + // Check data + const HashMap& keys = RequestHandlerTools::SelectConfigurationKeySet("specific"); + if (keys.empty()) + RequestHandlerTools::Error404(response); + + RequestHandlerTools::SetKeyValues(subSystem, keys, request.body(), response); +} + +void RequestHandler::SystemConfigurationOptions(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemConfigurationOptions"); + + // Check system + std::string subSystem = request.splatAt(0).name(); + if (!RequestHandlerTools::IsValidSystem(subSystem)) + RequestHandlerTools::Error404(response); + + // Check data + const HashMap& keys = RequestHandlerTools::SelectConfigurationKeySet("specific"); + if (keys.empty()) + RequestHandlerTools::Error404(response); + + RequestHandlerTools::GetKeyValueOptions(keys, response); +} + +void RequestHandler::SystemConfigurationDelete(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "SystemConfigurationDelete"); + + // Check system + std::string subSystem = request.splatAt(0).name(); + if (!RequestHandlerTools::IsValidSystem(subSystem)) + RequestHandlerTools::Error404(response); + + // Check data + const HashMap& keys = RequestHandlerTools::SelectConfigurationKeySet("specific"); + if (keys.empty()) + RequestHandlerTools::Error404(response); + + RequestHandlerTools::DeleteKeyValues(subSystem, keys, request.body(), response); +} + +void RequestHandler::MediaUserGetList(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "MediaUserGetList"); + + Path mediaPath("/recalbox/share/screenshots"); + Path::PathList list = mediaPath.GetDirectoryContent(); + + static std::string imagesExtentions(".jpg|.jpeg|.png|.gif"); + static std::string videosExtentions(".mkv|.avi|.mp4"); + + JSONBuilder result; + result.Open() + .Field("mediaPath", mediaPath.ToString()) + .OpenObject("mediaList"); + for(const Path& path : list) + { + bool ok = false; + result.OpenObject(path.MakeRelative(mediaPath, ok).ToChars()); + std::string ext = Strings::ToLowerASCII(path.Extension()); + if (imagesExtentions.find(ext) != std::string::npos) result.Field("type", "image"); + else if (videosExtentions.find(ext) != std::string::npos) result.Field("type", "video"); + else result.Field("type", "unknown"); + result.CloseObject(); + } + result.CloseObject() + .Close(); + RequestHandlerTools::Send(response, Http::Code::Ok, result, Mime::Json); +} + +void RequestHandler::MediaUserDelete(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "MediaUserDelete"); + + Strings::Vector list; + if (RequestHandlerTools::ExtractArray(request, list)) + { + Path mediaPath("/recalbox/share/screenshots"); + for(const std::string& mediaFile : list) + { + Path media = mediaPath / mediaFile; + if (media.Exists()) media.Delete(); + else RequestHandlerTools::Error404(response); + } + RequestHandlerTools::Send(response, Http::Code::Ok); + } + RequestHandlerTools::Send(response, Http::Code::Bad_Request, "Invalid json array!", Mime::PlainText); +} + +void RequestHandler::MediaGet(const Rest::Request& request, Http::ResponseWriter response) +{ + RequestHandlerTools::LogRoute(request, "MediaGameGet"); + + // Get path + Path path(Strings::Decode64(request.splatAt(0).name())); + // Check extension + std::string ext = Strings::ToLowerASCII(path.Extension()).append(1, '.'); + if (path.Exists()) + { + if (path.IsFile()) + { + // Image + if (ext == ".gif") RequestHandlerTools::SendResource(path, response, Mime::ImageGif); + else if (ext == ".jpg") RequestHandlerTools::SendResource(path, response, Mime::ImageJpg); + else if (ext == ".png") RequestHandlerTools::SendResource(path, response, Mime::ImagePng); + else if (ext == ".svg") RequestHandlerTools::SendResource(path, response, Mime::ImageSvg); + // Video + else if (ext == ".mkv") RequestHandlerTools::SendResource(path, response, Mime::VideoMkv); + else if (ext == ".mp4") RequestHandlerTools::SendResource(path, response, Mime::VideoMp4); + else if (ext == ".avi") RequestHandlerTools::SendResource(path, response, Mime::VideoAvi); + // Video + else if (ext == ".pdf") RequestHandlerTools::SendResource(path, response, Mime::FilePdf); + // Unknown + else RequestHandlerTools::Send(response, Http::Code::Bad_Request, "Invalid media extension!", Mime::PlainText); + } + else RequestHandlerTools::Send(response, Http::Code::Bad_Request, "Target is a directory!", Mime::PlainText); + } + else RequestHandlerTools::Error404(response); +} diff --git a/projects/frontend/server-webapi/server/handlers/RequestHandler.h b/projects/frontend/server-webapi/server/handlers/RequestHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..9336d12c99a5ae498a23a3502dad2a707bc23c04 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/RequestHandler.h @@ -0,0 +1,216 @@ +// +// Created by bkg2k on 30/03/2020. +// +#pragma once + +#include "server/IRouter.h" +#include +#include +#include +#include + +class RequestHandler : public IRouter +{ + private: + //! Bios Manager + BiosManager mBiosManager; + //! System information provider + SysInfos mSysInfos; + //! Event watcher + EmulationStationWatcher mWatcher; + //! + + //! WWW root + Path mWWWRoot; + //! WWW default file + std::string mDefaultFile; + + public: + RequestHandler(const std::string& wwwRoot, const std::string& defaultFile) + : mWWWRoot(wwwRoot), + mDefaultFile(defaultFile) + { + } + + /*! + * @brief Handle files + * @param request Request object + * @param response Response object + */ + void FileServer(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET version + * @param request Request object + * @param response Response object + */ + void Versions(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET version + * @param request Request object + * @param response Response object + */ + void SystemInfo(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET version + * @param request Request object + * @param response Response object + */ + void StorageInfo(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET version + * @param request Request object + * @param response Response object + */ + void BiosDownload(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET version + * @param request Request object + * @param response Response object + */ + void BiosUpload(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get array of all system's bios + * @param request Request object + * @param response Response object + */ + void BiosGetAll(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get bios for the given system + * @param request Request object + * @param response Response object + */ + void BiosGetSystem(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get all systems + * @param request Request object + * @param response Response object + */ + void SystemsGetAll(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get all active systems + * @param request Request object + * @param response Response object + */ + void SystemsGetActives(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get system console svg for the given system and region + * @param request Request object + * @param response Response object + */ + void SystemsResourceGetConsole(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get system controler svg for the given system and region + * @param request Request object + * @param response Response object + */ + void SystemsResourceGetControler(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get system controls svg for the given system and region + * @param request Request object + * @param response Response object + */ + void SystemsResourceGetControls(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get system game support svg for the given system and region + * @param request Request object + * @param response Response object + */ + void SystemsResourceGetGame(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get system logo svg for the given system and region + * @param request Request object + * @param response Response object + */ + void SystemsResourceGetLogo(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get configuration keys + * @param request Request object + * @param response Response object + */ + void ConfigurationGet(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle POST set configuration keys + * @param request Request object + * @param response Response object + */ + void ConfigurationSet(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle OPTIONS get configuration keys, type and validators + * @param request Request object + * @param response Response object + */ + void ConfigurationOptions(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle DELETE delete configuration keys + * @param request Request object + * @param response Response object + */ + void ConfigurationDelete(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get system configuration keys + * @param request Request object + * @param response Response object + */ + void SystemConfigurationGet(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle POST set system configuration keys + * @param request Request object + * @param response Response object + */ + void SystemConfigurationSet(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle OPTIONS get system configuration keys, type and validators + * @param request Request object + * @param response Response object + */ + void SystemConfigurationOptions(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle DELETE delete system configuration keys + * @param request Request object + * @param response Response object + */ + void SystemConfigurationDelete(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET get media list + * @param request Request object + * @param response Response object + */ + void MediaUserGetList(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle DELETE delete media + * @param request Request object + * @param response Response object + */ + void MediaUserDelete(const Rest::Request& request, Http::ResponseWriter response) override; + + /*! + * @brief Handle GET to get game media + * @param request Request object + * @param response Response object + */ + void MediaGet(const Rest::Request& request, Http::ResponseWriter response) override; +}; diff --git a/projects/frontend/server-webapi/server/handlers/RequestHandlerTools.cpp b/projects/frontend/server-webapi/server/handlers/RequestHandlerTools.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b010ebc05da871194936c243ec79ca188d823130 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/RequestHandlerTools.cpp @@ -0,0 +1,999 @@ +// +// Created by bkg2k on 03/04/2020. +// + +#include +#include +#include +#include +#include +#include +#include "RequestHandlerTools.h" +#include "Mime.h" +#include +#include + +using namespace Pistache; + +JSONBuilder RequestHandlerTools::BuildPartitionObject(const DeviceInfo& info, const std::string& recalboxtype) +{ + JSONBuilder result; + result.Open() + .Field("recalbox", recalboxtype) + .Field("devicebus", info.Bus) + .Field("devicetype", info.Type) + .Field("devicemodel", info.Model) + .Field("mount", info.Mount) + .Field("filesystem", info.FileSystem) + .Field("filesystemtype", info.FileSystemType) + .Field("size", info.Size << 10) + .Field("used", info.Used << 10) + .Close(); + return result; +} + +void RequestHandlerTools::GetDevicePropertiesOf(DeviceInfo& info) +{ + // Get info from udevadm + Strings::Vector strings = RequestHandlerTools::OutputLinesOf(std::string("/sbin/udevadm info --query=property --name=").append(info.FileSystem)); + for(const std::string& string : strings) + { + size_t pos = string.find('='); + if (pos == std::string::npos) continue; + if (Strings::StartsWith(string, LEGACY_STRING("ID_BUS="))) + info.Bus = string.substr(pos + 1); + else if (Strings::StartsWith(string, LEGACY_STRING("ID_VENDOR="))) + info.Model.insert(0, string.substr(pos + 1) + ' '); + else if (Strings::StartsWith(string, LEGACY_STRING("ID_MODEL="))) + info.Model.append(string.substr(pos + 1)); + else if (Strings::StartsWith(string, LEGACY_STRING("ID_TYPE="))) + info.Type = string.substr(pos + 1); + else if (Strings::StartsWith(string, LEGACY_STRING("ID_NAME="))) + info.Model = string.substr(pos + 1); + else if (Strings::StartsWith(string, LEGACY_STRING("ID_FS_TYPE="))) + info.FileSystemType = string.substr(pos + 1); + } + // Try to get missing info + if (Strings::StartsWith(info.Mount, "/dev/mmcblk")) + { + info.Bus = "sdio"; + info.Type = "sd"; + } + Strings::ReplaceAllIn(info.Model, '_', " ", 1); +} + +void RequestHandlerTools::GetSystemResourcePath(Path& regionPath, Path& basePath, const std::string& system, const std::string& region, const char* resourceFileName) +{ + std::string regionString(sSystemResourceRegionPath); + Strings::ReplaceAllIn(regionString, "%SYSTEM%", system); + Strings::ReplaceAllIn(regionString, "%REGION%", region); + Strings::ReplaceAllIn(regionString, "%FILE%", resourceFileName); + regionPath = regionString; + + std::string baseString(sSystemResourceBasePath); + Strings::ReplaceAllIn(baseString, "%SYSTEM%", system); + Strings::ReplaceAllIn(baseString, "%FILE%", resourceFileName); + basePath = baseString; +} + +JSONBuilder RequestHandlerTools::SerializeBiosToJSON(const Bios& bios) +{ + return JSONBuilder().Open() + .Field("mandatory", bios.IsMandatory()) + .Field("hashMatchingMandatory", bios.IsHashMatchingMandatory()) + .Field("displayFileName", bios.Filename(false)) + .Field("notes", bios.Notes()) + .Field("currentMd5", bios.CurrentMD5()) + .Field("md5List", bios.MD5List()) + .Field("cores", Strings::Split(bios.Cores(), ',')) + .Field("lightStatus", bios.LightStatusAsString()) + .Field("realStatus", bios.BiosStatusAsString()) + .Close(); +} + +JSONBuilder RequestHandlerTools::SerializeBiosListToJSON(const BiosList& biosList) +{ + JSONBuilder systemJson; + systemJson.Open() + .Field("fullName", biosList.FullName()) + .OpenObject("scanResult") + .Field("total", biosList.BiosCount()) + .Field("ok", biosList.TotalBiosOk()) + .Field("ko", biosList.TotalBiosKo()) + .Field("unsafe", biosList.TotalBiosUnsafe()) + .Field("notFound", biosList.TotalFileNotFound()) + .Field("hashOk", biosList.TotalHashMatching()) + .Field("hashKo", biosList.TotalHashNotMatching()) + .CloseObject() + .OpenObject("biosList"); + for(int biosIndex = biosList.BiosCount(); --biosIndex >= 0; ) + { + const Bios& bios = biosList.BiosAt(biosIndex); + systemJson.Field(bios.Filepath().ToChars(), SerializeBiosToJSON(bios)); + } + systemJson.CloseObject() + .Close(); + + return systemJson; +} + +std::string RequestHandlerTools::OutputOf(const std::string& command) +{ + std::string output; + FILE* pipe = popen(command.c_str(), "r"); + if (pipe != nullptr) + { + char buffer[1024]; + while (feof(pipe) == 0) + if (fgets(buffer, sizeof(buffer), pipe) != nullptr) + output.append(buffer); + pclose(pipe); + } + return output; +} + +Strings::Vector RequestHandlerTools::OutputLinesOf(const std::string& command) +{ + return Strings::Split(OutputOf(command), '\n'); +} + +void RequestHandlerTools::Error404(Http::ResponseWriter& response) +{ + LOG(LogError) << "Error 404"; + SetHeaders(response); + Send(response, Http::Code::Not_Found, "404 - Not found", Mime::PlainText); +} + +JSONBuilder RequestHandlerTools::SerializeEmulatorsAndCoreToJson(const EmulatorList& emulatorlist) +{ + JSONBuilder emulators; + emulators.Open(); + for(int i = emulatorlist.Count(); --i >= 0; ) + { + const EmulatorDescriptor& emulator = emulatorlist.EmulatorAt(i); + JSONBuilder CoreJson; + CoreJson.Open(); + for(int c = emulator.CoreCount(); --c >= 0; ) + CoreJson.Field(Strings::ToString((int)emulator.CorePriorityAt(c)).c_str(), emulator.CoreAt(c).mName); + CoreJson.Close(); + emulators.Field(emulator.Name().c_str(), CoreJson); + } + emulators.Close(); + return emulators; +} + +void RequestHandlerTools::LogRoute(const Pistache::Rest::Request& request, const char* methodName) +{ + LOG(LogDebug) << methodName << ": " << MethodToString(request.method()) << ' ' << request.resource(); +} + +const char* RequestHandlerTools::MethodToString(Pistache::Http::Method method) +{ + switch(method) + { + case Http::Method::Options: return "OPTIONS"; + case Http::Method::Get: return "GET"; + case Http::Method::Post: return "POST"; + case Http::Method::Head: return "HEAD"; + case Http::Method::Put: return "PUT"; + case Http::Method::Patch: return "PATCH"; + case Http::Method::Delete: return "DELETE"; + case Http::Method::Trace: return "TRACE"; + case Http::Method::Connect: return "CONNECT"; + } + return "UNKNOWN"; +} + +/* + * Send + */ + + +void RequestHandlerTools::SetHeaders(Http::ResponseWriter& response) +{ + response.headers().add("GET, POST, OPTIONS, DELETE"); + response.headers().add("*"); + response.headers().add("*"); +} + +void +RequestHandlerTools::Send(Pistache::Http::ResponseWriter& response, Pistache::Http::Code code, const std::string& body, + const Pistache::Http::Mime::MediaType& mime) +{ + SetHeaders(response); + response.send(code, body, mime); +} + +void RequestHandlerTools::Send(Pistache::Http::ResponseWriter& response, Pistache::Http::Code code) +{ + SetHeaders(response); + response.send(code, nullptr, 0, Pistache::Http::Mime::MediaType()); +} + +void RequestHandlerTools::SendResource(const Path& resourcepath, Http::ResponseWriter& response, const Pistache::Http::Mime::MediaType& mimeType) +{ + if (resourcepath.Exists()) + { + SetHeaders(response); + Http::serveFile(response, resourcepath.ToString(), mimeType); + } + else + Error404(response); +} + +void RequestHandlerTools::SendResource(const Path& preferedpath, const Path& fallbackpath, Http::ResponseWriter& response, const Http::Mime::MediaType& mimetype) +{ + if (preferedpath.Exists()) + { + SetHeaders(response); + Http::serveFile(response, preferedpath.ToString(), mimetype); + } + else if (fallbackpath.Exists()) + { + SetHeaders(response); + Http::serveFile(response, fallbackpath.ToString(), mimetype); + } + else + Error404(response); +} + +const HashMap& RequestHandlerTools::SelectConfigurationKeySet(const std::string& _namespace) +{ + enum class Namespace + { + Unknown, + System, + EmulationStation, + Scraper, + Kodi, + Hyperion, + Audio, + Wifi, + Controllers, + Updates, + Global, // sConverter + ({ + { "system", Namespace::System }, + { "emulationstation", Namespace::EmulationStation }, + { "scraper", Namespace::Scraper }, + { "kodi", Namespace::Kodi }, + { "hyperion", Namespace::Hyperion }, + { "audio", Namespace::Audio }, + { "wifi", Namespace::Wifi }, + { "controllers", Namespace::Controllers }, + { "updates", Namespace::Updates }, + { "global", Namespace::Global }, + { "specific", Namespace::Global }, + }); + + Namespace* pns = sConverter.try_get(_namespace); + Namespace ns = (pns != nullptr) ? *pns : Namespace::Unknown; + + switch(ns) + { + case Namespace::System: + { + static HashMap sList + ({ + { "fbcp.enabled" , Validator(true) }, + { "splash.length" , Validator(-1, INT32_MAX) }, + { "manager.enabled" , Validator(true) }, + { "security.enabled" , Validator(true) }, + { "api.enabled" , Validator(true) }, + { "es.videomode" , Validator(GetAvailableResolutions(), false) }, + { "emulators.specialkeys" , Validator(false, { "default", "nomenu", "none" }) }, + { "hostname" , Validator("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-") }, + { "samba.enabled" , Validator(true) }, + { "virtual-gamepads.enabled", Validator(true) }, + { "ssh.enabled" , Validator(true) }, + { "language" , Validator(GetAvailableLanguages(), false) }, + { "kblayout" , Validator(GetAvailableKeyboardLayout(), false) }, + { "timezone" , Validator(GetAvailableTimeZone(), false) }, + }); + + return sList; + } + case Namespace::EmulationStation: + { + static HashMap sList + ({ + { "menu" , Validator(false, { "default", "bartop", "none" }) }, + { "selectedsystem" , Validator(GetSupportedSystemList(), false) }, + { "bootongamelist" , Validator(true) }, + { "hidesystemview" , Validator(true) }, + { "gamelistonly" , Validator(true) }, + { "forcebasicgamelistview" , Validator(true) }, + { "filteradultgames" , Validator(true) }, + { "collection.allgames" , Validator(true) }, + { "collection.multiplayers", Validator(true) }, + { "collection.lastplayed" , Validator(true) }, + { "videosnaps.delay" , Validator(0, INT32_MAX) }, + { "videosnaps.loop" , Validator(0, INT32_MAX) }, + }); + + return sList; + } + case Namespace::Scraper: + { + static HashMap sList + ({ + { "extractregionfromfilename", Validator(true) }, + { "getnamefrom" , Validator(0, 2) }, + { "screenscraper.region" , Validator(false, { "eu", "us", "jp", "wor" }) }, + { "screenscraper.language" , Validator(false, { "en", "es", "pt", "fr", "de", "it", "nl", "ja", "zh", "ko", "ru", "da", "fi", "sv", "hu", "no", "pl", "cz", "sk", "tr" }) }, + { "screenscraper.media" , Validator(false, { "screenshot", "title", "logo", "marquee", "box2d", "box3d", "mixv1", "mixv2" }) }, + { "screenscraper.thumbnail" , Validator(false, { "screenshot", "title", "logo", "marquee", "box2d", "box3d", "mixv1", "mixv2" }) }, + { "screenscraper.video" , Validator(false, { "none", "original", "normalized" }) }, + { "screenscraper.manual" , Validator(true) }, + { "screenscraper.maps" , Validator(true) }, + { "screenscraper.user" , Validator() }, + { "screenscraper.password" , Validator() }, + }); + + return sList; + } + case Namespace::Kodi: + { + static HashMap sList + ({ + { "enabled" , Validator(true) }, + { "atstartup" , Validator(true) }, + { "xbutton" , Validator(true) }, + { "videomode" , Validator(GetAvailableResolutions(), false) }, + { "network.waitmode", Validator(false, { "required", "wish", "nonce" }) }, + { "network.waittime", Validator(0, INT32_MAX) }, + { "network.waithost", Validator("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-") }, + }); + + return sList; + } + case Namespace::Hyperion: + { + static HashMap sList + ({ + { "enabled" , Validator(true) }, + }); + + return sList; + } + case Namespace::Wifi: + { + static HashMap sList + ({ + { "enabled", Validator(true) }, + { "region" , Validator(false, { "US", "CA", "JP", "DE", "NL", "IT", "PT", "LU", "NO", "FI", "DK", "CH", "CZ", "ES", "GB", "KR", "CN", "FR", "HK", "SG", "TW", "BR", "IL", "SA", "LB", "AE", "ZA", "AR", "AU", "AT", "BO", "CL", "GR", "IS", "IN", "IE", "KW", "LI", "LT", "MX", "MA", "NZ", "PL", "PR", "SK", "SI", "TH", "UY", "PA", "RU", "KW", "LI", "LT", "MX", "MA", "NZ", "PL", "PR", "SK", "SI", "TH", "UY", "PA", "RU", "EG", "TT", "TR", "CR", "EC", "HN", "KE", "UA", "VN", "BG", "CY", "EE", "MU", "RO", "CS", "ID", "PE", "VE", "JM", "BH", "OM", "JO", "BM", "CO", "DO", "GT", "PH", "LK", "SV", "TN", "PK", "QA", "DZ" }) }, + { "ssid" , Validator() }, + { "key" , Validator() }, + { "ip" , Validator("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-") }, + { "gateway", Validator("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-") }, + { "netmask", Validator("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-") }, + }); + + return sList; + } + case Namespace::Audio: + { + static HashMap sList + ({ + { "device" , Validator(GetAvailableSoundDevices(), false) }, + { "volume" , Validator(0, 100) }, + { "bgmusic", Validator(true) }, + }); + + return sList; + } + case Namespace::Controllers: + { + static HashMap sList + ({ + { "bluetooth.enabled", Validator(true) }, + { "bluetooth.ertm" , Validator(true) }, + { "ps3.enabled" , Validator(true) }, + { "ps3.driver" , Validator(false, { "bluez", "official", "shanwan" }) }, + { "gpio.enabled" , Validator(true) }, + { "gpio.args" , Validator() }, + { "steam.enabled" , Validator(true) }, + { "db9.enabled" , Validator(true) }, + { "db9.args" , Validator() }, + { "gamecon.enabled" , Validator(true) }, + { "gamecon.args" , Validator() }, + { "xarcade.enabled" , Validator(true) }, + }); + + return sList; + } + case Namespace::Updates: + { + static HashMap sList + ({ + { "enabled" , Validator(true) }, + { "type" , Validator(false, std::vector({ "stable", "review" })) }, + { "review.key", Validator() }, + }); + + return sList; + } + case Namespace::Global: + { + static HashMap sList + ({ + { "videomode" , Validator(GetAvailableResolutions(), false) }, + { "shaderset" , Validator({ "none", "scanlines", "retro", "custom" }, false) }, + { "shaderset.file" , Validator(GetAvailableShaders(), false) }, + { "integerscale" , Validator(true) }, + { "shaders" , Validator(true) }, + { "ratio" , Validator(GetSupportedRatioList(), false) }, + { "smooth" , Validator(true) }, + { "rewind" , Validator(true) }, + { "autosave" , Validator(true) }, + { "retroachievements" , Validator(true) }, + { "retroachievements.hardcore", Validator(true) }, + { "retroachievements.username", Validator() }, + { "retroachievements.password", Validator() }, + { "inputdriver" , Validator(false, { "auto", "udev", "sdl2"}) }, + { "configfile" , Validator() }, + { "demo.systemlist" , Validator(GetSupportedSystemList(), true) }, + { "demo.duration" , Validator(30, 600) }, + { "demo.infoscreenduration" , Validator(5, 30) }, + { "translate" , Validator(true) }, + { "translate.from" , Validator(false, {"auto", "EN", "ES", "FR", "IT", "DE", "JP", "NL", "CS", "DA", "SV", "HR", "KO", "ZH_CN", "ZH_TW", "CA", "BG", "BN", "EU", "AZ", "AR", "SQ", "AF", "EO", "ET", "TL", "FI", "GL", "KA", "EL", "GU", "HT", "IW", "HI", "HU", "IS", "ID", "GA", "KN", "LA", "LV", "LT", "MK", "MS", "MT", "NO", "FA", "PL", "PT", "RO", "RU", "SR", "SK", "SL", "SW", "TA", "TE", "TH", "TR", "UK", "UR", "VI", "CY", "YI"}) }, + { "translate.to" , Validator(false, {"auto", "EN", "ES", "FR", "IT", "DE", "JP", "NL", "CS", "DA", "SV", "HR", "KO", "ZH_CN", "ZH_TW", "CA", "BG", "BN", "EU", "AZ", "AR", "SQ", "AF", "EO", "ET", "TL", "FI", "GL", "KA", "EL", "GU", "HT", "IW", "HI", "HU", "IS", "ID", "GA", "KN", "LA", "LV", "LT", "MK", "MS", "MT", "NO", "FA", "PL", "PT", "RO", "RU", "SR", "SK", "SL", "SW", "TA", "TE", "TH", "TR", "UK", "UR", "VI", "CY", "YI"}) }, + { "translate.apikey" , Validator() }, + { "translate.url" , Validator() }, + { "arcade" , Validator(true) }, + { "arcade.position" , Validator(-200, 200) }, + { "arcade.includeneogeo" , Validator(true) }, + { "arcade.hideoriginals" , Validator(true) }, + { "netplay" , Validator(true) }, + { "netplay.nickname" , Validator() }, + { "netplay.port" , Validator(1, 65535) }, + { "netplay.relay" , Validator() }, + { "netplay.systems" , Validator() }, + { "netplay.lobby" , Validator() }, + }); + + return sList; + } + case Namespace::Specific: + { + static HashMap sList + ({ + { "videomode" , Validator(GetAvailableResolutions(), false) }, + { "shaderset" , Validator({ "none", "scanlines", "retro", "custom" }, false) }, + { "shaderset.file" , Validator(GetAvailableShaders(), false) }, + { "integerscale" , Validator(true) }, + { "shaders" , Validator(true) }, + { "ratio" , Validator(GetSupportedRatioList(), false) }, + { "smooth" , Validator(true) }, + { "rewind" , Validator(true) }, + { "autosave" , Validator(true) }, + { "retroachievements" , Validator(true) }, + { "retroachievements.hardcore", Validator(true) }, + { "retroachievements.username", Validator() }, + { "retroachievements.password", Validator() }, + { "inputdriver" , Validator(false, { "auto", "udev", "sdl2"}) }, + { "configfile" , Validator() }, + { "translate" , Validator(true) }, + { "translate.from" , Validator(false, {"auto", "EN", "ES", "FR", "IT", "DE", "JP", "NL", "CS", "DA", "SV", "HR", "KO", "ZH_CN", "ZH_TW", "CA", "BG", "BN", "EU", "AZ", "AR", "SQ", "AF", "EO", "ET", "TL", "FI", "GL", "KA", "EL", "GU", "HT", "IW", "HI", "HU", "IS", "ID", "GA", "KN", "LA", "LV", "LT", "MK", "MS", "MT", "NO", "FA", "PL", "PT", "RO", "RU", "SR", "SK", "SL", "SW", "TA", "TE", "TH", "TR", "UK", "UR", "VI", "CY", "YI"}) }, + { "translate.to" , Validator(false, {"auto", "EN", "ES", "FR", "IT", "DE", "JP", "NL", "CS", "DA", "SV", "HR", "KO", "ZH_CN", "ZH_TW", "CA", "BG", "BN", "EU", "AZ", "AR", "SQ", "AF", "EO", "ET", "TL", "FI", "GL", "KA", "EL", "GU", "HT", "IW", "HI", "HU", "IS", "ID", "GA", "KN", "LA", "LV", "LT", "MK", "MS", "MT", "NO", "FA", "PL", "PT", "RO", "RU", "SR", "SK", "SL", "SW", "TA", "TE", "TH", "TR", "UK", "UR", "VI", "CY", "YI"}) }, + { "translate.apikey" , Validator() }, + { "translate.url" , Validator() }, + }); + + return sList; + } + case Namespace::Unknown: + default: break; + } + + static HashMap sList; + return sList; +} + +IniFile RequestHandlerTools::LoadConfiguration() +{ + return IniFile(Path(sConfiguration), false); +} + +void RequestHandlerTools::SaveConfiguration(IniFile& configuration) +{ + configuration.Save(); +} + +void RequestHandlerTools::GetKeyValues(const std::string& domain, const HashMap& keys, Http::ResponseWriter& response) +{ + IniFile configuration = RequestHandlerTools::LoadConfiguration(); + JSONBuilder result; + result.Open(); + for(const auto& key : keys) + { + std::string k(domain); k.append(1, '.'); k.append(key.first); + JSONBuilder value; + value.Open() + .Field("exist", configuration.Exists(k)); + switch (key.second.Type()) + { + case Validator::Types::StringFree: + case Validator::Types::StringConstrained: + case Validator::Types::StringMultiPicker: + case Validator::Types::StringPicker: value.Field("value", configuration.AsString(k)); break; + case Validator::Types::IntRange: value.Field("value", configuration.AsInt(k, 0)); break; + case Validator::Types::Bool: value.Field("value", configuration.AsBool(k, false)); break; + default: LOG(LogError) << "Unknown type " << (int) key.second.Type() << " for " << key.first; + } + value.Close(); + result.Field(key.first.c_str(), value); + } + result.Close(); + RequestHandlerTools::Send(response, Http::Code::Ok, result, Mime::Json); +} + +void RequestHandlerTools::GetKeyValueOptions(const HashMap& keys, Http::ResponseWriter& response) +{ + IniFile configuration = RequestHandlerTools::LoadConfiguration(); + JSONBuilder result; + result.Open(); + for(const auto& key : keys) + { + JSONBuilder properties; + properties.Open() + .Field("type", key.second.TypeAsString()); + switch (key.second.Type()) + { + case Validator::Types::StringFree: break; + case Validator::Types::StringConstrained: properties.Field("allowedChars", key.second.StringConstraint()); break; + case Validator::Types::StringPicker: + case Validator::Types::StringMultiPicker: properties.Field("allowedStringList", key.second.StringList()); break; + case Validator::Types::IntRange: properties.Field("lowerValue", key.second.Lower()).Field("higherValue", key.second.Higher()); break; + case Validator::Types::Bool: break; + default: LOG(LogError) << "Unknown type " << (int) key.second.Type() << " for " << key.first; + } + if (key.second.HasDisplay()) + properties.Field("displayableStringList", key.second.DisplayList()); + properties.Close(); + result.Field(key.first.c_str(), properties); + } + result.Close(); + RequestHandlerTools::Send(response, Http::Code::Ok, result, Mime::Json); +} + +void RequestHandlerTools::SetKeyValues(const std::string& domain, const HashMap& keys, const std::string& jsonString, Http::ResponseWriter& response) +{ + rapidjson::Document json; + json.Parse(jsonString.c_str()); + if (!json.HasParseError()) + { + IniFile configuration = RequestHandlerTools::LoadConfiguration(); + bool changed = false; + if (json.GetType() == rapidjson::Type::kObjectType) + for(const auto& key : json.GetObject()) + { + std::string shortKey(key.name.GetString()); + std::string value; + switch(key.value.GetType()) + { + case rapidjson::kFalseType: value = "0"; break; + case rapidjson::kTrueType: value = "1"; break; + case rapidjson::kNullType: + case rapidjson::kObjectType: + case rapidjson::kArrayType: + { + RequestHandlerTools::Send(response, Http::Code::Bad_Request, "Key '" + shortKey + "' has un invalid value!", Mime::PlainText); + return; + } + case rapidjson::kStringType: value = key.value.GetString(); break; + case rapidjson::kNumberType: value = Strings::ToString(key.value.GetInt()); break; + } + Validator* validator = keys.try_get(shortKey); + if (validator != nullptr) + { + if (validator->Validate(value)) + { + std::string longKey(domain); longKey.append(1, '.'); longKey.append(shortKey); + configuration.SetString(longKey, value); + changed = true; + } + else + { + RequestHandlerTools::Send(response, Http::Code::Bad_Request, "Key '" + shortKey + "' has un invalid value!", Mime::PlainText); + return; + } + } + else + { + RequestHandlerTools::Send(response, Http::Code::Bad_Request, "Unknown Key: " + shortKey, Mime::PlainText); + return; + } + } + if (changed) + RequestHandlerTools::SaveConfiguration(configuration); + GetKeyValues(domain, keys, response); + return; + } + RequestHandlerTools::Send(response, Http::Code::Bad_Request, "JSON Parsing error", Mime::PlainText); +} + +void RequestHandlerTools::DeleteKeyValues(const std::string& domain, const HashMap& keys, const std +::string& jsonString, Http::ResponseWriter& response) +{ + rapidjson::Document json; + json.Parse(jsonString.c_str()); + if (!json.HasParseError()) + { + IniFile configuration = RequestHandlerTools::LoadConfiguration(); + bool changed = false; + if (json.GetType() == rapidjson::Type::kArrayType) + for(const auto& key : json.GetArray()) + { + std::string shortKey(key.GetString()); + if (keys.contains(shortKey)) + { + std::string longKey(domain); longKey.append(1, '.'); longKey.append(shortKey); + configuration.Delete(longKey); + changed = true; + } + else + { + RequestHandlerTools::Send(response, Http::Code::Bad_Request, "Unknown Key: " + shortKey, Mime::PlainText); + return; + } + } + if (changed) + RequestHandlerTools::SaveConfiguration(configuration); + GetKeyValues(domain, keys, response); + return; + } + RequestHandlerTools::Send(response, Http::Code::Bad_Request, "JSON Parsing error", Mime::PlainText); +} + +const Strings::Vector& RequestHandlerTools::GetSupportedSystemList() +{ + static Strings::Vector result; + + if (result.empty()) + { + SystemDeserializer deserializer; + deserializer.LoadSystems(); + for(int i = deserializer.Count(); --i >= 0; ) + { + SystemDescriptor descriptor; + deserializer.Deserialize(i, descriptor); + result.push_back(descriptor.Name()); + } + // PATCH + result.push_back("favorites"); + } + + return result; +} + +const Strings::Vector& RequestHandlerTools::GetSupportedRatioList() +{ + static Strings::Vector result; + + if (result.empty()) + for(const auto& ratio : LibretroRatio::GetRatio()) + result.push_back(ratio.second); + + return result; +} + +const std::string& RequestHandlerTools::GetArchitecture() +{ + static std::string sArchitecture(Files::LoadFile(Path("/recalbox/recalbox.arch"))); + return sArchitecture; +} + +std::string RequestHandlerTools::GetCommandOutput(const std::string& command) +{ + std::string output; + char buffer[10240]; + FILE* pipe = popen(command.c_str(), "r"); + if (pipe != nullptr) + { + while (feof(pipe) == 0) + if (fgets(buffer, sizeof(buffer), pipe) != nullptr) + output.append(buffer); + pclose(pipe); + } + return output; +} + +HashMap RequestHandlerTools::GetAvailableResolutions() +{ + static HashMap sResolutions; + + if (sResolutions.empty()) + { + std::string arch = GetArchitecture(); + + sResolutions.insert_unique("default", "Default resolution"); + if (Strings::StartsWith(arch, "rpi", 3)) + { + rapidjson::Document json; + json.Parse(GetCommandOutput("tvservice -j -m CEA").c_str()); + if (!json.HasParseError()) + for(const auto& item : json.GetArray()) + { + std::string name("CEA "); + name.append(Strings::ToString(item["code"].GetInt())); + std::string display(Strings::ToString(item["width"].GetInt())); + display.append(1, 'x').append(Strings::ToString(item["height"].GetInt())) + .append(1, ' ') + .append(Strings::ToString(item["rate"].GetInt())) + .append("Hz ", 3) + .append(item["aspect_ratio"].GetString()); + if (item["scan"].GetString()[0] == 'i') + display.append(" (Interlaced)"); + sResolutions.insert_unique(name + " HDMI", display + ", sound over HDMI"); + sResolutions.insert_unique(name + " DVI", display + ", no sound"); + } + json.Parse(GetCommandOutput("tvservice -j -m DMT").c_str()); + if (!json.HasParseError()) + for(const auto& item : json.GetArray()) + { + std::string name("DMT "); + name.append(Strings::ToString(item["code"].GetInt())); + std::string display(Strings::ToString(item["width"].GetInt())); + display.append(1, 'x').append(Strings::ToString(item["height"].GetInt())) + .append(1, ' ') + .append(Strings::ToString(item["rate"].GetInt())) + .append("Hz ", 3) + .append(item["aspect_ratio"].GetString()); + if (item["scan"].GetString()[0] == 'i') + display.append(" (Interlaced)"); + sResolutions.insert_unique(name + " HDMI", display + ", sound over HDMI"); + sResolutions.insert_unique(name + " DVI", display + ", no sound"); + } + } + } + + return sResolutions; +} + +Strings::Vector RequestHandlerTools::GetAvailableSoundDevices() +{ + std::string output = GetCommandOutput("/recalbox/scripts/recalbox-config.sh lsaudio"); + return Strings::Split(output, '\n'); +} + +const Strings::Vector& RequestHandlerTools::GetAvailableTimeZone() +{ + static Strings::Vector output; + + if (output.empty()) + { + std::string zones = Files::LoadFile(Path("/usr/share/zoneinfo/zone.tab")); + Strings::Vector list = Strings::Split(zones, '\n'); + for (const std::string& line : list) + if (!line.empty() && line[0] != '#') + { + int start = (int)line.find('\t'); + if (start == (int)std::string::npos) continue; + start = (int)line.find('\t', start + 1); + if (start == (int)std::string::npos) continue; + int stop = (int)line.find('\t', ++start); + std::string tz = line.substr(start, stop - start); + output.push_back(tz); + } + } + + return output; +} + +HashMap RequestHandlerTools::GetAvailableLanguages() +{ + static HashMap sLanguages + ({ + { "eu_ES", "EUSKARA" }, + { "zh_TW", "正體中文" }, + { "zh_CN", "简体中文" }, + { "de_DE", "DEUTSCH" }, + { "en_US", "ENGLISH" }, + { "es_ES", "ESPAÑOL" }, + { "fr_FR", "FRANÇAIS" }, + { "it_IT", "ITALIANO" }, + { "pt_BR", "PORTUGUES" }, + { "sv_SE", "SVENSKA" }, + { "tr_TR", "TÜRKÇE" }, + { "ca_ES", "CATALÀ" }, + { "ar_YE", "اللغة العربية" }, + { "nl_NL", "NEDERLANDS" }, + { "el_GR", "ελληνικά" }, + { "ko_KR", "한국어" }, + { "nn_NO", "NORSK" }, + { "nb_NO", "BOKMAL" }, + { "pl_PL", "POLSKI" }, + { "ja_JP", "日本語" }, + { "ru_RU", "Русский" }, + { "hu_HU", "MAGYAR" }, + { "cs_CZ", "čeština" }, + { "lv_LV", "latviešu" }, + { "lb_LU", "Lëtzebuergesch"}, + }); + + return sLanguages; +} + +HashMap RequestHandlerTools::GetAvailableKeyboardLayout() +{ + static HashMap kbLayouts; + + if (kbLayouts.empty()) + { + static Path layouts("/usr/share/keymaps/i386"); + static std::string ext(".map.gz"); + for (const char* sub : { "azerty", "bepo", "qwerty", "qwertz" }) + for(const Path& filePath : (layouts / sub).GetDirectoryContent()) + { + std::string fileName = filePath.Filename(); + if (Strings::EndsWith(fileName, ext)) + { + fileName = fileName.substr(0, fileName.size() - ext.size()); + kbLayouts.insert_unique(std::string(fileName), std::string(sub) + ":" + fileName); + } + } + } + + return kbLayouts; +} + +void RequestHandlerTools::GetAvailableShadersIn(const Path& rootPath, Strings::Vector& results) +{ + Path::PathList pathList = rootPath.GetDirectoryContent(); + for(const Path& path : pathList) + if (path.IsDirectory()) + GetAvailableShadersIn(path, results); + else if (path.IsFile()) + if (path.Extension() == ".glslp") + results.push_back(path.ToString()); +} + +Strings::Vector RequestHandlerTools::GetAvailableShaders() +{ + Strings::Vector result; + static Path shaderPath("/recalbox/share_init/shaders"); + + GetAvailableShadersIn(shaderPath, result); + int commonPartLength = (int)shaderPath.ToString().length() + 1; + for(std::string& path : result) + path.erase(0, commonPartLength); + + return result; +} + +void RequestHandlerTools::GetEmbeddedBios(const Path& base, HashMap& results) +{ + // Read all embedded path. Bas must be /recalbox/share/ as bios is already in path of every line + Strings::Vector pathlist = Strings::Split(Files::LoadFile(Path("/recalbox/share_init/embedded.list")), '\n'); + results.clear(); + for(const std::string& path : pathlist) + results.insert_unique(std::string((base / path).ToString()), true); +} + +void RequestHandlerTools::GetAvailableBios(const Path& rootPath, Path::PathList& results, const HashMap& embedded) +{ + Path::PathList pathList = rootPath.GetDirectoryContent(); + for(const Path& path : pathList) + if (path.IsDirectory()) + GetAvailableBios(path, results, embedded); + else if (path.IsFile()) + if (!embedded.contains(path.ToString())) + results.push_back(path); +} + +Path RequestHandlerTools::GetCompressedBiosFolder() +{ + Path baseFolder("/recalbox/share/bios"); + + // Get embedded list + HashMap embedded; + GetEmbeddedBios(baseFolder.Directory(), embedded); + + // Get diff list embedded/user bios + Path::PathList biosPath; + GetAvailableBios(baseFolder, biosPath, embedded); + + Path destination("/recalbox/share/system/bios_backup.zip"); + { + Zip zip(destination, true); + for(const Path& path : biosPath) + zip.Add(path, baseFolder); + } + + return destination; +} + +bool RequestHandlerTools::IsValidSystem(const std::string& system) +{ + Strings::Vector systemList = RequestHandlerTools::GetSupportedSystemList(); + for(const std::string& s : systemList) + if (s == system) + return true; + + return false; +} + +bool +RequestHandlerTools::GetUploadedFile(const Rest::Request& request, std::string& filename, int& startOffset, int& size) +{ + #define GUF_DOUBLE_EOF "\r\n\r\n" + #define GUF_BOUNDARY "boundary=" + #define GUF_FILENAME "filename=\"" + + // Get barrier + const auto* contentType = request.headers().tryGet().get(); + if (contentType == nullptr) return false; + std::string boundary = contentType->mime().raw(); + size_t start = boundary.find(GUF_BOUNDARY); + if (start == std::string::npos) return false; + boundary.erase(0, start + (sizeof(GUF_BOUNDARY) - 1)); + boundary.insert(0, "--", 2); + + LOG(LogError) << "BOUNDARY: " << boundary; + + // Lookup filename + start = request.body().find(boundary, 0); + if (start != std::string::npos) + for(;;) + { + // Find start/stop + size_t end = request.body().find(boundary, start + boundary.size()); + if (end == std::string::npos) break; + + // Get EOL + size_t headerStart = request.body().find('\n', start + boundary.size()); + if (headerStart != std::string::npos && headerStart < end) + { + // Get double EOL + size_t headerStop = request.body().find(GUF_DOUBLE_EOF, ++headerStart); + if (headerStop != std::string::npos && headerStop < end) + { + // locate filename + size_t fileNameStart = request.body().find(GUF_FILENAME, headerStart); + if (headerStart != std::string::npos && headerStart < headerStop) + { + fileNameStart += (sizeof(GUF_FILENAME) - 1); + size_t fileNameStop = request.body().find('"', fileNameStart); + if (fileNameStop != std::string::npos && fileNameStop < headerStop) + { + // Extact filename + filename = request.body().substr(fileNameStart, fileNameStop - fileNameStart); + // Store indexes + startOffset = (int)(headerStop + sizeof(GUF_DOUBLE_EOF) - 1); + size = (int)(end - startOffset) - 2 /* size of last EOF */; + return true; + } + } + } + } + // Restart from next boundary + start = end; + } + + #undef GUF_FILENAME + #undef GUF_BOUNDARY + #undef GUF_DOUBLE_EOF + + return false; +} + +bool RequestHandlerTools::ExtractArray(const Rest::Request& request, Strings::Vector& result) +{ + rapidjson::Document json; + json.Parse(request.body().c_str()); + if (!json.HasParseError()) + if (json.GetType() == rapidjson::Type::kArrayType) + { + for (const auto& key : json.GetArray()) + result.push_back(key.GetString()); + return true; + } + + return false; +} + diff --git a/projects/frontend/server-webapi/server/handlers/RequestHandlerTools.h b/projects/frontend/server-webapi/server/handlers/RequestHandlerTools.h new file mode 100644 index 0000000000000000000000000000000000000000..4494d0314609161ce93c492368b685fd17a38153 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/RequestHandlerTools.h @@ -0,0 +1,281 @@ +// +// Created by bkg2k on 03/04/2020. +// +#pragma once + +#include +#include "utils/json/JSONBuilder.h" +#include "Validator.h" +#include +#include +#include +#include +#include + +class RequestHandlerTools +{ + private: + /*! + * @brief Get suported syetem list + * @return Systems' short names list + */ + static const Strings::Vector& GetSupportedSystemList(); + + static const Strings::Vector& GetSupportedRatioList(); + + static const std::string& GetArchitecture(); + + static std::string GetCommandOutput(const std::string& command); + + static HashMap GetAvailableResolutions(); + + static HashMap GetAvailableLanguages(); + + static HashMap GetAvailableKeyboardLayout(); + + static Strings::Vector GetAvailableSoundDevices(); + + static const Strings::Vector& GetAvailableTimeZone(); + + static void GetAvailableShadersIn(const Path& path, Strings::Vector& results); + + static void GetEmbeddedBios(const Path& base, HashMap& results); + + static void GetAvailableBios(const Path& path, Path::PathList& results, const HashMap& embedded); + + static Strings::Vector GetAvailableShaders(); + + static void SetHeaders(Pistache::Http::ResponseWriter& response); + + public: + //! Configuration file + static constexpr const char* sConfiguration = "/recalbox/share/system/recalbox.conf"; + //! Region-based System resource path + static constexpr const char* sSystemResourceRegionPath = "/recalbox/share_init/system/.emulationstation/themes/recalbox-next/%SYSTEM%/data/%REGION%/%FILE%"; + //! Basic System resource path + static constexpr const char* sSystemResourceBasePath = "/recalbox/share_init/system/.emulationstation/themes/recalbox-next/%SYSTEM%/data/%FILE%"; + + //! Device information structure + struct DeviceInfo + { + std::string Mount; //!< Mount point + std::string FileSystem; //!< File system / device + std::string FileSystemType; //!< File system name + std::string Bus; //!< Device bus + std::string Type; //!< Device type + std::string Model; //!< Device Model name + long long Size; //!< Total space in byte + long long Used; //!< Used space + + //! Constructor - Store info from df command + DeviceInfo(const std::string& mount, const std::string& filesystem, const std::string& filesystemtype, + const std::string& size, const std::string& used) + : Mount(mount) + , FileSystem(filesystem) + , FileSystemType(filesystemtype) + , Size(0) + , Used(0) + { + Strings::ToLong(size, Size); + Strings::ToLong(used, Used); + } + }; + + /*! + * @brief Get HTTP method name + * @param method Method + * @return Method name + */ + static const char* MethodToString(Pistache::Http::Method method); + + /*! + * @brief Log + * @param request Request to information from + * @param methodName Method called + */ + static void LogRoute(const Pistache::Rest::Request& request, const char* methodName); + + /*! + * @brief Send back a response + * @param response Response object + * @param code Http code + * @param body Response Body + * @param mime MIME type + */ + static void Send(Pistache::Http::ResponseWriter& response, Pistache::Http::Code code, const std::string& body, const Pistache::Http::Mime::MediaType& mime); + + /*! + * @brief Send back a response + * @param response Response object + * @param code Http code + */ + static void Send(Pistache::Http::ResponseWriter& response, Pistache::Http::Code code); + + /*! + * @brief Serialize a partition object got from "df" line + * @param parts Split lines. Must contains 7 items + * @return JSON object + */ + static JSONBuilder BuildPartitionObject(const DeviceInfo& deviceInfo, const std::string& recalboxtype); + + /*! + * @brief Get device propertie: *Device name, type and bus + * @param mount Device + * @param name Output name + * @param type Output type + * @param bus Output bus + */ + static void GetDevicePropertiesOf(DeviceInfo& deviceInfo); + + /*! + * @brief Build system resource path, both region-based and basic + * @param regionPath Output region-based path + * @param basePath Output basci path + * @param resourceFileName Target filename + */ + static void GetSystemResourcePath(Path& regionPath, Path& basePath, const std::string& system, const std::string& region, const char* resourceFileName); + + /*! + * @brief Execute external command + * @param command Command to execute + * @return Output lines + */ + static Strings::Vector OutputLinesOf(const std::string& command); + + /*! + * @brief Execute external command + * @param command Command to execute + * @return Output lines + */ + static std::string OutputOf(const std::string& command); + + /*! + * @brief Send a 404 error + */ + static void Error404(Pistache::Http::ResponseWriter& response); + + /*! + * @brief Serialize the given Bios object + * @param bios Bios object to serialize + * @return Serialized Bios object + */ + static JSONBuilder SerializeBiosToJSON(const Bios& bios); + + /*! + * @brief Serialize the given BiosList object + * @param biosList BiosList object to serialize + * @return Serialized BiosList object + */ + static JSONBuilder SerializeBiosListToJSON(const BiosList& biosList); + + /*! + * @brief Check and send target resource if it exists or return 404 + * @param resourcepath Resource path + * @param response Response object + */ + static void SendResource(const Path& resourcepath, Pistache::Http::ResponseWriter& response, const Pistache::Http::Mime::MediaType& mimeType); + + /*! + * @brief Check and send target resource if it exists or return 404 + * check and send preferedpath first, then fallback + * @param preferedpath Prefered resource path + * @param fallbackpath Fallback resource path if Prefered path does not exists + * @param response Response object + * @param mimetype Mime Type + */ + static void SendResource(const Path& preferedpath, const Path& fallbackpath, Pistache::Http::ResponseWriter& response, const Pistache::Http::Mime::MediaType& mimetype); + + /*! + * @brief Serialize an emulatortree to json + * @param emulatorlist Emulator tree + * @return JSON + */ + static JSONBuilder SerializeEmulatorsAndCoreToJson(const EmulatorList& emulatorlist); + + /*! + * @brief Get configuration key list for given namespace + * @return Key set & configuraion. Empty set if the namespace is unknown + */ + static const HashMap& SelectConfigurationKeySet(const std::string& _namespace); + + /*! + * @brief Load recalbox configuration + * @return Configuration object + */ + static IniFile LoadConfiguration(); + + /*! + * @brief Save recalbox configuration + * @param configuration Configuration object + */ + static void SaveConfiguration(IniFile& configuration); + + /*! + * @brief Get all configuration values from the given keys of the given domain and build + * a JSON object. Then send them all back to the caller using the response object + * @param domain Namespace/Domain + * @param keys Key/Validator collection + * @param response Response object + */ + static void GetKeyValues(const std::string& domain, const HashMap& keys, Pistache::Http::ResponseWriter& response); + + /*! + * @brief Get all configuration options from the given keys and build + * a JSON object. Then send them all back to the caller using the response object + * @param keys Key/Validator collection + * @param response Response object + */ + static void GetKeyValueOptions(const HashMap& keys, Pistache::Http::ResponseWriter& response); + + /*! + * @brief Get the key/values from the given JSON, try to validate them all using the keys collection, then write all + * values to the recalbox configuration file. + * Any unknown value or non-validated value send back a BAD_REQUEST HTTP code. + * @param domain Namespace/Domain + * @param keys Key/Validator collection + * @param json Key/value collection + * @param response Response object + */ + static void SetKeyValues(const std::string& domain, const HashMap& keys, const std::string& json, Pistache::Http::ResponseWriter& response); + + /*! + * @brief Get the keys from the given JSON, and try to delete them from the given domain, then write all + * values to the recalbox configuration file. + * Any unknown value or non-validated value send back a BAD_REQUEST HTTP code. + * @param domain Namespace/Domain + * @param keys Key/Validator collection + * @param json Key/value collection + * @param response Response object + */ + static void DeleteKeyValues(const std::string& domain, const HashMap& keys, const std::string& json, Pistache::Http::ResponseWriter& response); + + /*! + * @brief Compress files in /recalbox/share/bios in zip format en return the zip content + */ + static Path GetCompressedBiosFolder(); + + /*! + * @brief Check if the given system is in the supported system list + * @param system System short name + * @return True if the given system is in the supported system list, false otherwise + */ + static bool IsValidSystem(const std::string& system); + + /*! + * @brief Basic but efficient file extractor + * @param request Request + * @param filename Extracted filename + * @param startOffset Start offset of the extracted file in request's body + * @param size Size of the extracted file + * @return True if a valid file has been extracted, false otherwize + */ + static bool GetUploadedFile(const Pistache::Rest::Request& request, std::string& filename, int& startOffset, int& size); + + /*! + * @brief Extract values from the JSON array from the given request body + * @param request Requets to extract JSON array from + * @param result List to fill with array values + * @return True if the request body is a valid JSON array, false otherwise + */ + static bool ExtractArray(const Pistache::Rest::Request& request, Strings::Vector& result); +}; diff --git a/projects/frontend/server-webapi/server/handlers/Validator.cpp b/projects/frontend/server-webapi/server/handlers/Validator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..138f8530f24343a192b5fc5f10ac6182b77265d0 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/Validator.cpp @@ -0,0 +1,113 @@ +// +// Created by bkg2k on 30/04/2020. +// + +#include "Validator.h" +#include + +bool Validator::Validate(std::string& value) const +{ + switch (mType) + { + case Types::StringFree: return true; + case Types::StringConstrained: + { + for(int i = value.size(); --i >= 0; ) + if (mList.find(value[i]) == std::string::npos) + return false; + return true; + } + case Types::StringPicker: + { + size_t pos = mList.find(value); + if (pos != std::string::npos) + if ((pos == 0) || (mList[pos - 1] == '|')) + if ((pos + value.size() >= mList.size()) || (mList[pos + value.size()] == '|')) + return true; + return false; + } + case Types::StringMultiPicker: + { + for(const std::string& v : Strings::Split(value, ',')) + { + size_t pos = mList.find(v); + if (pos == std::string::npos) return false; + if ((pos != 0) && (mList[pos - 1] != '|')) return false; + if ((pos + value.size() < mList.size()) && (mList[pos + value.size()] != '|')) return false; + } + return true; + } + case Types::IntRange: + { + int intValue = 0; + if (Strings::ToInt(value, intValue)) + return (intValue >= mLower && intValue <= mHigher); + return false; + } + case Types::Bool: + { + if (value.length() == 1) + return (value[0] == '0' || value[0] == '1'); + value = Strings::ToLowerASCII(value); + if (value == "true") value = "1"; + else if (value == "false") value = "0"; + return (value[0] == '0' || value[0] == '1'); + } + default: break; + } + + LOG(LogError) << "Unknown type"; + return false; +} + +const char* Validator::TypeAsString() const +{ + switch(mType) + { + case Types::StringFree: return "string"; + case Types::StringConstrained: return "constrainedString"; + case Types::StringPicker: return "stringList"; + case Types::StringMultiPicker: return "stringListMulti"; + case Types::IntRange: return "intRange"; + case Types::Bool: return "boolean"; + default: break; + } + return "Unknown"; +} + +Strings::Vector Validator::StringList() const +{ + if (mType == Types::StringPicker || + mType == Types::StringMultiPicker) + return Strings::Split(Strings::Trim(mList, "|"), '|'); + return Strings::Vector(); +} + +Strings::Vector Validator::DisplayList() const +{ + if (mType == Types::StringPicker || + mType == Types::StringMultiPicker) + return Strings::Split(Strings::Trim(mDisplay, "|"), '|'); + return Strings::Vector(); +} + +std::vector Validator::IntList() const +{ + std::vector result; + if (mType == Types::StringConstrained) + { + result.resize((int)(mList.size() / sizeof(int))); + int* list = (int*) mList.data(); + for (int i = (int)(mList.size() / sizeof(int)); --i >= 0;) + result[i] = list[i]; + + } + return result; +} + +std::string Validator::StringConstraint() const +{ + if (mType == Types::StringConstrained) return mList; + return std::string(); +} + diff --git a/projects/frontend/server-webapi/server/handlers/Validator.h b/projects/frontend/server-webapi/server/handlers/Validator.h new file mode 100644 index 0000000000000000000000000000000000000000..1027e4aa4b5d4bdcd31cb32cba547566bf5628c9 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/Validator.h @@ -0,0 +1,158 @@ +// +// Created by bkg2k on 30/04/2020. +// +#pragma once + +#include +#include +#include + +class Validator +{ + public: + //! Configuration Datatype + enum class Types + { + StringFree, //!< Free String + StringConstrained, //!< Chars-restricted string + StringPicker, //!< String value among a pre-defined list + StringMultiPicker, //!< String value among a pre-defined list + IntRange, //!< Numeric value among a pre-defined range + Bool, //!< Boolean + }; + + private: + //! Data type + Types mType; + + //! String/Int list + std::string mList; + //! String/Int display info + std::string mDisplay; + //! Int range lower value + int mLower; + //! Int range higher value + int mHigher; + + public: + /*! + * @brief Default constructor: Free string + */ + Validator() + : mType(Types::StringFree), + mLower(0), + mHigher(0) + { + } + + /*! + * @brief Char restricted string + */ + explicit Validator(const char* charList) + : mType(Types::StringConstrained), + mList(charList), + mLower(0), + mHigher(0) + { + } + + /*! + * @brief String list constructor + * @param list string list + */ + explicit Validator(bool multi, const std::vector& list) + : mType(multi ? Types::StringMultiPicker : Types::StringPicker), + mList('|' + Strings::Join(list, "|")), + mLower(0), + mHigher(0) + { + } + + /*! + * @brief String list constructor + * @param list string list + */ + Validator(const std::vector& list, bool multi) + : mType(multi ? Types::StringMultiPicker : Types::StringPicker), + mList('|' + Strings::Join(list, "|")), + mLower(0), + mHigher(0) + { + } + + /*! + * @brief String list constructor w/ display info + * @param list string list + */ + explicit Validator(const HashMap& map, bool multi) + : mType(multi ? Types::StringMultiPicker : Types::StringPicker), + mLower(0), + mHigher(0) + { + mList = '|'; + mDisplay = '|'; + for(const auto& item : map) + { + mList.append(item.first).append(1, '|'); + mDisplay.append(item.second).append(1, '|'); + } + } + + /*! + * @brief Int range constructor + * @param from lower range value + * @param to higher range value + */ + explicit Validator(int from, int to) + : mType(Types::IntRange), + mLower(from), + mHigher(to) + { + } + + /*! + * @brief Bool constructor + * @param list + */ + explicit Validator(bool) + : mType(Types::Bool), + mLower(0), + mHigher(0) + { + } + + /*! + * @brief Validate the given string + * @param value String to validate + * @param outvalue Normalized output value + * @return True if the string is valid, false otherwise + */ + bool Validate(std::string& value) const; + + /* + * Getters + */ + + //! Get type + Types Type() const { return mType; } + + //! Get type as string + const char* TypeAsString() const; + + //! Get validation data + std::string StringConstraint() const; + //! Get validation data + Strings::Vector StringList() const; + //! Get validation data + std::vector IntList() const; + + //! Has display info? + bool HasDisplay() const { return !mDisplay.empty(); } + //! Get display info + Strings::Vector DisplayList() const; + + //! Get int lower range value + int Lower() const { return mLower; } + //! Get int lower range value + int Higher() const { return mHigher; } +}; diff --git a/projects/frontend/server-webapi/server/handlers/providers/EmulationStationWatcher.cpp b/projects/frontend/server-webapi/server/handlers/providers/EmulationStationWatcher.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9974fca2d14205d8824440ca8ba2b2d76cbd8486 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/providers/EmulationStationWatcher.cpp @@ -0,0 +1,287 @@ +// +// Created by bkg2k on 10/04/2020. +// + +#include +#include +#include "EmulationStationWatcher.h" + +EmulationStationWatcher::EmulationStationWatcher() + : mMqttClient("recalbox-api-server-watcher"), + mEventMethod + ({ + { "start", &EmulationStationWatcher::EventStart }, + { "stop" , &EmulationStationWatcher::EventStop }, + { "shutdown", &EmulationStationWatcher::EventShutdown }, + { "reboot", &EmulationStationWatcher::EventReboot }, + { "quit", &EmulationStationWatcher::EventQuit }, + { "relaunch", &EmulationStationWatcher::EventRelaunch }, + { "systembrowsing", &EmulationStationWatcher::EventSystemBrowsing }, + { "gamelistbrowsing", &EmulationStationWatcher::EventGamelistBrowsing }, + { "rungame", &EmulationStationWatcher::EventRunGame }, + { "rundemo", &EmulationStationWatcher::EventRunDemo }, + { "endgame", &EmulationStationWatcher::EventEndGame }, + { "enddemo", &EmulationStationWatcher::EventEndDemo }, + { "sleep", &EmulationStationWatcher::EventSleep }, + { "wakeup", &EmulationStationWatcher::EventWakup }, + { "scrapstart", &EmulationStationWatcher::EventScrapStart }, + { "scrapstop", &EmulationStationWatcher::EventScrapStop }, + { "scrapgame", &EmulationStationWatcher::EventScrapGame }, + { "configurationchanged", &EmulationStationWatcher::EventConfigurationChanged }, + }) +{ + + mMqttClient.Subscribe(MqttTopics::sEventTopic); +} + +void EmulationStationWatcher::MqttReceive(const std::string& topic, const std::string& payload) +{ + if (topic == MqttTopics::sEventTopic) + { + LOG(LogDebug) << "EmulationStation Event " << payload; + + SimpleMethodReference* reference = mEventMethod.try_get(payload); + if (reference != nullptr) + { + static Path sPath(sEmulationStationEventFile); + IniFile data(sPath, false); + JSONBuilder result = (*reference)(data); + mMqttClient.Send(MqttTopics::sEventObjectTopic, result); + } + else LOG(LogError) << "No processing available for " << payload; + } +} + +JSONBuilder EmulationStationWatcher::BuildGameObject(const IniFile& data) +{ + JSONBuilder result; + result.Open() + .Field("romPath", data.AsString("GamePath")) + .Field("name", data.AsString("Game")) + .Field("isFolder", data.AsBool("IsFolder")) + .OpenObject("metadata") + .CloseObject() + .Field("developer", data.AsString("Developer")) + .Field("publisher", data.AsString("Publisher")) + .Field("players", data.AsString("Players")) + .Field("region", data.AsString("Region")) + .Field("genreName", data.AsString("Genre")) + .Field("genreId", data.AsString("GenreId")) + .Field("Favorite", data.AsBool("Favorite")) + .Field("Hidden", data.AsBool("Hidden")) + .Field("Adult", data.AsBool("Adult")) + .OpenObject("media") + .Field("image", data.AsString("ImagePath")) + .Field("thumbnail", data.AsString("ThumbnailPath")) + .Field("video", data.AsString("VideoPath")) + .CloseObject() + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::BuildSystemObject(const IniFile& data) +{ + JSONBuilder result; + result.Open() + .Field("name", data.AsString("SystemId")) + .Field("fullname", data.AsString("System")) + .Field("defaultEmulator", data.AsString("DefaultEmulator")) + .Field("defaultCore", data.AsString("DefaultCore")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventStart(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "start") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventStop(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "stop") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventShutdown(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "shutdown") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventReboot(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "reboot") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventQuit(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "quit") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventRelaunch(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "relaunch") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventSystemBrowsing(const IniFile& data) +{ + JSONBuilder result(4096); + result.Open() + .Field("event", "systembrowsing") + .Field("param", data.AsString("ActionData")) + .Field("system", BuildSystemObject(data)) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventGamelistBrowsing(const IniFile& data) +{ + JSONBuilder result(4096); + result.Open() + .Field("event", "gamebrowsing") + .Field("param", data.AsString("ActionData")) + .Field("system", BuildSystemObject(data)) + .Field("game", BuildGameObject(data)) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventRunGame(const IniFile& data) +{ + JSONBuilder result(4096); + result.Open() + .Field("event", "rungame") + .Field("param", data.AsString("ActionData")) + .Field("system", BuildSystemObject(data)) + .Field("game", BuildGameObject(data)) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventRunDemo(const IniFile& data) +{ + JSONBuilder result(4096); + result.Open() + .Field("event", "rundemo") + .Field("param", data.AsString("ActionData")) + .Field("system", BuildSystemObject(data)) + .Field("game", BuildGameObject(data)) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventEndGame(const IniFile& data) +{ + JSONBuilder result(4096); + result.Open() + .Field("event", "endgame") + .Field("param", data.AsString("ActionData")) + .Field("system", BuildSystemObject(data)) + .Field("game", BuildGameObject(data)) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventEndDemo(const IniFile& data) +{ + JSONBuilder result(4096); + result.Open() + .Field("event", "enddemo") + .Field("param", data.AsString("ActionData")) + .Field("system", BuildSystemObject(data)) + .Field("game", BuildGameObject(data)) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventSleep(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "sleep") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventWakup(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "wakeup") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventScrapStart(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "scrapstart") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventScrapStop(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "scrapstop") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventScrapGame(const IniFile& data) +{ + JSONBuilder result(4096); + result.Open() + .Field("event", "scrapgame") + .Field("param", data.AsString("ActionData")) + .Field("system", BuildSystemObject(data)) + .Field("game", BuildGameObject(data)) + .Close(); + return result; +} + +JSONBuilder EmulationStationWatcher::EventConfigurationChanged(const IniFile& data) +{ + JSONBuilder result(1024); + result.Open() + .Field("event", "configurationchanged") + .Field("param", data.AsString("ActionData")) + .Close(); + return result; +} + + diff --git a/projects/frontend/server-webapi/server/handlers/providers/EmulationStationWatcher.h b/projects/frontend/server-webapi/server/handlers/providers/EmulationStationWatcher.h new file mode 100644 index 0000000000000000000000000000000000000000..8ea9e707aa8f40a2e3154d842602d68816d80a55 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/providers/EmulationStationWatcher.h @@ -0,0 +1,87 @@ +// +// Created by bkg2k on 10/04/2020. +// +#pragma once + +#include +#include +#include +#include +#include + +class EmulationStationWatcher : private IMqttReceiver +{ + private: + //! EmulationStation info file + static constexpr const char* sEmulationStationEventFile = "/tmp/es_state.inf"; + + //! Simple method datatype + typedef JSONBuilder (*SimpleMethodReference)(const IniFile& data); + + //! MQTT client reference + MqttClient mMqttClient; + + //! Fast event to method lookup + HashMap mEventMethod; + + /*! + * @brief Mqtt message receiver + * @param topic Topic + * @param payload Message content + */ + void MqttReceive(const std::string& topic, const std::string& payload) override; + + /* + * Tools + */ + + static JSONBuilder BuildSystemObject(const IniFile& data); + + static JSONBuilder BuildGameObject(const IniFile& data); + + /* + * Individual event processing + */ + + static JSONBuilder EventStart(const IniFile& data); + + static JSONBuilder EventStop(const IniFile& data); + + static JSONBuilder EventShutdown(const IniFile& data); + + static JSONBuilder EventReboot(const IniFile& data); + + static JSONBuilder EventQuit(const IniFile& data); + + static JSONBuilder EventRelaunch(const IniFile& data); + + static JSONBuilder EventSystemBrowsing(const IniFile& data); + + static JSONBuilder EventGamelistBrowsing(const IniFile& data); + + static JSONBuilder EventRunGame(const IniFile& data); + + static JSONBuilder EventRunDemo(const IniFile& data); + + static JSONBuilder EventEndGame(const IniFile& data); + + static JSONBuilder EventEndDemo(const IniFile& data); + + static JSONBuilder EventSleep(const IniFile& data); + + static JSONBuilder EventWakup(const IniFile& data); + + static JSONBuilder EventScrapStart(const IniFile& data); + + static JSONBuilder EventScrapStop(const IniFile& data); + + static JSONBuilder EventScrapGame(const IniFile& data); + + static JSONBuilder EventConfigurationChanged(const IniFile& data); + + public: + /*! + * @brief Default constructor + */ + EmulationStationWatcher(); +}; diff --git a/projects/frontend/server-webapi/server/handlers/providers/FakeInternationalizer.cpp b/projects/frontend/server-webapi/server/handlers/providers/FakeInternationalizer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2120519629fcdaad0bba57b429abd78b398e65fc --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/providers/FakeInternationalizer.cpp @@ -0,0 +1,10 @@ +// +// Created by bkg2k on 01/05/2020. +// + +#include "FakeInternationalizer.h" + +std::string Internationalizer::GetText(const char* p, int l) +{ + return std::string(p, l); +} \ No newline at end of file diff --git a/projects/frontend/server-webapi/server/handlers/providers/FakeInternationalizer.h b/projects/frontend/server-webapi/server/handlers/providers/FakeInternationalizer.h new file mode 100644 index 0000000000000000000000000000000000000000..af5e7d9bc22feadd63952e4027d866be574dab48 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/providers/FakeInternationalizer.h @@ -0,0 +1,15 @@ +// +// Created by bkg2k on 01/05/2020. +// + +#include + +/*! + * @brief We dont mind embedding a real Internationalize. + * Just fake the main method + */ +class Internationalizer +{ + public: + static std::string GetText(const char* p, int l); +}; diff --git a/projects/frontend/server-webapi/server/handlers/providers/SysInfos.cpp b/projects/frontend/server-webapi/server/handlers/providers/SysInfos.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3ce81d90d07aa85766126e803eca0ea6f6e5e508 --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/providers/SysInfos.cpp @@ -0,0 +1,302 @@ +// +// Created by bkg2k on 06/04/2020. +// + +#include +#include +#include +#include +#include +#include +#include "SysInfos.h" + +SysInfos::SysInfos() + : mMqttClient("recalbox-api-server-broadcaster"), + mPlatform(Platform::Unknown), + mData {} +{ + mData.Reset(); + ReadCPUNames(); + LOG(LogInfo) << "CPU Count: " << mData.CpuCount; + Start("SysInfo"); +} + +void SysInfos::Run() +{ + while(IsRunning()) + { + DateTime start; + RefreshAll(); + SendMqttObjects(start); + DateTime stop; + + if (IsRunning()) + { + TimeSpan elapsed(stop - start); + int msLeft = 1000 - (int)elapsed.TotalMilliseconds(); + if (msLeft > 0) + usleep(msLeft * 1000); + } + } +} + +void SysInfos::ReadCPUNames() +{ + Strings::Vector lines = Strings::Split(Files::LoadFile(Path("/proc/cpuinfo")), '\n'); + mData.CpuCount = 0; + int processorNumber = 0; + std::string hardware; + for(const std::string& line : lines) + { + int p = line.find(':'); + if (p != (int)std::string::npos) + { + std::string key = Strings::Trim(line.substr(0, p)); + std::string value = Strings::Trim(line.substr(p + 1)); + if (key == "processor") + { + if (Strings::ToInt(value, processorNumber)) + mData.CpuCount = (processorNumber + 1 > mData.CpuCount) ? processorNumber + 1 : mData.CpuCount; + else + LOG(LogError) << "Unreadable line: " << line; + } + if (key == "model name") + if ((unsigned int)processorNumber < sMaxCPU) + mCpuNames[processorNumber] = value; + if (key == "Hardware") + hardware = value; + } + } + // Get platform + mPlatform = GetPlatformFrom(hardware); +} + +void SysInfos::GetCpu() +{ + // Get raw values + Strings::Vector lines = Strings::Split(Files::LoadFile(Path("/proc/stat")), '\n'); + long long cpuData[sMaxCPU][CpuDataType::__Count]; + int cpuIndex = 0; + for(const std::string& line : lines) + if (line.length() > 3) + if (line[0] == 'c') + if (line[1] == 'p') + if (line[2] == 'u') + if ((unsigned int)line[3] - 0x30u <= 9) + { + if (Strings::ToInt(line, 3, ' ', cpuIndex)) + { + int start = 3; + for(int i = 0; i < CpuDataType::__Count; ++i) + { + start = line.find(' ', start + 1); + if (!Strings::ToLong(line, start + 1, ' ', cpuData[cpuIndex][i])) + Strings::ToLong(line, start + 1, 0, cpuData[cpuIndex][i]); + } + } + } + + // Get percent + for (int i = mData.CpuCount; --i >= 0;) + { + // Get total time for the current cpu + long long total = 0; + long long diffs[CpuDataType::__Count]; + for (int j = CpuDataType::__Count; --j >= 0;) + total += (diffs[j] = cpuData[i][j] - mData.CpuData[i][j]); + + // Get IDLE time + long long idle = diffs[CpuDataType::Idle] + diffs[CpuDataType::IOWait]; + + // Get percent + double percent = (double) (total - idle) / (double) total; + mData.Cpu[i][mData.StorageIndex] = (float) (percent * 100.0); + } + + // Store values + memcpy(mData.CpuData, cpuData, sizeof(mData.CpuData)); +} + +void SysInfos::GetTemperature() +{ + switch(mPlatform) + { + case Platform::Unknown: break; + case Platform::PC: + case Platform::RaspberryPi: + { + std::string temp = Files::LoadFile(Path("/sys/class/thermal/thermal_zone0/temp")); + int temperature = 0; + if (Strings::ToInt(Strings::Trim(temp, " \t\r\n"), temperature)) + mData.Temperature[mData.StorageIndex] = (float)temperature / 1000.0f; + break; + } + case Platform::Odroid: + { + std::string temp = Files::LoadFile(Path("/sys/devices/virtual/thermal/thermal_zone0/temp")); + int temperature = 0; + if (Strings::ToInt(temp, temperature)) + mData.Temperature[mData.StorageIndex] = (float)temperature / 1000.0f; + break; + } + } +} + +void SysInfos::GetMemory() +{ + #define __STR(x) x, (int)sizeof(x) - 1 + // Get raw values + Strings::Vector lines = Strings::Split(Files::LoadFile(Path("/proc/meminfo")), '\n'); + int dataToGet = 3; // Total, Free & Available + long long total = 0, free = 0, available = 0; + for(const std::string& line : lines) + { + // Collect target data + long long* data = nullptr; + if (Strings::StartsWith(line, __STR("MemTotal:"))) data = &total; + else if (Strings::StartsWith(line, __STR("MemFree:"))) data = &free; + else if (Strings::StartsWith(line, __STR("MemAvailable:"))) data = &available; + // Get data + if (data != nullptr) + { + int pos = (int)line.find_first_not_of(' ', line.find(':') + 1); + Strings::ToLong(line, pos, ' ', *data); + // Completed? + if (--dataToGet == 0) + { + mData.TotalMemory = total << 10; + mData.FreeMemory[mData.StorageIndex] = free << 10; + mData.AvailableMemory[mData.StorageIndex] = available << 10; + break; // All data collected + } + } + } + #undef __STR +} + +void SysInfos::RefreshAll() +{ + // Get all data + GetCpu(); + GetMemory(); + GetTemperature(); + + // Next storage + mData.StorageIndex = (mData.StorageIndex + 1) & sStorageMask; +} + +void SysInfos::CpuConsumption(int cpu, FloatStorage& percents) const +{ + GetData(percents, mData.Cpu[cpu]); +} + +SysInfos::Platform SysInfos::GetPlatformFrom(const std::string& platformName) +{ + if (Strings::StartsWith(platformName, "BCM2835")) return Platform::RaspberryPi; + if (Strings::StartsWith(platformName, "ODROID")) return Platform::Odroid; + return Platform::PC; +} + +const char* SysInfos::GetPlatformFrom(SysInfos::Platform platform) +{ + switch(platform) + { + case Platform::Unknown: break; + case Platform::RaspberryPi: return "Raspberry Pi"; + case Platform::Odroid: return "Odroid"; + case Platform::PC: return "Compatible PC"; + } + return "Unknown"; +} + +void SysInfos::Temperature(FloatStorage& temperature) const +{ + GetData(temperature, mData.Temperature); +} + +void SysInfos::Memory(LongStorage& free, LongStorage& available) const +{ + GetData(free, mData.FreeMemory); + GetData(available, mData.AvailableMemory); +} + +JSONBuilder SysInfos::BuildCpuObject(bool all) +{ + SysInfos::FloatStorage cpusData; + int length = all ? SysInfos::MeasureCount() : 1; + + JSONBuilder cpus; + cpus.Open(); + for(int i = mData.CpuCount; --i >= 0;) + { + CpuConsumption(i, cpusData); + cpus.OpenObject(Strings::ToString(i).c_str()) + .Field(all ? "model" : nullptr, mCpuNames[i]) + .Field("consumption", cpusData + SysInfos::MeasureCount() - length, length) + .CloseObject(); + } + cpus.Close(); + + return cpus; +} + +JSONBuilder SysInfos::BuildMemoryObject(bool all) +{ + // Build memory object + SysInfos::LongStorage freeData; + SysInfos::LongStorage availData; + Memory(freeData, availData); + int length = all ? SysInfos::MeasureCount() : 1; + + JSONBuilder memory; + memory.Open() + .Field("total", mData.TotalMemory) + .Field("free", freeData + SysInfos::MeasureCount() - length, length) + .Field("available", availData + SysInfos::MeasureCount() - length, length) + .Close(); + + return memory; +} + +JSONBuilder SysInfos::BuildPlatformObject() +{ + JSONBuilder platform; + platform.Open() + .Field("platform", GetPlatformFrom(mPlatform)) + .Close(); + + return platform; +} + +JSONBuilder SysInfos::BuildTemperatureObject(bool all) +{ + SysInfos::FloatStorage temperatures; + Temperature(temperatures); + int length = all ? SysInfos::MeasureCount() : 1; + + JSONBuilder temp; + temp.Open() + .Field("unit", "°C") + .Field("temperatures", temperatures + SysInfos::MeasureCount() - length, length) + .Close(); + + return temp; +} + +void SysInfos::SendMqttObjects(const DateTime& timestamp) +{ + JSONBuilder sysInfo; + sysInfo.Open() + .Field("timestamp", timestamp.ToISO8601()) + .Field("cpus", BuildCpuObject(false)) + .Field("memory", BuildMemoryObject(false)) + .Field("temperature", BuildTemperatureObject(false)) + .Close(); + + mMqttClient.Send(MqttTopics::sSystemInfoTopic, sysInfo); +} + +void SysInfos::DataBag::Reset() +{ + memset(this, 0, sizeof(SysInfos::DataBag)); +} diff --git a/projects/frontend/server-webapi/server/handlers/providers/SysInfos.h b/projects/frontend/server-webapi/server/handlers/providers/SysInfos.h new file mode 100644 index 0000000000000000000000000000000000000000..638747b67179dbee8d25ef0495943bef1497ba0f --- /dev/null +++ b/projects/frontend/server-webapi/server/handlers/providers/SysInfos.h @@ -0,0 +1,216 @@ +// +// Created by bkg2k on 06/04/2020. +// +#pragma once + +#include +#include +#include + +class SysInfos : public Thread +{ + private: + enum CpuDataType + { + User, //!< Time spent with normal processing in user mode. + Nice, //!< Time spent with niced processes in user mode. + System, //!< Time spent running in kernel mode. + Idle, //!< Time spent in vacations twiddling thumbs. + IOWait, //!< Time spent waiting for I/O to completed. This is considered idle time too. + Irq, //!< Time spent serving hardware interrupts. See the description of the intr line for more details. + SoftIrq, //!< Time spent serving software interrupts. + Steal, //!< Time stolen by other operating systems running in a virtual environment. + Guest, //!< Time spent for running a virtual CPU or guest OS under the control of the kernel. + __Count + }; + + //! Storage size in bit + static constexpr int sStorageBits = 6; + //! Storage size + static constexpr int sStorageSize = 1 << sStorageBits; + //! Storage mask + static constexpr int sStorageMask = sStorageSize - 1; + + //! Max cpu stored + static constexpr int sMaxCPU = 16; + + public: + // Sub type + typedef float FloatStorage[sStorageSize]; + + // Sub type + typedef long long LongStorage[sStorageSize]; + + enum class Platform + { + Unknown, + RaspberryPi, + Odroid, + PC, + }; + + private: + //! MQTT client reference + MqttClient mMqttClient; + + //! Platform type + Platform mPlatform; + + //! Cpu names + std::string mCpuNames[sMaxCPU]; + + // data + struct DataBag + { + //! Cpu count + int CpuCount; + //! Cpu storage + FloatStorage Cpu[sMaxCPU]; + //! Cpu data + long long CpuData[sMaxCPU][CpuDataType::__Count]; + + //! Free Ram storage + long long FreeMemory[sStorageSize]; + //! Available ram storage + long long AvailableMemory[sStorageSize]; + //! Ram total + long long TotalMemory; + + //! T° storage + float Temperature[sStorageSize]; + + //! Storage current index + unsigned int StorageIndex; + + //! Reset current databag + void Reset(); + } mData; + + /*! + * @brief Read and store CPU names + */ + void ReadCPUNames(); + + /*! + * @brief Get CPU data + */ + void GetCpu(); + + /*! + * @brief Get temperature data + */ + void GetTemperature(); + + /*! + * @brief Get memory data + */ + void GetMemory(); + + /*! + * @brief Refresh all data + */ + void RefreshAll(); + + /*! + * @brief Get platform type from name + * @param platformName Platform name + * @return Platform type + */ + static Platform GetPlatformFrom(const std::string& platformName); + + /*! + * @brief Get platform full name from type + * @param platform Platform type + * @return Platform type + */ + static const char* GetPlatformFrom(Platform platform); + + /*! + * @brief Get data array shifted left bt current storage index + * so that oldst value appear first and newer value appeay last + * @tparam T Underlying type + * @param destination Destination array + * @param source Source array + */ + template void GetData(T* destination, const T* source) const + { + memcpy(destination, &source[mData.StorageIndex], (sStorageSize - mData.StorageIndex) * sizeof(T)); + if (mData.StorageIndex != 0) + memcpy(&destination[sStorageSize - mData.StorageIndex], source, mData.StorageIndex * sizeof(T)); + } + + protected: + /*! + * Calls the inheriting object's Run() method. + * @note set fIsRunning false to exit + */ + void Run() override; + + public: + /*! + * @brief Default constructor + */ + SysInfos(); + + /* + * Getters + */ + + //! Get measure count + static int MeasureCount() { return sStorageSize; } + + //! Get Cpu count + int CpuCount() const { return mData.CpuCount; } + + //! Get all measures of the given cpu + void CpuConsumption(int cpu, FloatStorage& percents) const; + + //! Get cpu names + const std::string& CpuName(int cpu) const { return mCpuNames[cpu]; } + + //! Get platform name + const char* SystemPlatform() const { return GetPlatformFrom(mPlatform); } + + //! Get total memory + long long TotalMemory() const { return mData.TotalMemory; } + + //! Get all memory measures + void Memory(LongStorage& free, LongStorage& available) const; + + //! Get all memory measures + void Temperature(FloatStorage& temperature) const; + + /* + * Serializers + */ + + /*! + * @brief Build Platform object + * @return Platform object + */ + JSONBuilder BuildPlatformObject(); + + /*! + * @brief Build CPU object + * @return CPU object + */ + JSONBuilder BuildCpuObject(bool all); + + /*! + * @brief Build RAM object + * @return RAM object + */ + JSONBuilder BuildMemoryObject(bool all); + + /*! + * @brief Build Temperature object + * @return + */ + JSONBuilder BuildTemperatureObject(bool all); + + /*! + * @brief Send all cpu/ram/t° update object + * @param timestamp Timestamp of objects + */ + void SendMqttObjects(const DateTime& timestamp); +}; diff --git a/scripts/linux/recaldocker.sh b/scripts/linux/recaldocker.sh index d17aa5174ef6436bbba06af944dca2db58bb9cea..815511958365b0b1b7423d08be0f2a0b29c16c2c 100755 --- a/scripts/linux/recaldocker.sh +++ b/scripts/linux/recaldocker.sh @@ -28,5 +28,6 @@ docker run -ti --rm \ -e "GITLAB_TOKEN_THEMES=${GITLAB_TOKEN_THEMES}" \ -e "GITLAB_TOKEN_BEEBEM=${GITLAB_TOKEN_BEEBEM}" \ -e "GITLAB_TOKEN_RB5000=${GITLAB_TOKEN_RB5000}" \ + -e "GITLAB_TOKEN_WEBMANAGER3=${GITLAB_TOKEN_WEBMANAGER3}" \ --user="`id -u`:`id -g`" \ "recalbox-dev" ${@}