Protobuf Data Synchronisation
This utility provides a means of synchronising arbitrary protobuf data streams to multiple SQLITE databases.
The basic concept is that the app monitors a configured folder for incoming files.
When a new file arrives, the process inspects the file to determine the types of protobuf message it contains (see file format details below).
The process ingests the file and builds SQL CREATE TABLE IF NOT EXISTS
and INSERT
statements from the contents.
The protoDbSync ingestor uses reflection to translate the protobuf definition to the equivalent SQL schema and statements.
It then uses the tdx Volt API to run the SQL on any number of configured remote databases. A separate sync status is kept for each target database.
File Format
The file format used is essentially the de-facto standard for protobuf data, which is the length-prefixed binary
format. This can easily be generated from most protobuf client library auto-generated stubs, for example encodeDelimited
in Javascript and SerializeDelimitedToOStream
in C++.
Note that currently only basic protobuf message
structures are supported - messages with sub-message types or those that make use of oneof
or optional
are not currently supported.
Most of the time you will not need to manually create files in this format, you can use the Volt Logger command to do it for you.
Each file must have a header present which is a serialisation of a ProtobufSyncConfigurationHeader
message, as defined here. The serialisation of the header must include a length prefix.
This header contains a configuration entry (an instance of ProtobufSyncConfiguration) for each message type that will appear in the file.
For example, consider a data producer that is creating two types of message, tcp packets and udp packets, which are defined by the protobuf message types TcpPacket
and UdpPacket
respectively. In this scenario the ProtobufSyncConfigurationHeader
will contain two ProtobufSyncConfiguration
instances, the first describing TcpPacket
and the second describing UdpPacket
.
Each ProtobufSyncConfiguration
instance contains the actual protobuf that describes the data type. The main fields of the header are message_proto
, which is the protobuf definition of the target messages (as a string), and table_name
, which is the name of the table into which the data should be inserted. See ProtobufSyncConfiguration for full details.
As well as the header format described above, each message in the file must be wrapped in a ProtobufSyncWrapper instance and serialised to the file using a length prefix.
Logger integration
The tdx Volt CLI has a ‘logger’ command which can automatically create protoDbSync compatible files, taking input from STDIN or a wire subscription.
To be clear, the tdx Volt Logger will blindly write whatever data is on STDIN, splitting the input into multiple files of a configured maximum size in a configured folder. It makes no attempt to interpret the incoming data.
If a header is given
as part of the tdx Volt Logger configuration it will populate and write a ProtobufSyncConfigurationHeader
message to the start of each file it creates.
In order to create files that are compatible with the protoDbSync utility, data producers must output data in protobuf binary format as serialised instances of the ProtobufSyncWrapper
message type.
The idea is to eliminate the need for the producer process to know about the nitty-gritty of the sync file format.
So an arbitrary process can write data (in protobuf binary format for the database sync scenario) to STDIN, and the Volt Logger will create protoDbSync compatible files in a configured folder and pipe the data from STDIN into them up to a configured log size.
See the ‘logger’ command documentation for more details.
Configuration
The process can sync to multiple remote databases. The configuration takes the form of a list of target Volt configurations. See the Appendix for a full working example.
The basic structure of the configuration file is as follows:
{ "extension": "<file extension>", "rootFolder": "<folder to watch>", "targets": { "<target-database-id-1>": {<volt connection object>} "<target-database-id-2>": {<volt connection object>} ... "<target-database-id-n>": {<volt connection object>} }}
The configuration is made up of following properties:
extension
- optional file extension to match when watching for incoming data (pdat
is the default)rootFolder
- the folder the process will watch for incoming files.targets
- the list of target database configurations, each entry defines the tdx Volt connection details.target-database-id
- this is the id of the target database resource (12439d48-4c0a-4417-822a-e3db70a9d4d4
in the example below), and contains the details of the tdx Volt on which the database is hosted. This is essentially the normal tdx Volt client configuration data, optionally with the addition of thesync
property.sync
- optional configuration for the sync process.sync.fullInitialSync
- the process will send all the data it has seen when it first encounters a new database configuration. The default is ‘true’.
The example below shows a configuration file for a single tdx Volt target, namely resource 12439d48-4c0a-4417-822a-e3db70a9d4d4
on Volt
Local RPi
:
{ "extension": "pdat", "rootFolder": "/home/pi/dev/tdxvolt/tdxvolt-core/release/bin/fs20logs", "targets": { "12439d48-4c0a-4417-822a-e3db70a9d4d4": { "client_name": "RPi proto db sync", "credential": { "key": "<--REDACTED-->" }, "volt": { "ca_pem": "-----BEGIN CERTIFICATE-----\nMIIDnDCCAoSgAwIBAgIECZZpTjANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCRhYjBkNWM2ZS0yMzdhLTQ1YTUtOWMyNy0yMTVkZmQ2ZGEw\nYjAwHhcNMjExMTI5MTg1NjAxWhcNMjIxMTI5MTg1NjAyWjBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCRhYjBkNWM2ZS0yMzdhLTQ1YTUtOWMyNy0yMTVkZmQ2ZGEw\nYjAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfoMpIBSt0kKFFU98S\n/28moM8dLsVlUl0lvuwjw7zHxdEYTqr+ZEuSmDFXARBTdww7bL50TKzSPcPljSW/\nA7uOf29B+OqPtQZQwzOWJ847yyRPSwLgyEgkiUX8lMYH001oriQAVw85RE/FjE+J\nGvRrP988LPpyZltg6P/3ofqiiDlmZ67MikZ0YuqZmbkZj4wH0OnTg1+SIE3coORa\npqd+cEeN7xlzgX98pLTs8bl8W9nd//IkWYiLiw3CTZHr14rg38K+GufJZVyG8R0v\nGwSNuGet/vGN+PZpSoL4GmKmKWfcActkUpJ9IKPpXNde5Evnh+hmtf+0JSoLiAp2\nMP8HAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\nA1UdDgQWBBTTWm6z1B197MQEm14StUwrHDK1jzANBgkqhkiG9w0BAQsFAAOCAQEA\nfCsHqtU7r0HAsfSdGbfWASnFgUVWi3dB/pewrPqKNTDflijRN2LdlOg1G22tCPYm\nGG7HuMjaostuHNMJ2gBz1N0Ssptz2+veGvdt5M9gErPiNJoK1zJyPADW7mplbe3G\n97S1nv210erAfnXfriwjbiPw5edlpkMdv16V0QxWowIZytmpdHSiq8xX4S90LBMc\nmym9CVFQcwbtPhFg+vcbErtL4C/nE9TAhyG75j+mO1gZzumrrtGuC3CdVKKM+9+g\nQvFsmW9xXEj0lf4RHVibYBlE6Ur1me3BsxaemFIwYPTLOn+MNugJD29BtaHiRtUB\nBA2tApMqeE7CnnBobxmnWA==\n-----END CERTIFICATE-----\n", "id": "ab0d5c6e-237a-45a5-9c27-215dfd6da0b0" }, "relay_url": "https://tdxvolt.com", "sync": { "fullInitialSync": true } } }}
Sync algorithm
Possibly ‘sync’ is a misnomer as currently it is essentially a one-way upload. The main objective is to only send any given data file once.
It does this using a set of sub-folders that are dynamically created and managed off of the rootFolder
property, an
example of which is shown below.
.├── fs20logs│ ├── 12439d48-4c0a-4417-822a-e3db70a9d4d4│ │ ├── sync-2021-12-10T06:31:27.724Z.pdat│ │ ├── archive│ │ │ ├── sync-2021-12-01T14:06:38.421Z.pdat│ │ │ ├── sync-2021-12-01T14:30:30.498Z.pdat│ │ │ └── sync-2021-12-01T18:22:59.301Z.pdat│ │ ├── duplicate│ │ └── error│ ├── archive│ │ ├── sync-2021-12-01T14:06:38.421Z.pdat│ │ ├── sync-2021-12-01T14:30:30.498Z.pdat│ │ └── sync-2021-12-01T18:22:59.301Z.pdat│ ├── sync-2021-12-10T06:31:27.724Z.pdat│ └── sync-2021-12-10T06:47:57.196Z.pdat.lock
For example, considering the above example configuration file, the following directory structure is used:
- fs20logs - the root folder, which is essentially the ‘pending’ folder - incoming data should be placed here
- 12439d48-4c0a-4417-822a-e3db70a9d4d4 - the target-specific ‘pending’ folder, a sub-folder like this will be created for each target configured. When a new file arrives in the root folder, it is copied into this folder for each target configured.
- archive - the files that are successfully processed are moved here. Each target also has an ‘archive’ folder to keep track of the sync status of that target.
- duplicate - any new file that already exists in archive are skipped and moved here
- error - any files that fail to transmit are moved here
The process that is creating the data (producer) should only place a file matching the pattern in the root folder when
it is complete and ready for transmission. The example producer achieves this by writing to a <timestamp>.pdat.lock
file, and then renames the file to remove the .lock
when it has reached the desired size. If you’re using the Volt logger command it will do this for you.
File size
The particular maximum file size isn’t mandated, but bear in mind that by default each file is transmitted as a single GRPC call in the form of a bulk SQL INSERT statement. There doesn’t seem to be any hard data on this, but it seems the optimal protobuf message size is around 64 kB, although I’ve used 1 MB without any apparent issues.
Appendix
Configuration file example
This example shows a sync to 3 databases on 3 different Volts.
{ "extension": "pdat", "rootFolder": "/home/pi/dev/tdxvolt/tdxvolt-core/release/bin/fs20logs", "targets": { "05847828-799a-4cc5-975b-03f2908d1443": { "client_name": "protoDbSync", "credential": { "cert": "-----BEGIN CERTIFICATE-----\nMIIDWDCCAkCgAwIBAgIERpkVdzANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCRmODI5NzE4MC1iY2NkLTRjNDktYjRlNS0wY2I0ZTU3Mzhk\nMTUwHhcNMjIwMTI2MTUwMDAzWhcNMjMwMTI2MTUwMDA0WjBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCRkZTVlMGQ4MC0yZWI4LTRlN2YtYTRkMC1kMDgwYTIwY2Vk\nZjUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDbXcT1Syhg4OpN6+kS\nGjvPN8Dq2ZuraMMmmK+8Kc/Efkwr7p83rS/brIyu6R8Hw4ZsQMOFJpa0IhrE2KfJ\nOIn4yLyOJng1ivk8YvE1BvddMoHhamf2OMAQgmKYgZVhLdhA6iJu64gDy//ktrNF\nT3Dt8fOWG6rFtYRtA/SUL+9csIxUW3cSUnQ+BknmrYW2+EeS4GPkVKrVAozIz778\n/d+ceBRItR2p8iC0N3u/VGd3+jwBjjCq3Z1qqzyRzKPHiV7HmmvfrvzBnYqheP13\nolq4OccbJpBTvjQQcb7gXSTVqaGxcGStzYB+8ioGLKpUfBy58xf2odz+lFSsM6vP\nRLIFAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAHfD9br8Bi8ZYOq1AGzqwi1DLEmF\nut4RMK9vlssqHyWNsctoEQPJsABXFm37glOvxp5xAxNjE+KkoURjPG+W8PsxgLi6\nO7/TFLkCQ4TESDzzKG+r8+vRF4kqa5UBHv1SBdN1UhCRALFxvgE5JGWOOPA9ivkY\nbhtn22Bfcf6ECm4r+glLoPXs2r16/Ux1qHCQ1frjoM/+IdVFGlBKouVxGw656DGz\niTq4adABfPOEj/ENdzfNsslDCgtEtS6DTZxNsH29bJoSzxXobht04JwaYtqijRx9\nOkMx1sodN+SoaVV0yYFqf3V64WBWoHGLuQzFnQpGb2VfoZlCNYi355Pe92k=\n-----END CERTIFICATE-----\n", "client_id": "de5e0d80-2eb8-4e7f-a4d0-d080a20cedf5", "key": "<--REDACTED-->" }, "sync": { "fullInitialSync": true }, "volt": { "ca_pem": "-----BEGIN CERTIFICATE-----\nMIIDnDCCAoSgAwIBAgIEdxBiBjANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCRmODI5NzE4MC1iY2NkLTRjNDktYjRlNS0wY2I0ZTU3Mzhk\nMTUwHhcNMjIwMTI2MTQ0NTAwWhcNMjMwMTI2MTQ0NTAxWjBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCRmODI5NzE4MC1iY2NkLTRjNDktYjRlNS0wY2I0ZTU3Mzhk\nMTUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfoMpIBSt0kKFFU98S\n/28moM8dLsVlUl0lvuwjw7zHxdEYTqr+ZEuSmDFXARBTdww7bL50TKzSPcPljSW/\nA7uOf29B+OqPtQZQwzOWJ847yyRPSwLgyEgkiUX8lMYH001oriQAVw85RE/FjE+J\nGvRrP988LPpyZltg6P/3ofqiiDlmZ67MikZ0YuqZmbkZj4wH0OnTg1+SIE3coORa\npqd+cEeN7xlzgX98pLTs8bl8W9nd//IkWYiLiw3CTZHr14rg38K+GufJZVyG8R0v\nGwSNuGet/vGN+PZpSoL4GmKmKWfcActkUpJ9IKPpXNde5Evnh+hmtf+0JSoLiAp2\nMP8HAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\nA1UdDgQWBBTTWm6z1B197MQEm14StUwrHDK1jzANBgkqhkiG9w0BAQsFAAOCAQEA\ncxKQxsCuYlCEm1FgZUgYZ75abPDeyTliD73FwMtktpwtYMqaoMiSaEbuuUZiYjkq\nes+uOA9HNhuAfny/ufhqWp/Hlf1KnLVplEEIoP9l67wXg+/qGMsDehMDcxsALh13\nzvT4a6sSbhFPP/SZ43rumhEA2ZVZMYlBpW937KHhw7aa9j517uR7rOq64aOLjPWf\nsl4xmjNME4e/gYRGoh8RpWQbBnq7Fsrnxc7KDzX6PyRLHSoPmTARC0wchJ42Y4dY\n6k3XrQrccfAd0SvrU/xAUOFLh0tShH1eEBzold7wRPYwTzibeDzU74BoBVRIyxaW\nUiY/O+n407iUSlav95yVTA==\n-----END CERTIFICATE-----\n", "challenge_code": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI=", "id": "f8297180-bccd-4c49-b4e5-0cb4e5738d15", "address": "192.168.1.178:33325" } }, "6fd1f237-681c-4b58-8d10-39238ac82db2": { "client_name": "ProtoDbSync", "credential": { "cert": "-----BEGIN CERTIFICATE-----\nMIIDWDCCAkCgAwIBAgIEUNJ8szANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCQ2ZmEyZGQ2Yi0wMmZhLTQ3ZGUtYjQ4OC1kYTJkODEzOWNl\nNGMwHhcNMjIwMTI2MTUxMjQ1WhcNMjMwMTI2MTUxMjQ2WjBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCRiNWRkY2ExOS0xZGIzLTRhZWItYWYzNy0yZDA1OTg2YmJi\nZTkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDbXcT1Syhg4OpN6+kS\nGjvPN8Dq2ZuraMMmmK+8Kc/Efkwr7p83rS/brIyu6R8Hw4ZsQMOFJpa0IhrE2KfJ\nOIn4yLyOJng1ivk8YvE1BvddMoHhamf2OMAQgmKYgZVhLdhA6iJu64gDy//ktrNF\nT3Dt8fOWG6rFtYRtA/SUL+9csIxUW3cSUnQ+BknmrYW2+EeS4GPkVKrVAozIz778\n/d+ceBRItR2p8iC0N3u/VGd3+jwBjjCq3Z1qqzyRzKPHiV7HmmvfrvzBnYqheP13\nolq4OccbJpBTvjQQcb7gXSTVqaGxcGStzYB+8ioGLKpUfBy58xf2odz+lFSsM6vP\nRLIFAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAGcTyC5Li4sZAeT1F2MLEPrPtZ9S\nSAzADemAbbuyGPQ6+MzBlf3TPPZFG4MicN3g9V0lZzfGPOAdCnXeXQtaMlNDlQua\nwnH94b7sUhZVbmxZ1exX3aHphVTU3ZndhrmRYdRv9UK8PkbL/H8mk07rfUDLo3Nq\nuQycooux3iShd/Hw0kDYVmEaf+nX9WGEpTiuGuIxlsRVj2PLp0G+1pRt0RALV+hj\nvqnyoiYI6BX86hu/Gk1zcxwkWof/RcCbzhyqzbqWzWsjej2bVJE1+LM5PbzXZR2o\nYxrTN6AXcje4T5+WSPD6fD/RWYUAdsj1PvfuFWCGTCEpOx6LEC7hdAiV7rM=\n-----END CERTIFICATE-----\n", "client_id": "b5ddca19-1db3-4aeb-af37-2d05986bbbe9", "key": "<--REDACTED-->" }, "sync": { "fullInitialSync": true }, "volt": { "ca_pem": "-----BEGIN CERTIFICATE-----\nMIIDnDCCAoSgAwIBAgIET8kUKTANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCQ2ZmEyZGQ2Yi0wMmZhLTQ3ZGUtYjQ4OC1kYTJkODEzOWNl\nNGMwHhcNMjIwMTEwMTU1ODMxWhcNMjMwMTEwMTU1ODMyWjBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCQ2ZmEyZGQ2Yi0wMmZhLTQ3ZGUtYjQ4OC1kYTJkODEzOWNl\nNGMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9+iq3yX4JLs/D/tWF\nlhfdz75l5j46KVBkidBKR7K7I7PjVckcsHfA9ifBrohLlHxpVpvKrw4q8xuxAXaC\nAN92kwbjQW1f8cuRrUrKiFAwEiJGm4/aDiRCScrwPSAWRUtxAuPPbIqmbPVAuTt1\nZ1xO4Q0O8DWhDRVo2gwccjILo1NC4ak1AfG50cZ7GX9MpxUQ6GCLNJg1QGh9sxKz\nBY6fxDPYVzIWU7ZNPw799VqFb8PPhsdPXHMozkbIxM9zfUwLCUbOpdM/AuiSgtZj\nBFb9tUn5p+cvIowKpyqG735Crr1WcNH/i2YoXIpCqm18R2GHbVIOz4IvpjiPARq+\nXp0TAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\nA1UdDgQWBBQYR+nfRdzG5rIdq2bYkZzVzm1ImDANBgkqhkiG9w0BAQsFAAOCAQEA\nu91BCHx0RrBpweP61Q69CefI0xv+yDXz3nDKhkwHbjR9Bp7+6fF5nwp5X0xbFFED\nCt4/JEGZlDf/kAG7WhU8coJc+gufzQuFbwGimNKIWNd6M683Hbp3wtPd2msPVtn9\nlDf/8CarU5+BrYOeTKnjU9CCgHJthxaz/L+S21Y+lu4NSPzKFqRujT9CLhgTQ7pY\nJZ2TJ73QnWuCiG5zLl9lRbHJKTwfOJZsNR0lzHJmhMcFy2J61+iQ8Z5Qt//53F8e\narYP2qxNGev1AIhoRBOboCWOTuWmU4rkHkxBwGJWqVV1SuaNSjYW94omEUonk+bb\nhP3eCDv28hw9UlFveF4Ocw==\n-----END CERTIFICATE-----\n", "challenge_code": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI=", "id": "6fa2dd6b-02fa-47de-b488-da2d8139ce4c", "address": "tdxvolt.com:40725" } }, "f3a968df-58c2-44d7-b39c-97a5fa78ba90": { "client_name": "ProtoDbSync", "credential": { "cert": "-----BEGIN CERTIFICATE-----\nMIIDWDCCAkCgAwIBAgIEL7VkHjANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCQ3ODI2Mzc2NS03MzBiLTQ1MTQtOGZkOS0xYzc2MmViODFh\nYTIwHhcNMjIwMTI2MTUyMjUzWhcNMjMwMTI2MTUyMjU0WjBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCRlMWFmYzIwYi0yYmE3LTQxMDMtOGYzNS0yNmMzY2RjNTFi\nY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDbXcT1Syhg4OpN6+kS\nGjvPN8Dq2ZuraMMmmK+8Kc/Efkwr7p83rS/brIyu6R8Hw4ZsQMOFJpa0IhrE2KfJ\nOIn4yLyOJng1ivk8YvE1BvddMoHhamf2OMAQgmKYgZVhLdhA6iJu64gDy//ktrNF\nT3Dt8fOWG6rFtYRtA/SUL+9csIxUW3cSUnQ+BknmrYW2+EeS4GPkVKrVAozIz778\n/d+ceBRItR2p8iC0N3u/VGd3+jwBjjCq3Z1qqzyRzKPHiV7HmmvfrvzBnYqheP13\nolq4OccbJpBTvjQQcb7gXSTVqaGxcGStzYB+8ioGLKpUfBy58xf2odz+lFSsM6vP\nRLIFAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEeVgRvcOFfK+f8/IsPN/gX3nMfW\nKsh9BKTCYJR/66b1goRJljXN7cT1zbwxOq2qpDvWhpj8cjMJGbrhE0+OaRe4EThN\nDTBOJ9FgloLqaNpqHAHcmHj/sEdwshogWxxfJk1K0ynpRQjY8zGcATTza7SNdFxO\n3+WFqhn9LcniatP+lcFAqjrKBXCfnFNGi9cALx5UV/WFdLZ1GuA0p/nAymNuARqj\nq8TXah7BqH5YADafsjwOmp/QIC86wtI2yRIn9iTZW4WmeCEQo6gUXWnZ0dpSNORa\nQ1BODiDAlbx7UBz470mAfU5X2Ck/y/mBzJB07r6s8WHTI85CeoGfHOVnfaM=\n-----END CERTIFICATE-----\n", "client_id": "e1afc20b-2ba7-4103-8f35-26c3cdc51bce", "key": "<--REDACTED-->" }, "sync": { "fullInitialSync": true }, "volt": { "ca_pem": "-----BEGIN CERTIFICATE-----\nMIIDnDCCAoSgAwIBAgIELyHuBTANBgkqhkiG9w0BAQsFADBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCQ3ODI2Mzc2NS03MzBiLTQ1MTQtOGZkOS0xYzc2MmViODFh\nYTIwHhcNMjIwMTI2MTUxMzE4WhcNMjMwMTI2MTUxMzE5WjBuMQswCQYDVQQGEwJH\nQjEUMBIGA1UEBwwLU291dGhhbXB0b24xGjAYBgNVBAoMEW5xdWlyaW5nTWluZHMg\nTHRkMS0wKwYDVQQDDCQ3ODI2Mzc2NS03MzBiLTQ1MTQtOGZkOS0xYzc2MmViODFh\nYTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9F99FlMUxTaso+R11\nhP28bT4t9Q3aJFVU/5tpRz6zqD1ocxYs/URaER74iwjK7Z0DHuwpXm9jNOD4t2ef\nOsZ5Vq9iBQcqZCRZ3T6s6Em78ag3OQ2l3VVFBcKh8U4zZqFU2QAHtFHHXH3F3CbX\nuGhweiS6AQ4R479GGVJuoK9+rMQ9YLBG33hmb/XxMMR52i2xkQPa2sX+xGKnsPEY\n1T6vneRTVXHBQHxcScSfOk2fEi3/LX+rkbMTHRScbp7onWmwhc5VtpMpuYuiywxv\nm4TFiSeTJDvk3I1ujgQtcRRJ4ugZNppgh2wBxd8n0/CacC8ssfGtUm20NOdH+OIr\nRuEjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G\nA1UdDgQWBBRpwpS3YxFTyBXkloz8ryBT5dZ3tzANBgkqhkiG9w0BAQsFAAOCAQEA\nAHUuXJPTqucD4J099M1kqZZn/j5dQn3BfTooeFRqIljT0TXKaNMpBLt5qmfOrKUw\nXR/e7YhaMsHBrHGSwe4drIz/KwmfV1kstWUZbMbF5UQVbyM/vfMlia0CSki9Vy7G\nilA1OxztHMLbV/ToZkbtpBfWZJuXYM8fvA5T929IVr004J1fDot0KeQWxa/eaRVh\nv6OoTfUJnF6QzD5QvS0ZPy9oWiI0LQ4r762MihqtiEwEZ/mcqbvFNnWrsrs37NvL\nbtmQLwgEnnvzYyPA1koHE3rNIPhCjaOZQF08yC5Gjsrccwx+xOubBaXmALBMV457\nnMJq05nWZLkW9irryuIsRw==\n-----END CERTIFICATE-----\n", "challenge_code": "w6uP8Tcg6K2QR905Rms8iXTlksL6OD1KOWBxTK7wxPI=", "id": "78263765-730b-4514-8fd9-1c762eb81aa2", "address": "192.168.1.69:59969" } } }}