├── presets.txt ├── .DS_Store ├── handle_NDI_Advanced_SDK.sh ├── download_NDI_SDK.sh ├── preinstall.sh ├── jack2ndi.service ├── ndi2jack.service ├── easy-install-x86_64.sh ├── easy-install-rpi3-armhf.sh ├── easy-install-rpi4-aarch64.sh ├── easy-install-rpi4-armhf.sh ├── easy-install-generic-aarch64.sh ├── easy-install-generic-armhf.sh ├── install.sh ├── LICENSE ├── .vscode └── settings.json ├── README.md ├── assets ├── main.css └── index.html ├── include ├── mjson.h └── mongoose.h ├── jack2ndi.cpp ├── ndi2jack.cpp └── mjson.c /presets.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lplassman/NDI-to-JACK/HEAD/.DS_Store -------------------------------------------------------------------------------- /handle_NDI_Advanced_SDK.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #extract Advanced NDI SDK 4 | yes y | bash ./Install_NDI_Advanced_SDK_v5_Linux.sh > /dev/null -------------------------------------------------------------------------------- /download_NDI_SDK.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #download and extract NDI 4 | curl -s https://downloads.ndi.tv/SDK/NDI_SDK_Linux/Install_NDI_SDK_v6_Linux.tar.gz | tar xvz -C /tmp/ 5 | yes y | bash /tmp/Install_NDI_SDK_v6_Linux.sh > /dev/null 6 | -------------------------------------------------------------------------------- /preinstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | apt-get update 4 | 5 | #install prerequisites 6 | apt-get -y install --no-install-recommends build-essential libjack-jackd2-dev qjackctl jackd avahi-daemon avahi-discover avahi-utils libssl-dev libconfig++-dev g++ curl 7 | 8 | -------------------------------------------------------------------------------- /jack2ndi.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=JACK to NDI Converter 3 | After=network-online.target jackd.service 4 | 5 | [Service] 6 | User=root 7 | LimitRTPRIO=infinity 8 | LimitMEMLOCK=infinity 9 | CPUSchedulingPolicy=rr 10 | CPUSchedulingPriority=80 11 | ExecStart=/opt/ndi2jack/bin/jack2ndi 12 | Restart=always 13 | RestartSec=5 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /ndi2jack.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=NDI to JACK Converter 3 | After=network-online.target jackd.service 4 | 5 | [Service] 6 | User=root 7 | LimitRTPRIO=infinity 8 | LimitMEMLOCK=infinity 9 | CPUSchedulingPolicy=rr 10 | CPUSchedulingPriority=80 11 | ExecStart=/opt/ndi2jack/bin/ndi2jack 12 | Restart=always 13 | RestartSec=5 14 | 15 | [Install] 16 | WantedBy=multi-user.target 17 | -------------------------------------------------------------------------------- /easy-install-x86_64.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | echo "Starting install of NDI to JACK for x86_64-bit..." 6 | 7 | echo "Installing prerequisites..." 8 | sudo bash ./preinstall.sh 9 | 10 | echo "Downloading NDI libraries..." 11 | sudo bash ./download_NDI_SDK.sh 12 | 13 | echo "Building executable for x86_64-bit..." 14 | sudo bash ./build_x86_64.sh 15 | 16 | echo "Installing in final directory..." 17 | sudo bash ./install.sh 18 | 19 | echo "Done" 20 | -------------------------------------------------------------------------------- /easy-install-rpi3-armhf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | echo "Starting install of NDI to JACK for Raspberry Pi 3 32-bit..." 6 | 7 | echo "Installing prerequisites..." 8 | sudo bash ./preinstall.sh 9 | 10 | echo "Downloading NDI libraries..." 11 | sudo bash ./download_NDI_SDK.sh 12 | 13 | echo "Building executable for Raspberry Pi 3 32-bit..." 14 | sudo bash ./build_rpi3_armhf.sh 15 | 16 | echo "Installing in final directory..." 17 | sudo bash ./install.sh 18 | 19 | echo "Done" 20 | -------------------------------------------------------------------------------- /easy-install-rpi4-aarch64.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | echo "Starting install of NDI to JACK for Raspberry Pi 4 64-bit..." 6 | 7 | echo "Installing prerequisites..." 8 | sudo bash ./preinstall.sh 9 | 10 | echo "Downloading NDI libraries..." 11 | sudo bash ./download_NDI_SDK.sh 12 | 13 | echo "Building executable for Raspberry Pi 4 64-bit..." 14 | sudo bash ./build_rpi4_arm64.sh 15 | 16 | echo "Installing in final directory..." 17 | sudo bash ./install.sh 18 | 19 | echo "Done" 20 | -------------------------------------------------------------------------------- /easy-install-rpi4-armhf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | echo "Starting install of NDI to JACK for Raspberry Pi 4 32-bit..." 6 | 7 | echo "Installing prerequisites..." 8 | sudo bash ./preinstall.sh 9 | 10 | echo "Downloading NDI libraries..." 11 | sudo bash ./download_NDI_SDK.sh 12 | 13 | echo "Building executable for Raspberry Pi 4 32-bit..." 14 | sudo bash ./build_rpi4_armhf.sh 15 | 16 | echo "Installing in final directory..." 17 | sudo bash ./install.sh 18 | 19 | echo "Done" 20 | -------------------------------------------------------------------------------- /easy-install-generic-aarch64.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | echo "Starting install of NDI to JACK for Generic ARM 64-bit..." 6 | 7 | echo "Installing prerequisites..." 8 | sudo bash ./preinstall.sh 9 | 10 | echo "Extracting NDI libraries..." 11 | sudo bash ./handle_NDI_Advanced_SDK.sh 12 | 13 | echo "Building executable for Generic ARM 64-bit..." 14 | sudo bash ./build_generic_arm64.sh 15 | 16 | echo "Installing in final directory..." 17 | sudo bash ./install.sh 18 | 19 | echo "Done" 20 | -------------------------------------------------------------------------------- /easy-install-generic-armhf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eu 4 | 5 | echo "Starting install of NDI to JACK for Generic ARM 32-bit..." 6 | 7 | echo "Installing prerequisites..." 8 | sudo bash ./preinstall.sh 9 | 10 | echo "Extracting NDI libraries..." 11 | sudo bash ./handle_NDI_Advanced_SDK.sh 12 | 13 | echo "Building executable for Generic ARM 32-bit..." 14 | sudo bash ./build_generic_armhf.sh 15 | 16 | echo "Installing in final directory..." 17 | sudo bash ./install.sh 18 | 19 | echo "Done" 20 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | INSTALL_DIR="/opt/ndi2jack" 4 | BIN_DIR="$INSTALL_DIR/bin" 5 | ASSETS_DIR="$INSTALL_DIR/assets" 6 | LIB_DIR="/usr/lib" 7 | 8 | rm -R "$INSTALL_DIR" 9 | rm -R "$LIB_DIR/libndi*" 10 | 11 | if [ ! -d "$INSTALL_DIR" ]; then 12 | mkdir "$INSTALL_DIR" 13 | fi 14 | 15 | if [ ! -d "$LIB_DIR" ]; then 16 | mkdir "$LIB_DIR" 17 | fi 18 | 19 | if [ ! -d "$BIN_DIR" ]; then 20 | mkdir "$BIN_DIR" 21 | fi 22 | 23 | if [ ! -d "$ASSETS_DIR" ]; then 24 | mkdir "$ASSETS_DIR" 25 | fi 26 | 27 | cp lib/* "$LIB_DIR" 28 | 29 | cp build/ndi2jack "$BIN_DIR" 30 | cp build/jack2ndi "$BIN_DIR" 31 | 32 | cp assets/* "$ASSETS_DIR" 33 | 34 | chmod +x "$BIN_DIR/ndi2jack" 35 | chmod +x "$BIN_DIR/jack2ndi" 36 | 37 | #symlink to the /usr/bin directory 38 | ln -s "$BIN_DIR/ndi2jack" /usr/bin/ 39 | ln -s "$BIN_DIR/jack2ndi" /usr/bin/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Luke Plassman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "ostream": "cpp", 4 | "array": "cpp", 5 | "atomic": "cpp", 6 | "bit": "cpp", 7 | "*.tcc": "cpp", 8 | "cctype": "cpp", 9 | "chrono": "cpp", 10 | "clocale": "cpp", 11 | "cmath": "cpp", 12 | "compare": "cpp", 13 | "concepts": "cpp", 14 | "condition_variable": "cpp", 15 | "csignal": "cpp", 16 | "cstdarg": "cpp", 17 | "cstddef": "cpp", 18 | "cstdint": "cpp", 19 | "cstdio": "cpp", 20 | "cstdlib": "cpp", 21 | "ctime": "cpp", 22 | "cwchar": "cpp", 23 | "cwctype": "cpp", 24 | "deque": "cpp", 25 | "string": "cpp", 26 | "unordered_map": "cpp", 27 | "vector": "cpp", 28 | "exception": "cpp", 29 | "algorithm": "cpp", 30 | "functional": "cpp", 31 | "iterator": "cpp", 32 | "memory": "cpp", 33 | "memory_resource": "cpp", 34 | "numeric": "cpp", 35 | "random": "cpp", 36 | "ratio": "cpp", 37 | "string_view": "cpp", 38 | "system_error": "cpp", 39 | "tuple": "cpp", 40 | "type_traits": "cpp", 41 | "utility": "cpp", 42 | "fstream": "cpp", 43 | "initializer_list": "cpp", 44 | "iosfwd": "cpp", 45 | "iostream": "cpp", 46 | "istream": "cpp", 47 | "limits": "cpp", 48 | "new": "cpp", 49 | "numbers": "cpp", 50 | "semaphore": "cpp", 51 | "stdexcept": "cpp", 52 | "stop_token": "cpp", 53 | "streambuf": "cpp", 54 | "thread": "cpp", 55 | "cinttypes": "cpp", 56 | "typeinfo": "cpp" 57 | } 58 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NDI to JACK 2 | 3 | NDI to JACK is an application that connects an NDI audio source to JACK for use as an audio output into other JACK compatible applications. 4 | The JACK to NDI application, which is included, creates an NDI source from a JACK input. 5 | 6 | ## Features 7 | - Manage NDI connections using the integrated web server 8 | - Support for up to 30 simultaneous 2 channel unique NDI audio sources 9 | - Uses the latest version of NDI - NDI 5 10 | - Nearly zero latency 11 | 12 | ## Supported devices 13 | 14 | This software is tested with a Raspberry Pi 4 (32 and 64-bit). This software does not require much CPU power and so other lower power devices could possibly work. 15 | 16 | ### Download required installation files 17 | 18 | Make sure git is installed. 19 | 20 | ``` 21 | sudo apt update 22 | sudo apt install git 23 | ``` 24 | Clone this repository and `cd` into it. 25 | 26 | ``` 27 | git clone https://github.com/windows10luke/NDI-to-JACK.git && cd NDI-to-JACK 28 | ``` 29 | 30 | ### Install on Raspberry Pi 4 64-bit 31 | 32 | Run this compile and install script 33 | 34 | ``` 35 | sudo bash ./easy-install-rpi4-aarch64.sh 36 | ``` 37 | Installation is now complete! 38 | 39 | 40 | ### Install on Raspberry Pi 4 32-bit 41 | 42 | Run this compile and install script 43 | 44 | ``` 45 | sudo bash ./easy-install-rpi4-armhf.sh 46 | ``` 47 | Installation is now complete! 48 | 49 | 50 | ### Install on Raspberry Pi 3 32-bit 51 | 52 | Run this compile and install script 53 | 54 | ``` 55 | sudo bash ./easy-install-rpi3-armhf.sh 56 | ``` 57 | Installation is now complete! 58 | 59 | 60 | 61 | ### Install on x86_64 bit (Intel/AMD) 62 | 63 | Run this compile and install script 64 | 65 | ``` 66 | sudo bash ./easy-install-x86_64.sh 67 | ``` 68 | Installation is now complete! 69 | 70 | 71 | ### Install on generic ARM64 72 | 73 | Compiling on generic ARM64 requires use of the NDI Advanced SDK. Due to licensing restrictions, the NDI Advanced SDK must be downloaded manually from NDI's website: [ndi.tv](https://ndi.tv) 74 | Extract the downloaded NDI Advanced SDK .tar file and copy it to the NDI-to-JACK directory on the target device. This can be achieved by using FTP, SCP, or Samba. 75 | 76 | Compile and install 77 | 78 | ``` 79 | sudo bash ./easy-install-generic-aarch64.sh 80 | ``` 81 | Installation is now complete! 82 | 83 | ### Install on generic ARM32 84 | 85 | Compiling on generic ARM32 requires use of the NDI Advanced SDK. Due to licensing restrictions, the NDI Advanced SDK must be downloaded manually from NDI's website: [ndi.tv](https://ndi.tv) 86 | Extract the downloaded NDI Advanced SDK .tar file and copy it to the NDI-to-JACK directory on the target device. This can be achieved by using FTP, SCP, or Samba. 87 | 88 | Compile and install 89 | 90 | ``` 91 | sudo bash ./easy-install-generic-armhf.sh 92 | ``` 93 | Installation is now complete! 94 | 95 | 96 | ## Usage for NDI to JACK converter 97 | 98 | Once the installation process is complete, it will create an executable file located at /opt/ndi2jack/bin/ndi2jack 99 | 100 | The installer also creates a symlink to /usr/bin so that it can be run from a normal terminal. 101 | 102 | To run and start the web server: 103 | 104 | ``` 105 | sudo ndi2jack 106 | ``` 107 | 108 | ## Usage for JACK to NDI converter 109 | 110 | Once the installation process is complete, it will create an executable file located at /opt/ndi2jack/bin/jack2ndi 111 | 112 | The installer also creates a symlink to /usr/bin so that it can be run from a normal terminal. 113 | 114 | To run (multiple instances can be run with different options for multiple NDI send instances): 115 | 116 | ``` 117 | sudo jack2ndi 118 | ``` 119 | 120 | ## Install service file for starting ndi2jack on boot 121 | 122 | By default this service file runs ndi2jack as the root user with realtime CPU scheduling. This also assumes that JACK is running as a service as the root user. 123 | 124 | ``` 125 | sudo cp ./ndi2jack.service /etc/systemd/system/ 126 | sudo systemctl enable ndi2jack.service 127 | sudo systemctl start ndi2jack.service 128 | ``` 129 | 130 | ## Install service file for starting jack2ndi on boot 131 | 132 | By default this service file runs jack2ndi as the root user with realtime CPU scheduling. This also assumes that JACK is running as a service as the root user. 133 | 134 | ``` 135 | sudo cp ./jack2ndi.service /etc/systemd/system/ 136 | sudo systemctl enable jack2ndi.service 137 | sudo systemctl start jack2ndi.service 138 | ``` 139 | 140 | ## To upgrade, run the following commands 141 | 142 | ``` 143 | sudo service jackd stop 144 | sudo rm -R NDI-to-JACK 145 | git clone https://github.com/windows10luke/NDI-to-JACK.git && cd NDI-to-JACK 146 | ``` 147 | Run the easy install for the CPU type it is running on (e.g. `sudo bash ./easy-install-rpi4-aarch64.sh`). Reboot when finished 148 | 149 | ## Helpful links 150 | 151 | https://wiki.linuxaudio.org/wiki/list_of_jack_frame_period_settings_ideal_for_usb_interface 152 | -------------------------------------------------------------------------------- /assets/main.css: -------------------------------------------------------------------------------- 1 | /* loader code start */ 2 | #loader { 3 | position: absolute; 4 | left: 50%; 5 | top: 50%; 6 | z-index: 1; 7 | width: 150px; 8 | height: 150px; 9 | margin: -75px 0 0 -75px; 10 | border: 16px solid #f3f3f3; 11 | border-radius: 50%; 12 | border-top: 16px solid #1f6feb; 13 | width: 120px; 14 | height: 120px; 15 | -webkit-animation: spin 2s linear infinite; 16 | animation: spin 2s linear infinite; 17 | } 18 | 19 | @-webkit-keyframes spin { 20 | 0% { -webkit-transform: rotate(0deg); } 21 | 100% { -webkit-transform: rotate(360deg); } 22 | } 23 | 24 | @keyframes spin { 25 | 0% { transform: rotate(0deg); } 26 | 100% { transform: rotate(360deg); } 27 | } 28 | 29 | html, body { 30 | margin:0; 31 | height:100%; 32 | min-height:100%; 33 | } 34 | 35 | body { 36 | margin:0; 37 | display: flex; 38 | flex-direction: column; 39 | font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"; 40 | font-size: 14px; 41 | } 42 | .templateContainer { 43 | display: flex; 44 | flex-direction: column; 45 | overflow-y: auto; 46 | overflow-x: hidden; 47 | height: 100% 48 | } 49 | 50 | .navbarTopContainer { 51 | display: flex; 52 | flex-direction: row; 53 | justify-content: space-between; 54 | min-height: 40px; 55 | box-shadow: 0px 1px 10px rgb(0,0,0); 56 | background: #161b22; /* Default color if no customization value is provided */ 57 | } 58 | 59 | .navbarTopBrand { 60 | padding: 10px 12px; 61 | color: #f0f6fc; 62 | background-color: #1f6feb; 63 | cursor: pointer; 64 | } 65 | 66 | .navbarTopContainer > .leftContainer { 67 | display: flex; 68 | flex-direction: row; 69 | } 70 | .navbarTopContainer > .rightContainer { 71 | display: flex; 72 | flex-direction: row; 73 | } 74 | 75 | .header-link { 76 | font-weight: 600; 77 | color: #f0f6fc; 78 | white-space: nowrap; 79 | padding: 10px 12px; 80 | cursor: pointer; 81 | } 82 | 83 | .totalAppContainer { 84 | display: flex; 85 | flex-direction: row; 86 | overflow-y: auto; 87 | overflow-x: hidden; 88 | height: 100%; 89 | } 90 | 91 | .navbar.left, .navbar.right { 92 | overflow-y: auto; /*this makes content scrollable if content goes beyond bottom of window size*/ 93 | overflow-x: hidden; 94 | background: #0d1117; /* Default color if no customization value is provided */ 95 | } 96 | 97 | .navbar-header { 98 | display: flex; 99 | font-size: 14px; 100 | padding: 10px; 101 | align-items: center; 102 | justify-content: center; 103 | color: #ffffff; 104 | background-color: #111c41; 105 | border: 1px solid #30363d; 106 | } 107 | 108 | .navbar.left { 109 | z-index: 5; 110 | display: block; 111 | left: 0; 112 | width: 200px; 113 | height: 100%; 114 | } 115 | 116 | .navbar.right { 117 | z-index: 4; 118 | display: block; 119 | right: 0; 120 | max-width: 120px; 121 | } 122 | 123 | .leftnav { 124 | flex: 0 0 auto; 125 | order: 0; 126 | overflow-y: auto; 127 | } 128 | 129 | .border-right { 130 | border-right: 1px solid #30363d; 131 | } 132 | 133 | .border-left { 134 | border-left: 1px solid #30363d; 135 | } 136 | /* Container where the app is rendered */ 137 | .appContainer { 138 | width: 100%; 139 | background: #010409; 140 | overflow-y: auto; 141 | overflow-x: hidden; 142 | } 143 | 144 | .dropdown-menu-sw { 145 | right: 0; 146 | left: auto; 147 | } 148 | 149 | .dropdown-menu { 150 | position: absolute; 151 | z-index: 100; 152 | width: 160px; 153 | padding-top: 4px; 154 | padding-bottom: 4px; 155 | margin-top: 2px; 156 | list-style: none; 157 | background-color: #161b22; 158 | background-clip: padding-box; 159 | border: 1px solid #30363d; 160 | border-radius: 6px; 161 | display: none; 162 | } 163 | 164 | .dropdown-item { 165 | display: block; 166 | padding: 4px 8px 4px 16px; 167 | overflow: hidden; 168 | color: #c9d1d9; 169 | text-overflow: ellipsis; 170 | white-space: nowrap; 171 | text-decoration: none; 172 | cursor: pointer; 173 | } 174 | 175 | .info-container { 176 | display: flex; 177 | flex-direction: row; 178 | flex-wrap: wrap; 179 | align-content: center; 180 | justify-content: center; 181 | align-items: center; 182 | } 183 | 184 | .d-box { 185 | display: flex; 186 | flex-direction: column; 187 | padding: 32px; 188 | margin: 10px; 189 | background-color: #161b22; 190 | background-clip: padding-box; 191 | border: 1px solid #30363d; 192 | border-radius: 6px; 193 | } 194 | 195 | .d-box > .header { 196 | display: flex; 197 | font-size: 14px; 198 | align-items: center; 199 | justify-content: space-between; 200 | color: #c9d1d9; 201 | } 202 | 203 | .d-box-container { 204 | display: flex; 205 | flex-direction: row; 206 | } 207 | 208 | .form-control { 209 | background-color: #010409; 210 | padding: 5px 12px; 211 | font-size: 14px; 212 | line-height: 20px; 213 | vertical-align: middle; 214 | color: #c9d1d9; 215 | border: 1px solid #30363d; 216 | border-radius: 6px; 217 | margin-bottom: 7px; 218 | } 219 | 220 | .button-primary { 221 | padding: 3px 12px; 222 | font-size: 12px; 223 | color: #ffffff; 224 | background-color: #1f6feb; 225 | cursor: pointer; 226 | border: 1px solid; 227 | border-radius: 6px; 228 | } 229 | 230 | .button-big { 231 | display: flex; 232 | font-size: 14px; 233 | align-items: center; 234 | justify-content: center; 235 | 236 | padding: 20px 0px; 237 | margin: 15px 5px; 238 | font-size: 16px; 239 | color: #ffffff; 240 | background-color: #161b22; 241 | cursor: pointer; 242 | border: 1px solid #30363d; 243 | border-radius: 6px; 244 | } 245 | 246 | .button-big.selected { 247 | background-color: #7e0a0a; 248 | } 249 | 250 | label { 251 | padding: 5px 3px; 252 | font-size: 14px; 253 | color: #c9d1d9; 254 | } -------------------------------------------------------------------------------- /assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sources - NDI Audio Client 6 | 7 | 8 | 9 | 10 |
11 |
12 | 22 | 23 |
24 | 30 |
31 |
32 | 33 |
34 |
35 |
36 |
37 | 38 | 43 | 44 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /include/mjson.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2020 Cesanta Software Limited 2 | // All rights reserved 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | #ifndef MJSON_H 23 | #define MJSON_H 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #ifndef MJSON_ENABLE_PRINT 30 | #define MJSON_ENABLE_PRINT 1 31 | #endif 32 | 33 | #ifndef MJSON_ENABLE_RPC 34 | #define MJSON_ENABLE_RPC 1 35 | #endif 36 | 37 | #ifndef MJSON_ENABLE_BASE64 38 | #define MJSON_ENABLE_BASE64 1 39 | #endif 40 | 41 | #ifndef MJSON_ENABLE_MERGE 42 | #define MJSON_ENABLE_MERGE 1 43 | #endif 44 | 45 | #ifndef MJSON_ENABLE_PRETTY 46 | #define MJSON_ENABLE_PRETTY 1 47 | #endif 48 | 49 | #ifndef MJSON_ENABLE_NEXT 50 | #define MJSON_ENABLE_NEXT 1 51 | #endif 52 | 53 | #ifndef MJSON_RPC_LIST_NAME 54 | #define MJSON_RPC_LIST_NAME "rpc.list" 55 | #endif 56 | 57 | #ifndef MJSON_DYNBUF_CHUNK 58 | #define MJSON_DYNBUF_CHUNK 256 // Allocation granularity for print_dynamic_buf 59 | #endif 60 | 61 | #ifdef __cplusplus 62 | extern "C" { 63 | #endif 64 | 65 | #define MJSON_ERROR_INVALID_INPUT (-1) 66 | #define MJSON_ERROR_TOO_DEEP (-2) 67 | #define MJSON_TOK_INVALID 0 68 | #define MJSON_TOK_KEY 1 69 | #define MJSON_TOK_STRING 11 70 | #define MJSON_TOK_NUMBER 12 71 | #define MJSON_TOK_TRUE 13 72 | #define MJSON_TOK_FALSE 14 73 | #define MJSON_TOK_NULL 15 74 | #define MJSON_TOK_ARRAY 91 75 | #define MJSON_TOK_OBJECT 123 76 | #define MJSON_TOK_IS_VALUE(t) ((t) > 10 && (t) < 20) 77 | 78 | typedef int (*mjson_cb_t)(int ev, const char *s, int off, int len, void *ud); 79 | 80 | #ifndef MJSON_MAX_DEPTH 81 | #define MJSON_MAX_DEPTH 20 82 | #endif 83 | 84 | int mjson(const char *s, int len, mjson_cb_t cb, void *ud); 85 | int mjson_find(const char *s, int len, const char *jp, const char **, int *); 86 | int mjson_get_number(const char *s, int len, const char *path, double *v); 87 | int mjson_get_bool(const char *s, int len, const char *path, int *v); 88 | int mjson_get_string(const char *s, int len, const char *path, char *to, int n); 89 | int mjson_get_hex(const char *s, int len, const char *path, char *to, int n); 90 | 91 | #if MJSON_ENABLE_NEXT 92 | int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff, 93 | int *vlen, int *vtype); 94 | #endif 95 | 96 | #if MJSON_ENABLE_BASE64 97 | int mjson_get_base64(const char *s, int len, const char *path, char *to, int n); 98 | int mjson_base64_dec(const char *src, int n, char *dst, int dlen); 99 | #endif 100 | 101 | #if MJSON_ENABLE_PRINT 102 | typedef int (*mjson_print_fn_t)(const char *buf, int len, void *userdata); 103 | typedef int (*mjson_vprint_fn_t)(mjson_print_fn_t, void *, va_list *); 104 | 105 | struct mjson_fixedbuf { 106 | char *ptr; 107 | int size, len; 108 | }; 109 | 110 | int mjson_printf(mjson_print_fn_t, void *, const char *fmt, ...); 111 | int mjson_vprintf(mjson_print_fn_t, void *, const char *fmt, va_list *ap); 112 | int mjson_print_str(mjson_print_fn_t, void *, const char *s, int len); 113 | int mjson_print_int(mjson_print_fn_t, void *, int value, int is_signed); 114 | int mjson_print_long(mjson_print_fn_t, void *, long value, int is_signed); 115 | int mjson_print_buf(mjson_print_fn_t fn, void *, const char *buf, int len); 116 | int mjson_print_dbl(mjson_print_fn_t fn, void *, double, int width); 117 | 118 | int mjson_print_null(const char *ptr, int len, void *userdata); 119 | int mjson_print_fixed_buf(const char *ptr, int len, void *userdata); 120 | int mjson_print_dynamic_buf(const char *ptr, int len, void *userdata); 121 | 122 | int mjson_snprintf(char *buf, size_t len, const char *fmt, ...); 123 | char *mjson_aprintf(const char *fmt, ...); 124 | 125 | #if MJSON_ENABLE_PRETTY 126 | int mjson_pretty(const char *, int, const char *, mjson_print_fn_t, void *); 127 | #endif 128 | 129 | #if MJSON_ENABLE_MERGE 130 | int mjson_merge(const char *, int, const char *, int, mjson_print_fn_t, void *); 131 | #endif 132 | 133 | #endif // MJSON_ENABLE_PRINT 134 | 135 | #if MJSON_ENABLE_RPC 136 | 137 | void jsonrpc_init(mjson_print_fn_t, void *userdata); 138 | int mjson_globmatch(const char *s1, int n1, const char *s2, int n2); 139 | 140 | struct jsonrpc_request { 141 | struct jsonrpc_ctx *ctx; 142 | const char *frame; // Points to the whole frame 143 | int frame_len; // Frame length 144 | const char *params; // Points to the "params" in the request frame 145 | int params_len; // Length of the "params" 146 | const char *id; // Points to the "id" in the request frame 147 | int id_len; // Length of the "id" 148 | const char *method; // Points to the "method" in the request frame 149 | int method_len; // Length of the "method" 150 | mjson_print_fn_t fn; // Printer function 151 | void *fndata; // Printer function data 152 | void *userdata; // Callback's user data as specified at export time 153 | }; 154 | 155 | struct jsonrpc_method { 156 | const char *method; 157 | int method_sz; 158 | void (*cb)(struct jsonrpc_request *); 159 | struct jsonrpc_method *next; 160 | }; 161 | 162 | // Main RPC context, stores current request information and a list of 163 | // exported RPC methods. 164 | struct jsonrpc_ctx { 165 | struct jsonrpc_method *methods; 166 | mjson_print_fn_t response_cb; 167 | void *response_cb_data; 168 | }; 169 | 170 | // Registers function fn under the given name within the given RPC context 171 | #define jsonrpc_ctx_export(ctx, name, fn) \ 172 | do { \ 173 | static struct jsonrpc_method m = {(name), sizeof(name) - 1, (fn), 0}; \ 174 | m.next = (ctx)->methods; \ 175 | (ctx)->methods = &m; \ 176 | } while (0) 177 | 178 | void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t, void *); 179 | void jsonrpc_return_error(struct jsonrpc_request *r, int code, 180 | const char *message, const char *data_fmt, ...); 181 | void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt, 182 | ...); 183 | void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *req, int req_sz, 184 | mjson_print_fn_t fn, void *fndata, void *userdata); 185 | 186 | extern struct jsonrpc_ctx jsonrpc_default_context; 187 | extern void jsonrpc_list(struct jsonrpc_request *r); 188 | 189 | #define jsonrpc_export(name, fn) \ 190 | jsonrpc_ctx_export(&jsonrpc_default_context, (name), (fn)) 191 | 192 | #define jsonrpc_process(buf, len, fn, fnd, ud) \ 193 | jsonrpc_ctx_process(&jsonrpc_default_context, (buf), (len), (fn), (fnd), (ud)) 194 | 195 | #define JSONRPC_ERROR_INVALID -32700 /* Invalid JSON was received */ 196 | #define JSONRPC_ERROR_NOT_FOUND -32601 /* The method does not exist */ 197 | #define JSONRPC_ERROR_BAD_PARAMS -32602 /* Invalid params passed */ 198 | #define JSONRPC_ERROR_INTERNAL -32603 /* Internal JSON-RPC error */ 199 | 200 | #endif // MJSON_ENABLE_RPC 201 | #ifdef __cplusplus 202 | } 203 | #endif 204 | #endif // MJSON_H 205 | -------------------------------------------------------------------------------- /jack2ndi.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * JACK to NDI Output 3 | * 4 | * This program can be used and distrubuted without resrictions 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include //for reading and writing preset file 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | bool auto_connect_jack_ports = false; 31 | 32 | static char *ndi_name; 33 | static char *client_name; 34 | 35 | //Function Definitions 36 | int process_callback(jack_nframes_t x, void *p); 37 | 38 | 39 | 40 | struct send_audio { 41 | send_audio(const char *c_name="ndi",const char *n_name="NDI_send",bool a_ports=false); //constructor 42 | ~send_audio(void); //destructor 43 | public: 44 | int process(jack_nframes_t nframes); 45 | void queue_wait(void); 46 | void process_audio_thread(void); 47 | void queue_push(jack_default_audio_sample_t** frame); 48 | jack_default_audio_sample_t** queue_pop_opt(void); 49 | private: 50 | NDIlib_send_instance_t m_pNDI_send; //create the NDI sender 51 | NDIlib_audio_frame_v2_t m_NDI_audio_frame; //create the audio frame for sending 52 | jack_port_t **in_ports; 53 | jack_default_audio_sample_t **in; 54 | jack_client_t *jack_client; 55 | jack_nframes_t jack_sample_rate; 56 | int num_channels = 2; 57 | jack_nframes_t num_frames; 58 | std::thread audio_thread; 59 | std::size_t m_max_depth = 1; // How many items we will queue before dropping them 60 | std::mutex m_lock; 61 | std::condition_variable m_condvar; 62 | std::queue m_queue; 63 | std::atomic m_exit; // Are we ready to exit 64 | static void jack_shutdown(void *arg); //This is called when JACK is shutdown 65 | }; 66 | 67 | void send_audio::queue_wait(void){ 68 | std::unique_lock lock_queue(m_lock); //lock the queue 69 | while (m_queue.empty()){ //wait until there is a new frame 70 | m_condvar.wait(lock_queue); 71 | } 72 | } 73 | 74 | void send_audio::queue_push(jack_default_audio_sample_t** frame){ 75 | std::unique_lock lock_queue(m_lock); //Lock the queue 76 | m_queue.push(std::move(frame)); // Queue the frame 77 | 78 | // Drop items that are too old if the queue is not keeping up 79 | while ((m_max_depth) && (m_queue.size() > m_max_depth)){ 80 | m_queue.pop(); //drop an audio frame from the queue 81 | printf("!"); fflush(stdout); 82 | } 83 | lock_queue.unlock(); //unlock the queue 84 | m_condvar.notify_one(); //notify the listener that there is data 85 | } 86 | 87 | jack_default_audio_sample_t** send_audio::queue_pop_opt(void){ 88 | // Lock the queue 89 | std::unique_lock lock_queue(m_lock); 90 | if(m_queue.empty()) { 91 | return {}; 92 | } 93 | //Get the item from the queue 94 | jack_default_audio_sample_t** item = std::move(m_queue.front()); 95 | m_queue.pop(); 96 | lock_queue.unlock(); //unlock the queue 97 | return item; 98 | } 99 | 100 | int send_audio::process(jack_nframes_t nframes){ 101 | //Get JACK Audio Buffers 102 | for (int channel = 0; channel < num_channels; channel++){ 103 | in[channel] = (jack_default_audio_sample_t*)jack_port_get_buffer (in_ports[channel], nframes); 104 | } 105 | send_audio::queue_push(std::move(in)); 106 | num_frames = nframes; 107 | return 0; 108 | } 109 | 110 | void send_audio::process_audio_thread(void){ 111 | bool exit_thread = false; 112 | while (true){ 113 | queue_wait(); //wait until there is some data to process 114 | while (true){ 115 | auto frame = queue_pop_opt(); //get the data frame off of the queue 116 | if(!frame){ //no data - wait for more data at queue_wait() 117 | break; 118 | } 119 | m_NDI_audio_frame.no_samples = num_frames; 120 | m_NDI_audio_frame.p_data = (float*)malloc(num_frames * num_channels * sizeof(float)); 121 | m_NDI_audio_frame.channel_stride_in_bytes = num_frames * sizeof(float); 122 | 123 | if(m_NDI_audio_frame.p_data != 0){ //make sure that there is data in the buffer before trying to copy anything 124 | for (int channel = 0; channel < num_channels; channel++){ 125 | float* p_ch = (float*)((uint8_t*)m_NDI_audio_frame.p_data + channel*m_NDI_audio_frame.channel_stride_in_bytes); //Initialize channels in NDI frame 126 | memcpy(p_ch, frame[channel], sizeof(jack_default_audio_sample_t) * num_frames); //copy the audio frame from JACK buffer to the NDI frame 127 | } 128 | } 129 | // Send the NDI audio frame 130 | NDIlib_send_send_audio_v2(m_pNDI_send, &m_NDI_audio_frame); 131 | free(m_NDI_audio_frame.p_data); //free the audio frame 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * JACK calls this shutdown_callback if the server ever shuts down or 138 | * decides to disconnect the client. 139 | */ 140 | void send_audio::jack_shutdown(void *arg){ 141 | exit(1); 142 | } 143 | 144 | //Constructor 145 | send_audio::send_audio(const char *c_name, const char *n_name, bool a_ports): m_pNDI_send(NULL), m_exit(false), jack_client(NULL){ 146 | printf("Starting Sender for %s\n", n_name); 147 | printf("Connecting to JACK as %s\n", c_name); 148 | const char **ports; 149 | const char *server_name = NULL; 150 | jack_options_t options = JackNullOption; 151 | jack_status_t status; 152 | 153 | // Create an NDI source 154 | NDIlib_send_create_t NDI_send_create_desc; 155 | NDI_send_create_desc.p_ndi_name = n_name; 156 | //NDI_send_create_desc.clock_audio = true; 157 | 158 | //Create the NDI sender using the description 159 | m_pNDI_send = NDIlib_send_create(&NDI_send_create_desc); 160 | 161 | /* open a client connection to the JACK server */ 162 | jack_client = jack_client_open (c_name, options, &status, server_name); 163 | if(jack_client == NULL){ 164 | fprintf (stderr, "jack_client_open() failed, ""status = 0x%2.0x\n", status); 165 | if(status & JackServerFailed){ 166 | fprintf (stderr, "Unable to connect to JACK server\n"); 167 | } 168 | exit (1); 169 | } 170 | if(status & JackServerStarted){ 171 | fprintf (stderr, "JACK server started\n"); 172 | } 173 | if(status & JackNameNotUnique){ 174 | //client_name = jack_get_client_name(jack_client); 175 | //fprintf (stderr, "unique name `%s' assigned\n", client_name); 176 | } 177 | 178 | jack_sample_rate = jack_get_sample_rate(jack_client); 179 | 180 | jack_set_process_callback (jack_client, ::process_callback, this); //This callback is called on every every time JACK does work - every audio sample 181 | jack_on_shutdown (jack_client, send_audio::jack_shutdown, 0); //JACK shutdown callback - gets called on JACK shutdown 182 | 183 | //initialize data structures for variable channels 184 | in_ports = (jack_port_t**)malloc(sizeof (jack_port_t*) * num_channels); 185 | size_t in_size = num_channels * sizeof(jack_default_audio_sample_t*); 186 | in = (jack_default_audio_sample_t**)malloc(in_size); 187 | 188 | /* create input JACK ports */ 189 | for (int channel = 0; channel < num_channels; channel++){ 190 | std::string channel_name_string = "input" + std::to_string(channel); 191 | //std::cout << "Current Channel Name: " << channel_name_string << std::endl; 192 | const char* channel_name_char = channel_name_string.c_str(); 193 | printf("Creating JACK input port: %s, Channel: %d\n", channel_name_char, channel); 194 | in_ports[channel] = jack_port_register (jack_client, channel_name_char, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); 195 | printf("Input JACK port created for %s\n", channel_name_char); 196 | if(in_ports[channel] == NULL){ //can't create JACK output ports - error 197 | fprintf(stderr, "no more JACK ports available\n"); 198 | exit (1); 199 | } 200 | } 201 | 202 | /* Tell the JACK server that we are ready to roll. Our 203 | * process() callback will start running now. */ 204 | if(jack_activate (jack_client)){ 205 | fprintf (stderr, "cannot activate client"); 206 | exit (1); 207 | } 208 | 209 | /* Connect the ports. You can't do this before the client is 210 | * activated, because we can't make connections to clients 211 | * that aren't running. Note the confusing (but necessary) 212 | * orientation of the driver backend ports: playback ports are 213 | * "input" to the backend, and capture ports are "output" from 214 | * it. 215 | */ 216 | if(a_ports == true){ //make sure that auto connect of JACK ports is enabled 217 | ports = jack_get_ports (jack_client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput); 218 | if(ports == NULL){ 219 | fprintf(stderr, "no physical capture ports\n"); 220 | exit (1); 221 | } 222 | 223 | if(jack_connect (jack_client, ports[0], jack_port_name (in_ports[0]))){ 224 | fprintf(stderr, "cannot connect input ports\n"); 225 | } 226 | 227 | if(jack_connect (jack_client, ports[1], jack_port_name (in_ports[1]))){ 228 | fprintf (stderr, "cannot connect input ports\n"); 229 | } 230 | 231 | jack_free (ports); 232 | } 233 | 234 | m_NDI_audio_frame.sample_rate = jack_sample_rate; 235 | m_NDI_audio_frame.no_channels = num_channels; 236 | audio_thread = std::thread(&send_audio::process_audio_thread, this); //start the audio processing in its own thread 237 | } 238 | 239 | // Destructor 240 | send_audio::~send_audio(void){ // Wait for the thread to exit 241 | m_exit = true; 242 | jack_client_close(jack_client); 243 | // Destroy the sender thread 244 | audio_thread.join(); 245 | } 246 | 247 | /** 248 | * The process callback for this JACK application is called in a 249 | * special realtime thread once for each audio cycle. 250 | */ 251 | int process_callback(jack_nframes_t x, void *p){ 252 | return static_cast(p)->process(x); 253 | } 254 | 255 | static const int no_senders = 30; //max number of senders 256 | send_audio* p_senders[no_senders] = { 0 }; 257 | std::string ndi_running_name[no_senders] = { "" }; 258 | 259 | static void usage(FILE *fp, int argc, char **argv){ 260 | fprintf(fp, 261 | "Usage: JACK to NDI [options]\n\n" 262 | "Version 1.1\n" 263 | "Options:\n" 264 | "-h | --help Print this message\n" 265 | "-n | --ndi-name NDI output stream name\n" 266 | "-j | --jack-name JACK client name\n" 267 | "-a | --auto-connect Disable auto connect JACK ports (default to true)\n" 268 | "", 269 | argv[0]); 270 | } 271 | 272 | static const char short_options[] = "n:j:a"; 273 | 274 | static const struct option 275 | long_options[] = { 276 | { "help", no_argument, NULL, 'h' }, 277 | { "ndi-name", required_argument, NULL, 'n' }, 278 | { "jack-name", required_argument, NULL, 'j' }, 279 | { "auto-connect", no_argument, NULL, 'a' }, 280 | { 0, 0, 0, 0 } 281 | }; 282 | 283 | int main (int argc, char **argv){ 284 | ndi_name = (char*)"Stream"; //default NDI stream name 285 | client_name = (char*)"NDI_send"; //default JACK client name 286 | for (;;) { 287 | int idx; 288 | int c; 289 | c = getopt_long(argc, argv,short_options, long_options, &idx); 290 | if (-1 == c){ 291 | break; 292 | } 293 | switch(c){ 294 | case 'h': 295 | usage(stdout, argc, argv); 296 | exit(EXIT_SUCCESS); 297 | case 'n': 298 | ndi_name = optarg; 299 | break; 300 | case 'j': 301 | client_name = optarg; 302 | break; 303 | case 'a': 304 | auto_connect_jack_ports = false; 305 | break; 306 | default: 307 | usage(stderr, argc, argv); 308 | exit(EXIT_FAILURE); 309 | } 310 | } 311 | 312 | if(!NDIlib_initialize()){ 313 | printf("Cannot run NDI."); // Cannot run NDI. Most likely because the CPU is not sufficient. 314 | return 0; 315 | } 316 | 317 | // Create a NDI finder 318 | printf("JACK Client Name %s\n", client_name); 319 | printf("NDI Sender Name %s\n", ndi_name); 320 | if(auto_connect_jack_ports == true){ 321 | printf("Auto Connect Ports\n"); 322 | }else{ 323 | printf("No Auto Connect Ports\n"); 324 | } 325 | p_senders[0] = new send_audio(client_name,ndi_name,auto_connect_jack_ports); 326 | 327 | /* keep running until the Ctrl+C */ 328 | while(1){ 329 | sleep(1); 330 | } 331 | 332 | exit (0); 333 | } 334 | -------------------------------------------------------------------------------- /ndi2jack.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * NDI Client to JACK (Jack Audio Connection Kit) Output 3 | * 4 | * This program can be used and distrubuted without resrictions 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include //for reading and writing preset file 20 | #include 21 | #include "mjson.h" 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | NDIlib_find_create_t NDI_find_create_desc; /* Default settings for NDI find */ 32 | NDIlib_find_instance_t pNDI_find; 33 | NDIlib_recv_instance_t pNDI_recv; //receiver for finding stream info 34 | int stream_info[3]; //stream info for the NDI stream 35 | const NDIlib_source_t* p_sources = NULL; 36 | struct mg_mgr mgr; 37 | bool auto_connect_jack_ports = true; 38 | float main_volume = 0.5f; //set to half volume by default 39 | 40 | //Function Definitions 41 | int process_callback(jack_nframes_t x, void *p); 42 | std::string convertToString(char* a); 43 | bool get_ndi_info(const char* source); 44 | 45 | 46 | 47 | struct receive_audio { 48 | receive_audio(const char* source, const char *client_name="NDI_recv", int channel_count = 2); //constructor 49 | ~receive_audio(void); //destructor 50 | public: 51 | int process(jack_nframes_t nframes); 52 | private: 53 | NDIlib_recv_instance_t m_pNDI_recv; // Create the receiver 54 | NDIlib_framesync_instance_t m_pNDI_framesync; //NDI framesync 55 | NDIlib_audio_frame_v3_t audio_frame; 56 | jack_port_t **out_ports; 57 | jack_default_audio_sample_t *out; 58 | jack_default_audio_sample_t *p_ch; 59 | jack_client_t *jack_client; 60 | jack_nframes_t jack_sample_rate; 61 | float channel_volume = 1.0f; //set channel volume to full 62 | int num_channels = 2; //default number of channels 63 | std::atomic m_exit; // Are we ready to exit 64 | static void jack_shutdown(void *arg); //This is called when JACK is shutdown 65 | }; 66 | 67 | int receive_audio::process(jack_nframes_t nframes){ 68 | //Get JACK Audio Buffers 69 | NDIlib_framesync_capture_audio_v2(m_pNDI_framesync, &audio_frame, jack_sample_rate, num_channels, nframes); 70 | //printf("Audio data received (%d samples).\n", audio_frame.no_samples); 71 | //std::cout << "Number of audio frames (JACK): " << nframes << std::endl; 72 | //std::cout << "Audio Frame Data (NDI): " << audio_frame.p_data << std::endl; 73 | //std::cout << "Channel Stride in Bytes (NDI): " << audio_frame.channel_stride_in_bytes << std::endl; 74 | //std::cout << "Size of Audio Frame (NDI): " << sizeof(audio_frame.p_data) << std::endl; 75 | //std::cout << "Number of Audio Channels (NDI): " << sizeof(audio_frame.no_channels) << std::endl; 76 | for (int channel = 0; channel < num_channels; channel++){ //go through each channel 77 | out = (jack_default_audio_sample_t*)jack_port_get_buffer(out_ports[channel], nframes); 78 | p_ch = (jack_default_audio_sample_t*)(uint8_t *)(&audio_frame.p_data[channel * audio_frame.channel_stride_in_bytes]); //Get channels from NDI audio frame 79 | for (int sample_no = 0; sample_no < audio_frame.no_samples; sample_no++){ //apply the volume to each sample 80 | out[sample_no] = p_ch[sample_no] * main_volume * channel_volume; //copies the adjusted NDI framedata into the JACK buffer 81 | } 82 | } 83 | // Release the NDI audio frame. You could keep the frame if you want and release it later. 84 | NDIlib_framesync_free_audio_v2(m_pNDI_framesync, &audio_frame); 85 | return 0; 86 | } 87 | 88 | /** 89 | * JACK calls this shutdown_callback if the server ever shuts down or 90 | * decides to disconnect the client. 91 | */ 92 | void receive_audio::jack_shutdown(void *arg){ 93 | exit(1); 94 | } 95 | 96 | //Constructor 97 | receive_audio::receive_audio(const char* source, const char *client_name, int channel_count): m_pNDI_recv(NULL), m_pNDI_framesync(NULL), m_exit(false), jack_client(NULL){ 98 | printf("Starting Receiver for %s\n", source); 99 | const char **found_ports; 100 | const char *server_name = NULL; 101 | jack_options_t options = JackNullOption; 102 | jack_status_t status; 103 | 104 | NDIlib_recv_create_v3_t recv_create_desc; 105 | recv_create_desc.source_to_connect_to = source; 106 | recv_create_desc.bandwidth = NDIlib_recv_bandwidth_audio_only; //specify receiving audio frames only 107 | recv_create_desc.p_ndi_recv_name = "NDI Receiver"; 108 | num_channels = channel_count; 109 | 110 | /* open a client connection to the JACK server */ 111 | fprintf (stderr, "Opening connection to JACK server...\n"); 112 | jack_client = jack_client_open (client_name, options, &status, server_name); 113 | fprintf (stderr, "JACK server connection opened\n"); 114 | if(jack_client == NULL){ 115 | fprintf (stderr, "jack_client_open() failed, ""status = 0x%2.0x\n", status); 116 | if(status & JackServerFailed){ 117 | fprintf (stderr, "Unable to connect to JACK server\n"); 118 | } 119 | exit (1); 120 | } 121 | if(status & JackServerStarted){ 122 | fprintf (stderr, "JACK server started\n"); 123 | } 124 | if(status & JackNameNotUnique){ 125 | client_name = jack_get_client_name(jack_client); 126 | fprintf (stderr, "unique name `%s' assigned\n", client_name); 127 | } 128 | 129 | jack_sample_rate = jack_get_sample_rate(jack_client); 130 | 131 | jack_set_process_callback (jack_client, ::process_callback, this); //This callback is called on every every time JACK does work - every audio sample 132 | jack_on_shutdown (jack_client, receive_audio::jack_shutdown, 0); //JACK shutdown callback - gets called on JACK shutdown 133 | 134 | //initialize data structures for variable channels 135 | out_ports = (jack_port_t**)malloc(sizeof (jack_port_t*) * num_channels); 136 | 137 | /* create output JACK ports */ 138 | for (int channel = 0; channel < num_channels; channel++){ 139 | std::string channel_name_string = "output_" + std::to_string(channel); 140 | //std::cout << "Current Channel Name: " << channel_name_string << std::endl; 141 | const char* channel_name_char = channel_name_string.c_str(); 142 | printf("Creating JACK output port: %s, Channel: %d\n", channel_name_char, channel); 143 | out_ports[channel] = jack_port_register (jack_client, channel_name_char, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); 144 | printf("Output JACK port created for %s\n", channel_name_char); 145 | if(out_ports[channel] == NULL){ //can't create JACK output ports - error 146 | fprintf(stderr, "no more JACK ports available\n"); 147 | exit (1); 148 | } 149 | } 150 | 151 | /* Tell the JACK server that we are ready to roll. Our 152 | * process() callback will start running now. */ 153 | if(jack_activate (jack_client)){ 154 | fprintf (stderr, "cannot activate client"); 155 | exit (1); 156 | } 157 | 158 | /* Connect the ports. You can't do this before the client is 159 | * activated, because we can't make connections to clients 160 | * that aren't running. Note the confusing (but necessary) 161 | * orientation of the driver backend ports: playback ports are 162 | * "input" to the backend, and capture ports are "output" from 163 | * it. 164 | */ 165 | if(auto_connect_jack_ports == true){ //make sure auto connect is enabled 166 | found_ports = jack_get_ports (jack_client, NULL, NULL, JackPortIsInput); 167 | if (found_ports) { 168 | int i; 169 | for (i = 0; found_ports[i]; ++i) { 170 | printf("name: %s\n", found_ports[i]); 171 | } 172 | } 173 | 174 | if(jack_connect (jack_client, jack_port_name (out_ports[0]), found_ports[0])){ 175 | fprintf(stderr, "cannot connect output ports\n"); 176 | } 177 | 178 | if(jack_connect (jack_client, jack_port_name (out_ports[1]), found_ports[1])){ 179 | fprintf (stderr, "cannot connect output ports\n"); 180 | } 181 | 182 | jack_free (found_ports); 183 | } 184 | 185 | // Create the receiver 186 | m_pNDI_recv = NDIlib_recv_create_v3(&recv_create_desc); 187 | assert(m_pNDI_recv); 188 | 189 | // Use a frame-synchronizer to ensure that the audio is dynamically resampled 190 | m_pNDI_framesync = NDIlib_framesync_create(m_pNDI_recv); //starts in its own thread 191 | } 192 | 193 | // Destructor 194 | receive_audio::~receive_audio(void){ // Wait for the thread to exit 195 | m_exit = true; 196 | jack_client_close(jack_client); 197 | // Destroy the receiver 198 | NDIlib_framesync_destroy(m_pNDI_framesync); 199 | NDIlib_recv_destroy(m_pNDI_recv); 200 | } 201 | 202 | /** 203 | * The process callback for this JACK application is called in a 204 | * special realtime thread once for each audio cycle. 205 | */ 206 | int process_callback(jack_nframes_t x, void *p){ 207 | return static_cast(p)->process(x); 208 | } 209 | 210 | static const int no_receivers = 30; //max number of receivers 211 | receive_audio* p_receivers[no_receivers] = { 0 }; 212 | std::string ndi_running_name[no_receivers] = { "" }; //name of the connected NDI stream 213 | 214 | static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data){ 215 | if(ev == MG_EV_WS_OPEN){ 216 | c->label[0] = 'W'; // Mark this connection as an established WS client 217 | } 218 | if (ev == MG_EV_HTTP_MSG){ 219 | struct mg_http_message *hm = (struct mg_http_message *) ev_data; 220 | struct mg_connection *c2 = mgr.conns; 221 | if(mg_http_match_uri(hm, "/ws")){ //upgrade to WebSocket 222 | mg_ws_upgrade(c, hm, NULL); 223 | }else if(mg_http_match_uri(hm, "/rest")) { //handle REST events 224 | mg_http_reply(c, 200, "", "{\"result\": %d}\n", 123); 225 | }else{ // Serve static files 226 | struct mg_http_serve_opts opts = {.root_dir = "/opt/ndi2jack/assets/"}; 227 | mg_http_serve_dir(c, (mg_http_message*)ev_data, &opts); 228 | } 229 | }else if (ev == MG_EV_WS_MSG){ 230 | // Got websocket frame. Received data is wm->data. Echo it back! 231 | struct mg_ws_message *wm = (struct mg_ws_message *) ev_data; 232 | //std::cout << "WebSocket: " << wm->data.ptr << std::endl; 233 | char prefix_buf[100]; 234 | char action_buf[100]; 235 | mjson_get_string(wm->data.ptr, wm->data.len, "$.prefix", prefix_buf, sizeof(prefix_buf)); //get prefix 236 | mjson_get_string(wm->data.ptr, wm->data.len, "$.action", action_buf, sizeof(action_buf)); //get action 237 | std::string prefix_string = convertToString(prefix_buf); 238 | std::string action_string = convertToString(action_buf); 239 | //std::cout << "prefixString: " << prefix_string << std::endl; 240 | //std::cout << "actionString: " << action_string << std::endl; 241 | if(prefix_string == "refresh"){ 242 | if(action_string == "refresh"){ 243 | 244 | uint32_t no_sources = 0; 245 | p_sources = NDIlib_find_get_current_sources(pNDI_find, &no_sources); 246 | std::string discover_json; 247 | std::string source_json = ""; 248 | discover_json = "{\"prefix\":\"discover_source\",\"action\":\"display\",\"source_list\":{"; 249 | for(uint32_t i = 0; i < no_sources; i++){ 250 | std::string ndi_string = p_sources[i].p_ndi_name; 251 | std::string url_string = p_sources[i].p_url_address; 252 | std::string source_id = std::to_string(i); 253 | int conflict = 0; 254 | for(uint32_t i = 0; i < no_receivers; i++){ //check for conflicts 255 | if((ndi_running_name[i] == ndi_string)&&(conflict == 0)){ 256 | conflict = 1; //found conflict with a name that is already stored - already running this receiver 257 | } 258 | } 259 | if(conflict == 0){ //since this is not running on a receiver - display 260 | //std::cout << "Source IP: " << p_sources[i].p_url_address << std::endl; 261 | if(source_json == ""){ 262 | source_json += "\""+source_id + "\":{\"name\":\""+ndi_string+"\",\"url\":\""+url_string+"\"}"; 263 | }else{ 264 | source_json += ",\""+source_id + "\":{\"name\":\""+ndi_string+"\",\"url\":\""+url_string+"\"}"; 265 | } 266 | } 267 | } 268 | discover_json += source_json; 269 | discover_json += "}"; 270 | discover_json += "}"; 271 | const char* pub_json1 = discover_json.c_str(); 272 | for (struct mg_connection *c2 = mgr.conns; c2 != NULL; c2 = c2->next) { //traverse over all client connections 273 | if (c2->label[0] == 'W'){ //make sure it is a websocket connection 274 | mg_ws_send(c2, pub_json1, strlen(pub_json1), WEBSOCKET_OP_TEXT); 275 | } 276 | } 277 | 278 | std::string connected_json; 279 | source_json = ""; 280 | connected_json = "{\"prefix\":\"playing_source\",\"action\":\"display\",\"source_list\":{"; 281 | for(uint32_t i = 0; i < no_receivers; i++){ 282 | if(ndi_running_name[i] != ""){ //make sure receiver is not empty 283 | std::string source_id = std::to_string(i); 284 | if(source_json == ""){ 285 | source_json += "\""+source_id + "\":{\"name\":\""+ndi_running_name[i]+"\"}"; 286 | }else{ 287 | source_json += ",\""+source_id + "\":{\"name\":\""+ndi_running_name[i]+"\"}"; 288 | } 289 | } 290 | } 291 | connected_json += source_json; 292 | connected_json += "}"; 293 | connected_json += "}"; 294 | const char* pub_json2 = connected_json.c_str(); 295 | for (struct mg_connection *c2 = mgr.conns; c2 != NULL; c2 = c2->next) { //traverse over all client connections 296 | if (c2->label[0] == 'W'){ //make sure it is a websocket connection 297 | mg_ws_send(c2, pub_json2, strlen(pub_json2), WEBSOCKET_OP_TEXT); 298 | } 299 | } 300 | } 301 | if(action_string == "re_vol"){ 302 | std::string volume_json; 303 | std::string source_json = ""; 304 | volume_json = "{\"prefix\":\"update_volume\",\"action\":\"display\",\"volume_info\":{"; 305 | std::string volume_level = std::to_string(main_volume); 306 | if(source_json == ""){ 307 | source_json += "\"main_vol\":\""+volume_level+"\""; 308 | } 309 | 310 | volume_json += source_json; 311 | volume_json += "}"; 312 | volume_json += "}"; 313 | const char* pub_json3 = volume_json.c_str(); 314 | for (struct mg_connection *c2 = mgr.conns; c2 != NULL; c2 = c2->next) { //traverse over all client connections 315 | if (c2->label[0] == 'W'){ //make sure it is a websocket connection 316 | mg_ws_send(c2, pub_json3, strlen(pub_json3), WEBSOCKET_OP_TEXT); 317 | } 318 | } 319 | } 320 | } 321 | 322 | if(prefix_string == "connect_source"){ 323 | int source_id = std::stoi(action_string); 324 | int stored = 0; 325 | int receiver_id = 0; 326 | int conflict = 0; 327 | std::string ndi_string = p_sources[source_id].p_ndi_name; 328 | for(uint32_t i = 0; i < no_receivers; i++){ //check for conflicts 329 | if((ndi_running_name[i] == ndi_string)&&(conflict == 0)){ 330 | conflict = 1; //found conflict with a name that is already stored - already running this receiver 331 | } 332 | } 333 | if(conflict == 0){ 334 | for(uint32_t i = 0; i < no_receivers; i++){ 335 | if(stored == 0){ 336 | if(ndi_running_name[i] == ""){ //empty string array 337 | ndi_running_name[i] = ndi_string; 338 | std::cout << "ID: " << i << std::endl; 339 | stored = 1; 340 | receiver_id = i; 341 | } 342 | } 343 | } 344 | get_ndi_info(p_sources[source_id].p_ndi_name); 345 | p_receivers[receiver_id] = new receive_audio(p_sources[source_id].p_ndi_name, "NDI_recv", stream_info[2]); 346 | }else{ 347 | //std::cout << "Receiver already running for: " << p_sources[source_id].p_ndi_name << std::endl; 348 | } 349 | } 350 | 351 | if(prefix_string == "disconnect_source"){ //remove a connected source 352 | int source_id = std::stoi(action_string); 353 | delete p_receivers[source_id]; //delete receiver 354 | ndi_running_name[source_id] = ""; //update the running receiver 355 | } 356 | 357 | if(prefix_string == "save_streams"){ //save the current connected streams 358 | std::ofstream preset_file("/opt/ndi2jack/assets/presets.txt"); 359 | for(uint32_t i = 0; i < no_receivers; i++){ 360 | if(ndi_running_name[i] != ""){ //make sure a receiver is stored before trying to save in file 361 | preset_file << ndi_running_name[i]; 362 | preset_file << std::endl; 363 | } 364 | } 365 | preset_file.close(); 366 | } 367 | 368 | if(prefix_string == "am"){ //adjust the main volume - all output channels are adjusted 369 | main_volume = std::stof(action_string); //get the float volume from the websocket and set the main_volume variable 370 | } 371 | 372 | } 373 | } 374 | 375 | bool get_ndi_info(const char* source){ 376 | NDIlib_recv_create_v3_t recv_create_desc; 377 | recv_create_desc.source_to_connect_to = source; 378 | recv_create_desc.bandwidth = NDIlib_recv_bandwidth_audio_only; //specify receiving audio frames only 379 | recv_create_desc.p_ndi_recv_name = "NDI Info"; 380 | pNDI_recv = NDIlib_recv_create_v3(&recv_create_desc); //create a receiver that connects to the source 381 | assert(pNDI_recv); 382 | NDIlib_audio_frame_v3_t audio_frame; 383 | printf("Getting NDI audio info for %s...\n", source); 384 | bool timeout = false; 385 | bool got_info = false; 386 | using namespace std::chrono; 387 | const auto start_time = high_resolution_clock::now(); //get start time 388 | while(timeout == false){ 389 | switch (NDIlib_recv_capture_v3(pNDI_recv, nullptr, &audio_frame, nullptr, 5000)){ //try to get data from the source 390 | // No data 391 | case NDIlib_frame_type_none: 392 | printf("No data received for NDI stream info.\n"); 393 | break; 394 | 395 | // Audio data 396 | case NDIlib_frame_type_audio: 397 | printf("Samples (%d).\n", audio_frame.no_samples); 398 | printf("Sample Rate (%d).\n", audio_frame.sample_rate); 399 | printf("No Channels (%d).\n", audio_frame.no_channels); 400 | stream_info[0] = audio_frame.no_samples; //store the stream info in the array 401 | stream_info[1] = audio_frame.sample_rate; 402 | stream_info[2] = audio_frame.no_channels; 403 | timeout = true; 404 | got_info = true; 405 | NDIlib_recv_free_audio_v3(pNDI_recv, &audio_frame); //free the audio frame 406 | break; 407 | } 408 | if(high_resolution_clock::now() - start_time > seconds(5)){ //timeout after 5 seconds of no data 409 | timeout = true; 410 | printf("Timeout in getting NDI stream info.\n"); 411 | } 412 | } 413 | // Destroy the receiver 414 | NDIlib_recv_destroy(pNDI_recv); 415 | return got_info; 416 | } 417 | 418 | static void usage(FILE *fp, int argc, char **argv){ 419 | fprintf(fp, 420 | "Usage: NDI to JACK [options]\n\n" 421 | "Version 1.0\n" 422 | "Options:\n" 423 | "-h | --help Print this message\n" 424 | "-a | --auto-connect Disable auto connect JACK ports (default to true)\n" 425 | "", 426 | argv[0]); 427 | } 428 | 429 | static const char short_options[] = "a"; 430 | 431 | static const struct option 432 | long_options[] = { 433 | { "help", no_argument, NULL, 'h' }, 434 | { "auto-connect", no_argument, NULL, 'a' }, 435 | { 0, 0, 0, 0 } 436 | }; 437 | 438 | int main (int argc, char *argv[]){ 439 | for (;;) { 440 | int idx; 441 | int c; 442 | c = getopt_long(argc, argv,short_options, long_options, &idx); 443 | if (-1 == c){ 444 | break; 445 | } 446 | switch(c){ 447 | case 'h': 448 | usage(stdout, argc, argv); 449 | exit(EXIT_SUCCESS); 450 | case 'a': 451 | auto_connect_jack_ports = false; 452 | break; 453 | default: 454 | usage(stderr, argc, argv); 455 | exit(EXIT_FAILURE); 456 | } 457 | } 458 | 459 | if(!NDIlib_initialize()){ 460 | printf("Cannot run NDI."); // Cannot run NDI. Most likely because the CPU is not sufficient. 461 | return 0; 462 | } 463 | 464 | // Create a NDI finder 465 | NDI_find_create_desc.show_local_sources = (bool)false; //don't include local sources when searching for NDI 466 | pNDI_find = NDIlib_find_create_v2(&NDI_find_create_desc); 467 | if (!pNDI_find) return 0; //error out if the NDI finder can't be created 468 | 469 | std::string output_text; //preset file is temporary stored in this variable 470 | std::ifstream preset_file("/opt/ndi2jack/assets/presets.txt"); //open the presets file 471 | while(getline(preset_file, output_text)){ 472 | int stored = 0; 473 | int receiver_id = 0; 474 | const char* ndi_name = output_text.c_str();; 475 | std::string ndi_string = ndi_name; 476 | for(uint32_t i = 0; i < no_receivers; i++){ 477 | if(stored == 0){ 478 | if(ndi_running_name[i] == ""){ //empty string array - make sure it is empty before trying to start receiver 479 | ndi_running_name[i] = ndi_string; 480 | stored = 1; 481 | receiver_id = i; 482 | } 483 | } 484 | } 485 | p_receivers[receiver_id] = new receive_audio(ndi_name, "NDI_recv", 2); //2 channels by default 486 | } 487 | 488 | mg_mgr_init(&mgr); 489 | mg_http_listen(&mgr, "ws://0.0.0.0:80", fn, NULL); // Create WebSocket and HTTP connection 490 | for (;;) mg_mgr_poll(&mgr, 1000); // Block forever 491 | /* keep running until the Ctrl+C */ 492 | while(1){ 493 | sleep(1); 494 | } 495 | 496 | exit (0); 497 | } 498 | 499 | std::string convertToString(char* a){ 500 | std::string s = a; 501 | return s; 502 | } -------------------------------------------------------------------------------- /include/mongoose.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2004-2013 Sergey Lyubka 2 | // Copyright (c) 2013-2021 Cesanta Software Limited 3 | // All rights reserved 4 | // 5 | // This software is dual-licensed: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License version 2 as 7 | // published by the Free Software Foundation. For the terms of this 8 | // license, see http://www.gnu.org/licenses/ 9 | // 10 | // You are free to use this software under the terms of the GNU General 11 | // Public License, but WITHOUT ANY WARRANTY; without even the implied 12 | // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | // See the GNU General Public License for more details. 14 | // 15 | // Alternatively, you can license this software under a commercial 16 | // license, as set out in https://www.mongoose.ws/licensing/ 17 | 18 | #ifndef MONGOOSE_H 19 | #define MONGOOSE_H 20 | 21 | #define MG_VERSION "7.5" 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | 28 | #define MG_ARCH_CUSTOM 0 29 | #define MG_ARCH_UNIX 1 30 | #define MG_ARCH_WIN32 2 31 | #define MG_ARCH_ESP32 3 32 | #define MG_ARCH_ESP8266 4 33 | #define MG_ARCH_FREERTOS_TCP 5 34 | #define MG_ARCH_FREERTOS_LWIP 6 35 | #define MG_ARCH_AZURERTOS 7 36 | 37 | #if !defined(MG_ARCH) 38 | #if defined(__unix__) || defined(__APPLE__) 39 | #define MG_ARCH MG_ARCH_UNIX 40 | #elif defined(_WIN32) 41 | #define MG_ARCH MG_ARCH_WIN32 42 | #elif defined(ICACHE_FLASH) || defined(ICACHE_RAM_ATTR) 43 | #define MG_ARCH MG_ARCH_ESP8266 44 | #elif defined(ESP_PLATFORM) 45 | #define MG_ARCH MG_ARCH_ESP32 46 | #elif defined(FREERTOS_IP_H) 47 | #define MG_ARCH MG_ARCH_FREERTOS_TCP 48 | #elif defined(AZURE_RTOS_THREADX) 49 | #define MG_ARCH MG_ARCH_AZURERTOS 50 | #endif 51 | 52 | #if !defined(MG_ARCH) 53 | #error "MG_ARCH is not specified and we couldn't guess it." 54 | #endif 55 | #endif // !defined(MG_ARCH) 56 | 57 | #if !defined(PRINTF_LIKE) 58 | #if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__) 59 | #define PRINTF_LIKE(f, a) __attribute__((format(printf, f, a))) 60 | #else 61 | #define PRINTF_LIKE(f, a) 62 | #endif 63 | #endif 64 | 65 | #if MG_ARCH == MG_ARCH_CUSTOM 66 | #include 67 | #endif 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | #if MG_ARCH == MG_ARCH_AZURERTOS 78 | 79 | #include 80 | #include 81 | #include 82 | #include 83 | #include 84 | 85 | #include 86 | #include 87 | 88 | #include 89 | #include 90 | #include 91 | #include 92 | 93 | #ifdef __REDLIB__ 94 | #define va_copy(d, s) __builtin_va_copy(d, s) 95 | #endif 96 | 97 | #define PATH_MAX FX_MAXIMUM_PATH 98 | #define MG_DIRSEP '\\' 99 | 100 | #define socklen_t int 101 | #define closesocket(x) soc_close(x) 102 | #define gmtime_r(a, b) gmtime(a) 103 | #define MG_INT64_FMT "%lld" 104 | 105 | static __inline struct tm *localtime_r(const time_t *t, struct tm *tm) { 106 | struct tm *x = localtime(t); 107 | *tm = *x; 108 | return tm; 109 | } 110 | 111 | #undef FOPEN_MAX 112 | 113 | #endif 114 | 115 | 116 | #if MG_ARCH == MG_ARCH_ESP32 117 | 118 | #include 119 | #include 120 | #include 121 | #include 122 | #include 123 | #include 124 | #include 125 | #include 126 | #include 127 | #include 128 | #include 129 | #include 130 | #include 131 | #include 132 | 133 | #define MG_PATH_MAX 128 134 | #define MG_ENABLE_DIRLIST 1 135 | 136 | #endif 137 | 138 | 139 | #if MG_ARCH == MG_ARCH_ESP8266 140 | 141 | #include 142 | #include 143 | #include 144 | #include 145 | #include 146 | #include 147 | #include 148 | #include 149 | #include 150 | #include 151 | #include 152 | #include 153 | #include 154 | #include 155 | #include 156 | #include 157 | 158 | #include 159 | 160 | #define MG_PATH_MAX 128 161 | #define MG_ENABLE_DIRLIST 1 162 | 163 | #endif 164 | 165 | 166 | #if MG_ARCH == MG_ARCH_FREERTOS_LWIP 167 | 168 | #include 169 | #include 170 | #include 171 | #include 172 | #include 173 | 174 | #if defined(__GNUC__) 175 | #include 176 | #include 177 | #else 178 | struct timeval { 179 | time_t tv_sec; 180 | long tv_usec; 181 | }; 182 | #endif 183 | 184 | #include 185 | #include 186 | 187 | #include 188 | 189 | #if LWIP_SOCKET != 1 190 | // Sockets support disabled in LWIP by default 191 | #error Set LWIP_SOCKET variable to 1 (in lwipopts.h) 192 | #endif 193 | 194 | #if LWIP_POSIX_SOCKETS_IO_NAMES != 0 195 | // LWIP_POSIX_SOCKETS_IO_NAMES must be disabled in posix-compatible OS 196 | // enviroment (freertos mimics to one) otherwise names like `read` and `write` 197 | // conflict 198 | #error LWIP_POSIX_SOCKETS_IO_NAMES must be set to 0 (in lwipopts.h) for FreeRTOS 199 | #endif 200 | 201 | #define MG_INT64_FMT "%lld" 202 | #define MG_DIRSEP '/' 203 | 204 | // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib 205 | static inline void *mg_calloc(int cnt, size_t size) { 206 | void *p = pvPortMalloc(cnt * size); 207 | if (p != NULL) memset(p, 0, size); 208 | return p; 209 | } 210 | #define calloc(a, b) mg_calloc((a), (b)) 211 | #define free(a) vPortFree(a) 212 | #define malloc(a) pvPortMalloc(a) 213 | #define gmtime_r(a, b) gmtime(a) 214 | #define mkdir(a, b) (-1) 215 | 216 | #endif // MG_ARCH == MG_ARCH_FREERTOS_LWIP 217 | 218 | 219 | #if MG_ARCH == MG_ARCH_FREERTOS_TCP 220 | 221 | #include 222 | #include 223 | #include 224 | #include 225 | #include 226 | #include 227 | #include 228 | #include 229 | #include 230 | #include 231 | #include 232 | 233 | #include 234 | #include 235 | #include 236 | #include 237 | 238 | // Why FreeRTOS-TCP did not implement a clean BSD API, but its own thing 239 | // with FreeRTOS_ prefix, is beyond me 240 | #define IPPROTO_TCP FREERTOS_IPPROTO_TCP 241 | #define IPPROTO_UDP FREERTOS_IPPROTO_UDP 242 | #define AF_INET FREERTOS_AF_INET 243 | #define SOCK_STREAM FREERTOS_SOCK_STREAM 244 | #define SOCK_DGRAM FREERTOS_SOCK_DGRAM 245 | #define SO_BROADCAST 0 246 | #define SO_ERROR 0 247 | #define SOL_SOCKET 0 248 | #define SO_REUSEADDR 0 249 | #define sockaddr_in freertos_sockaddr 250 | #define sockaddr freertos_sockaddr 251 | #define accept(a, b, c) FreeRTOS_accept((a), (b), (c)) 252 | #define connect(a, b, c) FreeRTOS_connect((a), (b), (c)) 253 | #define bind(a, b, c) FreeRTOS_bind((a), (b), (c)) 254 | #define listen(a, b) FreeRTOS_listen((a), (b)) 255 | #define socket(a, b, c) FreeRTOS_socket((a), (b), (c)) 256 | #define send(a, b, c, d) FreeRTOS_send((a), (b), (c), (d)) 257 | #define recv(a, b, c, d) FreeRTOS_recv((a), (b), (c), (d)) 258 | #define setsockopt(a, b, c, d, e) FreeRTOS_setsockopt((a), (b), (c), (d), (e)) 259 | #define sendto(a, b, c, d, e, f) FreeRTOS_sendto((a), (b), (c), (d), (e), (f)) 260 | #define recvfrom(a, b, c, d, e, f) \ 261 | FreeRTOS_recvfrom((a), (b), (c), (d), (e), (f)) 262 | #define closesocket(x) FreeRTOS_closesocket(x) 263 | #define gethostbyname(x) FreeRTOS_gethostbyname(x) 264 | #define getsockname(a, b, c) (-1) 265 | 266 | // Re-route calloc/free to the FreeRTOS's functions, don't use stdlib 267 | static inline void *mg_calloc(int cnt, size_t size) { 268 | void *p = pvPortMalloc(cnt * size); 269 | if (p != NULL) memset(p, 0, size); 270 | return p; 271 | } 272 | #define calloc(a, b) mg_calloc((a), (b)) 273 | #define free(a) vPortFree(a) 274 | #define malloc(a) pvPortMalloc(a) 275 | #define mkdir(a, b) (-1) 276 | 277 | #define gmtime_r(a, b) gmtime(a) 278 | 279 | #if !defined(__GNUC__) 280 | // copied from GCC on ARM; for some reason useconds are signed 281 | struct timeval { 282 | time_t tv_sec; 283 | long tv_usec; 284 | }; 285 | #endif 286 | 287 | #ifndef EINPROGRESS 288 | #define EINPROGRESS pdFREERTOS_ERRNO_EINPROGRESS 289 | #endif 290 | #ifndef EWOULDBLOCK 291 | #define EWOULDBLOCK pdFREERTOS_ERRNO_EWOULDBLOCK 292 | #endif 293 | #ifndef EAGAIN 294 | #define EAGAIN pdFREERTOS_ERRNO_EAGAIN 295 | #endif 296 | #ifndef EINTR 297 | #define EINTR pdFREERTOS_ERRNO_EINTR 298 | #endif 299 | 300 | #endif // MG_ARCH == MG_ARCH_FREERTOS_TCP 301 | 302 | 303 | #if MG_ARCH == MG_ARCH_UNIX 304 | 305 | #define _DARWIN_UNLIMITED_SELECT 1 // No limit on file descriptors 306 | 307 | #include 308 | #include 309 | #include 310 | #include 311 | #include 312 | #include 313 | #include 314 | #include 315 | #include 316 | #include 317 | #include 318 | #include 319 | #include 320 | #include 321 | #include 322 | #include 323 | #include 324 | #include 325 | #include 326 | #include 327 | #include 328 | #include 329 | #include 330 | #include 331 | 332 | #define MG_INT64_FMT "%" PRId64 333 | 334 | #ifndef MG_ENABLE_DIRLIST 335 | #define MG_ENABLE_DIRLIST 1 336 | #endif 337 | 338 | #endif 339 | 340 | 341 | #if MG_ARCH == MG_ARCH_WIN32 342 | 343 | #ifndef WIN32_LEAN_AND_MEAN 344 | #define WIN32_LEAN_AND_MEAN 345 | #endif 346 | 347 | #ifndef _CRT_SECURE_NO_WARNINGS 348 | #define _CRT_SECURE_NO_WARNINGS 349 | #endif 350 | 351 | #ifndef _WINSOCK_DEPRECATED_NO_WARNINGS 352 | #define _WINSOCK_DEPRECATED_NO_WARNINGS 353 | #endif 354 | 355 | #include 356 | #include 357 | #include 358 | #include 359 | #include 360 | #include 361 | #include 362 | #include 363 | #include 364 | #include 365 | #include 366 | #include 367 | 368 | #if defined(_MSC_VER) && _MSC_VER < 1700 369 | #define __func__ "" 370 | typedef __int64 int64_t; 371 | typedef unsigned __int64 uint64_t; 372 | typedef unsigned char uint8_t; 373 | typedef char int8_t; 374 | typedef unsigned short uint16_t; 375 | typedef short int16_t; 376 | typedef unsigned int uint32_t; 377 | typedef int int32_t; 378 | typedef enum { false = 0, true = 1 } bool; 379 | #else 380 | #include 381 | #include 382 | #include 383 | #endif 384 | 385 | #include 386 | 387 | // Protect from calls like std::snprintf in app code 388 | // See https://github.com/cesanta/mongoose/issues/1047 389 | #ifndef __cplusplus 390 | #define snprintf _snprintf 391 | #define vsnprintf _vsnprintf 392 | #ifndef strdup // For MSVC with _DEBUG, see #1359 393 | #define strdup(x) _strdup(x) 394 | #endif 395 | #endif 396 | 397 | typedef int socklen_t; 398 | #define MG_DIRSEP '\\' 399 | #ifndef PATH_MAX 400 | #define PATH_MAX MAX_PATH 401 | #endif 402 | #ifndef EINPROGRESS 403 | #define EINPROGRESS WSAEINPROGRESS 404 | #endif 405 | #ifndef EWOULDBLOCK 406 | #define EWOULDBLOCK WSAEWOULDBLOCK 407 | #endif 408 | 409 | #define realpath(a, b) _fullpath((b), (a), MG_PATH_MAX) 410 | #define sleep(x) Sleep(x) 411 | 412 | #ifndef va_copy 413 | #ifdef __va_copy 414 | #define va_copy __va_copy 415 | #else 416 | #define va_copy(x, y) (x) = (y) 417 | #endif 418 | #endif 419 | #ifndef S_ISDIR 420 | #define S_ISDIR(x) (((x) &_S_IFMT) == _S_IFDIR) 421 | #endif 422 | 423 | #define MG_INT64_FMT "%I64d" 424 | 425 | #ifndef MG_ENABLE_DIRLIST 426 | #define MG_ENABLE_DIRLIST 1 427 | #endif 428 | 429 | // https://lgtm.com/rules/2154840805/ -gmtime, localtime, ctime and asctime 430 | static __inline struct tm *gmtime_r(const time_t *t, struct tm *tm) { 431 | struct tm *x = gmtime(t); 432 | *tm = *x; 433 | return tm; 434 | } 435 | 436 | static __inline struct tm *localtime_r(const time_t *t, struct tm *tm) { 437 | struct tm *x = localtime(t); 438 | *tm = *x; 439 | return tm; 440 | } 441 | 442 | #endif 443 | 444 | 445 | #ifndef MG_ENABLE_FATFS 446 | #define MG_ENABLE_FATFS 0 447 | #endif 448 | 449 | #ifndef MG_ENABLE_SOCKET 450 | #define MG_ENABLE_SOCKET 1 451 | #endif 452 | 453 | #ifndef MG_ENABLE_MBEDTLS 454 | #define MG_ENABLE_MBEDTLS 0 455 | #endif 456 | 457 | #ifndef MG_ENABLE_OPENSSL 458 | #define MG_ENABLE_OPENSSL 0 459 | #endif 460 | 461 | #ifndef MG_ENABLE_CUSTOM_TLS 462 | #define MG_ENABLE_CUSTOM_TLS 0 463 | #endif 464 | 465 | #ifndef MG_ENABLE_SSI 466 | #define MG_ENABLE_SSI 1 467 | #endif 468 | 469 | #ifndef MG_ENABLE_IPV6 470 | #define MG_ENABLE_IPV6 0 471 | #endif 472 | 473 | #ifndef MG_ENABLE_LOG 474 | #define MG_ENABLE_LOG 1 475 | #endif 476 | 477 | #ifndef MG_ENABLE_MD5 478 | #define MG_ENABLE_MD5 0 479 | #endif 480 | 481 | // Set MG_ENABLE_WINSOCK=0 for Win32 builds with external IP stack (like LWIP) 482 | #ifndef MG_ENABLE_WINSOCK 483 | #define MG_ENABLE_WINSOCK 1 484 | #endif 485 | 486 | #ifndef MG_ENABLE_DIRLIST 487 | #define MG_ENABLE_DIRLIST 0 488 | #endif 489 | 490 | #ifndef MG_ENABLE_CUSTOM_RANDOM 491 | #define MG_ENABLE_CUSTOM_RANDOM 0 492 | #endif 493 | 494 | #ifndef MG_ENABLE_PACKED_FS 495 | #define MG_ENABLE_PACKED_FS 0 496 | #endif 497 | 498 | // Granularity of the send/recv IO buffer growth 499 | #ifndef MG_IO_SIZE 500 | #define MG_IO_SIZE 2048 501 | #endif 502 | 503 | // Maximum size of the recv IO buffer 504 | #ifndef MG_MAX_RECV_BUF_SIZE 505 | #define MG_MAX_RECV_BUF_SIZE (3 * 1024 * 1024) 506 | #endif 507 | 508 | #ifndef MG_MAX_HTTP_HEADERS 509 | #define MG_MAX_HTTP_HEADERS 40 510 | #endif 511 | 512 | #ifndef MG_HTTP_INDEX 513 | #define MG_HTTP_INDEX "index.html" 514 | #endif 515 | 516 | #ifndef MG_PATH_MAX 517 | #ifdef PATH_MAX 518 | #define MG_PATH_MAX PATH_MAX 519 | #else 520 | #define MG_PATH_MAX 128 521 | #endif 522 | #endif 523 | 524 | #ifndef MG_SOCK_LISTEN_BACKLOG_SIZE 525 | #define MG_SOCK_LISTEN_BACKLOG_SIZE 128 526 | #endif 527 | 528 | #ifndef MG_DIRSEP 529 | #define MG_DIRSEP '/' 530 | #endif 531 | 532 | #ifndef MG_INT64_FMT 533 | #define MG_INT64_FMT "%lld" 534 | #endif 535 | 536 | #ifndef MG_ENABLE_FILE 537 | #if defined(FOPEN_MAX) 538 | #define MG_ENABLE_FILE 1 539 | #else 540 | #define MG_ENABLE_FILE 0 541 | #endif 542 | #endif 543 | 544 | 545 | 546 | 547 | struct mg_str { 548 | const char *ptr; // Pointer to string data 549 | size_t len; // String len 550 | }; 551 | 552 | #define MG_NULL_STR \ 553 | { NULL, 0 } 554 | 555 | // Using macro to avoid shadowing C++ struct constructor, see #1298 556 | #define mg_str(s) mg_str_s(s) 557 | 558 | struct mg_str mg_str(const char *s); 559 | struct mg_str mg_str_n(const char *s, size_t n); 560 | int mg_lower(const char *s); 561 | int mg_ncasecmp(const char *s1, const char *s2, size_t len); 562 | int mg_casecmp(const char *s1, const char *s2); 563 | int mg_vcmp(const struct mg_str *s1, const char *s2); 564 | int mg_vcasecmp(const struct mg_str *str1, const char *str2); 565 | int mg_strcmp(const struct mg_str str1, const struct mg_str str2); 566 | struct mg_str mg_strstrip(struct mg_str s); 567 | struct mg_str mg_strdup(const struct mg_str s); 568 | const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle); 569 | 570 | 571 | 572 | 573 | 574 | #if MG_ENABLE_LOG 575 | #define LOG(level, args) \ 576 | do { \ 577 | if (mg_log_prefix((level), __FILE__, __LINE__, __func__)) mg_log args; \ 578 | } while (0) 579 | enum { LL_NONE, LL_ERROR, LL_INFO, LL_DEBUG, LL_VERBOSE_DEBUG }; 580 | bool mg_log_prefix(int ll, const char *file, int line, const char *fname); 581 | void mg_log(const char *fmt, ...) PRINTF_LIKE(1, 2); 582 | void mg_log_set(const char *spec); 583 | void mg_log_set_callback(void (*fn)(const void *, size_t, void *), void *param); 584 | #else 585 | #define LOG(level, args) (void) 0 586 | #define mg_log_set(x) (void) (x) 587 | #endif 588 | 589 | 590 | 591 | 592 | struct mg_timer { 593 | int64_t period_ms; // Timer period in milliseconds 594 | int64_t expire; // Expiration timestamp in milliseconds 595 | unsigned flags; // Possible flags values below 596 | #define MG_TIMER_REPEAT 1 // Call function periodically, otherwise run once 597 | #define MG_TIMER_RUN_NOW 2 // Call immediately when timer is set 598 | void (*fn)(void *); // Function to call 599 | void *arg; // Function argument 600 | struct mg_timer *next; // Linkage in g_timers list 601 | }; 602 | 603 | extern struct mg_timer *g_timers; // Global list of timers 604 | 605 | void mg_timer_init(struct mg_timer *, int64_t, unsigned, void (*)(void *), 606 | void *); 607 | void mg_timer_free(struct mg_timer *); 608 | void mg_timer_poll(int64_t current_time_ms); 609 | 610 | 611 | 612 | 613 | 614 | enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 }; 615 | 616 | // Filesystem API functions 617 | // stat() returns MG_FS_* flags and populates file size and modification time 618 | // list() calls fn() for every directory entry, allowing to list a directory 619 | struct mg_fs { 620 | int (*stat)(const char *path, size_t *size, time_t *mtime); 621 | void (*list)(const char *path, void (*fn)(const char *, void *), void *); 622 | void *(*open)(const char *path, int flags); // Open file 623 | void (*close)(void *fd); // Close file 624 | size_t (*read)(void *fd, void *buf, size_t len); // Read file 625 | size_t (*write)(void *fd, const void *buf, size_t len); // Write file 626 | size_t (*seek)(void *fd, size_t offset); // Set file position 627 | bool (*rename)(const char *from, const char *to); // Rename 628 | bool (*remove)(const char *path); // Delete file 629 | bool (*mkdir)(const char *path); // Create directory 630 | }; 631 | 632 | // File descriptor 633 | struct mg_fd { 634 | void *fd; 635 | struct mg_fs *fs; 636 | }; 637 | 638 | struct mg_fd *mg_fs_open(struct mg_fs *fs, const char *path, int flags); 639 | void mg_fs_close(struct mg_fd *fd); 640 | char *mg_file_read(struct mg_fs *fs, const char *path, size_t *size); 641 | bool mg_file_write(struct mg_fs *fs, const char *path, const void *, size_t); 642 | bool mg_file_printf(struct mg_fs *fs, const char *path, const char *fmt, ...); 643 | 644 | extern struct mg_fs mg_fs_posix; // POSIX open/close/read/write/seek 645 | extern struct mg_fs mg_fs_packed; // Packed FS, see examples/complete 646 | extern struct mg_fs mg_fs_fat; // FAT FS 647 | 648 | 649 | 650 | 651 | 652 | 653 | void mg_random(void *buf, size_t len); 654 | bool mg_globmatch(const char *pattern, size_t plen, const char *s, size_t n); 655 | bool mg_commalist(struct mg_str *s, struct mg_str *k, struct mg_str *v); 656 | uint16_t mg_ntohs(uint16_t net); 657 | uint32_t mg_ntohl(uint32_t net); 658 | uint32_t mg_crc32(uint32_t crc, const char *buf, size_t len); 659 | char *mg_hexdump(const void *buf, size_t len); 660 | char *mg_hex(const void *buf, size_t len, char *dst); 661 | void mg_unhex(const char *buf, size_t len, unsigned char *to); 662 | unsigned long mg_unhexn(const char *s, size_t len); 663 | int mg_asprintf(char **buf, size_t size, const char *fmt, ...); 664 | int mg_vasprintf(char **buf, size_t size, const char *fmt, va_list ap); 665 | int mg_check_ip_acl(struct mg_str acl, uint32_t remote_ip); 666 | int64_t mg_to64(struct mg_str str); 667 | int64_t mg_millis(void); 668 | 669 | #define mg_htons(x) mg_ntohs(x) 670 | #define mg_htonl(x) mg_ntohl(x) 671 | 672 | #ifndef EXTERN_C 673 | #ifdef __cplusplus 674 | #define EXTERN_C extern "C" 675 | #else 676 | #define EXTERN_C 677 | #endif 678 | #endif 679 | 680 | // Expands to a string representation of its argument: e.g. 681 | // MG_STRINGIFY_LITERAL(5) expands to "5" 682 | #if !defined(_MSC_VER) || _MSC_VER >= 1900 683 | #define MG_STRINGIFY_LITERAL(...) #__VA_ARGS__ 684 | #else 685 | #define MG_STRINGIFY_LITERAL(x) #x 686 | #endif 687 | // Expands to a string representation of its argument, which can be a macro: 688 | // #define FOO 123 689 | // MG_STRINGIFY_MACRO(FOO) // Expands to 123 690 | #define MG_STRINGIFY_MACRO(x) MG_STRINGIFY_LITERAL(x) 691 | 692 | // Linked list management macros 693 | #define LIST_ADD_HEAD(type_, head_, elem_) \ 694 | do { \ 695 | (elem_)->next = (*head_); \ 696 | *(head_) = (elem_); \ 697 | } while (0) 698 | 699 | #define LIST_ADD_TAIL(type_, head_, elem_) \ 700 | do { \ 701 | type_ **h = head_; \ 702 | while (*h != NULL) h = &(*h)->next; \ 703 | *h = (elem_); \ 704 | } while (0) 705 | 706 | #define LIST_DELETE(type_, head_, elem_) \ 707 | do { \ 708 | type_ **h = head_; \ 709 | while (*h != (elem_)) h = &(*h)->next; \ 710 | *h = (elem_)->next; \ 711 | } while (0) 712 | 713 | 714 | 715 | unsigned short mg_url_port(const char *url); 716 | int mg_url_is_ssl(const char *url); 717 | struct mg_str mg_url_host(const char *url); 718 | struct mg_str mg_url_user(const char *url); 719 | struct mg_str mg_url_pass(const char *url); 720 | const char *mg_url_uri(const char *url); 721 | 722 | 723 | #include 724 | 725 | struct mg_iobuf { 726 | unsigned char *buf; // Pointer to stored data 727 | size_t size; // Total size available 728 | size_t len; // Current number of bytes 729 | }; 730 | 731 | int mg_iobuf_init(struct mg_iobuf *, size_t); 732 | int mg_iobuf_resize(struct mg_iobuf *, size_t); 733 | void mg_iobuf_free(struct mg_iobuf *); 734 | size_t mg_iobuf_add(struct mg_iobuf *, size_t, const void *, size_t, size_t); 735 | size_t mg_iobuf_del(struct mg_iobuf *, size_t ofs, size_t len); 736 | 737 | int mg_base64_update(unsigned char p, char *to, int len); 738 | int mg_base64_final(char *to, int len); 739 | int mg_base64_encode(const unsigned char *p, int n, char *to); 740 | int mg_base64_decode(const char *src, int n, char *dst); 741 | 742 | 743 | 744 | 745 | typedef struct { 746 | uint32_t buf[4]; 747 | uint32_t bits[2]; 748 | unsigned char in[64]; 749 | } mg_md5_ctx; 750 | 751 | void mg_md5_init(mg_md5_ctx *c); 752 | void mg_md5_update(mg_md5_ctx *c, const unsigned char *data, size_t len); 753 | void mg_md5_final(mg_md5_ctx *c, unsigned char[16]); 754 | 755 | 756 | 757 | 758 | typedef struct { 759 | uint32_t state[5]; 760 | uint32_t count[2]; 761 | unsigned char buffer[64]; 762 | } mg_sha1_ctx; 763 | 764 | void mg_sha1_init(mg_sha1_ctx *); 765 | void mg_sha1_update(mg_sha1_ctx *, const unsigned char *data, size_t len); 766 | void mg_sha1_final(unsigned char digest[20], mg_sha1_ctx *); 767 | void mg_hmac_sha1(const unsigned char *key, size_t key_len, 768 | const unsigned char *text, size_t text_len, 769 | unsigned char out[20]); 770 | 771 | 772 | struct mg_connection; 773 | typedef void (*mg_event_handler_t)(struct mg_connection *, int ev, 774 | void *ev_data, void *fn_data); 775 | void mg_call(struct mg_connection *c, int ev, void *ev_data); 776 | void mg_error(struct mg_connection *c, const char *fmt, ...); 777 | 778 | enum { 779 | MG_EV_ERROR, // Error char *error_message 780 | MG_EV_OPEN, // Connection created NULL 781 | MG_EV_POLL, // mg_mgr_poll iteration int64_t *milliseconds 782 | MG_EV_RESOLVE, // Host name is resolved NULL 783 | MG_EV_CONNECT, // Connection established NULL 784 | MG_EV_ACCEPT, // Connection accepted NULL 785 | MG_EV_READ, // Data received from socket struct mg_str * 786 | MG_EV_WRITE, // Data written to socket long *bytes_written 787 | MG_EV_CLOSE, // Connection closed NULL 788 | MG_EV_HTTP_MSG, // HTTP request/response struct mg_http_message * 789 | MG_EV_HTTP_CHUNK, // HTTP chunk (partial msg) struct mg_http_message * 790 | MG_EV_WS_OPEN, // Websocket handshake done struct mg_http_message * 791 | MG_EV_WS_MSG, // Websocket msg, text or bin struct mg_ws_message * 792 | MG_EV_WS_CTL, // Websocket control msg struct mg_ws_message * 793 | MG_EV_MQTT_CMD, // MQTT low-level command struct mg_mqtt_message * 794 | MG_EV_MQTT_MSG, // MQTT PUBLISH received struct mg_mqtt_message * 795 | MG_EV_MQTT_OPEN, // MQTT CONNACK received int *connack_status_code 796 | MG_EV_SNTP_TIME, // SNTP time received int64_t *milliseconds 797 | MG_EV_USER, // Starting ID for user events 798 | }; 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | struct mg_dns { 807 | const char *url; // DNS server URL 808 | struct mg_connection *c; // DNS server connection 809 | }; 810 | 811 | struct mg_addr { 812 | uint16_t port; // TCP or UDP port in network byte order 813 | uint32_t ip; // IP address in network byte order 814 | uint8_t ip6[16]; // IPv6 address 815 | bool is_ip6; // True when address is IPv6 address 816 | }; 817 | 818 | struct mg_mgr { 819 | struct mg_connection *conns; // List of active connections 820 | struct mg_dns dns4; // DNS for IPv4 821 | struct mg_dns dns6; // DNS for IPv6 822 | int dnstimeout; // DNS resolve timeout in milliseconds 823 | unsigned long nextid; // Next connection ID 824 | void *userdata; // Arbitrary user data pointer 825 | #if MG_ARCH == MG_ARCH_FREERTOS_TCP 826 | SocketSet_t ss; // NOTE(lsm): referenced from socket struct 827 | #endif 828 | }; 829 | 830 | struct mg_connection { 831 | struct mg_connection *next; // Linkage in struct mg_mgr :: connections 832 | struct mg_mgr *mgr; // Our container 833 | struct mg_addr peer; // Remote address. For listeners, local address 834 | void *fd; // Connected socket, or LWIP data 835 | unsigned long id; // Auto-incrementing unique connection ID 836 | struct mg_iobuf recv; // Incoming data 837 | struct mg_iobuf send; // Outgoing data 838 | mg_event_handler_t fn; // User-specified event handler function 839 | void *fn_data; // User-specified function parameter 840 | mg_event_handler_t pfn; // Protocol-specific handler function 841 | void *pfn_data; // Protocol-specific function parameter 842 | char label[50]; // Arbitrary label 843 | void *tls; // TLS specific data 844 | unsigned is_listening : 1; // Listening connection 845 | unsigned is_client : 1; // Outbound (client) connection 846 | unsigned is_accepted : 1; // Accepted (server) connection 847 | unsigned is_resolving : 1; // Non-blocking DNS resolution is in progress 848 | unsigned is_connecting : 1; // Non-blocking connect is in progress 849 | unsigned is_tls : 1; // TLS-enabled connection 850 | unsigned is_tls_hs : 1; // TLS handshake is in progress 851 | unsigned is_udp : 1; // UDP connection 852 | unsigned is_websocket : 1; // WebSocket connection 853 | unsigned is_hexdumping : 1; // Hexdump in/out traffic 854 | unsigned is_draining : 1; // Send remaining data, then close and free 855 | unsigned is_closing : 1; // Close and free the connection immediately 856 | unsigned is_readable : 1; // Connection is ready to read 857 | unsigned is_writable : 1; // Connection is ready to write 858 | }; 859 | 860 | void mg_mgr_poll(struct mg_mgr *, int ms); 861 | void mg_mgr_init(struct mg_mgr *); 862 | void mg_mgr_free(struct mg_mgr *); 863 | 864 | struct mg_connection *mg_listen(struct mg_mgr *, const char *url, 865 | mg_event_handler_t fn, void *fn_data); 866 | struct mg_connection *mg_connect(struct mg_mgr *, const char *url, 867 | mg_event_handler_t fn, void *fn_data); 868 | void mg_connect_resolved(struct mg_connection *); 869 | bool mg_send(struct mg_connection *, const void *, size_t); 870 | int mg_printf(struct mg_connection *, const char *fmt, ...); 871 | int mg_vprintf(struct mg_connection *, const char *fmt, va_list ap); 872 | char *mg_straddr(struct mg_addr *, char *, size_t); 873 | bool mg_aton(struct mg_str str, struct mg_addr *addr); 874 | char *mg_ntoa(const struct mg_addr *addr, char *buf, size_t len); 875 | 876 | struct mg_connection *mg_mkpipe(struct mg_mgr *, mg_event_handler_t, void *); 877 | void mg_mgr_wakeup(struct mg_connection *pipe); 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | struct mg_http_header { 887 | struct mg_str name; // Header name 888 | struct mg_str value; // Header value 889 | }; 890 | 891 | struct mg_http_message { 892 | struct mg_str method, uri, query, proto; // Request/response line 893 | struct mg_http_header headers[MG_MAX_HTTP_HEADERS]; // Headers 894 | struct mg_str body; // Body 895 | struct mg_str head; // Request + headers 896 | struct mg_str chunk; // Chunk for chunked encoding, or partial body 897 | struct mg_str message; // Request + headers + body 898 | }; 899 | 900 | // Parameter for mg_http_serve_dir() 901 | struct mg_http_serve_opts { 902 | const char *root_dir; // Web root directory, must be non-NULL 903 | const char *ssi_pattern; // SSI file name pattern, e.g. #.shtml 904 | const char *extra_headers; // Extra HTTP headers to add in responses 905 | const char *mime_types; // Extra mime types, ext1=type1,ext2=type2,.. 906 | struct mg_fs *fs; // Filesystem implementation. Use NULL for POSIX 907 | }; 908 | 909 | // Parameter for mg_http_next_multipart 910 | struct mg_http_part { 911 | struct mg_str name; // Form field name 912 | struct mg_str filename; // Filename for file uploads 913 | struct mg_str body; // Part contents 914 | }; 915 | 916 | int mg_http_parse(const char *s, size_t len, struct mg_http_message *); 917 | int mg_http_get_request_len(const unsigned char *buf, size_t buf_len); 918 | void mg_http_printf_chunk(struct mg_connection *cnn, const char *fmt, ...); 919 | void mg_http_write_chunk(struct mg_connection *c, const char *buf, size_t len); 920 | void mg_http_delete_chunk(struct mg_connection *c, struct mg_http_message *hm); 921 | struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url, 922 | mg_event_handler_t fn, void *fn_data); 923 | struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, 924 | mg_event_handler_t fn, void *fn_data); 925 | void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, 926 | struct mg_http_serve_opts *opts); 927 | void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm, 928 | const char *path, struct mg_http_serve_opts *opts); 929 | void mg_http_reply(struct mg_connection *, int status_code, const char *headers, 930 | const char *body_fmt, ...); 931 | struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); 932 | int mg_http_get_var(const struct mg_str *, const char *name, char *, size_t); 933 | int mg_url_decode(const char *s, size_t n, char *to, size_t to_len, int form); 934 | size_t mg_url_encode(const char *s, size_t n, char *buf, size_t len); 935 | void mg_http_creds(struct mg_http_message *, char *, size_t, char *, size_t); 936 | bool mg_http_match_uri(const struct mg_http_message *, const char *glob); 937 | int mg_http_upload(struct mg_connection *, struct mg_http_message *hm, 938 | struct mg_fs *fs, const char *dir); 939 | void mg_http_bauth(struct mg_connection *, const char *user, const char *pass); 940 | struct mg_str mg_http_get_header_var(struct mg_str s, struct mg_str v); 941 | size_t mg_http_next_multipart(struct mg_str, size_t, struct mg_http_part *); 942 | 943 | 944 | void mg_http_serve_ssi(struct mg_connection *c, const char *root, 945 | const char *fullpath); 946 | 947 | 948 | 949 | 950 | 951 | 952 | struct mg_tls_opts { 953 | const char *ca; // CA certificate file. For both listeners and clients 954 | const char *crl; // Certificate Revocation List. For clients 955 | const char *cert; // Certificate 956 | const char *certkey; // Certificate key 957 | const char *ciphers; // Cipher list 958 | struct mg_str srvname; // If not empty, enables server name verification 959 | struct mg_fs *fs; // FS API for reading certificate files 960 | }; 961 | 962 | void mg_tls_init(struct mg_connection *, struct mg_tls_opts *); 963 | void mg_tls_free(struct mg_connection *); 964 | long mg_tls_send(struct mg_connection *, const void *buf, size_t len); 965 | long mg_tls_recv(struct mg_connection *, void *buf, size_t len); 966 | void mg_tls_handshake(struct mg_connection *); 967 | 968 | 969 | #if MG_ENABLE_MBEDTLS 970 | 971 | 972 | 973 | 974 | #include 975 | #include 976 | #include 977 | 978 | struct mg_tls { 979 | char *cafile; // CA certificate path 980 | mbedtls_x509_crt ca; // Parsed CA certificate 981 | mbedtls_x509_crl crl; // Parsed Certificate Revocation List 982 | mbedtls_x509_crt cert; // Parsed certificate 983 | mbedtls_ssl_context ssl; // SSL/TLS context 984 | mbedtls_ssl_config conf; // SSL-TLS config 985 | mbedtls_pk_context pk; // Private key context 986 | }; 987 | #endif 988 | 989 | 990 | #if MG_ENABLE_OPENSSL 991 | 992 | #include 993 | #include 994 | 995 | struct mg_tls { 996 | SSL_CTX *ctx; 997 | SSL *ssl; 998 | }; 999 | #endif 1000 | 1001 | 1002 | #define WEBSOCKET_OP_CONTINUE 0 1003 | #define WEBSOCKET_OP_TEXT 1 1004 | #define WEBSOCKET_OP_BINARY 2 1005 | #define WEBSOCKET_OP_CLOSE 8 1006 | #define WEBSOCKET_OP_PING 9 1007 | #define WEBSOCKET_OP_PONG 10 1008 | 1009 | 1010 | 1011 | struct mg_ws_message { 1012 | struct mg_str data; // Websocket message data 1013 | uint8_t flags; // Websocket message flags 1014 | }; 1015 | 1016 | struct mg_connection *mg_ws_connect(struct mg_mgr *, const char *url, 1017 | mg_event_handler_t fn, void *fn_data, 1018 | const char *fmt, ...); 1019 | void mg_ws_upgrade(struct mg_connection *, struct mg_http_message *, 1020 | const char *fmt, ...); 1021 | size_t mg_ws_send(struct mg_connection *, const char *buf, size_t len, int op); 1022 | size_t mg_ws_wrap(struct mg_connection *, size_t len, int op); 1023 | 1024 | 1025 | 1026 | 1027 | struct mg_connection *mg_sntp_connect(struct mg_mgr *mgr, const char *url, 1028 | mg_event_handler_t fn, void *fn_data); 1029 | void mg_sntp_send(struct mg_connection *c, unsigned long utc); 1030 | int64_t mg_sntp_parse(const unsigned char *buf, size_t len); 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | #define MQTT_CMD_CONNECT 1 1037 | #define MQTT_CMD_CONNACK 2 1038 | #define MQTT_CMD_PUBLISH 3 1039 | #define MQTT_CMD_PUBACK 4 1040 | #define MQTT_CMD_PUBREC 5 1041 | #define MQTT_CMD_PUBREL 6 1042 | #define MQTT_CMD_PUBCOMP 7 1043 | #define MQTT_CMD_SUBSCRIBE 8 1044 | #define MQTT_CMD_SUBACK 9 1045 | #define MQTT_CMD_UNSUBSCRIBE 10 1046 | #define MQTT_CMD_UNSUBACK 11 1047 | #define MQTT_CMD_PINGREQ 12 1048 | #define MQTT_CMD_PINGRESP 13 1049 | #define MQTT_CMD_DISCONNECT 14 1050 | 1051 | struct mg_mqtt_opts { 1052 | struct mg_str user; // Username, can be empty 1053 | struct mg_str pass; // Password, can be empty 1054 | struct mg_str client_id; // Client ID 1055 | struct mg_str will_topic; // Will topic 1056 | struct mg_str will_message; // Will message 1057 | uint8_t will_qos; // Will message quality of service 1058 | bool will_retain; // Retain last will 1059 | bool clean; // Use clean session, 0 or 1 1060 | uint16_t keepalive; // Keep-alive timer in seconds 1061 | }; 1062 | 1063 | struct mg_mqtt_message { 1064 | struct mg_str topic; // Parsed topic 1065 | struct mg_str data; // Parsed message 1066 | struct mg_str dgram; // Whole MQTT datagram, including headers 1067 | uint16_t id; // Set for PUBACK, PUBREC, PUBREL, PUBCOMP, SUBACK, PUBLISH 1068 | uint8_t cmd; // MQTT command, one of MQTT_CMD_* 1069 | uint8_t qos; // Quality of service 1070 | uint8_t ack; // Connack return code. 0 - success 1071 | }; 1072 | 1073 | struct mg_connection *mg_mqtt_connect(struct mg_mgr *, const char *url, 1074 | struct mg_mqtt_opts *opts, 1075 | mg_event_handler_t fn, void *fn_data); 1076 | struct mg_connection *mg_mqtt_listen(struct mg_mgr *mgr, const char *url, 1077 | mg_event_handler_t fn, void *fn_data); 1078 | void mg_mqtt_login(struct mg_connection *c, struct mg_mqtt_opts *opts); 1079 | void mg_mqtt_pub(struct mg_connection *c, struct mg_str *topic, 1080 | struct mg_str *data, int qos, bool retain); 1081 | void mg_mqtt_sub(struct mg_connection *, struct mg_str *topic, int qos); 1082 | int mg_mqtt_parse(const uint8_t *buf, size_t len, struct mg_mqtt_message *m); 1083 | void mg_mqtt_send_header(struct mg_connection *, uint8_t cmd, uint8_t flags, 1084 | uint32_t len); 1085 | size_t mg_mqtt_next_sub(struct mg_mqtt_message *msg, struct mg_str *topic, 1086 | uint8_t *qos, size_t pos); 1087 | size_t mg_mqtt_next_unsub(struct mg_mqtt_message *msg, struct mg_str *topic, 1088 | size_t pos); 1089 | void mg_mqtt_ping(struct mg_connection *); 1090 | void mg_mqtt_pong(struct mg_connection *); 1091 | void mg_mqtt_disconnect(struct mg_connection *); 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | // Mongoose sends DNS queries that contain only one question: 1098 | // either A (IPv4) or AAAA (IPv6) address lookup. 1099 | // Therefore, we expect zero or one answer. 1100 | // If `resolved` is true, then `addr` contains resolved IPv4 or IPV6 address. 1101 | struct mg_dns_message { 1102 | uint16_t txnid; // Transaction ID 1103 | bool resolved; // Resolve successful, addr is set 1104 | struct mg_addr addr; // Resolved address 1105 | char name[256]; // Host name 1106 | }; 1107 | 1108 | struct mg_dns_header { 1109 | uint16_t txnid; // Transaction ID 1110 | uint16_t flags; 1111 | uint16_t num_questions; 1112 | uint16_t num_answers; 1113 | uint16_t num_authority_prs; 1114 | uint16_t num_other_prs; 1115 | }; 1116 | 1117 | // DNS resource record 1118 | struct mg_dns_rr { 1119 | uint16_t nlen; // Name or pointer length 1120 | uint16_t atype; // Address type 1121 | uint16_t aclass; // Address class 1122 | uint16_t alen; // Address length 1123 | }; 1124 | 1125 | void mg_resolve(struct mg_connection *, const char *url); 1126 | void mg_resolve_cancel(struct mg_connection *); 1127 | bool mg_dns_parse(const uint8_t *buf, size_t len, struct mg_dns_message *); 1128 | size_t mg_dns_parse_rr(const uint8_t *buf, size_t len, size_t ofs, 1129 | bool is_question, struct mg_dns_rr *); 1130 | size_t mg_dns_decode_name(const uint8_t *, size_t, size_t, char *, size_t); 1131 | 1132 | #ifdef __cplusplus 1133 | } 1134 | #endif 1135 | #endif // MONGOOSE_H 1136 | -------------------------------------------------------------------------------- /mjson.c: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018-2020 Cesanta Software Limited 2 | // All rights reserved 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in 12 | // all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | 22 | #include 23 | #include 24 | 25 | #include "mjson.h" 26 | 27 | #if defined(_MSC_VER) 28 | #define alloca(x) _alloca(x) 29 | #endif 30 | 31 | #if defined(_MSC_VER) && _MSC_VER < 1700 32 | #define va_copy(x, y) (x) = (y) 33 | #define isinf(x) !_finite(x) 34 | #define isnan(x) _isnan(x) 35 | #endif 36 | 37 | static double mystrtod(const char *str, char **end); 38 | 39 | static int mjson_esc(int c, int esc) { 40 | const char *p, *esc1 = "\b\f\n\r\t\\\"", *esc2 = "bfnrt\\\""; 41 | for (p = esc ? esc1 : esc2; *p != '\0'; p++) { 42 | if (*p == c) return esc ? esc2[p - esc1] : esc1[p - esc2]; 43 | } 44 | return 0; 45 | } 46 | 47 | static int mjson_escape(int c) { 48 | return mjson_esc(c, 1); 49 | } 50 | 51 | static int mjson_pass_string(const char *s, int len) { 52 | int i; 53 | for (i = 0; i < len; i++) { 54 | if (s[i] == '\\' && i + 1 < len && mjson_escape(s[i + 1])) { 55 | i++; 56 | } else if (s[i] == '\0') { 57 | return MJSON_ERROR_INVALID_INPUT; 58 | } else if (s[i] == '"') { 59 | return i; 60 | } 61 | } 62 | return MJSON_ERROR_INVALID_INPUT; 63 | } 64 | 65 | int mjson(const char *s, int len, mjson_cb_t cb, void *ud) { 66 | enum { S_VALUE, S_KEY, S_COLON, S_COMMA_OR_EOO } expecting = S_VALUE; 67 | unsigned char nesting[MJSON_MAX_DEPTH]; 68 | int i, depth = 0; 69 | #define MJSONCALL(ev) \ 70 | if (cb != NULL && cb(ev, s, start, i - start + 1, ud)) return i + 1; 71 | 72 | // In the ascii table, the distance between `[` and `]` is 2. 73 | // Ditto for `{` and `}`. Hence +2 in the code below. 74 | #define MJSONEOO() \ 75 | do { \ 76 | if (c != nesting[depth - 1] + 2) return MJSON_ERROR_INVALID_INPUT; \ 77 | depth--; \ 78 | if (depth == 0) { \ 79 | MJSONCALL(tok); \ 80 | return i + 1; \ 81 | } \ 82 | } while (0) 83 | 84 | for (i = 0; i < len; i++) { 85 | int start = i; 86 | unsigned char c = ((unsigned char *) s)[i]; 87 | int tok = c; 88 | if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue; 89 | // printf("- %c [%.*s] %d %d\n", c, i, s, depth, expecting); 90 | switch (expecting) { 91 | case S_VALUE: 92 | if (c == '{') { 93 | if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP; 94 | nesting[depth++] = c; 95 | expecting = S_KEY; 96 | break; 97 | } else if (c == '[') { 98 | if (depth >= (int) sizeof(nesting)) return MJSON_ERROR_TOO_DEEP; 99 | nesting[depth++] = c; 100 | break; 101 | } else if (c == ']' && depth > 0) { // Empty array 102 | MJSONEOO(); 103 | } else if (c == 't' && i + 3 < len && memcmp(&s[i], "true", 4) == 0) { 104 | i += 3; 105 | tok = MJSON_TOK_TRUE; 106 | } else if (c == 'n' && i + 3 < len && memcmp(&s[i], "null", 4) == 0) { 107 | i += 3; 108 | tok = MJSON_TOK_NULL; 109 | } else if (c == 'f' && i + 4 < len && memcmp(&s[i], "false", 5) == 0) { 110 | i += 4; 111 | tok = MJSON_TOK_FALSE; 112 | } else if (c == '-' || ((c >= '0' && c <= '9'))) { 113 | char *end = NULL; 114 | mystrtod(&s[i], &end); 115 | if (end != NULL) i += (int) (end - &s[i] - 1); 116 | tok = MJSON_TOK_NUMBER; 117 | } else if (c == '"') { 118 | int n = mjson_pass_string(&s[i + 1], len - i - 1); 119 | if (n < 0) return n; 120 | i += n + 1; 121 | tok = MJSON_TOK_STRING; 122 | } else { 123 | return MJSON_ERROR_INVALID_INPUT; 124 | } 125 | if (depth == 0) { 126 | MJSONCALL(tok); 127 | return i + 1; 128 | } 129 | expecting = S_COMMA_OR_EOO; 130 | break; 131 | 132 | case S_KEY: 133 | if (c == '"') { 134 | int n = mjson_pass_string(&s[i + 1], len - i - 1); 135 | if (n < 0) return n; 136 | i += n + 1; 137 | tok = MJSON_TOK_KEY; 138 | expecting = S_COLON; 139 | } else if (c == '}') { // Empty object 140 | MJSONEOO(); 141 | expecting = S_COMMA_OR_EOO; 142 | } else { 143 | return MJSON_ERROR_INVALID_INPUT; 144 | } 145 | break; 146 | 147 | case S_COLON: 148 | if (c == ':') { 149 | expecting = S_VALUE; 150 | } else { 151 | return MJSON_ERROR_INVALID_INPUT; 152 | } 153 | break; 154 | 155 | case S_COMMA_OR_EOO: 156 | if (depth <= 0) return MJSON_ERROR_INVALID_INPUT; 157 | if (c == ',') { 158 | expecting = (nesting[depth - 1] == '{') ? S_KEY : S_VALUE; 159 | } else if (c == ']' || c == '}') { 160 | MJSONEOO(); 161 | } else { 162 | return MJSON_ERROR_INVALID_INPUT; 163 | } 164 | break; 165 | } 166 | MJSONCALL(tok); 167 | } 168 | return MJSON_ERROR_INVALID_INPUT; 169 | } 170 | 171 | struct msjon_get_data { 172 | const char *path; // Lookup json path 173 | int pos; // Current path position 174 | int d1; // Current depth of traversal 175 | int d2; // Expected depth of traversal 176 | int i1; // Index in an array 177 | int i2; // Expected index in an array 178 | int obj; // If the value is array/object, offset where it starts 179 | const char **tokptr; // Destination 180 | int *toklen; // Destination length 181 | int tok; // Returned token 182 | }; 183 | 184 | #include 185 | 186 | static int plen1(const char *s) { 187 | int i = 0, n = 0; 188 | while (s[i] != '\0' && s[i] != '.' && s[i] != '[') 189 | n++, i += s[i] == '\\' ? 2 : 1; 190 | // printf("PLEN: s: [%s], [%.*s] => %d\n", s, i, s, n); 191 | return n; 192 | } 193 | 194 | static int plen2(const char *s) { 195 | int i = 0, n = 0; 196 | while (s[i] != '\0' && s[i] != '.' && s[i] != '[') 197 | n++, i += s[i] == '\\' ? 2 : 1; 198 | // printf("PLEN: s: [%s], [%.*s] => %d\n", s, i, s, n); 199 | return i; 200 | } 201 | 202 | static int kcmp(const char *a, const char *b, int n) { 203 | int i = 0, j = 0, r = 0; 204 | for (i = 0, j = 0; j < n; i++, j++) { 205 | if (b[i] == '\\') i++; 206 | if ((r = a[j] - b[i]) != 0) return r; 207 | } 208 | // printf("KCMP: a: [%.*s], b:[%.*s] ==> %d\n", n, a, i, b, r); 209 | return r; 210 | } 211 | 212 | static int mjson_get_cb(int tok, const char *s, int off, int len, void *ud) { 213 | struct msjon_get_data *d = (struct msjon_get_data *) ud; 214 | #if 0 215 | printf("--> %2x %2d %2d %2d %2d\t %2d %2d\t'%s' '%s' '%s' '%s'\n", tok, d->d1, 216 | d->d2, d->i1, d->i2, (int) off, (int) d->pos, s, d->path, s + off, 217 | d->path + d->pos); 218 | #endif 219 | if (d->tok != MJSON_TOK_INVALID) return 1; // Found 220 | if (tok == '{' || tok == '[') { 221 | if (d->d1 < d->d2) d->obj = -1; 222 | if (d->d1 == d->d2) d->obj = off; 223 | if (d->d1 == d->d2 && tok == '[' && d->path[d->pos] == '[') { 224 | d->i1 = 0; 225 | d->i2 = (int) mystrtod(&d->path[d->pos + 1], NULL); 226 | if (d->i1 == d->i2) { 227 | while (d->path[d->pos] && d->path[d->pos] != ']') d->pos++; 228 | if (d->path[d->pos] == ']') d->pos++; 229 | d->d2++; 230 | } 231 | } 232 | d->d1++; 233 | } else if (tok == '}' || tok == ']') { 234 | if (tok == ']' && d->d1 == d->d2) d->i1 = 0; 235 | d->d1--; 236 | // printf("X %s %d %d %d %d %d\n", d->path + d->pos, d->d1, d->d2, d->i1, 237 | // d->i2, d->obj); 238 | if (!d->path[d->pos] && d->d1 == d->d2 && d->obj != -1) { 239 | d->tok = tok - 2; 240 | if (d->tokptr) *d->tokptr = s + d->obj; 241 | if (d->toklen) *d->toklen = off - d->obj + 1; 242 | return 1; 243 | } 244 | } else if (tok == ',' && d->d1 == d->d2 && d->pos && 245 | d->path[d->pos - 1] == ']') { 246 | return 1; // Not found in the current elem array 247 | } else if (tok == ',' && d->d1 == d->d2 + 1 && d->path[d->pos] == '[') { 248 | // printf("GG '%s' '%s'\n", d->path, &d->path[d->pos]); 249 | d->i1++; 250 | if (d->i1 == d->i2) { 251 | while (d->path[d->pos] && d->path[d->pos] != ']') d->pos++; 252 | if (d->path[d->pos] == ']') d->pos++; 253 | d->d2++; 254 | } 255 | } else if (tok == MJSON_TOK_KEY && d->d1 == d->d2 + 1 && 256 | d->path[d->pos] == '.' && s[off] == '"' && 257 | s[off + len - 1] == '"' && 258 | plen1(&d->path[d->pos + 1]) == len - 2 && 259 | kcmp(s + off + 1, &d->path[d->pos + 1], len - 2) == 0) { 260 | d->d2++; 261 | d->pos += plen2(&d->path[d->pos + 1]) + 1; 262 | } else if (tok == MJSON_TOK_KEY && d->d1 == d->d2) { 263 | return 1; // Exhausted path, not found 264 | } else if (MJSON_TOK_IS_VALUE(tok)) { 265 | // printf("T %d %d %d %d %d\n", tok, d->d1, d->d2, d->i1, d->i2); 266 | if (d->d1 == d->d2 && d->i1 == d->i2 && !d->path[d->pos]) { 267 | d->tok = tok; 268 | if (d->tokptr) *d->tokptr = s + off; 269 | if (d->toklen) *d->toklen = len; 270 | return 1; 271 | } 272 | } 273 | return 0; 274 | } 275 | 276 | int mjson_find(const char *s, int n, const char *jp, const char **tp, int *tl) { 277 | struct msjon_get_data data = {jp, 1, 0, 0, 0, 278 | 0, -1, tp, tl, MJSON_TOK_INVALID}; 279 | if (jp[0] != '$') return MJSON_TOK_INVALID; 280 | if (mjson(s, n, mjson_get_cb, &data) < 0) return MJSON_TOK_INVALID; 281 | return data.tok; 282 | } 283 | 284 | int mjson_get_number(const char *s, int len, const char *path, double *v) { 285 | const char *p; 286 | int tok, n; 287 | if ((tok = mjson_find(s, len, path, &p, &n)) == MJSON_TOK_NUMBER) { 288 | if (v != NULL) *v = mystrtod(p, NULL); 289 | } 290 | return tok == MJSON_TOK_NUMBER ? 1 : 0; 291 | } 292 | 293 | int mjson_get_bool(const char *s, int len, const char *path, int *v) { 294 | int tok = mjson_find(s, len, path, NULL, NULL); 295 | if (tok == MJSON_TOK_TRUE && v != NULL) *v = 1; 296 | if (tok == MJSON_TOK_FALSE && v != NULL) *v = 0; 297 | return tok == MJSON_TOK_TRUE || tok == MJSON_TOK_FALSE ? 1 : 0; 298 | } 299 | 300 | static unsigned char unhex(unsigned char c) { 301 | return (c >= '0' && c <= '9') ? (unsigned char) (c - '0') 302 | : (c >= 'A' && c <= 'F') ? (unsigned char) (c - '7') 303 | : (unsigned char) (c - 'W'); 304 | } 305 | 306 | static unsigned char mjson_unhex_nimble(const char *s) { 307 | return (unsigned char) (unhex(((unsigned char *) s)[0]) << 4) | 308 | unhex(((unsigned char *) s)[1]); 309 | } 310 | 311 | static int mjson_unescape(const char *s, int len, char *to, int n) { 312 | int i, j; 313 | for (i = 0, j = 0; i < len && j < n; i++, j++) { 314 | if (s[i] == '\\' && i + 5 < len && s[i + 1] == 'u') { 315 | // \uXXXX escape. We could process a simple one-byte chars 316 | // \u00xx from the ASCII range. More complex chars would require 317 | // dragging in a UTF8 library, which is too much for us 318 | if (s[i + 2] != '0' || s[i + 3] != '0') return -1; // Too much, give up 319 | ((unsigned char *) to)[j] = mjson_unhex_nimble(s + i + 4); 320 | i += 5; 321 | } else if (s[i] == '\\' && i + 1 < len) { 322 | int c = mjson_esc(s[i + 1], 0); 323 | if (c == 0) return -1; 324 | to[j] = (char) (unsigned char) c; 325 | i++; 326 | } else { 327 | to[j] = s[i]; 328 | } 329 | } 330 | if (j >= n) return -1; 331 | if (n > 0) to[j] = '\0'; 332 | return j; 333 | } 334 | 335 | int mjson_get_string(const char *s, int len, const char *path, char *to, 336 | int n) { 337 | const char *p; 338 | int sz; 339 | if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return -1; 340 | return mjson_unescape(p + 1, sz - 2, to, n); 341 | } 342 | 343 | int mjson_get_hex(const char *s, int len, const char *x, char *to, int n) { 344 | const char *p; 345 | int i, j, sz; 346 | if (mjson_find(s, len, x, &p, &sz) != MJSON_TOK_STRING) return -1; 347 | for (i = j = 0; i < sz - 3 && j < n; i += 2, j++) { 348 | ((unsigned char *) to)[j] = mjson_unhex_nimble(p + i + 1); 349 | } 350 | if (j < n) to[j] = '\0'; 351 | return j; 352 | } 353 | 354 | #if MJSON_ENABLE_BASE64 355 | static unsigned char mjson_base64rev(int c) { 356 | if (c >= 'A' && c <= 'Z') { 357 | return (unsigned char) (c - 'A'); 358 | } else if (c >= 'a' && c <= 'z') { 359 | return (unsigned char) (c + 26 - 'a'); 360 | } else if (c >= '0' && c <= '9') { 361 | return (unsigned char) (c + 52 - '0'); 362 | } else if (c == '+') { 363 | return 62; 364 | } else if (c == '/') { 365 | return 63; 366 | } else { 367 | return 64; 368 | } 369 | } 370 | 371 | int mjson_base64_dec(const char *src, int n, char *dst, int dlen) { 372 | const char *end = src + n; 373 | int len = 0; 374 | while (src + 3 < end && len < dlen) { 375 | unsigned char a = mjson_base64rev(src[0]), b = mjson_base64rev(src[1]), 376 | c = mjson_base64rev(src[2]), d = mjson_base64rev(src[3]); 377 | dst[len++] = (char) (unsigned char) ((a << 2) | (b >> 4)); 378 | if (src[2] != '=' && len < dlen) { 379 | dst[len++] = (char) (unsigned char) ((b << 4) | (c >> 2)); 380 | if (src[3] != '=' && len < dlen) { 381 | dst[len++] = (char) (unsigned char) ((c << 6) | d); 382 | } 383 | } 384 | src += 4; 385 | } 386 | if (len < dlen) dst[len] = '\0'; 387 | return len; 388 | } 389 | 390 | int mjson_get_base64(const char *s, int len, const char *path, char *to, 391 | int n) { 392 | const char *p; 393 | int sz; 394 | if (mjson_find(s, len, path, &p, &sz) != MJSON_TOK_STRING) return 0; 395 | return mjson_base64_dec(p + 1, sz - 2, to, n); 396 | } 397 | #endif // MJSON_ENABLE_BASE64 398 | 399 | #if MJSON_ENABLE_NEXT 400 | struct nextdata { 401 | int off, len, depth, t, vo, arrayindex; 402 | int *koff, *klen, *voff, *vlen, *vtype; 403 | }; 404 | 405 | static int next_cb(int tok, const char *s, int off, int len, void *ud) { 406 | struct nextdata *d = (struct nextdata *) ud; 407 | // int i; 408 | switch (tok) { 409 | case '{': 410 | case '[': 411 | if (d->depth == 0 && tok == '[') d->arrayindex = 0; 412 | if (d->depth == 1 && off > d->off) { 413 | d->vo = off; 414 | d->t = tok == '{' ? MJSON_TOK_OBJECT : MJSON_TOK_ARRAY; 415 | if (d->voff) *d->voff = off; 416 | if (d->vtype) *d->vtype = d->t; 417 | } 418 | d->depth++; 419 | break; 420 | case '}': 421 | case ']': 422 | d->depth--; 423 | if (d->depth == 1 && d->vo) { 424 | d->len = off + len; 425 | if (d->vlen) *d->vlen = d->len - d->vo; 426 | if (d->arrayindex >= 0) { 427 | if (d->koff) *d->koff = d->arrayindex; // koff holds array index 428 | if (d->klen) *d->klen = 0; // klen holds 0 429 | } 430 | return 1; 431 | } 432 | if (d->depth == 1 && d->arrayindex >= 0) d->arrayindex++; 433 | break; 434 | case ',': 435 | case ':': 436 | break; 437 | case MJSON_TOK_KEY: 438 | if (d->depth == 1 && d->off < off) { 439 | if (d->koff) *d->koff = off; // And report back to the user 440 | if (d->klen) *d->klen = len; // If we have to 441 | } 442 | break; 443 | default: 444 | if (d->depth != 1) break; 445 | // If we're iterating over the array 446 | if (off > d->off) { 447 | d->len = off + len; 448 | if (d->vlen) *d->vlen = len; // value length 449 | if (d->voff) *d->voff = off; // value offset 450 | if (d->vtype) *d->vtype = tok; // value type 451 | if (d->arrayindex >= 0) { 452 | if (d->koff) *d->koff = d->arrayindex; // koff holds array index 453 | if (d->klen) *d->klen = 0; // klen holds 0 454 | } 455 | return 1; 456 | } 457 | if (d->arrayindex >= 0) d->arrayindex++; 458 | break; 459 | } 460 | (void) s; 461 | return 0; 462 | } 463 | 464 | int mjson_next(const char *s, int n, int off, int *koff, int *klen, int *voff, 465 | int *vlen, int *vtype) { 466 | struct nextdata d = {off, 0, 0, 0, 0, -1, koff, klen, voff, vlen, vtype}; 467 | mjson(s, n, next_cb, &d); 468 | return d.len; 469 | } 470 | #endif 471 | 472 | #if MJSON_ENABLE_PRINT 473 | int mjson_print_fixed_buf(const char *ptr, int len, void *fndata) { 474 | struct mjson_fixedbuf *fb = (struct mjson_fixedbuf *) fndata; 475 | int i, left = fb->size - 1 - fb->len; 476 | if (left < len) len = left; 477 | for (i = 0; i < len; i++) fb->ptr[fb->len + i] = ptr[i]; 478 | fb->len += len; 479 | fb->ptr[fb->len] = '\0'; 480 | return len; 481 | } 482 | 483 | // This function allocates memory in chunks of size MJSON_DYNBUF_CHUNK 484 | // to decrease memory fragmentation, when many calls are executed to 485 | // print e.g. a base64 string or a hex string. 486 | int mjson_print_dynamic_buf(const char *ptr, int len, void *fndata) { 487 | char *s, *buf = *(char **) fndata; 488 | size_t curlen = buf == NULL ? 0 : strlen(buf); 489 | size_t new_size = curlen + (size_t) len + 1 + MJSON_DYNBUF_CHUNK; 490 | new_size -= new_size % MJSON_DYNBUF_CHUNK; 491 | 492 | if ((s = (char *) realloc(buf, new_size)) == NULL) { 493 | return 0; 494 | } else { 495 | memcpy(s + curlen, ptr, (size_t) len); 496 | s[curlen + (size_t) len] = '\0'; 497 | *(char **) fndata = s; 498 | return len; 499 | } 500 | } 501 | 502 | int mjson_snprintf(char *buf, size_t len, const char *fmt, ...) { 503 | va_list ap; 504 | struct mjson_fixedbuf fb = {buf, (int) len, 0}; 505 | va_start(ap, fmt); 506 | mjson_vprintf(mjson_print_fixed_buf, &fb, fmt, &ap); 507 | va_end(ap); 508 | return fb.len; 509 | } 510 | 511 | char *mjson_aprintf(const char *fmt, ...) { 512 | va_list ap; 513 | char *result = NULL; 514 | va_start(ap, fmt); 515 | mjson_vprintf(mjson_print_dynamic_buf, &result, fmt, &ap); 516 | va_end(ap); 517 | return result; 518 | } 519 | 520 | int mjson_print_null(const char *ptr, int len, void *userdata) { 521 | (void) ptr; 522 | (void) userdata; 523 | return len; 524 | } 525 | 526 | int mjson_print_buf(mjson_print_fn_t fn, void *fnd, const char *buf, int len) { 527 | return fn(buf, len, fnd); 528 | } 529 | 530 | int mjson_print_long(mjson_print_fn_t fn, void *fnd, long val, int is_signed) { 531 | unsigned long v = (unsigned long) val, s = 0, n, i; 532 | char buf[20], t; 533 | if (is_signed && val < 0) buf[s++] = '-', v = (unsigned long) (-val); 534 | // This loop prints a number in reverse order. I guess this is because we 535 | // write numbers from right to left: least significant digit comes last. 536 | // Maybe because we use Arabic numbers, and Arabs write RTL? 537 | for (n = 0; v > 0; v /= 10) buf[s + n++] = "0123456789"[v % 10]; 538 | // Reverse a string 539 | for (i = 0; i < n / 2; i++) 540 | t = buf[s + i], buf[s + i] = buf[s + n - i - 1], buf[s + n - i - 1] = t; 541 | if (val == 0) buf[n++] = '0'; // Handle special case 542 | return fn(buf, (int) (s + n), fnd); 543 | } 544 | 545 | int mjson_print_int(mjson_print_fn_t fn, void *fnd, int v, int s) { 546 | return mjson_print_long(fn, fnd, s ? (long) v : (long) (unsigned) v, s); 547 | } 548 | 549 | static int addexp(char *buf, int e, int sign) { 550 | int n = 0; 551 | buf[n++] = 'e'; 552 | buf[n++] = (char) sign; 553 | if (e > 400) return 0; 554 | if (e < 10) buf[n++] = '0'; 555 | if (e >= 100) buf[n++] = (char) (e / 100 + '0'), e -= 100 * (e / 100); 556 | if (e >= 10) buf[n++] = (char) (e / 10 + '0'), e -= 10 * (e / 10); 557 | buf[n++] = (char) (e + '0'); 558 | return n; 559 | } 560 | 561 | int mjson_print_dbl(mjson_print_fn_t fn, void *fnd, double d, int width) { 562 | char buf[40]; 563 | int i, s = 0, n = 0, e = 0; 564 | double t, mul, saved; 565 | if (d == 0.0) return fn("0", 1, fnd); 566 | if (isinf(d)) return fn(d > 0 ? "inf" : "-inf", d > 0 ? 3 : 4, fnd); 567 | if (isnan(d)) return fn("nan", 3, fnd); 568 | if (d < 0.0) d = -d, buf[s++] = '-'; 569 | 570 | // Round 571 | saved = d; 572 | mul = 1.0; 573 | while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0; 574 | while (d <= 1.0 && d / mul <= 1.0) mul /= 10.0; 575 | for (i = 0, t = mul * 5; i < width; i++) t /= 10.0; 576 | d += t; 577 | // Calculate exponent, and 'mul' for scientific representation 578 | mul = 1.0; 579 | while (d >= 10.0 && d / mul >= 10.0) mul *= 10.0, e++; 580 | while (d < 1.0 && d / mul < 1.0) mul /= 10.0, e--; 581 | // printf(" --> %g %d %g %g\n", saved, e, t, mul); 582 | 583 | if (e >= width) { 584 | struct mjson_fixedbuf fb = {buf + s, (int) sizeof(buf) - s, 0}; 585 | n = mjson_print_dbl(mjson_print_fixed_buf, &fb, saved / mul, width); 586 | // printf(" --> %.*g %d [%.*s]\n", 10, d / t, e, fb.len, fb.ptr); 587 | n += addexp(buf + s + n, e, '+'); 588 | return fn(buf, s + n, fnd); 589 | } else if (e <= -width) { 590 | struct mjson_fixedbuf fb = {buf + s, (int) sizeof(buf) - s, 0}; 591 | n = mjson_print_dbl(mjson_print_fixed_buf, &fb, saved / mul, width); 592 | // printf(" --> %.*g %d [%.*s]\n", 10, d / mul, e, fb.len, fb.ptr); 593 | n += addexp(buf + s + n, -e, '-'); 594 | return fn(buf, s + n, fnd); 595 | } else { 596 | for (i = 0, t = mul; t >= 1.0 && s + n < (int) sizeof(buf); i++) { 597 | int ch = (int) (d / t); 598 | if (n > 0 || ch > 0) buf[s + n++] = (char) (ch + '0'); 599 | d -= ch * t; 600 | t /= 10.0; 601 | } 602 | // printf(" --> [%g] -> %g %g (%d) [%.*s]\n", saved, d, t, n, s + n, buf); 603 | if (n == 0) buf[s++] = '0'; 604 | while (t >= 1.0 && n + s < (int) sizeof(buf)) buf[n++] = '0', t /= 10.0; 605 | if (s + n < (int) sizeof(buf)) buf[n + s++] = '.'; 606 | // printf(" 1--> [%g] -> [%.*s]\n", saved, s + n, buf); 607 | for (i = 0, t = 0.1; s + n < (int) sizeof(buf) && n < width; i++) { 608 | int ch = (int) (d / t); 609 | buf[s + n++] = (char) (ch + '0'); 610 | d -= ch * t; 611 | t /= 10.0; 612 | } 613 | } 614 | while (n > 0 && buf[s + n - 1] == '0') n--; // Trim trailing zeros 615 | if (n > 0 && buf[s + n - 1] == '.') n--; // Trim trailing dot 616 | buf[s + n] = '\0'; 617 | return fn(buf, s + n, fnd); 618 | } 619 | 620 | int mjson_print_str(mjson_print_fn_t fn, void *fnd, const char *s, int len) { 621 | int i, n = fn("\"", 1, fnd); 622 | for (i = 0; i < len; i++) { 623 | char c = (char) (unsigned char) mjson_escape(s[i]); 624 | if (c) { 625 | n += fn("\\", 1, fnd); 626 | n += fn(&c, 1, fnd); 627 | } else { 628 | n += fn(&s[i], 1, fnd); 629 | } 630 | } 631 | return n + fn("\"", 1, fnd); 632 | } 633 | 634 | #if MJSON_ENABLE_BASE64 635 | int mjson_print_b64(mjson_print_fn_t fn, void *fnd, const unsigned char *s, 636 | int n) { 637 | const char *t = 638 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 639 | int i, len = fn("\"", 1, fnd); 640 | for (i = 0; i < n; i += 3) { 641 | int a = s[i], b = i + 1 < n ? s[i + 1] : 0, c = i + 2 < n ? s[i + 2] : 0; 642 | char buf[4] = {t[a >> 2], t[(a & 3) << 4 | (b >> 4)], '=', '='}; 643 | if (i + 1 < n) buf[2] = t[(b & 15) << 2 | (c >> 6)]; 644 | if (i + 2 < n) buf[3] = t[c & 63]; 645 | len += fn(buf, sizeof(buf), fnd); 646 | } 647 | return len + fn("\"", 1, fnd); 648 | } 649 | #endif /* MJSON_ENABLE_BASE64 */ 650 | 651 | int mjson_vprintf(mjson_print_fn_t fn, void *fnd, const char *fmt, 652 | va_list *ap) { 653 | int i = 0, n = 0; 654 | while (fmt[i] != '\0') { 655 | if (fmt[i] == '%') { 656 | char fc = fmt[++i]; 657 | int is_long = 0; 658 | if (fc == 'l') { 659 | is_long = 1; 660 | fc = fmt[i + 1]; 661 | } 662 | if (fc == 'Q') { 663 | char *buf = va_arg(*ap, char *); 664 | n += mjson_print_str(fn, fnd, buf ? buf : "", 665 | buf ? (int) strlen(buf) : 0); 666 | } else if (strncmp(&fmt[i], ".*Q", 3) == 0) { 667 | int len = va_arg(*ap, int); 668 | char *buf = va_arg(*ap, char *); 669 | n += mjson_print_str(fn, fnd, buf, len); 670 | i += 2; 671 | } else if (fc == 'd' || fc == 'u') { 672 | int is_signed = (fc == 'd'); 673 | if (is_long) { 674 | long val = va_arg(*ap, long); 675 | n += mjson_print_long(fn, fnd, val, is_signed); 676 | i++; 677 | } else { 678 | int val = va_arg(*ap, int); 679 | n += mjson_print_int(fn, fnd, val, is_signed); 680 | } 681 | } else if (fc == 'B') { 682 | const char *s = va_arg(*ap, int) ? "true" : "false"; 683 | n += mjson_print_buf(fn, fnd, s, (int) strlen(s)); 684 | } else if (fc == 's') { 685 | char *buf = va_arg(*ap, char *); 686 | n += mjson_print_buf(fn, fnd, buf, (int) strlen(buf)); 687 | } else if (strncmp(&fmt[i], ".*s", 3) == 0) { 688 | int len = va_arg(*ap, int); 689 | char *buf = va_arg(*ap, char *); 690 | n += mjson_print_buf(fn, fnd, buf, len); 691 | i += 2; 692 | } else if (fc == 'g') { 693 | n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), 6); 694 | } else if (strncmp(&fmt[i], ".*g", 3) == 0) { 695 | int width = va_arg(*ap, int); 696 | n += mjson_print_dbl(fn, fnd, va_arg(*ap, double), width); 697 | i += 2; 698 | #if MJSON_ENABLE_BASE64 699 | } else if (fc == 'V') { 700 | int len = va_arg(*ap, int); 701 | const char *buf = va_arg(*ap, const char *); 702 | n += mjson_print_b64(fn, fnd, (unsigned char *) buf, len); 703 | #endif 704 | } else if (fc == 'H') { 705 | const char *hex = "0123456789abcdef"; 706 | int j, len = va_arg(*ap, int); 707 | const unsigned char *p = va_arg(*ap, const unsigned char *); 708 | n += fn("\"", 1, fnd); 709 | for (j = 0; j < len; j++) { 710 | n += fn(&hex[(p[j] >> 4) & 15], 1, fnd); 711 | n += fn(&hex[p[j] & 15], 1, fnd); 712 | } 713 | n += fn("\"", 1, fnd); 714 | } else if (fc == 'M') { 715 | mjson_vprint_fn_t vfn = va_arg(*ap, mjson_vprint_fn_t); 716 | n += vfn(fn, fnd, ap); 717 | } 718 | i++; 719 | } else { 720 | n += mjson_print_buf(fn, fnd, &fmt[i++], 1); 721 | } 722 | } 723 | return n; 724 | } 725 | 726 | int mjson_printf(mjson_print_fn_t fn, void *fnd, const char *fmt, ...) { 727 | va_list ap; 728 | int len; 729 | va_start(ap, fmt); 730 | len = mjson_vprintf(fn, fnd, fmt, &ap); 731 | va_end(ap); 732 | return len; 733 | } 734 | #endif /* MJSON_ENABLE_PRINT */ 735 | 736 | static int is_digit(int c) { 737 | return c >= '0' && c <= '9'; 738 | } 739 | 740 | /* NOTE: strtod() implementation by Yasuhiro Matsumoto. */ 741 | static double mystrtod(const char *str, char **end) { 742 | double d = 0.0; 743 | int sign = 1, n = 0; 744 | const char *p = str, *a = str; 745 | 746 | /* decimal part */ 747 | if (*p == '-') { 748 | sign = -1; 749 | ++p; 750 | } else if (*p == '+') { 751 | ++p; 752 | } 753 | if (is_digit(*p)) { 754 | d = (double) (*p++ - '0'); 755 | while (*p && is_digit(*p)) { 756 | d = d * 10.0 + (double) (*p - '0'); 757 | ++p; 758 | ++n; 759 | } 760 | a = p; 761 | } else if (*p != '.') { 762 | goto done; 763 | } 764 | d *= sign; 765 | 766 | /* fraction part */ 767 | if (*p == '.') { 768 | double f = 0.0; 769 | double base = 0.1; 770 | ++p; 771 | 772 | if (is_digit(*p)) { 773 | while (*p && is_digit(*p)) { 774 | f += base * (*p - '0'); 775 | base /= 10.0; 776 | ++p; 777 | ++n; 778 | } 779 | } 780 | d += f * sign; 781 | a = p; 782 | } 783 | 784 | /* exponential part */ 785 | if ((*p == 'E') || (*p == 'e')) { 786 | int i, e = 0, neg = 0; 787 | p++; 788 | if (*p == '-') p++, neg++; 789 | if (*p == '+') p++; 790 | while (is_digit(*p)) e = e * 10 + *p++ - '0'; 791 | if (neg) e = -e; 792 | #if 0 793 | if (d == 2.2250738585072011 && e == -308) { 794 | d = 0.0; 795 | a = p; 796 | goto done; 797 | } 798 | if (d == 2.2250738585072012 && e <= -308) { 799 | d *= 1.0e-308; 800 | a = p; 801 | goto done; 802 | } 803 | #endif 804 | for (i = 0; i < e; i++) d *= 10; 805 | for (i = 0; i < -e; i++) d /= 10; 806 | a = p; 807 | } else if (p > str && !is_digit(*(p - 1))) { 808 | a = str; 809 | goto done; 810 | } 811 | 812 | done: 813 | if (end) *end = (char *) a; 814 | return d; 815 | } 816 | 817 | #if MJSON_ENABLE_MERGE 818 | int mjson_merge(const char *s, int n, const char *s2, int n2, 819 | mjson_print_fn_t fn, void *userdata) { 820 | int koff, klen, voff, vlen, t, t2, k, off = 0, len = 0, comma = 0; 821 | if (n < 2) return len; 822 | len += fn("{", 1, userdata); 823 | while ((off = mjson_next(s, n, off, &koff, &klen, &voff, &vlen, &t)) != 0) { 824 | char *path = (char *) alloca((size_t) klen + 1); 825 | const char *val; 826 | memcpy(path, "$.", 2); 827 | memcpy(path + 2, s + koff + 1, (size_t) (klen - 2)); 828 | path[klen] = '\0'; 829 | if ((t2 = mjson_find(s2, n2, path, &val, &k)) != MJSON_TOK_INVALID) { 830 | if (t2 == MJSON_TOK_NULL) continue; // null deletes the key 831 | } else { 832 | val = s + voff; // Key is not found in the update. Copy the old value. 833 | } 834 | if (comma) len += fn(",", 1, userdata); 835 | len += fn(s + koff, klen, userdata); 836 | len += fn(":", 1, userdata); 837 | if (t == MJSON_TOK_OBJECT && t2 == MJSON_TOK_OBJECT) { 838 | len += mjson_merge(s + voff, vlen, val, k, fn, userdata); 839 | } else { 840 | if (t2 != MJSON_TOK_INVALID) vlen = k; 841 | len += fn(val, vlen, userdata); 842 | } 843 | comma = 1; 844 | } 845 | // Add missing keys 846 | off = 0; 847 | while ((off = mjson_next(s2, n2, off, &koff, &klen, &voff, &vlen, &t)) != 0) { 848 | char *path = (char *) alloca((size_t) klen + 1); 849 | const char *val; 850 | if (t == MJSON_TOK_NULL) continue; 851 | memcpy(path, "$.", 2); 852 | memcpy(path + 2, s2 + koff + 1, (size_t) (klen - 2)); 853 | path[klen] = '\0'; 854 | if (mjson_find(s, n, path, &val, &vlen) != MJSON_TOK_INVALID) continue; 855 | if (comma) len += fn(",", 1, userdata); 856 | len += fn(s2 + koff, klen, userdata); 857 | len += fn(":", 1, userdata); 858 | len += fn(s2 + voff, vlen, userdata); 859 | comma = 1; 860 | } 861 | len += fn("}", 1, userdata); 862 | return len; 863 | } 864 | #endif // MJSON_ENABLE_MERGE 865 | 866 | #if MJSON_ENABLE_PRETTY 867 | struct prettydata { 868 | int level; 869 | int len; 870 | int prev; 871 | const char *pad; 872 | int padlen; 873 | mjson_print_fn_t fn; 874 | void *userdata; 875 | }; 876 | 877 | static int pretty_cb(int ev, const char *s, int off, int len, void *ud) { 878 | struct prettydata *d = (struct prettydata *) ud; 879 | int i; 880 | switch (ev) { 881 | case '{': 882 | case '[': 883 | d->level++; 884 | d->len += d->fn(s + off, len, d->userdata); 885 | break; 886 | case '}': 887 | case ']': 888 | d->level--; 889 | if (d->prev != '[' && d->prev != '{' && d->padlen > 0) { 890 | d->len += d->fn("\n", 1, d->userdata); 891 | for (i = 0; i < d->level; i++) 892 | d->len += d->fn(d->pad, d->padlen, d->userdata); 893 | } 894 | d->len += d->fn(s + off, len, d->userdata); 895 | break; 896 | case ',': 897 | d->len += d->fn(s + off, len, d->userdata); 898 | if (d->padlen > 0) { 899 | d->len += d->fn("\n", 1, d->userdata); 900 | for (i = 0; i < d->level; i++) 901 | d->len += d->fn(d->pad, d->padlen, d->userdata); 902 | } 903 | break; 904 | case ':': 905 | d->len += d->fn(s + off, len, d->userdata); 906 | if (d->padlen > 0) d->len += d->fn(" ", 1, d->userdata); 907 | break; 908 | case MJSON_TOK_KEY: 909 | if (d->prev == '{' && d->padlen > 0) { 910 | d->len += d->fn("\n", 1, d->userdata); 911 | for (i = 0; i < d->level; i++) 912 | d->len += d->fn(d->pad, d->padlen, d->userdata); 913 | } 914 | d->len += d->fn(s + off, len, d->userdata); 915 | break; 916 | default: 917 | if (d->prev == '[' && d->padlen > 0) { 918 | d->len += d->fn("\n", 1, d->userdata); 919 | for (i = 0; i < d->level; i++) 920 | d->len += d->fn(d->pad, d->padlen, d->userdata); 921 | } 922 | d->len += d->fn(s + off, len, d->userdata); 923 | break; 924 | } 925 | d->prev = ev; 926 | return 0; 927 | } 928 | 929 | int mjson_pretty(const char *s, int n, const char *pad, mjson_print_fn_t fn, 930 | void *userdata) { 931 | struct prettydata d = {0, 0, 0, pad, (int) strlen(pad), fn, userdata}; 932 | if (mjson(s, n, pretty_cb, &d) < 0) return -1; 933 | return d.len; 934 | } 935 | #endif // MJSON_ENABLE_PRETTY 936 | 937 | #if MJSON_ENABLE_RPC 938 | struct jsonrpc_ctx jsonrpc_default_context; 939 | 940 | int mjson_globmatch(const char *s1, int n1, const char *s2, int n2) { 941 | int i = 0, j = 0, ni = 0, nj = 0; 942 | while (i < n1 || j < n2) { 943 | if (i < n1 && j < n2 && (s1[i] == '?' || s2[j] == s1[i])) { 944 | i++, j++; 945 | } else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) { 946 | ni = i, nj = j + 1, i++; 947 | } else if (nj > 0 && nj <= n2 && (s1[i - 1] == '#' || s2[j] != '/')) { 948 | i = ni, j = nj; 949 | } else { 950 | return 0; 951 | } 952 | } 953 | return 1; 954 | } 955 | 956 | void jsonrpc_return_errorv(struct jsonrpc_request *r, int code, 957 | const char *message, const char *data_fmt, 958 | va_list *ap) { 959 | if (r->id_len == 0) return; 960 | mjson_printf(r->fn, r->fndata, 961 | "{\"id\":%.*s,\"error\":{\"code\":%d,\"message\":%Q", r->id_len, 962 | r->id, code, message == NULL ? "" : message); 963 | if (data_fmt != NULL) { 964 | mjson_printf(r->fn, r->fndata, ",\"data\":"); 965 | mjson_vprintf(r->fn, r->fndata, data_fmt, ap); 966 | } 967 | mjson_printf(r->fn, r->fndata, "}}\n"); 968 | } 969 | 970 | void jsonrpc_return_error(struct jsonrpc_request *r, int code, 971 | const char *message, const char *data_fmt, ...) { 972 | va_list ap; 973 | va_start(ap, data_fmt); 974 | jsonrpc_return_errorv(r, code, message, data_fmt, &ap); 975 | va_end(ap); 976 | } 977 | 978 | void jsonrpc_return_successv(struct jsonrpc_request *r, const char *result_fmt, 979 | va_list *ap) { 980 | if (r->id_len == 0) return; 981 | mjson_printf(r->fn, r->fndata, "{\"id\":%.*s,\"result\":", r->id_len, r->id); 982 | if (result_fmt != NULL) { 983 | mjson_vprintf(r->fn, r->fndata, result_fmt, ap); 984 | } else { 985 | mjson_printf(r->fn, r->fndata, "%s", "null"); 986 | } 987 | mjson_printf(r->fn, r->fndata, "}\n"); 988 | } 989 | 990 | void jsonrpc_return_success(struct jsonrpc_request *r, const char *result_fmt, 991 | ...) { 992 | va_list ap; 993 | va_start(ap, result_fmt); 994 | jsonrpc_return_successv(r, result_fmt, &ap); 995 | va_end(ap); 996 | } 997 | 998 | void jsonrpc_ctx_process(struct jsonrpc_ctx *ctx, const char *buf, int len, 999 | mjson_print_fn_t fn, void *fndata, void *ud) { 1000 | const char *result = NULL, *error = NULL; 1001 | int result_sz = 0, error_sz = 0; 1002 | struct jsonrpc_method *m = NULL; 1003 | struct jsonrpc_request r = {ctx, buf, len, 0, 0, 0, 0, 0, 0, fn, fndata, ud}; 1004 | 1005 | // Is is a response frame? 1006 | mjson_find(buf, len, "$.result", &result, &result_sz); 1007 | if (result == NULL) mjson_find(buf, len, "$.error", &error, &error_sz); 1008 | if (result_sz > 0 || error_sz > 0) { 1009 | if (ctx->response_cb) ctx->response_cb(buf, len, ctx->response_cb_data); 1010 | return; 1011 | } 1012 | 1013 | // Method must exist and must be a string 1014 | if (mjson_find(buf, len, "$.method", &r.method, &r.method_len) != 1015 | MJSON_TOK_STRING) { 1016 | mjson_printf(fn, fndata, "{\"error\":{\"code\":-32700,\"message\":%.*Q}}\n", 1017 | len, buf); 1018 | return; 1019 | } 1020 | 1021 | // id and params are optional 1022 | mjson_find(buf, len, "$.id", &r.id, &r.id_len); 1023 | mjson_find(buf, len, "$.params", &r.params, &r.params_len); 1024 | 1025 | for (m = ctx->methods; m != NULL; m = m->next) { 1026 | if (mjson_globmatch(m->method, m->method_sz, r.method + 1, 1027 | r.method_len - 2) > 0) { 1028 | if (r.params == NULL) r.params = ""; 1029 | m->cb(&r); 1030 | break; 1031 | } 1032 | } 1033 | if (m == NULL) { 1034 | jsonrpc_return_error(&r, JSONRPC_ERROR_NOT_FOUND, "method not found", NULL); 1035 | } 1036 | } 1037 | 1038 | static int jsonrpc_print_methods(mjson_print_fn_t fn, void *fndata, 1039 | va_list *ap) { 1040 | struct jsonrpc_ctx *ctx = va_arg(*ap, struct jsonrpc_ctx *); 1041 | struct jsonrpc_method *m; 1042 | int len = 0; 1043 | for (m = ctx->methods; m != NULL; m = m->next) { 1044 | if (m != ctx->methods) len += mjson_print_buf(fn, fndata, ",", 1); 1045 | len += mjson_print_str(fn, fndata, m->method, (int) strlen(m->method)); 1046 | } 1047 | return len; 1048 | } 1049 | 1050 | void jsonrpc_list(struct jsonrpc_request *r) { 1051 | jsonrpc_return_success(r, "[%M]", jsonrpc_print_methods, r->ctx); 1052 | } 1053 | 1054 | void jsonrpc_ctx_init(struct jsonrpc_ctx *ctx, mjson_print_fn_t response_cb, 1055 | void *response_cb_data) { 1056 | ctx->methods = NULL; 1057 | ctx->response_cb = response_cb; 1058 | ctx->response_cb_data = response_cb_data; 1059 | } 1060 | 1061 | void jsonrpc_init(mjson_print_fn_t response_cb, void *userdata) { 1062 | struct jsonrpc_ctx *ctx = &jsonrpc_default_context; 1063 | jsonrpc_ctx_init(ctx, response_cb, userdata); 1064 | jsonrpc_ctx_export(ctx, MJSON_RPC_LIST_NAME, jsonrpc_list); 1065 | } 1066 | #endif // MJSON_ENABLE_RPC 1067 | --------------------------------------------------------------------------------