1
0
mirror of https://github.com/taigrr/arduinolibs synced 2025-01-18 04:33:12 -08:00

Mini protobufs implementation to support NoiseLink

This commit is contained in:
Rhys Weatherley 2018-06-20 15:51:07 +10:00
parent dd9c99ff07
commit e4b90184fd
3 changed files with 885 additions and 0 deletions

View File

@ -97,6 +97,7 @@ SOURCES += \
Noise_NNpsk0_25519_ChaChaPoly_BLAKE2s.cpp \
Noise_NNpsk0_25519_ChaChaPoly_SHA256.cpp \
Noise_NNpsk0.cpp \
NoiseProtobufs.cpp \
NoiseProtocolDescriptor.cpp \
NoiseSymmetricState_AESGCM_SHA256.cpp \
NoiseSymmetricState_ChaChaPoly_BLAKE2s.cpp \

View File

@ -0,0 +1,679 @@
/*
* Copyright (C) 2018 Southern Storm Software, Pty Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include "NoiseProtobufs.h"
#include <string.h>
/**
* \class NoiseProtobufFormatter NoiseProtobufs.h <NoiseProtobufs.h>
* \brief Formats data into a buffer using the protobufs format.
*
* This class provides support for creating messages in the protobufs
* format. Fields in a message consist of a tag followed by a value.
*
* This implementation provides a subset of the full protobufs encoding,
* limited to the types bool, uint32, uint64, sint32, sint64, string,
* bytes, and embedded messages. In addition, tag numbers are limited
* to unsigned 8-bit values.
*
* Reference: https://developers.google.com/protocol-buffers/docs/encoding
*
* \sa NoiseProtobufParser
*/
/**
* \type NoiseProtobufTag_t
* \brief Integer type that defines a protobuf tag number (unsigned 8-bit).
*/
/**
* \brief Constructs a new protobuf formatter.
*
* \param data Points to the buffer to format fields into.
* \param maxSize Maximum size of the buffer in bytes.
*/
NoiseProtobufFormatter::NoiseProtobufFormatter(void *data, size_t maxSize)
: buf((uint8_t *)data)
, maxsz(maxSize)
, pos(0)
, err(false)
{
}
/**
* \brief Destroys this protobuf formatter.
*/
NoiseProtobufFormatter::~NoiseProtobufFormatter()
{
}
/**
* \fn size_t NoiseProtobufFormatter::size() const
* \brief Returns the final size of the data after all fields have been added.
*
* \return The number of bytes that were written to the buffer.
*
* If an error has occurred, then size() will be invalid.
*
* \sa error()
*/
/**
* \fn bool NoiseProtobufFormatter::error() const
* \brief Determine if an error occurred during formatting.
*
* \return Returns true if the buffer ran out of space while trying to
* write the fields; false if formatting was successful.
*
* \sa size()
*/
/**
* \fn void NoiseProtobufFormatter::addBool(NoiseProtobufTag_t tag, bool value)
* \brief Adds a boolean field.
*
* \param tag Tag number for the field.
* \param value Boolean value for the field.
*/
/**
* \fn void NoiseProtobufFormatter::addUInt32(NoiseProtobufTag_t tag, uint32_t value)
* \brief Adds an unsigned 32-bit integer field.
*
* \param tag Tag number for the field.
* \param value Unsigned 32-bit value for the field.
*/
/**
* \fn void NoiseProtobufFormatter::addUInt64(NoiseProtobufTag_t tag, uint64_t value)
* \brief Adds an unsigned 64-bit integer field.
*
* \param tag Tag number for the field.
* \param value Unsigned 64-bit value for the field.
*/
/**
* \fn void NoiseProtobufFormatter::addInt32(NoiseProtobufTag_t tag, int32_t value)
* \brief Adds a signed 32-bit integer field.
*
* \param tag Tag number for the field.
* \param value Signed 32-bit value for the field.
*
* This function uses the ZigZag encoding for the signed integer value.
*/
/**
* \fn void NoiseProtobufFormatter::addInt64(NoiseProtobufTag_t tag, int64_t value)
* \brief Adds a signed 64-bit integer field.
*
* \param tag Tag number for the field.
* \param value Signed 64-bit value for the field.
*
* This function uses the ZigZag encoding for the signed integer value.
*/
/**
* \brief Adds an arbitrary byte array as a field.
*
* \param tag Tag number for the field.
* \param data Points to the array of bytes for the field's value.
* \param len Number of bytes in the array.
*/
void NoiseProtobufFormatter::addBytes
(NoiseProtobufTag_t tag, const void *data, size_t len)
{
if (!data && len > 0) {
err = true;
return;
}
addHeader(tag, 2);
if (sizeof(len) <= 4)
addVarUInt32(len);
else
addVarUInt64(len);
if (!err) {
if (len <= (maxsz - pos)) {
memcpy(buf + pos, data, len);
pos += len;
} else {
err = true;
}
}
}
/**
* \brief Adds a NUL-terminated string as a field.
*
* \param tag Tag number for the field.
* \param str String value for the field, NUL-terminated.
*
* If \a str is NULL, then an empty string will be added.
*/
void NoiseProtobufFormatter::addString(NoiseProtobufTag_t tag, const char *str)
{
addBytes(tag, str, str ? strlen(str) : 0);
}
/**
* \fn void NoiseProtobufFormatter::addString(NoiseProtobufTag_t tag, const char *str, size_t len)
* \brief Adds a length-delimited string as a field.
*
* \param tag Tag number for the field.
* \param str String value for the field.
* \param len Number of bytes in the string value.
*/
#define ADD_BYTE(b) \
do { \
if (!err) { \
if (pos < maxsz) { \
buf[pos++] = (uint8_t)(b); \
} else { \
err = true; \
} \
} \
} while (0)
/**
* \brief Starts an embedded message.
*
* \param tag Tag number for the embedded message.
*
* \return A reference to the start of the embedded message, to be passed
* to endEmbedded() when the application has finished adding nested fields.
*
* \note The size of the embedded data is limited to no more than 65535 bytes,
* to simplify the implementation of endEmbedded(). Since Noise messages are
* also limited to no more than 65535 bytes, this shouldn't be a problem
* in practice.
*
* \sa endEmbedded()
*/
size_t NoiseProtobufFormatter::startEmbedded(NoiseProtobufTag_t tag)
{
addHeader(tag, 2);
size_t start = pos;
ADD_BYTE(0); // Placeholder to be filled in by endEmbedded().
return start;
}
/**
* \brief Ends an embedded message and returns to the previous level.
*
* \param start The value that was returned from startEmbedded() at the
* beginning of the embedded message.
*
* \sa startEmbedded()
*/
void NoiseProtobufFormatter::endEmbedded(size_t start)
{
// If an error already occurred, then bail out.
if (err)
return;
// If the starting position is invalid, then bail out.
if (start >= pos) {
err = true;
return;
}
// Determine the actual length of the embedded message.
size_t len = pos - start - 1;
// Encode the length as 1, 2, or 3 bytes. For the 2 and 3 byte
// versions we need to shift the message data up to make room.
if (len < (1U << 7)) {
buf[start] = (uint8_t)len;
} else if (len < (1U << 14)) {
if (pos < maxsz) {
memmove(buf + start + 2, buf + start + 1, len);
++pos;
buf[start] = (uint8_t)(len | 0x80);
buf[start + 1] = (uint8_t)(len >> 7);
} else {
err = true;
}
} else if (len < (1U << 16)) {
if ((pos + 1) < maxsz) {
memmove(buf + start + 3, buf + start + 1, len);
pos += 2;
buf[start] = (uint8_t)(len | 0x80);
buf[start + 1] = (uint8_t)((len >> 7) | 0x80);
buf[start + 2] = (uint8_t)(len >> 14);
} else {
err = true;
}
} else {
err = true;
}
}
/**
* \fn void NoiseProtobufFormatter::addHeader(NoiseProtobufTag_t tag, uint8_t wireType)
* \brief Adds a protobuf tag header.
*
* \param tag Tag number for the field.
* \param wireType Protobuf wire type for the field; 0 for integer, 2 for
* length-delimited.
*/
/**
* \brief Adds an unsigned 14-bit value as a varint.
*
* \param value Unsigned 14-bit value to add.
*/
void NoiseProtobufFormatter::addVarUInt14(uint16_t value)
{
if (value < (1U << 7)) {
ADD_BYTE(value);
} else {
ADD_BYTE(value | 0x80);
ADD_BYTE(value >> 7);
}
}
/**
* \brief Adds an unsigned 32-bit value as a varint.
*
* \param value Unsigned 32-bit value to add.
*/
void NoiseProtobufFormatter::addVarUInt32(uint32_t value)
{
if (value < (1U << 7)) {
ADD_BYTE(value);
} else if (value < (1U << 14)) {
ADD_BYTE(value | 0x80);
ADD_BYTE(value >> 7);
} else if (value < (1U << 21)) {
ADD_BYTE(value | 0x80);
ADD_BYTE((value >> 7) | 0x80);
ADD_BYTE(value >> 14);
} else if (value < (1U << 28)) {
ADD_BYTE(value | 0x80);
ADD_BYTE((value >> 7) | 0x80);
ADD_BYTE((value >> 14) | 0x80);
ADD_BYTE(value >> 21);
} else {
ADD_BYTE(value | 0x80);
ADD_BYTE((value >> 7) | 0x80);
ADD_BYTE((value >> 14) | 0x80);
ADD_BYTE((value >> 21) | 0x80);
ADD_BYTE(value >> 28);
}
}
/**
* \brief Adds an unsigned 64-bit value as a varint.
*
* \param value Unsigned 64-bit value to add.
*/
void NoiseProtobufFormatter::addVarUInt64(uint64_t value)
{
if (value < 0x100000000ULL) {
addVarUInt32((uint32_t)value);
} else {
while (value >= (1ULL << 7)) {
ADD_BYTE(value | 0x80);
value >>= 7;
}
ADD_BYTE(value);
}
}
/**
* \class NoiseProtobufParser NoiseProtobufs.h <NoiseProtobufs.h>
* \brief Parses data from a buffer using the protobufs format.
*
* This class provides support for parsing messages in the protobufs
* format. Fields in a message consist of a tag followed by a value.
*
* This implementation provides a subset of the full protobufs encoding,
* limited to the types bool, uint32, uint64, sint32, sint64, string,
* bytes, and embedded messages. In addition, tag numbers are limited
* to unsigned 8-bit values.
*
* Reference: https://developers.google.com/protocol-buffers/docs/encoding
*
* \sa NoiseProtobufFormatter
*/
/**
* \brief Constructs a new protobuf parser for reading the contents
* of a buffer.
*
* \param data Points to the buffer to parse.
* \param size Size of the buffer in bytes.
*/
NoiseProtobufParser::NoiseProtobufParser(const void *data, size_t size)
: buf((const uint8_t *)data)
, pos(0)
, end(size)
, ptr(0)
, value(0)
, wire(0xFF)
, err(false)
{
}
/**
* \brief Destroys this protobuf parser.
*/
NoiseProtobufParser::~NoiseProtobufParser()
{
}
/**
* \fn bool NoiseProtobufParser::error() const
* \brief Determine if an error occurred during parsing.
*
* \return Returns true if malformed data was found while parsing,
* false if parsing was successful.
*/
/**
* \brief Reads the next field from a protobuf-formatted message.
*
* \param tag Returns the tag number for the next field.
*
* \return Returns true if a new field was found, or false at the end
* of the message. Also returns false if an error occurs.
*/
bool NoiseProtobufParser::readNext(NoiseProtobufTag_t &tag)
{
tag = 0;
if (err || pos >= end)
return false;
uint64_t header = readVarint();
if (err)
return false;
if (header >= (256U << 3)) {
err = true;
return false;
}
tag = (NoiseProtobufTag_t)(header >> 3);
wire = (uint8_t)(header & 0x07);
if (wire == 0) {
value = readVarint();
return !err;
} else if (wire == 2) {
value = readVarint();
if (err)
return false;
if (value > (end - pos)) {
err = true;
return false;
}
ptr = pos;
pos += (size_t)value;
return true;
} else {
err = true;
return false;
}
}
/**
* \brief Reads a boolean value from the current field.
*
* \return The boolean value in the current field.
*
* This function will raise an error() if the current field is not boolean.
*/
bool NoiseProtobufParser::readBool()
{
if (err)
return false;
if (wire != 0) {
err = true;
return false;
}
return value != 0;
}
/**
* \brief Reads an unsigned 32-bit integer value from the current field.
*
* \return The integer value in the current field.
*
* This function will raise an error() if the current field is not an
* integer or the value is out of range.
*
* \sa readUInt64(), readInt32()
*/
uint32_t NoiseProtobufParser::readUInt32()
{
if (err)
return 0;
if (wire != 0 || value >= 0x100000000ULL) {
err = true;
return false;
}
return (uint32_t)value;
}
/**
* \brief Reads an unsigned 64-bit integer value from the current field.
*
* \return The integer value in the current field.
*
* This function will raise an error() if the current field is not an integer.
*
* \sa readUInt32(), readInt64()
*/
uint64_t NoiseProtobufParser::readUInt64()
{
if (err)
return 0;
if (wire != 0) {
err = true;
return false;
}
return value;
}
/**
* \brief Reads a signed 32-bit integer value from the current field.
*
* \return The integer value in the current field.
*
* This function uses the ZigZag encoding for the signed integer value.
* An error() is raised if the current field is not an integer or the value
* is out of range.
*
* \sa readInt64(), readUInt32()
*/
int32_t NoiseProtobufParser::readInt32()
{
if (err)
return 0;
if (wire != 0 || value >= 0x100000000ULL) {
err = true;
return false;
}
return ((int32_t)(value >> 1)) ^ (-((int32_t)(value & 0x01)));
}
/**
* \brief Reads a signed 64-bit integer value from the current field.
*
* \return The integer value in the current field.
*
* This function uses the ZigZag encoding for the signed integer value.
* An error() is raised if the current field is not an integer.
*
* \sa readInt32(), readUInt64()
*/
int64_t NoiseProtobufParser::readInt64()
{
if (err)
return 0;
if (wire != 0) {
err = true;
return false;
}
return ((int64_t)(value >> 1)) ^ (-((int64_t)(value & 0x01)));
}
/**
* \brief Reads a string value from the current field.
*
* \param str Returns a pointer to the start of the string. This points
* directly into the original buffer and will not be NUL-terminated.
*
* \return The length of the string in bytes.
*
* This function will raise an error() if the current field is not
* length-delimited.
*
* \sa readBytes()
*/
size_t NoiseProtobufParser::readString(const char * &str)
{
if (err) {
str = 0;
return 0;
}
if (wire != 2) {
err = true;
str = 0;
return 0;
}
str = (const char *)(buf + ptr);
return (size_t)value;
}
/**
* \brief Reads a byte array from the current field.
*
* \param str Returns a pointer to the start of the byte array.
*
* \return The length of the byte array in bytes.
*
* This function will raise an error() if the current field is not
* length-delimited.
*
* \sa readString()
*/
size_t NoiseProtobufParser::readBytes(const void * &data)
{
if (err) {
data = 0;
return 0;
}
if (wire != 2) {
err = true;
data = 0;
return 0;
}
data = (const void *)(buf + ptr);
return (size_t)value;
}
/**
* \brief Starts parsing an embedded message.
*
* \return A value to pass to endEmbedded() to pop out a level at the
* end of the embedded message.
*
* This function will raise an error() if the current field is not
* length-delimited.
*
* The following is an example of descending into an embedded message
* to parse all of the fields, and then returning to the previous
* message level at the end:
*
* \code
* size_t posn = parser.startEmbedded();
* while (parser.readNext(tag)) {
* ...
* }
* parser.endEmbedded(posn);
* \endcode
*
* \sa endEmbedded()
*/
size_t NoiseProtobufParser::startEmbedded()
{
if (err)
return 0;
if (wire != 2) {
err = true;
return 0;
}
size_t prev = end;
end = pos;
pos = ptr;
wire = 0xFF;
return prev;
}
/**
* \brief Ends parsing of an embedded message.
*
* \param posn The position in the outer message to return to. This must be
* the return value from startEmbedded() for the same nesting level.
*
* \sa startEmbedded()
*/
void NoiseProtobufParser::endEmbedded(size_t posn)
{
if (err)
return;
if (posn < end) {
err = true;
return;
}
pos = end;
end = posn;
wire = 0xFF;
}
/**
* \brief Reads a variable-length integer from the message.
*
* \return The integer value.
*/
uint64_t NoiseProtobufParser::readVarint()
{
uint64_t value = 0;
unsigned bit = 0;
while (pos < end && (buf[pos] & 0x80) != 0) {
value |= ((uint64_t)(buf[pos] & 0x7F)) << bit;
bit += 7;
if (bit >= 63) {
err = true;
return 0;
}
++pos;
}
if (pos >= end) {
err = true;
return 0;
}
if (bit >= 63 && (buf[pos] & 0xFE) != 0) {
err = true;
return 0;
}
value |= ((uint64_t)(buf[pos] & 0x7F)) << bit;
++pos;
return value;
}

View File

@ -0,0 +1,205 @@
/*
* Copyright (C) 2018 Southern Storm Software, Pty Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef NOISE_PROTOBUFS_h
#define NOISE_PROTOBUFS_h
#include <inttypes.h>
#include <stddef.h>
typedef uint8_t NoiseProtobufTag_t;
namespace Noise
{
// Tags for all NoiseLink negotiation options, for convenience.
// Not all of these are used for the NoiseTinyLink profile but we
// provide them in case we need more of full NoiseLink in the future.
/**
* \brief Protobuf tags for NoiseLink session negotiation requests
* from the client.
*/
enum NegotiationRequest
{
ReqServerName = 1, /**< Server name */
ReqInitialProtocol = 2, /**< Initial Noise protocol to try */
ReqSwitchProtocol = 3, /**< List of supported fallback protocols */
ReqRetryProtocol = 4, /**< List of other non-fallback protocols */
ReqRejectedProtocol = 5, /**< Previous protocol rejected by server */
ReqPSKId = 6 /**< Identifier for the client's PSK */
};
/**
* \brief Protobuf tags for NoiseLink session negotiation responses
* from the server.
*/
enum NegotiationResponse
{
RespSwitchProtocol = 3, /**< Switch to a fallback protocol */
RespRetryProtocol = 4, /**< Retry with a non-fallback protocol */
RespRejected = 5 /**< Server totally rejected the client */
};
/**
* \brief Protobuf tags for NoiseLink options that may appear in
* handshake message payloads.
*/
enum PayloadOptions
{
OptEvidenceReqType = 1, /**< Evidence request type */
OptEvidenceBlobType = 2, /**< Evidence blob type */
OptEvidenceBlob = 3, /**< Evidence blob */
OptPSKId = 4, /**< Identifier for the client's PSK */
OptTransport = 5 /**< Embedded transport options */
};
/**
* \brief Protobuf tags for NoiseLink transport options that may
* appear in handshake message payloads within an embedded message
* with the tag number OptTransport.
*/
enum TransportOptions
{
TrMaxSendLength = 1, /**< Maximum send buffer length */
TrMaxRecvLength = 2, /**< Maximum receive buffer length */
TrContinuousRekey = 3, /**< Rekey after every message */
TrShortTerminated = 4 /**< Terminate session on short message */
};
};
class NoiseProtobufFormatter
{
public:
NoiseProtobufFormatter(void *data, size_t maxSize);
~NoiseProtobufFormatter();
size_t size() const { return pos; }
bool error() const { return err; }
void addBool(NoiseProtobufTag_t tag, bool value);
void addUInt32(NoiseProtobufTag_t tag, uint32_t value);
void addUInt64(NoiseProtobufTag_t tag, uint64_t value);
void addInt32(NoiseProtobufTag_t tag, int32_t value);
void addInt64(NoiseProtobufTag_t tag, int64_t value);
void addBytes(NoiseProtobufTag_t tag, const void *data, size_t len);
void addString(NoiseProtobufTag_t tag, const char *str);
void addString(NoiseProtobufTag_t tag, const char *str, size_t len);
size_t startEmbedded(NoiseProtobufTag_t tag);
void endEmbedded(size_t start);
private:
uint8_t *buf;
size_t maxsz;
size_t pos;
bool err;
void addHeader(NoiseProtobufTag_t tag, uint8_t wireType);
void addVarUInt14(uint16_t value);
void addVarUInt32(uint32_t value);
void addVarUInt64(uint64_t value);
};
class NoiseProtobufParser
{
public:
NoiseProtobufParser(const void *data, size_t size);
~NoiseProtobufParser();
bool error() const { return err; }
bool readNext(NoiseProtobufTag_t &tag);
bool readBool();
uint32_t readUInt32();
uint64_t readUInt64();
int32_t readInt32();
int64_t readInt64();
size_t readString(const char * &str);
size_t readBytes(const void * &data);
size_t startEmbedded();
void endEmbedded(size_t posn);
private:
const uint8_t *buf;
size_t pos;
size_t end;
size_t ptr;
uint64_t value;
uint8_t wire;
bool err;
uint64_t readVarint();
};
inline void NoiseProtobufFormatter::addHeader
(NoiseProtobufTag_t tag, uint8_t wireType)
{
addVarUInt14((((uint16_t)tag) << 3) | wireType);
}
inline void NoiseProtobufFormatter::addBool(NoiseProtobufTag_t tag, bool value)
{
addHeader(tag, 0);
addVarUInt14(value ? 1 : 0);
}
inline void NoiseProtobufFormatter::addUInt32
(NoiseProtobufTag_t tag, uint32_t value)
{
addHeader(tag, 0);
addVarUInt32(value);
}
inline void NoiseProtobufFormatter::addUInt64
(NoiseProtobufTag_t tag, uint64_t value)
{
addHeader(tag, 0);
addVarUInt64(value);
}
inline void NoiseProtobufFormatter::addInt32
(NoiseProtobufTag_t tag, int32_t value)
{
addHeader(tag, 0);
addVarUInt32((uint32_t)((value << 1) ^ (value >> 31)));
}
inline void NoiseProtobufFormatter::addInt64
(NoiseProtobufTag_t tag, int64_t value)
{
addHeader(tag, 0);
addVarUInt64((uint64_t)((value << 1) ^ (value >> 63)));
}
inline void NoiseProtobufFormatter::addString
(NoiseProtobufTag_t tag, const char *str, size_t len)
{
addBytes(tag, str, len);
}
#endif