C/C++

How to enable TLS SNI using boost::beast

First include the required header:

#include <openssl/ssl.h>

Once you have initialized the boost::beast::ssl_stream, add the following code (with host being a std::string containing the hostname to connect to such as api.ipify.org):

if(!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) {
    beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
    throw beast::system_error{ec};
}

Original source: boost::beast official HTTPS client example

Posted by Uli Köhler in Boost, C/C++

Minimal boost::beast HTTPS client JSON example

This example uses boost::beast to make a HTTPS request to ipify.org and parses the response using boost::json.

#include <iostream>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include <boost/beast/core.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/json.hpp>
#include <openssl/ssl.h>

namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
namespace ip = boost::asio::ip;
namespace ssl = boost::asio::ssl;
namespace json = boost::json;
using tcp = boost::asio::ip::tcp;

int main()
{
    std::string host = "api64.ipify.org";

    // Initialize IO context
    net::io_context ioc;
    ssl::context ctx(ssl::context::tlsv13_client);
    ctx.set_default_verify_paths();

    // Set up an SSL context
    beast::ssl_stream<beast::tcp_stream> stream(ioc, ctx);
    stream.set_verify_mode(ssl::verify_none);
    stream.set_verify_callback([](bool preverified, ssl::verify_context& ctx) {
        return true; // Accept any certificate
    });
    // Enable SNI
    if(!SSL_set_tlsext_host_name(stream.native_handle(), host.c_str())) {
        beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
        throw beast::system_error{ec};
    }
    // Connect to the HTTPS server
    ip::tcp::resolver resolver(ioc);
    get_lowest_layer(stream).connect(resolver.resolve({host, "https"}));
    get_lowest_layer(stream).expires_after(std::chrono::seconds(30));

    // Construct request
    http::request<http::empty_body> req{http::verb::get, "/?format=json" , 11};
    req.set(http::field::host, host);
    req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);

    // Send the request
    stream.handshake(ssl::stream_base::client);
    http::write(stream, req);

    // Receive the response
    beast::flat_buffer buffer;
    http::response<http::dynamic_body> res;
    http::read(stream, buffer, res);
    
    // Parse the JSON response
    json::error_code err;
    json::value j = json::parse(buffers_to_string(res.body().data()), err);

    std::cout << "IP address: " << j.at("ip").as_string() << std::endl;

    if (err) {
        std::cerr << "Error parsing JSON: " << err.message() << std::endl;
    }

    // Cleanup
    beast::error_code ec;
    stream.shutdown(ec);

    if (ec == net::error::eof) {
        ec = {};
    }
    if (ec) {
        throw beast::system_error{ec};
    }

    return 0;
}

Compile using

g++ -g -o http-example http-example.cpp -lcrypto -lssl -lboost_json

Example output:

IP address: "2a01:c22:6f8c:ef00:f0bb:a8a8:9c00:ff49"

 

Posted by Uli Köhler in Boost, C/C++

How to fix boost::beast handshake: no protocols available (SSL routines)

Problem:

While running your boost::beast application, you see the following exception:

terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  handshake: no protocols available (SSL routines) [asio.ssl:167772351]

Solution:

You are connecting using a SSL or TLS version the server does not support.

The TLS/SSL version is selected while initializing the boost::asio::ssl::context:

namespace ssl = boost::asio::ssl;

ssl::context ctx(ssl::context::tlsv11_client);

In this case, we’re using the outdated TLSv1.1 protocol.

Change the line to

ssl::context ctx(ssl::context::tlsv13_client);

to use TLSv1.3 instead. Keep in mind that some older TLS/SSL versions are considered to be insecure.

Posted by Uli Köhler in Boost, C/C++

How to link boost::beast HTTP client library

boost::beast is a header-only library. While boost::beast heavily depends on boost::asio as a networking library, asio is also a header-only library.

Therefore, typically, you don’t need to link boost::beast at all.

The only exception is if you are using SSL/TLS functions for HTTPS connections, in which case you need to link the OpenSSL library which provides the SSL/TLS layer. In that case, link it using

-lcrypto -lssl

 

Posted by Uli Köhler in Boost, C/C++

Cross-compiling boost for OpenWRT

This post showcases how to cross-compile boost for OpenWRT. We’ll use the Teltonika RUTX10 SDK which is already pre-configured with the relevant OpenWRT settings for that router.

The prerequisite for this is that you have:

We assume the SDK is located in ~/rutos-ipq40xx-rutx-gpl.

Compiling boost

First, you need to consider that many boost libraries are header-only libraries. For those libraries, you don’t need to compile boost, but it might still be easier to prepare a complete boost folder for installation.

First, download boost and unpack it. Now run

./bootstrap.sh

to compile bjam.

 

Now, open project-config.jam inside the boost folder and add the following line at the top of the file:

using gcc : openwrt : arm-openwrt-linux-muslgnueabi-g++ ;

Now, inside the boost folder, run the following bash script:

export STAGING_DIR=~/rutos-ipq40xx-rutx-gpl/staging_dir
export TOOLCHAIN=${STAGING_DIR}/toolchain-arm_cortex-a7+neon-vfpv4_gcc-8.4.0_musl_eabi
export CC=${TOOLCHAIN}/bin/arm-openwrt-linux-muslgnueabi-gcc
export CPP=${TOOLCHAIN}/bin/arm-openwrt-linux-muslgnueabi-g++
export PATH=$PATH:${TOOLCHAIN}/bin/

# Insert at the top of project-config.jam
# using gcc : openwrt : arm-openwrt-linux-muslgnueabi-g++ ;

rm -rf ~/boost_rutx10
mkdir -p ~/boost_rutx10/
./b2 --prefix=$HOME/boost_rutx10 install toolset=gcc-openwrt -sNO_COMPRESSION=1

Now, boost is installed in ~/boost_rutx10, which has include and lib subdirectories.

Posted by Uli Köhler in Boost, OpenWRT

How to compute SHA256 of std::string (hex or binary) using OpenSSL

This code uses the OpenSSL library to compute the SHA256 hash of a given std::string. Two variants are provided, one of them computing a binary hash (returning a std::array<uint8_t, 32>), the other computing a hex hash (returning a std::string).

#include <string>
#include <openssl/sha.h>

template<typename T>
std::string convertToHex(const T& binaryResult)
{
    std::ostringstream ss;
    ss << std::hex << std::setfill('0');
    for (unsigned int i = 0; i < binaryResult.size(); ++i) {
        ss << std::setw(2) << static_cast<unsigned>(binaryResult.at(i));
    }

    return ss.str();
}

std::array<uint8_t, 32> computeSHA256(const std::string& input) {
    std::array<uint8_t, 32> hash{};

    EVP_MD_CTX* mdctx = EVP_MD_CTX_new();
    const EVP_MD* md = EVP_sha256();

    EVP_DigestInit_ex(mdctx, md, nullptr);
    EVP_DigestUpdate(mdctx, input.c_str(), input.length());
    EVP_DigestFinal_ex(mdctx, hash.data(), nullptr);

    EVP_MD_CTX_free(mdctx);
    return hash;
}

std::string computeSHA256Hex(const std::string& input) {
    auto hash = computeSHA256(input);
    return convertToHex(hash);
}

Compile using

g++ -o main main.cpp -lcrypto -lssl

 

Posted by Uli Köhler in C/C++, Cryptography

How to debug boost::beast with mitmproxy

mitmproxy is a valuable tool to debug HTTPS request made using boost::beast.

In order to use it, remember that mitmproxy listents to localhost on port 8080 and forwards requests based on the HTTP Host header.

First, you need to disable SSL certificate verification:

beast::ssl_stream<beast::tcp_stream> stream(ioc, ctx);
stream.set_verify_mode(ssl::verify_none);

Now, instead of connecting to an IP address based on DNS results such as

ip::tcp::resolver resolver(ioc);
get_lowest_layer(stream).connect(resolver.resolve({endpoint, "https"}));

directly connect to the 127.0.0.1:8080 endpoint:

boost::asio::ip::tcp::endpoint ep(
    boost::asio::ip::address::from_string("127.0.0.1"),
    8080
);
get_lowest_layer(stream).connect(ep);

Besides that, you can do anything as usual.

Posted by Uli Köhler in Boost, C/C++

How to initialize boost::asio::tcp::endpoint from IPv4 address string

This code will initialize a boost::asio::tcp::endpoint from a IPv4 address string ("127.0.0.1" as an exmple) and a port number (443 in this example)

boost::asio::ip::tcp::endpoint ep(
    boost::asio::ip::address::from_string("127.0.0.1"),
    443
);

 

Posted by Uli Köhler in Boost, C/C++

How to fix boost::beast system_error uninitialized (SSL routines)

Problem:

While running your boost::beast https client you see the following error message:

terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  uninitialized (SSL routines) [asio.ssl:167772436]

Solution:

You forgot to handshake before writing the HTTP request:

stream.handshake(ssl::stream_base::client);

 

Posted by Uli Köhler in Boost, C/C++

How to fix C++ undefined reference to symbol ‘BIO_ctrl_pending@@OPENSSL_3.0.0’

Problem:

When trying to compile your C++ program that is using OpenSSL using a command such as

g++ -o myprogram myprogram.cpp -lssl

you see the following error message:

/usr/bin/ld: /tmp/ccEB0Pid.o: undefined reference to symbol 'BIO_ctrl_pending@@OPENSSL_3.0.0'
/usr/bin/ld: /lib/x86_64-linux-gnu/libcrypto.so.3: error adding symbols: DSO missing from command line
collect2: error: ld returned 1 exit status

Solution:

You need to link crypto as well as ssl:

g++ -o myprogram myprogram.cpp -lcrypto -lssl

 

Posted by Uli Köhler in GCC errors

Minimal C/C++ libcurl IPify API example in C++

This simple program uses the libcurl easy API to make a HTTPS request to ipify to get the current IP address.

#include <iostream>
#include <cstdio>
#include <curl/curl.h>

int main(int argc, char **argv)
{
    CURL *curl;
    CURLcode res;

    curl = curl_easy_init();
    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://api.ipify.org"); // Get IP address
        res = curl_easy_perform(curl);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, [](void *contents, size_t size, size_t nmemb, void *userp) {
            // Write (stream) to stdout
            fwrite(contents, size, nmemb, stdout);
        });

        if (res != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        }

        curl_easy_cleanup(curl); // Clean up the CURL handle
    }

    return 0;
}

Compile using

g++ -o curltest curltest.cpp -lcurl

 

Posted by Uli Köhler in C/C++

How to initialize C++ project using conan 2.x

The simplest way of starting is by running the following command in your project directory:

conan new basic

This will create conanfile.py from the basic template.

Alternatively, you can initialize it from a predefined template such as cmake_exe:

conan new cmake_exe -d name=myproject -d version=1.0.0

 

Posted by Uli Köhler in C/C++, Conan

MAX31855: How to skip invalid values

Using the MAX31855 as a thermocouple amplifier & digitizer sometimes leads to invalid values, be it due to noise on the analog (thermocouple) side or due to digital signal integrity issues such as GND noise.

This code modified from our post on ESP32 MAX31855 thermocouple LCD minimal example using PlatformIO provides an example of how to skip & ignore those values, but report an error if more than 10 (configurable) consecutive values are invalid.

Depending on your application, you might need to choose a more suitable temperature range in IsValidReading(). Choosing a valid range as narrow as possible will avoid more bad values being sent to downstream code.

SPIClass vspi(VSPI);
Adafruit_MAX31855 thermocouple1(Pin_MAX31855_A_CS, &vspi);

bool IsValidReading(double reading) {
  return !isnan(reading) && reading > -40.0 && reading < 1000.0;
}

void ReadTemperature() {
  double celsius = thermocouple1.readCelsius();
  if(!IsValidReading(celsius)) {
    consecutiveNaNReads++;
    if(consecutiveNaNReads >= maxConsecutiveNaNReads) {
      // Thermocouple is disconnected
      Serial.println("Thermocouple error");
    }
  } else {
    consecutiveNaNReads = 0;
    Serial.println(celsius);
  }
}

 

Posted by Uli Köhler in C/C++, Electronics

How to fix C++ link errors such as “undefined reference to std::filesystem::…”

Problem:

While compiling/linking your C++ project using GCC or Clang you see error messages such as

/usr/bin/ld: CMakeFiles/myproject.dir/main.cpp.o: in function `main':
main.cpp:(.text+0x44d): undefined reference to `std::filesystem::create_directories(std::filesystem::__cxx11::path const&)'
/usr/bin/ld: CMakeFiles/myproject.dir/main.cpp.o: in function `std::filesystem::__cxx11::path::path<char [7], std::filesystem::__cxx11::path>(char const (&) [7], std::filesystem::__cxx11::path::format)':
main.cpp:(.text._ZNSt10filesystem7__cxx114pathC2IA7_cS1_EERKT_NS1_6formatE[_ZNSt10filesystem7__cxx114pathC5IA7_cS1_EERKT_NS1_6formatE]+0x5e): undefined reference to `std::filesystem::__cxx11::path::_M_split_cmpts()'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/myproject.dir/build.make:84: myproject] Error 1
make[1]: *** [CMakeFiles/Makefile2:73: CMakeFiles/myproject.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

Solution:

You need to link the stdc++fs library which comes with your compiler and implements the std::filesystem functionality.

Link it by adding -lstdc++fs to your compiler flags.

Posted by Uli Köhler in C/C++, GCC errors

boost::json How to serialize to std::string minimal example

#include <boost/json.hpp>
#include <fstream>
#include <iostream>

namespace json = boost::json;

int main() {
    // Create a JSON object
    json::object obj{
        {"name", "John Doe"},
        {"age", 30},
        {"city", "New York"}
    };

    // Serialize the JSON object
    std::string serialized = json::serialize(obj);

    // Example of what to do with the serialized JSON
    std::cout << serialized << std::endl;

    return 0;
}

Compile using:

g++ -o main main.cpp -lboost_json

Running the program using ./main will print

{"name":"John Doe","age":30,"city":"New York"}

 

Posted by Uli Köhler in C/C++

boost::json How to serialize to file (std::ofstream) minimal example

#include <boost/json.hpp>
#include <fstream>
#include <iostream>

namespace json = boost::json;

int main() {
    // Create a JSON object
    json::object obj{
        {"name", "John Doe"},
        {"age", 30},
        {"city", "New York"}
    };

    // Open the output file stream
    std::ofstream file("output.json");
    if (file.is_open()) {
        // Serialize the JSON object and write it to the file
        file << obj;

        // Close the file stream
        file.close();
        std::cout << "JSON object serialized and written to file successfully." << std::endl;
    } else {
        std::cout << "Unable to open file for writing." << std::endl;
    }

    return 0;
}

Compile using:

g++ -o main main.cpp -lboost_json

After running the program using ./mainoutput.json will look this this:

{"name":"John Doe","age":30,"city":"New York"}

 

 

Posted by Uli Köhler in C/C++

How to configure CMake to use C++20

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

 

Posted by Uli Köhler in Build systems, C/C++, CMake

C++ equivalent of Python’s os.makedirs(…, exist_ok=True)

In C++17, you can use std::filesystem which provides std::filesystem::create_directories.

Similar to Python’s os.makedirs(..., exist_ok=True) or the shell command mkdir -p, this will recursively create directories.

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    std::string directoryPath = "output";

    try {
        // Create the directory
        fs::create_directories(directoryPath);
        std::cout << "Directory created successfully." << std::endl;
    } catch (const std::exception& ex) {
        std::cerr << "Error creating directory: " << ex.what() << std::endl;
    }

    return 0;
}

Note that you typically need to tell your compiler to use the C++17 standard. For example, for GCC use  -std=c++17 or -std=gnu++17 in case you want to use GNU extensions

g++ -o main main.cpp -std=c++17

If you have to use an older compiler not supporting C++17 , you might need to use std::experimental::filesystem which basically provides the same API but in the std::experimental::filesystem namespace:

#include <iostream>
#include <experimental/filesystem>

namespace fs = std::experimental::filesystem;

int main() {
    std::string directoryPath = "output";

    try {
        // Create the directory
        fs::create_directories(directoryPath);
        std::cout << "Directory created successfully." << std::endl;
    } catch (const std::exception& ex) {
        std::cerr << "Error creating directory: " << ex.what() << std::endl;
    }

    return 0;
}

 

 

Posted by Uli Köhler in C/C++

How to iterate JSON array in C++ using Boost.JSON

You can use a standard for iteration loon on j.as_array() in order to iterate all values in the given JSON array:

for (auto& value : json.as_array()) {
    std::cout << value.as_int64() << std::endl;
}

Full example:

#include <boost/json.hpp>
#include <iostream>

int main()
{
    const char* json_str = R"([1, 2, 3, 4, 5])";
    boost::json::value j = boost::json::parse(json_str);

    if (j.is_array()) {
        for (auto& value : j.as_array()) {
            std::cout << value.as_int64() << std::endl;
        }
    }

    return 0;
}

Compile using

g++ -o json json.cpp -lboost_json

This will print

1
2
3
4
5

 

Posted by Uli Köhler in Boost, C/C++

How to copy boost::urls::url in C++ (Boost.Url)

You can copy a boost::urls::url by using it copy constructor:

boost::urls::url url1 = boost::urls::url::parse("https://example.com/path1");
boost::urls::url url2(url1); // Copy url1 to url2 using the copy constructor

 

Posted by Uli Köhler in Boost, C/C++