Compare commits

...

26 Commits

Author SHA1 Message Date
Kevin Hester
50ec03229f Merge pull request #780 from geeksville/mqtt
1.2.23 - fix gpio access
2021-04-06 11:09:03 +08:00
Kevin Hester
4bfbb33a42 1.2.23 2021-04-06 11:00:03 +08:00
Kevin Hester
57bf4073c5 Merge remote-tracking branch 'root/master' into mqtt 2021-04-06 10:58:01 +08:00
Kevin Hester
cbfd80f893 fix #779. Add basic GPIO documentation 2021-04-06 10:56:23 +08:00
Kevin Hester
cec905914c Fix GPIO service and cleanup response handling 2021-04-06 10:34:23 +08:00
Jm Casler
4382caad88 Update RangeTestPlugin.md 2021-04-05 19:25:53 -07:00
Kevin Hester
c0cfd0bb41 remove dead code 2021-04-05 16:27:52 +08:00
Kevin Hester
276526005b run-both should target tbeams 2021-04-05 16:27:44 +08:00
Kevin Hester
4a556099dc Merge pull request #778 from geeksville/mqtt
Add basics of MQTT (disabled by default)
2021-04-05 16:22:19 +08:00
Kevin Hester
7abc3534c4 fix build for !wifi devices 2021-04-05 13:00:56 +08:00
Kevin Hester
65914fad07 Merge remote-tracking branch 'root/master' into mqtt 2021-04-05 12:21:52 +08:00
Kevin Hester
63c976d4f1 update docs 2021-04-05 12:11:07 +08:00
Kevin Hester
7f5ab472b9 mqtt: downlink now works 2021-04-05 09:38:00 +08:00
Kevin Hester
a845406a19 cleanup packet encrypt/descrypt 2021-04-05 09:24:00 +08:00
Kevin Hester
8ef36bcc9c note about security 2021-04-05 08:56:11 +08:00
Kevin Hester
3cd64bb8b5 allow passing even encrypted packets through the plugins 2021-04-05 08:44:47 +08:00
Kevin Hester
50a69d77e6 mqtt: begin subscription support 2021-04-05 08:42:52 +08:00
Kevin Hester
55b8314a2a mqtt: only publish uplink_enabled channels 2021-04-05 07:57:06 +08:00
Kevin Hester
e84edc676f mqtt: automatically start or stop as neede & attempt reconnect 2021-04-05 07:53:07 +08:00
Kevin Hester
d19af8b83d mqtt: send packets after they are encrypted 2021-04-04 09:20:37 +08:00
Kevin Hester
638cec7f25 we now send packets to mqtt server 2021-04-03 22:27:06 +08:00
Kevin Hester
f3f09f0dcf MQTT WIP 2021-04-03 16:06:40 +08:00
Kevin Hester
8890ca759d fix nodeid 2021-04-03 15:04:03 +08:00
Kevin Hester
139da372e7 fix warning 2021-04-03 14:56:46 +08:00
Kevin Hester
dcf64dfacd MQTT at least talks to server, works in native and esp32 2021-04-03 14:54:10 +08:00
Kevin Hester
2acde3333c todo updates 2021-04-03 10:40:56 +08:00
34 changed files with 989 additions and 527 deletions

22
.idea/workspace.xml generated
View File

@@ -15,7 +15,9 @@
<component name="ChangeListManager">
<list default="true" id="58922733-b05b-4b90-9655-b9b18914977a" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/platformio.ini" beforeDir="false" afterPath="$PROJECT_DIR$/platformio.ini" afterDir="false" />
<change beforePath="$PROJECT_DIR$/proto" beforeDir="false" afterPath="$PROJECT_DIR$/proto" afterDir="false" />
<change beforePath="$PROJECT_DIR$/proto/docs/docs.md" beforeDir="false" afterPath="$PROJECT_DIR$/proto/docs/docs.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/mqtt/MQTT.h" beforeDir="false" afterPath="$PROJECT_DIR$/src/mqtt/MQTT.h" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -65,6 +67,11 @@
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration default="true" type="GradleAppRunConfiguration" factoryName="Application" REDIRECT_INPUT="false" ELEVATE="false" PASS_PARENT_ENVS_2="true">
<method v="2">
<option name="com.jetbrains.cidr.cpp.gradle.execution.GradleNativeBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
</method>
</configuration>
<configuration name="PlatformIO Debug" type="platformio" factoryName="PlatformIO Debug" REDIRECT_INPUT="false" ELEVATE="false" PASS_PARENT_ENVS_2="true" PROJECT_NAME="meshtastic-esp32" TARGET_NAME="Debug" CONFIG_NAME="native" RUN_TARGET_PROJECT_NAME="meshtastic-esp32" RUN_TARGET_NAME="Debug">
<method v="2">
<option name="com.jetbrains.cidr.execution.CidrBuildBeforeRunTaskProvider$BuildBeforeRunTask" enabled="true" />
@@ -97,6 +104,9 @@
<workItem from="1617115747078" duration="1391000" />
<workItem from="1617117632667" duration="307000" />
<workItem from="1617160691713" duration="1016000" />
<workItem from="1617279002260" duration="1626000" />
<workItem from="1617425689081" duration="1896000" />
<workItem from="1617437366919" duration="1182000" />
</task>
<servers />
</component>
@@ -128,6 +138,16 @@
<line>37</line>
<option name="timeStamp" value="7" />
</line-breakpoint>
<line-breakpoint enabled="true" type="com.jetbrains.cidr.execution.debugger.OCBreakpointType">
<url>file://$PROJECT_DIR$/src/mqtt/MQTT.cpp</url>
<line>84</line>
<option name="timeStamp" value="10" />
</line-breakpoint>
<line-breakpoint enabled="true" type="com.jetbrains.cidr.execution.debugger.OCBreakpointType">
<url>file://$PROJECT_DIR$/.pio/libdeps/native/PubSubClient/src/PubSubClient.cpp</url>
<line>468</line>
<option name="timeStamp" value="11" />
</line-breakpoint>
</breakpoints>
</breakpoint-manager>
<watches-manager>

3
bin/mqtt-listen.sh Executable file
View File

@@ -0,0 +1,3 @@
mosquitto_sub -h test.mosquitto.org -v -t mesh/stat/\# -t mesh/json/\#
# mosquitto_sub -h test.mosquitto.org -v -t mesh/\# -F "%j"

1
bin/mqtt-send-status.sh Executable file
View File

@@ -0,0 +1 @@
mosquitto_pub -h test.mosquitto.org -t mesh/stat/FakeNode -m online -d

View File

@@ -1,11 +1,13 @@
set -e
pio run
TARG=tbeam
pio run -e $TARG
echo uploading to usb1
pio run --upload-port /dev/ttyUSB1 -t upload &
pio run --upload-port /dev/ttyUSB1 -t upload -e $TARG &
echo uploading to usb0
pio run --upload-port /dev/ttyUSB0 -t upload &
pio run --upload-port /dev/ttyUSB0 -t upload -e $TARG &
wait
wait

View File

@@ -4,7 +4,9 @@ You probably don't care about this section - skip to the next one.
## before next release
* DONE android speed settings https://github.com/meshtastic/Meshtastic-Android/issues/271
* fix heltec battery scaling
* DONE remote admin busted?
* DONE check android code - @havealoha comments about odd sleep behavior
* ABANDONED test github actions locally on linux
@@ -14,7 +16,7 @@ You probably don't care about this section - skip to the next one.
* remove linux dependency in native build
* DONE tcp stream problem in python+pordtuino, server thinks client dropped when client DID NOT DROP
* DONE TCP mode for android, localhost is at 10.0.2.2
* make sure USB still works in android
* DONE make sure USB still works in android
* add portduino builds to zip
* add license to portduino and make announcement
* DONE naks are being dropped (though enqueuedLocal) sometimes before phone/PC gets them
@@ -42,7 +44,7 @@ You probably don't care about this section - skip to the next one.
* DONE before next relase: test empty channel sets on android
* DONE channel sharing in android
* DONE test 1.0 firmware update on android
* DONE test 1.1 firmware update on android
* DONE test 1.1 firmwhttps://github.com/meshtastic/Meshtastic-Android/issues/271are update on android
* DONE test 1.2.10 firmware update on android
* DONE test link sharing on android
* FIXED? luxon bug report - seeing rx acks for nodes that are not on the network
@@ -50,14 +52,6 @@ You probably don't care about this section - skip to the next one.
* DONE show GPS time only if we know what global time is
* DONE android should always provide time to nodes - so that it is easier for the mesh to learn the current time
## MQTT
* mqtt.meshtastic.org should have VERY basic auth at launch (to prevent abuse)
* use MQTT for simulator mesh network
* do initial development inside of portduino
* do as much possible on the device side (so we can eventually just have ESP32 talk directly to server)
* eventually add a MQTTPacket on the ToRadio & FromRadio links
## Multichannel support
* DONE cleanup the external notification and serial plugins

View File

@@ -36,6 +36,14 @@ If you are reviewing our implementation, this is a brief statement of our method
- Each 16 byte BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet.
- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Since our packets are small counter portion will really never be higher than 32 (five bits).
### Details on the nonce encoding
(For those porting to other architectures)
Bytes 0-3 of the nonce are the "packet number" in little-endian order
Bytes 4-7 are currently zero
Bytes 8-11 are the sending node ID in little-endian order
Bytes 12-15 are currently zero
## Comments from reviewer #1
This reviewer is a cryptography professional, but would like to remain anonymous. We thank them for their comments ;-):

View File

@@ -12,14 +12,18 @@
- [NODEID](#nodeid)
- [USERID](#userid)
- [CHANNELID](#channelid)
- [PORTID](#portid)
- [Gateway nodes](#gateway-nodes)
- [Optional web services](#optional-web-services)
- [Public MQTT broker service](#public-mqtt-broker-service)
- [Riot.im messaging bridge](#riotim-messaging-bridge)
- [Deprecated concepts](#deprecated-concepts)
- [MESHID (deprecated)](#meshid-deprecated)
- [DESTCLASS (deprecated)](#destclass-deprecated)
- [DESTID (deprecated)](#destid-deprecated)
- [MQTTSimInterface](#mqttsiminterface)
- [Web services](#web-services)
- [Public MQTT broker service](#public-mqtt-broker-service)
- [Broker selection](#broker-selection)
- [Admin service](#admin-service)
- [Riot.im messaging bridge](#riotim-messaging-bridge)
- [Deprecated concepts](#deprecated-concepts)
- [MESHID (deprecated)](#meshid-deprecated)
- [DESTCLASS (deprecated)](#destclass-deprecated)
- [DESTID (deprecated)](#destid-deprecated)
- [Rejected idea: RAW UDP](#rejected-idea-raw-udp)
- [Development plan](#development-plan)
- [Work items](#work-items)
@@ -68,17 +72,21 @@ Any gateway-device will contact the MQTT broker.
### Topics
The "mesh/crypt/CHANNELID/NODEID/PORTID" [topic](https://www.hivemq.com/blog/mqtt-essentials-part-5-mqtt-topics-best-practices/) will be used for messages sent from/to a mesh.
* The "/mesh/crypt/CHANNELID/NODEID" [topic](https://www.hivemq.com/blog/mqtt-essentials-part-5-mqtt-topics-best-practices/) will be used for (encrypted) messages sent from/to a mesh.
Gateway nodes will foward any MeshPacket from a local mesh channel with uplink_enabled. The packet (encapsulated in a ServiceEnvelope) will remain encrypted with the key for the specified channel.
For any channels in the local node with downlink_enabled, the gateway node will forward packets from MQTT to the local mesh. It will do this by subscribing to mesh/crypt/CHANNELID/# and forwarding relevant packets.
For any channels in the gateway node with downlink_enabled, the gateway node will forward packets from MQTT to the local mesh. It will do this by subscribing to mesh/crypt/CHANNELID/# and forwarding relevant packets.
If the channelid 'well known'/public it could be decrypted by a web service (if the web service was provided with the associated channel key), in which case it will be decrypted by a web service and appear at "mesh/clear/CHANNELID/NODEID/PORTID". Note: This is not in the initial deliverable.
* If the channelid 'well known'/public it could be decrypted by a web service (if the web service was provided with the associated channel key), in which case it will be decrypted by a web service and appear at "mesh/clear/CHANNELID/NODEID/PORTID".
FIXME, discuss how text message global mirroring could scale (or not)
* If it was possible to republish on mesh/clear/... and the PORTID is wellknown (i.e. the protobufs needed for decoding it are in the master github), it will be decoded and republished as JSON on mesh/json/CHANNELID/NODEID/PortName. This is to facilitate the development of third party apps to visualize 'live' node positions and 'texts' (for users that have opted to send on those explicitly public channels).
FIXME, consider how text message global mirroring could scale (or not)
FIXME, possibly don't global mirror text messages - instead rely on matrix/riot?
FIXME, discuss possible attacks by griefers and how they can be prvented
FIXME, consider possible attacks by griefers and how they can be prvented
* The "/mesh/stat/NODEID" topic contains a simple string showing connection status of nodes. We rely on the MQTT feature for automatically publishing special failrue messages to this topic when the device disconnects.
#### Service Envelope
@@ -88,7 +96,7 @@ ServiceEnvelope will include the message, and full information about arrival tim
#### NODEID
The unique ID for a node. A hex string that starts with a ! symbol.
The unique ID for a node. A 8 byte (16 character) hex string that starts with a ! symbol.
#### USERID
@@ -96,7 +104,15 @@ A user ID string. This string is either a user ID if known or a nodeid to simply
#### CHANNELID
FIXME, figure out how channelids work
For the time being we simply use the local "channel name" - which is not quite good enough.
FIXME, figure out how channelids work in more detail. They should generally be globally unique, but this is not a requirement. If someone accidentially (or maliciously) sends data using a channel ID they do not 'own' they will still lacking a valid AES256 encryption, so it will be ignored by others.
idea to be pondered: When the user clicks to enable uplink/downlink check the name they entered and 'claim' it on the server?
#### PORTID
Portid is used to descriminated between different packet types which are sent over a channel. As used here it is an integer typically (but not necessarily) chosen from portnums.proto.
### Gateway nodes
@@ -107,31 +123,74 @@ Gateway nodes (via code running in the phone) will contain two tables to whiteli
Since multiple gateway nodes might be connected to a single mesh, it is possible that duplicate messages will be published on any particular topic. Therefore subscribers to these topics should
deduplicate if needed by using the packet ID of each message.
### Optional web services
#### Public MQTT broker service
### MQTTSimInterface
This is a bit orthogonal from the main MQTT feature set, but a special simulated LoRa interface called MQTTSimInterface uses the
MQTT messaging infrastructure to send "LoRa" packets between simulated nodes running on Linux. This allows us to test radio topologies and code without having to use real hardware.
This service uses the standard mesh/crypt/... topic, but it picks a special CHANNEL_ID. That CHANNEL_ID is typcially of the form "simmesh_xxx".
FIXME: Figure out how to secure the creation and use of well known CHANNEL_IDs.
## Web services
### Public MQTT broker service
An existing public [MQTT broker](https://mosquitto.org/) will be the default for this service, but clients can use any MQTT broker they choose.
FIXME - figure out how to avoid impersonation (because we are initially using a public mqtt server with no special security options). FIXME, include some ideas on this in the ServiceEnvelope documentation.
#### Riot.im messaging bridge
#### Broker selection
On a previous project I used mosqitto, which I liked, but the admin interface for programmatically managing access was ugly. [This](https://www.openlogic.com/blog/activemq-vs-rabbitmq) article makes me think RabbitMQ might be best for us.
Initially I will try to avoid using any non MQTT broker/library/API
### Admin service
(This is a WIP draft collection of not complete ideas)
The admin service deals with misc global arbibration/access tasks. It is actually reached **through** the MQTT broker, though for security we depend on that broker having a few specialized rules about who can post to or see particular topics (see below).
Topics:
* mesh/ta/# - all requests going towards the admin server (only the admin server can see this topic)
* mesh/tn/NODEID/# - all responses/requests going towards a particlar gateway node (only this particular gateway node is allowed to see this topic)
* mesh/to/NODEID/# - unsecured messages sent to a gateway node (any attacker can see this topic) - used only for "request gateway id" responses
* mesh/ta/toadmin - a request to the admin server, payload is a ToAdmin protobuf
* mesh/tn/NODEID/tonode - a request/response to a particular gateway node. payload is a ToNode protobuf
Operations provided via the ToAdmin/ToNode protocol:
* Register a global channel ID (request a new channel ID). Optionally include the AES key if you would like the web service to automatically decrypt in the cloud
* Request gateway ID - the response is used to re-sign in to the broker.
Possibly might need public key encryption for the gateway request? Since the response is sent to the mesh/to endpoint? I would really like to use MQTT for all comms so I don't need yet another protocol/port from the device.
Idea 1: A gateway ID/signin can only be assigned once per node ID. If a user loses their signin info, they'll need to change their node number. yucky.
Idea 2: Instead gateway signins are assigned at "manufacture" time (and if lost, yes the user would need to "remanufacture" their node). Possibly a simple web service (which can be accessed via the python install script?) that goes to an https endpoint, gets signin info (and server keeps a copy) and stores it in the device. Hardware manufacturers could ask for N gateway IDs via the same API and get back a bunch of small files that could be programmed on each device. Would include node id, etc... Investigate alternatives like storing a particular private key to allow each device to generate their own signin key and the server would trust it by checking against a public key?
TODO/FIXME: look into mqtt broker options, possibly find one with better API support than mosquitto?
### Riot.im messaging bridge
@Geeksville will run a riot.im bridge that talks to the public MQTT broker and sends/receives into the riot.im network.
There is apparently [already](https://github.com/derEisele/tuple) a riot.im [bridge](https://matrix.org/bridges/) for MQTT. That will possibly need to be customized a bit. But by doing this, we should be able to let random riot.im users send/receive messages to/from any meshtastic device. (FIXME ponder security). See this [issue](https://github.com/meshtastic/Meshtastic-Android/issues/2#issuecomment-645660990) with discussion with the dev.
### Deprecated concepts
## Deprecated concepts
You can ignore these for now...
#### MESHID (deprecated)
### MESHID (deprecated)
Earlier drafts of this document included the concept of a MESHID. That concept has been removed for now, but might be useful in the future. The old idea is listed below:
A unique ID for this mesh. There will be some sort of key exchange process so that the mesh ID can not be impersonated by other meshes.
#### DESTCLASS (deprecated)
### DESTCLASS (deprecated)
Earlier drafts of this document included the concept of a DESTCLASS. That concept has been removed for now, but might be useful in the future. The old idea is listed below:
@@ -145,7 +204,7 @@ The type of DESTID this message should be delivered to. A short one letter seque
| S | SMS gateway, DESTID is a phone number to reach via Twilio.com |
| E | Emergency message, see bug #fixme for more context |
#### DESTID (deprecated)
### DESTID (deprecated)
Earlier drafts of this document included the concept of a DESTCLASS. That concept has been removed for now, but might be useful in the future. The old idea is listed below:

View File

@@ -26,7 +26,7 @@ These are the settings that can be configured.
For basic usage, you will need two devices both with a GPS. A device with a paired phone with GPS may work, I have not tried it.
The first thing to do is to turn on the plugin. With the plugin turned on, the other settings will be available:
The first thing to do is to turn on the plugin. The device will need to be restarted after appling the settings. With the plugin turned on, the other settings will be available:
range_test_plugin_enabled = 1

View File

@@ -1,23 +1,60 @@
# Remote Hardware Service
FIXME - the following are a collection of notes moved from elsewhere. We need to refactor these notes into actual documentation on the remote-hardware/gpio service.
These are 'programmer focused' notes on using the "remote hardware" feature.
### 1.7.2. New 'no-code-IOT' mini-app
Note: This feature uses a preinstalled plugin in the device code and associated commandline flags/classes in the python code. You'll need to be running at least version 1.2.23 (or later) of the python and device code to use this feature.
Add a new 'remote GPIO/serial port/SPI/I2C access' mini-app. This new standard app would use the MQTT messaging layer to let users (developers that don't need to write device code) do basic (potentially dangerous) operations remotely.
You can get the latest python tool/library with "pip3 install --upgrade meshtastic" on Windows/Linux/OS-X.
#### 1.7.2.1. Supported operations in the initial release
Initially supported features for no-code-IOT.
## Supported operations in the initial release
- Set any GPIO
- Read any GPIO
- Receive notification of changes in any GPIO.
#### 1.7.2.2. Supported operations eventually
## Setup
General ideas for no-code IOT.
GPIO access is fundamentally 'dangerous' because invalid options can physically burn-up hardware. To prevent access from untrusted users you must first make a "gpio" channel that is used for authenticated access to this feature. You'll need to install this channel on both the local and remote node.
- Subscribe for notification of GPIO input status change (i.e. when pin goes low, send my app a message)
- Write/read N bytes over I2C/SPI bus Y (as one atomic I2C/SPI transaction)
- Send N bytes out serial port Z
- Subscribe for notification for when regex X matches the bytes that were received on serial port Z
The procedure using the python command line tool is:
1. Connect local device via USB
2. "meshtastic --ch-add admin; meshtastic --info" thn copy the (long) "Complete URL" that info printed
3. Connect remote device via USB (or use the remote admin feature to reach it through the mesh, but that's beyond the scope of this tutorial)
4. "meshtastic --seturl theurlyoucopiedinstep2"
Now both devices can talk over the "gpio" channel.
## Doing GPIO operations
Here's some examples using the command line tool.
## Using GPIOs from python
You can programmatically do operations from your own python code by using the meshtastic "RemoteHardwareClient" class - see the python documentation for more details.
Writing a GPIO
```
meshtastic --port /dev/ttyUSB0 --gpio-wrb 4 1 --dest \!28979058
Connected to radio
Writing GPIO mask 0x10 with value 0x10 to !28979058
```
Reading a GPIO
```
meshtastic --port /dev/ttyUSB0 --gpio-rd 0x10 --dest \!28979058
Connected to radio
Reading GPIO mask 0x10 from !28979058
GPIO read response gpio_value=16
```
Watching for GPIO changes:
```
meshtastic --port /dev/ttyUSB0 --gpio-watch 0x10 --dest \!28979058
Connected to radio
Watching GPIO mask 0x10 from !28979058
Received RemoteHardware typ=GPIOS_CHANGED, gpio_value=16
Received RemoteHardware typ=GPIOS_CHANGED, gpio_value=0
Received RemoteHardware typ=GPIOS_CHANGED, gpio_value=16
< press ctrl-c to exit >
```

View File

@@ -11,7 +11,7 @@
*****************************************************************/
/* Enable support for dynamically allocated fields */
/* #define PB_ENABLE_MALLOC 1 */
#define PB_ENABLE_MALLOC 1
/* Define this if your CPU / compiler combination does not support
* unaligned memory access to packed structures. */
@@ -52,10 +52,9 @@
* Feel free to look around and use the defined macros, though. *
******************************************************************/
/* Version of the nanopb library. Just in case you want to check it in
* your own program. */
#define NANOPB_VERSION nanopb-0.4.4
#define NANOPB_VERSION nanopb - 0.4.4
/* Include all the system headers needed by nanopb. You will need the
* definitions of the following:
@@ -71,11 +70,11 @@
#ifdef PB_SYSTEM_HEADER
#include PB_SYSTEM_HEADER
#else
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#ifdef PB_ENABLE_MALLOC
#include <stdlib.h>
@@ -90,30 +89,30 @@ extern "C" {
* This just reduces memory requirements, but is not required.
*/
#if defined(PB_NO_PACKED_STRUCTS)
/* Disable struct packing */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed
/* Disable struct packing */
#define PB_PACKED_STRUCT_START
#define PB_PACKED_STRUCT_END
#define pb_packed
#elif defined(__GNUC__) || defined(__clang__)
/* For GCC and clang */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed __attribute__((packed))
/* For GCC and clang */
#define PB_PACKED_STRUCT_START
#define PB_PACKED_STRUCT_END
#define pb_packed __attribute__((packed))
#elif defined(__ICCARM__) || defined(__CC_ARM)
/* For IAR ARM and Keil MDK-ARM compilers */
# define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)")
# define PB_PACKED_STRUCT_END _Pragma("pack(pop)")
# define pb_packed
/* For IAR ARM and Keil MDK-ARM compilers */
#define PB_PACKED_STRUCT_START _Pragma("pack(push, 1)")
#define PB_PACKED_STRUCT_END _Pragma("pack(pop)")
#define pb_packed
#elif defined(_MSC_VER) && (_MSC_VER >= 1500)
/* For Microsoft Visual C++ */
# define PB_PACKED_STRUCT_START __pragma(pack(push, 1))
# define PB_PACKED_STRUCT_END __pragma(pack(pop))
# define pb_packed
/* For Microsoft Visual C++ */
#define PB_PACKED_STRUCT_START __pragma(pack(push, 1))
#define PB_PACKED_STRUCT_END __pragma(pack(pop))
#define pb_packed
#else
/* Unknown compiler */
# define PB_PACKED_STRUCT_START
# define PB_PACKED_STRUCT_END
# define pb_packed
/* Unknown compiler */
#define PB_PACKED_STRUCT_START
#define PB_PACKED_STRUCT_END
#define pb_packed
#endif
/* Handly macro for suppressing unreferenced-parameter compiler warnings. */
@@ -126,11 +125,11 @@ extern "C" {
#ifndef PB_PROGMEM
#ifdef __AVR__
#include <avr/pgmspace.h>
#define PB_PROGMEM PROGMEM
#define PB_PROGMEM_READU32(x) pgm_read_dword(&x)
#define PB_PROGMEM PROGMEM
#define PB_PROGMEM_READU32(x) pgm_read_dword(&x)
#else
#define PB_PROGMEM
#define PB_PROGMEM_READU32(x) (x)
#define PB_PROGMEM_READU32(x) (x)
#endif
#endif
@@ -144,20 +143,20 @@ extern "C" {
* in the place where the PB_STATIC_ASSERT macro was called.
*/
#ifndef PB_NO_STATIC_ASSERT
# ifndef PB_STATIC_ASSERT
# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
/* C11 standard _Static_assert mechanism */
# define PB_STATIC_ASSERT(COND,MSG) _Static_assert(COND,#MSG);
# else
/* Classic negative-size-array static assert mechanism */
# define PB_STATIC_ASSERT(COND,MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND)?1:-1];
# define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER)
# define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##_##LINE##_##COUNTER
# endif
# endif
#ifndef PB_STATIC_ASSERT
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
/* C11 standard _Static_assert mechanism */
#define PB_STATIC_ASSERT(COND, MSG) _Static_assert(COND, #MSG);
#else
/* Static asserts disabled by PB_NO_STATIC_ASSERT */
# define PB_STATIC_ASSERT(COND,MSG)
/* Classic negative-size-array static assert mechanism */
#define PB_STATIC_ASSERT(COND, MSG) typedef char PB_STATIC_ASSERT_MSG(MSG, __LINE__, __COUNTER__)[(COND) ? 1 : -1];
#define PB_STATIC_ASSERT_MSG(MSG, LINE, COUNTER) PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER)
#define PB_STATIC_ASSERT_MSG_(MSG, LINE, COUNTER) pb_static_assertion_##MSG##_##LINE##_##COUNTER
#endif
#endif
#else
/* Static asserts disabled by PB_NO_STATIC_ASSERT */
#define PB_STATIC_ASSERT(COND, MSG)
#endif
/* Number of required fields to keep track of. */
@@ -186,8 +185,8 @@ typedef uint_least8_t pb_type_t;
/**** Field data types ****/
/* Numeric types */
#define PB_LTYPE_BOOL 0x00U /* bool */
#define PB_LTYPE_VARINT 0x01U /* int32, int64, enum, bool */
#define PB_LTYPE_BOOL 0x00U /* bool */
#define PB_LTYPE_VARINT 0x01U /* int32, int64, enum, bool */
#define PB_LTYPE_UVARINT 0x02U /* uint32, uint64 */
#define PB_LTYPE_SVARINT 0x03U /* sint32, sint64 */
#define PB_LTYPE_FIXED32 0x04U /* fixed32, sfixed32, float */
@@ -234,31 +233,30 @@ typedef uint_least8_t pb_type_t;
#define PB_HTYPE_SINGULAR 0x10U
#define PB_HTYPE_REPEATED 0x20U
#define PB_HTYPE_FIXARRAY 0x20U
#define PB_HTYPE_ONEOF 0x30U
#define PB_HTYPE_MASK 0x30U
#define PB_HTYPE_ONEOF 0x30U
#define PB_HTYPE_MASK 0x30U
/**** Field allocation types ****/
#define PB_ATYPE_STATIC 0x00U
#define PB_ATYPE_POINTER 0x80U
#define PB_ATYPE_CALLBACK 0x40U
#define PB_ATYPE_MASK 0xC0U
#define PB_ATYPE(x) ((x) & PB_ATYPE_MASK)
#define PB_HTYPE(x) ((x) & PB_HTYPE_MASK)
#define PB_LTYPE(x) ((x) & PB_LTYPE_MASK)
#define PB_LTYPE_IS_SUBMSG(x) (PB_LTYPE(x) == PB_LTYPE_SUBMESSAGE || \
PB_LTYPE(x) == PB_LTYPE_SUBMSG_W_CB)
#define PB_ATYPE_STATIC 0x00U
#define PB_ATYPE_POINTER 0x80U
#define PB_ATYPE_CALLBACK 0x40U
#define PB_ATYPE_MASK 0xC0U
#define PB_ATYPE(x) ((x)&PB_ATYPE_MASK)
#define PB_HTYPE(x) ((x)&PB_HTYPE_MASK)
#define PB_LTYPE(x) ((x)&PB_LTYPE_MASK)
#define PB_LTYPE_IS_SUBMSG(x) (PB_LTYPE(x) == PB_LTYPE_SUBMESSAGE || PB_LTYPE(x) == PB_LTYPE_SUBMSG_W_CB)
/* Data type used for storing sizes of struct fields
* and array counts.
*/
#if defined(PB_FIELD_32BIT)
typedef uint32_t pb_size_t;
typedef int32_t pb_ssize_t;
typedef uint32_t pb_size_t;
typedef int32_t pb_ssize_t;
#else
typedef uint_least16_t pb_size_t;
typedef int_least16_t pb_ssize_t;
typedef uint_least16_t pb_size_t;
typedef int_least16_t pb_ssize_t;
#endif
#define PB_SIZE_MAX ((pb_size_t)-1)
@@ -279,7 +277,7 @@ typedef struct pb_field_iter_s pb_field_iter_t;
typedef struct pb_msgdesc_s pb_msgdesc_t;
struct pb_msgdesc_s {
const uint32_t *field_info;
const pb_msgdesc_t * const * submsg_info;
const pb_msgdesc_t *const *submsg_info;
const pb_byte_t *default_value;
bool (*field_callback)(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field);
@@ -291,22 +289,22 @@ struct pb_msgdesc_s {
/* Iterator for message descriptor */
struct pb_field_iter_s {
const pb_msgdesc_t *descriptor; /* Pointer to message descriptor constant */
void *message; /* Pointer to start of the structure */
const pb_msgdesc_t *descriptor; /* Pointer to message descriptor constant */
void *message; /* Pointer to start of the structure */
pb_size_t index; /* Index of the field */
pb_size_t field_info_index; /* Index to descriptor->field_info array */
pb_size_t required_field_index; /* Index that counts only the required fields */
pb_size_t submessage_index; /* Index that counts only submessages */
pb_size_t index; /* Index of the field */
pb_size_t field_info_index; /* Index to descriptor->field_info array */
pb_size_t required_field_index; /* Index that counts only the required fields */
pb_size_t submessage_index; /* Index that counts only submessages */
pb_size_t tag; /* Tag of current field */
pb_size_t data_size; /* sizeof() of a single item */
pb_size_t array_size; /* Number of array entries */
pb_type_t type; /* Type of current field */
pb_size_t tag; /* Tag of current field */
pb_size_t data_size; /* sizeof() of a single item */
pb_size_t array_size; /* Number of array entries */
pb_type_t type; /* Type of current field */
void *pField; /* Pointer to current field in struct */
void *pData; /* Pointer to current data contents. Different than pField for arrays and pointers. */
void *pSize; /* Pointer to count/has field */
void *pField; /* Pointer to current field in struct */
void *pData; /* Pointer to current data contents. Different than pField for arrays and pointers. */
void *pSize; /* Pointer to count/has field */
const pb_msgdesc_t *submsg_desc; /* For submessage fields, pointer to field descriptor for the submessage. */
};
@@ -329,7 +327,11 @@ PB_STATIC_ASSERT(sizeof(uint64_t) == 2 * sizeof(uint32_t), UINT64_T_WRONG_SIZE)
* It has the number of bytes in the beginning, and after that an array.
* Note that actual structs used will have a different length of bytes array.
*/
#define PB_BYTES_ARRAY_T(n) struct { pb_size_t size; pb_byte_t bytes[n]; }
#define PB_BYTES_ARRAY_T(n) \
struct { \
pb_size_t size; \
pb_byte_t bytes[n]; \
}
#define PB_BYTES_ARRAY_T_ALLOCSIZE(n) ((size_t)n + offsetof(pb_bytes_array_t, bytes))
struct pb_bytes_array_s {
@@ -363,9 +365,9 @@ struct pb_callback_s {
*/
union {
bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg);
bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg);
bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void *const *arg);
} funcs;
/* Free arg for use by callback */
void *arg;
};
@@ -373,12 +375,7 @@ struct pb_callback_s {
extern bool pb_default_field_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
/* Wire types. Library user needs these only in encoder callbacks. */
typedef enum {
PB_WT_VARINT = 0,
PB_WT_64BIT = 1,
PB_WT_STRING = 2,
PB_WT_32BIT = 5
} pb_wire_type_t;
typedef enum { PB_WT_VARINT = 0, PB_WT_64BIT = 1, PB_WT_STRING = 2, PB_WT_32BIT = 5 } pb_wire_type_t;
/* Structure for defining the handling of unknown/extension fields.
* Usually the pb_extension_type_t structure is automatically generated,
@@ -395,9 +392,8 @@ struct pb_extension_type_s {
* If you run into an error, return false.
* Set to NULL for default handler.
*/
bool (*decode)(pb_istream_t *stream, pb_extension_t *extension,
uint32_t tag, pb_wire_type_t wire_type);
bool (*decode)(pb_istream_t *stream, pb_extension_t *extension, uint32_t tag, pb_wire_type_t wire_type);
/* Called once after all regular fields have been encoded.
* If you have something to write, do so and return true.
* If you do not have anything to write, just return true.
@@ -405,7 +401,7 @@ struct pb_extension_type_s {
* Set to NULL for default handler.
*/
bool (*encode)(pb_ostream_t *stream, const pb_extension_t *extension);
/* Free field for use by the callback. */
const void *arg;
};
@@ -414,11 +410,11 @@ struct pb_extension_s {
/* Type describing the extension field. Usually you'll initialize
* this to a pointer to the automatically generated structure. */
const pb_extension_type_t *type;
/* Destination for the decoded data. This must match the datatype
* of the extension field. */
void *dest;
/* Pointer to the next extension handler, or NULL.
* If this extension does not match a field, the next handler is
* automatically called. */
@@ -429,17 +425,20 @@ struct pb_extension_s {
bool found;
};
#define pb_extension_init_zero {NULL,NULL,NULL,false}
#define pb_extension_init_zero \
{ \
NULL, NULL, NULL, false \
}
/* Memory allocation functions to use. You can define pb_realloc and
* pb_free to custom functions if you want. */
#ifdef PB_ENABLE_MALLOC
# ifndef pb_realloc
# define pb_realloc(ptr, size) realloc(ptr, size)
# endif
# ifndef pb_free
# define pb_free(ptr) free(ptr)
# endif
#ifndef pb_realloc
#define pb_realloc(ptr, size) realloc(ptr, size)
#endif
#ifndef pb_free
#define pb_free(ptr) free(ptr)
#endif
#endif
/* This is used to inform about need to regenerate .pb.h/.pb.c files. */
@@ -447,7 +446,7 @@ struct pb_extension_s {
/* These macros are used to declare pb_field_t's in the constant array. */
/* Size of a structure member, in bytes. */
#define pb_membersize(st, m) (sizeof ((st*)0)->m)
#define pb_membersize(st, m) (sizeof((st *)0)->m)
/* Number of entries in an array. */
#define pb_arraysize(st, m) (pb_membersize(st, m) / pb_membersize(st, m[0]))
/* Delta from start of one member to the start of another member. */
@@ -457,126 +456,115 @@ struct pb_extension_s {
#define PB_EXPAND(x) x
/* Binding of a message field set into a specific structure */
#define PB_BIND(msgname, structname, width) \
const uint32_t structname ## _field_info[] PB_PROGMEM = \
{ \
msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ ## width, structname) \
0 \
}; \
const pb_msgdesc_t* const structname ## _submsg_info[] = \
{ \
msgname ## _FIELDLIST(PB_GEN_SUBMSG_INFO, structname) \
NULL \
}; \
const pb_msgdesc_t structname ## _msg = \
{ \
structname ## _field_info, \
structname ## _submsg_info, \
msgname ## _DEFAULT, \
msgname ## _CALLBACK, \
0 msgname ## _FIELDLIST(PB_GEN_FIELD_COUNT, structname), \
0 msgname ## _FIELDLIST(PB_GEN_REQ_FIELD_COUNT, structname), \
0 msgname ## _FIELDLIST(PB_GEN_LARGEST_TAG, structname), \
}; \
msgname ## _FIELDLIST(PB_GEN_FIELD_INFO_ASSERT_ ## width, structname)
#define PB_BIND(msgname, structname, width) \
const uint32_t structname##_field_info[] PB_PROGMEM = {msgname##_FIELDLIST(PB_GEN_FIELD_INFO_##width, structname) 0}; \
const pb_msgdesc_t *const structname##_submsg_info[] = {msgname##_FIELDLIST(PB_GEN_SUBMSG_INFO, structname) NULL}; \
const pb_msgdesc_t structname##_msg = { \
structname##_field_info, \
structname##_submsg_info, \
msgname##_DEFAULT, \
msgname##_CALLBACK, \
0 msgname##_FIELDLIST(PB_GEN_FIELD_COUNT, structname), \
0 msgname##_FIELDLIST(PB_GEN_REQ_FIELD_COUNT, structname), \
0 msgname##_FIELDLIST(PB_GEN_LARGEST_TAG, structname), \
}; \
msgname##_FIELDLIST(PB_GEN_FIELD_INFO_ASSERT_##width, structname)
#define PB_GEN_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) +1
#define PB_GEN_REQ_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) \
+ (PB_HTYPE_ ## htype == PB_HTYPE_REQUIRED)
#define PB_GEN_LARGEST_TAG(structname, atype, htype, ltype, fieldname, tag) \
* 0 + tag
#define PB_GEN_REQ_FIELD_COUNT(structname, atype, htype, ltype, fieldname, tag) +(PB_HTYPE_##htype == PB_HTYPE_REQUIRED)
#define PB_GEN_LARGEST_TAG(structname, atype, htype, ltype, fieldname, tag) *0 + tag
/* X-macro for generating the entries in struct_field_info[] array. */
#define PB_GEN_FIELD_INFO_1(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_1(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_1(tag, PB_ATYPE_##atype | PB_HTYPE_##htype | PB_LTYPE_MAP_##ltype, \
PB_DATA_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_DATA_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_SIZE_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_ARRAY_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_2(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_2(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_2(tag, PB_ATYPE_##atype | PB_HTYPE_##htype | PB_LTYPE_MAP_##ltype, \
PB_DATA_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_DATA_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_SIZE_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_ARRAY_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_4(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_4(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_4(tag, PB_ATYPE_##atype | PB_HTYPE_##htype | PB_LTYPE_MAP_##ltype, \
PB_DATA_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_DATA_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_SIZE_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_ARRAY_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_8(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_8(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_8(tag, PB_ATYPE_##atype | PB_HTYPE_##htype | PB_LTYPE_MAP_##ltype, \
PB_DATA_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_DATA_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_SIZE_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_ARRAY_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_AUTO(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \
tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_AUTO(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_##atype, _PB_HTYPE_##htype, _PB_LTYPE_##ltype), tag, \
PB_ATYPE_##atype | PB_HTYPE_##htype | PB_LTYPE_MAP_##ltype, \
PB_DATA_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_DATA_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_SIZE_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_ARRAY_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname))
#define PB_FIELDINFO_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \
#define PB_FIELDINFO_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size)
#define PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_ ## width(tag, type, data_offset, data_size, size_offset, array_size)
#define PB_FIELDINFO_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_##width(tag, type, data_offset, data_size, size_offset, array_size)
/* X-macro for generating asserts that entries fit in struct_field_info[] array.
* The structure of macros here must match the structure above in PB_GEN_FIELD_INFO_x(),
* but it is not easily reused because of how macro substitutions work. */
#define PB_GEN_FIELD_INFO_ASSERT_1(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_1(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_1(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_1(tag, PB_ATYPE_##atype | PB_HTYPE_##htype | PB_LTYPE_MAP_##ltype, \
PB_DATA_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_DATA_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_SIZE_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_ARRAY_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_2(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_2(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_2(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_2(tag, PB_ATYPE_##atype | PB_HTYPE_##htype | PB_LTYPE_MAP_##ltype, \
PB_DATA_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_DATA_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_SIZE_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_ARRAY_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_4(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_4(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_4(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_4(tag, PB_ATYPE_##atype | PB_HTYPE_##htype | PB_LTYPE_MAP_##ltype, \
PB_DATA_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_DATA_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_SIZE_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_ARRAY_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_8(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_8(tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_8(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_8(tag, PB_ATYPE_##atype | PB_HTYPE_##htype | PB_LTYPE_MAP_##ltype, \
PB_DATA_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_DATA_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_SIZE_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_ARRAY_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_AUTO(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_ ## atype, _PB_HTYPE_ ## htype, _PB_LTYPE_ ## ltype), \
tag, PB_ATYPE_ ## atype | PB_HTYPE_ ## htype | PB_LTYPE_MAP_ ## ltype, \
PB_DATA_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_DATA_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_SIZE_OFFSET_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname), \
PB_ARRAY_SIZE_ ## atype(_PB_HTYPE_ ## htype, structname, fieldname))
#define PB_GEN_FIELD_INFO_ASSERT_AUTO(structname, atype, htype, ltype, fieldname, tag) \
PB_FIELDINFO_ASSERT_AUTO2(PB_FIELDINFO_WIDTH_AUTO(_PB_ATYPE_##atype, _PB_HTYPE_##htype, _PB_LTYPE_##ltype), tag, \
PB_ATYPE_##atype | PB_HTYPE_##htype | PB_LTYPE_MAP_##ltype, \
PB_DATA_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_DATA_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_SIZE_OFFSET_##atype(_PB_HTYPE_##htype, structname, fieldname), \
PB_ARRAY_SIZE_##atype(_PB_HTYPE_##htype, structname, fieldname))
#define PB_FIELDINFO_ASSERT_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \
#define PB_FIELDINFO_ASSERT_AUTO2(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size)
#define PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_ASSERT_ ## width(tag, type, data_offset, data_size, size_offset, array_size)
#define PB_FIELDINFO_ASSERT_AUTO3(width, tag, type, data_offset, data_size, size_offset, array_size) \
PB_FIELDINFO_ASSERT_##width(tag, type, data_offset, data_size, size_offset, array_size)
#define PB_DATA_OFFSET_STATIC(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
#define PB_DATA_OFFSET_POINTER(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
#define PB_DATA_OFFSET_CALLBACK(htype, structname, fieldname) PB_DO ## htype(structname, fieldname)
#define PB_DATA_OFFSET_STATIC(htype, structname, fieldname) PB_DO##htype(structname, fieldname)
#define PB_DATA_OFFSET_POINTER(htype, structname, fieldname) PB_DO##htype(structname, fieldname)
#define PB_DATA_OFFSET_CALLBACK(htype, structname, fieldname) PB_DO##htype(structname, fieldname)
#define PB_DO_PB_HTYPE_REQUIRED(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_SINGULAR(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_ONEOF(structname, fieldname) offsetof(structname, PB_ONEOF_NAME(FULL, fieldname))
@@ -584,16 +572,17 @@ struct pb_extension_s {
#define PB_DO_PB_HTYPE_REPEATED(structname, fieldname) offsetof(structname, fieldname)
#define PB_DO_PB_HTYPE_FIXARRAY(structname, fieldname) offsetof(structname, fieldname)
#define PB_SIZE_OFFSET_STATIC(htype, structname, fieldname) PB_SO ## htype(structname, fieldname)
#define PB_SIZE_OFFSET_POINTER(htype, structname, fieldname) PB_SO_PTR ## htype(structname, fieldname)
#define PB_SIZE_OFFSET_CALLBACK(htype, structname, fieldname) PB_SO_CB ## htype(structname, fieldname)
#define PB_SIZE_OFFSET_STATIC(htype, structname, fieldname) PB_SO##htype(structname, fieldname)
#define PB_SIZE_OFFSET_POINTER(htype, structname, fieldname) PB_SO_PTR##htype(structname, fieldname)
#define PB_SIZE_OFFSET_CALLBACK(htype, structname, fieldname) PB_SO_CB##htype(structname, fieldname)
#define PB_SO_PB_HTYPE_REQUIRED(structname, fieldname) 0
#define PB_SO_PB_HTYPE_SINGULAR(structname, fieldname) 0
#define PB_SO_PB_HTYPE_ONEOF(structname, fieldname) PB_SO_PB_HTYPE_ONEOF2(structname, PB_ONEOF_NAME(FULL, fieldname), PB_ONEOF_NAME(UNION, fieldname))
#define PB_SO_PB_HTYPE_ONEOF(structname, fieldname) \
PB_SO_PB_HTYPE_ONEOF2(structname, PB_ONEOF_NAME(FULL, fieldname), PB_ONEOF_NAME(UNION, fieldname))
#define PB_SO_PB_HTYPE_ONEOF2(structname, fullname, unionname) PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname)
#define PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) pb_delta(structname, fullname, which_ ## unionname)
#define PB_SO_PB_HTYPE_OPTIONAL(structname, fieldname) pb_delta(structname, fieldname, has_ ## fieldname)
#define PB_SO_PB_HTYPE_REPEATED(structname, fieldname) pb_delta(structname, fieldname, fieldname ## _count)
#define PB_SO_PB_HTYPE_ONEOF3(structname, fullname, unionname) pb_delta(structname, fullname, which_##unionname)
#define PB_SO_PB_HTYPE_OPTIONAL(structname, fieldname) pb_delta(structname, fieldname, has_##fieldname)
#define PB_SO_PB_HTYPE_REPEATED(structname, fieldname) pb_delta(structname, fieldname, fieldname##_count)
#define PB_SO_PB_HTYPE_FIXARRAY(structname, fieldname) 0
#define PB_SO_PTR_PB_HTYPE_REQUIRED(structname, fieldname) 0
#define PB_SO_PTR_PB_HTYPE_SINGULAR(structname, fieldname) 0
@@ -608,8 +597,8 @@ struct pb_extension_s {
#define PB_SO_CB_PB_HTYPE_REPEATED(structname, fieldname) 0
#define PB_SO_CB_PB_HTYPE_FIXARRAY(structname, fieldname) 0
#define PB_ARRAY_SIZE_STATIC(htype, structname, fieldname) PB_AS ## htype(structname, fieldname)
#define PB_ARRAY_SIZE_POINTER(htype, structname, fieldname) PB_AS_PTR ## htype(structname, fieldname)
#define PB_ARRAY_SIZE_STATIC(htype, structname, fieldname) PB_AS##htype(structname, fieldname)
#define PB_ARRAY_SIZE_POINTER(htype, structname, fieldname) PB_AS_PTR##htype(structname, fieldname)
#define PB_ARRAY_SIZE_CALLBACK(htype, structname, fieldname) 1
#define PB_AS_PB_HTYPE_REQUIRED(structname, fieldname) 1
#define PB_AS_PB_HTYPE_SINGULAR(structname, fieldname) 1
@@ -624,9 +613,9 @@ struct pb_extension_s {
#define PB_AS_PTR_PB_HTYPE_REPEATED(structname, fieldname) 1
#define PB_AS_PTR_PB_HTYPE_FIXARRAY(structname, fieldname) pb_arraysize(structname, fieldname[0])
#define PB_DATA_SIZE_STATIC(htype, structname, fieldname) PB_DS ## htype(structname, fieldname)
#define PB_DATA_SIZE_POINTER(htype, structname, fieldname) PB_DS_PTR ## htype(structname, fieldname)
#define PB_DATA_SIZE_CALLBACK(htype, structname, fieldname) PB_DS_CB ## htype(structname, fieldname)
#define PB_DATA_SIZE_STATIC(htype, structname, fieldname) PB_DS##htype(structname, fieldname)
#define PB_DATA_SIZE_POINTER(htype, structname, fieldname) PB_DS_PTR##htype(structname, fieldname)
#define PB_DATA_SIZE_CALLBACK(htype, structname, fieldname) PB_DS_CB##htype(structname, fieldname)
#define PB_DS_PB_HTYPE_REQUIRED(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_PB_HTYPE_SINGULAR(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_PB_HTYPE_OPTIONAL(structname, fieldname) pb_membersize(structname, fieldname)
@@ -646,22 +635,25 @@ struct pb_extension_s {
#define PB_DS_CB_PB_HTYPE_REPEATED(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_DS_CB_PB_HTYPE_FIXARRAY(structname, fieldname) pb_membersize(structname, fieldname)
#define PB_ONEOF_NAME(type, tuple) PB_EXPAND(PB_ONEOF_NAME_ ## type tuple)
#define PB_ONEOF_NAME_UNION(unionname,membername,fullname) unionname
#define PB_ONEOF_NAME_MEMBER(unionname,membername,fullname) membername
#define PB_ONEOF_NAME_FULL(unionname,membername,fullname) fullname
#define PB_ONEOF_NAME(type, tuple) PB_EXPAND(PB_ONEOF_NAME_##type tuple)
#define PB_ONEOF_NAME_UNION(unionname, membername, fullname) unionname
#define PB_ONEOF_NAME_MEMBER(unionname, membername, fullname) membername
#define PB_ONEOF_NAME_FULL(unionname, membername, fullname) fullname
#define PB_GEN_SUBMSG_INFO(structname, atype, htype, ltype, fieldname, tag) \
PB_SUBMSG_INFO_ ## htype(_PB_LTYPE_ ## ltype, structname, fieldname)
#define PB_GEN_SUBMSG_INFO(structname, atype, htype, ltype, fieldname, tag) \
PB_SUBMSG_INFO_##htype(_PB_LTYPE_##ltype, structname, fieldname)
#define PB_SUBMSG_INFO_REQUIRED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_SINGULAR(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_OPTIONAL(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_ONEOF(ltype, structname, fieldname) PB_SUBMSG_INFO_ONEOF2(ltype, structname, PB_ONEOF_NAME(UNION, fieldname), PB_ONEOF_NAME(MEMBER, fieldname))
#define PB_SUBMSG_INFO_ONEOF2(ltype, structname, unionname, membername) PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername)
#define PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) PB_SI ## ltype(structname ## _ ## unionname ## _ ## membername ## _MSGTYPE)
#define PB_SUBMSG_INFO_REPEATED(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_FIXARRAY(ltype, structname, fieldname) PB_SI ## ltype(structname ## _ ## fieldname ## _MSGTYPE)
#define PB_SUBMSG_INFO_REQUIRED(ltype, structname, fieldname) PB_SI##ltype(structname##_##fieldname##_MSGTYPE)
#define PB_SUBMSG_INFO_SINGULAR(ltype, structname, fieldname) PB_SI##ltype(structname##_##fieldname##_MSGTYPE)
#define PB_SUBMSG_INFO_OPTIONAL(ltype, structname, fieldname) PB_SI##ltype(structname##_##fieldname##_MSGTYPE)
#define PB_SUBMSG_INFO_ONEOF(ltype, structname, fieldname) \
PB_SUBMSG_INFO_ONEOF2(ltype, structname, PB_ONEOF_NAME(UNION, fieldname), PB_ONEOF_NAME(MEMBER, fieldname))
#define PB_SUBMSG_INFO_ONEOF2(ltype, structname, unionname, membername) \
PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername)
#define PB_SUBMSG_INFO_ONEOF3(ltype, structname, unionname, membername) \
PB_SI##ltype(structname##_##unionname##_##membername##_MSGTYPE)
#define PB_SUBMSG_INFO_REPEATED(ltype, structname, fieldname) PB_SI##ltype(structname##_##fieldname##_MSGTYPE)
#define PB_SUBMSG_INFO_FIXARRAY(ltype, structname, fieldname) PB_SI##ltype(structname##_##fieldname##_MSGTYPE)
#define PB_SI_PB_LTYPE_BOOL(t)
#define PB_SI_PB_LTYPE_BYTES(t)
#define PB_SI_PB_LTYPE_DOUBLE(t)
@@ -672,7 +664,7 @@ struct pb_extension_s {
#define PB_SI_PB_LTYPE_FLOAT(t)
#define PB_SI_PB_LTYPE_INT32(t)
#define PB_SI_PB_LTYPE_INT64(t)
#define PB_SI_PB_LTYPE_MESSAGE(t) PB_SUBMSG_DESCRIPTOR(t)
#define PB_SI_PB_LTYPE_MESSAGE(t) PB_SUBMSG_DESCRIPTOR(t)
#define PB_SI_PB_LTYPE_MSG_W_CB(t) PB_SUBMSG_DESCRIPTOR(t)
#define PB_SI_PB_LTYPE_SFIXED32(t)
#define PB_SI_PB_LTYPE_SFIXED64(t)
@@ -683,7 +675,7 @@ struct pb_extension_s {
#define PB_SI_PB_LTYPE_UINT64(t)
#define PB_SI_PB_LTYPE_EXTENSION(t)
#define PB_SI_PB_LTYPE_FIXED_LENGTH_BYTES(t)
#define PB_SUBMSG_DESCRIPTOR(t) &(t ## _msg),
#define PB_SUBMSG_DESCRIPTOR(t) &(t##_msg),
/* The field descriptors use a variable width format, with width of either
* 1, 2, 4 or 8 of 32-bit words. The two lowest bytes of the first byte always
@@ -713,23 +705,23 @@ struct pb_extension_s {
* [32-bit reserved]
*/
#define PB_FIELDINFO_1(tag, type, data_offset, data_size, size_offset, array_size) \
(0 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(data_offset) & 0xFF) << 16) | \
(((uint32_t)(size_offset) & 0x0F) << 24) | (((uint32_t)(data_size) & 0x0F) << 28)),
#define PB_FIELDINFO_1(tag, type, data_offset, data_size, size_offset, array_size) \
(0 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(data_offset)&0xFF) << 16) | \
(((uint32_t)(size_offset)&0x0F) << 24) | (((uint32_t)(data_size)&0x0F) << 28)),
#define PB_FIELDINFO_2(tag, type, data_offset, data_size, size_offset, array_size) \
(1 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFF) << 16) | (((uint32_t)(size_offset) & 0x0F) << 28)), \
(((uint32_t)(data_offset) & 0xFFFF) | (((uint32_t)(data_size) & 0xFFF) << 16) | (((uint32_t)(tag) & 0x3c0) << 22)),
#define PB_FIELDINFO_2(tag, type, data_offset, data_size, size_offset, array_size) \
(1 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size)&0xFFF) << 16) | \
(((uint32_t)(size_offset)&0x0F) << 28)), \
(((uint32_t)(data_offset)&0xFFFF) | (((uint32_t)(data_size)&0xFFF) << 16) | (((uint32_t)(tag)&0x3c0) << 22)),
#define PB_FIELDINFO_4(tag, type, data_offset, data_size, size_offset, array_size) \
(2 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size) & 0xFFFF) << 16)), \
((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \
(data_offset), (data_size),
#define PB_FIELDINFO_4(tag, type, data_offset, data_size, size_offset, array_size) \
(2 | (((tag) << 2) & 0xFF) | ((type) << 8) | (((uint32_t)(array_size)&0xFFFF) << 16)), \
((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), (data_offset), (data_size),
#define PB_FIELDINFO_8(tag, type, data_offset, data_size, size_offset, array_size) \
(3 | (((tag) << 2) & 0xFF) | ((type) << 8)), \
((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), \
(data_offset), (data_size), (array_size), 0, 0, 0,
#define PB_FIELDINFO_8(tag, type, data_offset, data_size, size_offset, array_size) \
(3 | (((tag) << 2) & 0xFF) | ((type) << 8)), \
((uint32_t)(int_least8_t)(size_offset) | (((uint32_t)(tag) << 2) & 0xFFFFFF00)), (data_offset), (data_size), \
(array_size), 0, 0, 0,
/* These assertions verify that the field information fits in the allocated space.
* The generator tries to automatically determine the correct width that can fit all
@@ -738,92 +730,103 @@ struct pb_extension_s {
* you can increase the descriptor width by defining PB_FIELDINFO_WIDTH or by setting
* descriptorsize option in .options file.
*/
#define PB_FITS(value,bits) ((uint32_t)(value) < ((uint32_t)1<<bits))
#define PB_FIELDINFO_ASSERT_1(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,6) && PB_FITS(data_offset,8) && PB_FITS(size_offset,4) && PB_FITS(data_size,4) && PB_FITS(array_size,1), FIELDINFO_DOES_NOT_FIT_width1_field ## tag)
#define PB_FITS(value, bits) ((uint32_t)(value) < ((uint32_t)1 << bits))
#define PB_FIELDINFO_ASSERT_1(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag, 6) && PB_FITS(data_offset, 8) && PB_FITS(size_offset, 4) && PB_FITS(data_size, 4) && \
PB_FITS(array_size, 1), \
FIELDINFO_DOES_NOT_FIT_width1_field##tag)
#define PB_FIELDINFO_ASSERT_2(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,10) && PB_FITS(data_offset,16) && PB_FITS(size_offset,4) && PB_FITS(data_size,12) && PB_FITS(array_size,12), FIELDINFO_DOES_NOT_FIT_width2_field ## tag)
#define PB_FIELDINFO_ASSERT_2(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag, 10) && PB_FITS(data_offset, 16) && PB_FITS(size_offset, 4) && PB_FITS(data_size, 12) && \
PB_FITS(array_size, 12), \
FIELDINFO_DOES_NOT_FIT_width2_field##tag)
#ifndef PB_FIELD_32BIT
/* Maximum field sizes are still 16-bit if pb_size_t is 16-bit */
#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,16) && PB_FITS(data_offset,16) && PB_FITS((int_least8_t)size_offset,8) && PB_FITS(data_size,16) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width4_field ## tag)
#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag, 16) && PB_FITS(data_offset, 16) && PB_FITS((int_least8_t)size_offset, 8) && \
PB_FITS(data_size, 16) && PB_FITS(array_size, 16), \
FIELDINFO_DOES_NOT_FIT_width4_field##tag)
#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,16) && PB_FITS(data_offset,16) && PB_FITS((int_least8_t)size_offset,8) && PB_FITS(data_size,16) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width8_field ## tag)
#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag, 16) && PB_FITS(data_offset, 16) && PB_FITS((int_least8_t)size_offset, 8) && \
PB_FITS(data_size, 16) && PB_FITS(array_size, 16), \
FIELDINFO_DOES_NOT_FIT_width8_field##tag)
#else
/* Up to 32-bit fields supported.
* Note that the checks are against 31 bits to avoid compiler warnings about shift wider than type in the test.
* I expect that there is no reasonable use for >2GB messages with nanopb anyway.
*/
#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,16), FIELDINFO_DOES_NOT_FIT_width4_field ## tag)
#define PB_FIELDINFO_ASSERT_4(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag, 30) && PB_FITS(data_offset, 31) && PB_FITS(size_offset, 8) && PB_FITS(data_size, 31) && \
PB_FITS(array_size, 16), \
FIELDINFO_DOES_NOT_FIT_width4_field##tag)
#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag,30) && PB_FITS(data_offset,31) && PB_FITS(size_offset,8) && PB_FITS(data_size,31) && PB_FITS(array_size,31), FIELDINFO_DOES_NOT_FIT_width8_field ## tag)
#define PB_FIELDINFO_ASSERT_8(tag, type, data_offset, data_size, size_offset, array_size) \
PB_STATIC_ASSERT(PB_FITS(tag, 30) && PB_FITS(data_offset, 31) && PB_FITS(size_offset, 8) && PB_FITS(data_size, 31) && \
PB_FITS(array_size, 31), \
FIELDINFO_DOES_NOT_FIT_width8_field##tag)
#endif
/* Automatic picking of FIELDINFO width:
* Uses width 1 when possible, otherwise resorts to width 2.
* This is used when PB_BIND() is called with "AUTO" as the argument.
* The generator will give explicit size argument when it knows that a message
* structure grows beyond 1-word format limits.
*/
#define PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype) PB_FI_WIDTH ## atype(htype, ltype)
#define PB_FI_WIDTH_PB_ATYPE_STATIC(htype, ltype) PB_FI_WIDTH ## htype(ltype)
#define PB_FI_WIDTH_PB_ATYPE_POINTER(htype, ltype) PB_FI_WIDTH ## htype(ltype)
#define PB_FIELDINFO_WIDTH_AUTO(atype, htype, ltype) PB_FI_WIDTH##atype(htype, ltype)
#define PB_FI_WIDTH_PB_ATYPE_STATIC(htype, ltype) PB_FI_WIDTH##htype(ltype)
#define PB_FI_WIDTH_PB_ATYPE_POINTER(htype, ltype) PB_FI_WIDTH##htype(ltype)
#define PB_FI_WIDTH_PB_ATYPE_CALLBACK(htype, ltype) 2
#define PB_FI_WIDTH_PB_HTYPE_REQUIRED(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_SINGULAR(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_OPTIONAL(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_ONEOF(ltype) PB_FI_WIDTH ## ltype
#define PB_FI_WIDTH_PB_HTYPE_REQUIRED(ltype) PB_FI_WIDTH##ltype
#define PB_FI_WIDTH_PB_HTYPE_SINGULAR(ltype) PB_FI_WIDTH##ltype
#define PB_FI_WIDTH_PB_HTYPE_OPTIONAL(ltype) PB_FI_WIDTH##ltype
#define PB_FI_WIDTH_PB_HTYPE_ONEOF(ltype) PB_FI_WIDTH##ltype
#define PB_FI_WIDTH_PB_HTYPE_REPEATED(ltype) 2
#define PB_FI_WIDTH_PB_HTYPE_FIXARRAY(ltype) 2
#define PB_FI_WIDTH_PB_LTYPE_BOOL 1
#define PB_FI_WIDTH_PB_LTYPE_BYTES 2
#define PB_FI_WIDTH_PB_LTYPE_DOUBLE 1
#define PB_FI_WIDTH_PB_LTYPE_ENUM 1
#define PB_FI_WIDTH_PB_LTYPE_UENUM 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED32 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED64 1
#define PB_FI_WIDTH_PB_LTYPE_FLOAT 1
#define PB_FI_WIDTH_PB_LTYPE_INT32 1
#define PB_FI_WIDTH_PB_LTYPE_INT64 1
#define PB_FI_WIDTH_PB_LTYPE_MESSAGE 2
#define PB_FI_WIDTH_PB_LTYPE_MSG_W_CB 2
#define PB_FI_WIDTH_PB_LTYPE_SFIXED32 1
#define PB_FI_WIDTH_PB_LTYPE_SFIXED64 1
#define PB_FI_WIDTH_PB_LTYPE_SINT32 1
#define PB_FI_WIDTH_PB_LTYPE_SINT64 1
#define PB_FI_WIDTH_PB_LTYPE_STRING 2
#define PB_FI_WIDTH_PB_LTYPE_UINT32 1
#define PB_FI_WIDTH_PB_LTYPE_UINT64 1
#define PB_FI_WIDTH_PB_LTYPE_BOOL 1
#define PB_FI_WIDTH_PB_LTYPE_BYTES 2
#define PB_FI_WIDTH_PB_LTYPE_DOUBLE 1
#define PB_FI_WIDTH_PB_LTYPE_ENUM 1
#define PB_FI_WIDTH_PB_LTYPE_UENUM 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED32 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED64 1
#define PB_FI_WIDTH_PB_LTYPE_FLOAT 1
#define PB_FI_WIDTH_PB_LTYPE_INT32 1
#define PB_FI_WIDTH_PB_LTYPE_INT64 1
#define PB_FI_WIDTH_PB_LTYPE_MESSAGE 2
#define PB_FI_WIDTH_PB_LTYPE_MSG_W_CB 2
#define PB_FI_WIDTH_PB_LTYPE_SFIXED32 1
#define PB_FI_WIDTH_PB_LTYPE_SFIXED64 1
#define PB_FI_WIDTH_PB_LTYPE_SINT32 1
#define PB_FI_WIDTH_PB_LTYPE_SINT64 1
#define PB_FI_WIDTH_PB_LTYPE_STRING 2
#define PB_FI_WIDTH_PB_LTYPE_UINT32 1
#define PB_FI_WIDTH_PB_LTYPE_UINT64 1
#define PB_FI_WIDTH_PB_LTYPE_EXTENSION 1
#define PB_FI_WIDTH_PB_LTYPE_FIXED_LENGTH_BYTES 2
/* The mapping from protobuf types to LTYPEs is done using these macros. */
#define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL
#define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES
#define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT
#define PB_LTYPE_MAP_UENUM PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_FIXED32 PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_FIXED64 PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_FLOAT PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_INT32 PB_LTYPE_VARINT
#define PB_LTYPE_MAP_INT64 PB_LTYPE_VARINT
#define PB_LTYPE_MAP_MESSAGE PB_LTYPE_SUBMESSAGE
#define PB_LTYPE_MAP_MSG_W_CB PB_LTYPE_SUBMSG_W_CB
#define PB_LTYPE_MAP_SFIXED32 PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_SFIXED64 PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_SINT32 PB_LTYPE_SVARINT
#define PB_LTYPE_MAP_SINT64 PB_LTYPE_SVARINT
#define PB_LTYPE_MAP_STRING PB_LTYPE_STRING
#define PB_LTYPE_MAP_UINT32 PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_UINT64 PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_EXTENSION PB_LTYPE_EXTENSION
#define PB_LTYPE_MAP_BOOL PB_LTYPE_BOOL
#define PB_LTYPE_MAP_BYTES PB_LTYPE_BYTES
#define PB_LTYPE_MAP_DOUBLE PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_ENUM PB_LTYPE_VARINT
#define PB_LTYPE_MAP_UENUM PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_FIXED32 PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_FIXED64 PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_FLOAT PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_INT32 PB_LTYPE_VARINT
#define PB_LTYPE_MAP_INT64 PB_LTYPE_VARINT
#define PB_LTYPE_MAP_MESSAGE PB_LTYPE_SUBMESSAGE
#define PB_LTYPE_MAP_MSG_W_CB PB_LTYPE_SUBMSG_W_CB
#define PB_LTYPE_MAP_SFIXED32 PB_LTYPE_FIXED32
#define PB_LTYPE_MAP_SFIXED64 PB_LTYPE_FIXED64
#define PB_LTYPE_MAP_SINT32 PB_LTYPE_SVARINT
#define PB_LTYPE_MAP_SINT64 PB_LTYPE_SVARINT
#define PB_LTYPE_MAP_STRING PB_LTYPE_STRING
#define PB_LTYPE_MAP_UINT32 PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_UINT64 PB_LTYPE_UVARINT
#define PB_LTYPE_MAP_EXTENSION PB_LTYPE_EXTENSION
#define PB_LTYPE_MAP_FIXED_LENGTH_BYTES PB_LTYPE_FIXED_LENGTH_BYTES
/* These macros are used for giving out error messages.
@@ -855,21 +858,21 @@ struct pb_extension_s {
#ifdef __cplusplus
#if __cplusplus >= 201103L
#define PB_CONSTEXPR constexpr
#else // __cplusplus >= 201103L
#else // __cplusplus >= 201103L
#define PB_CONSTEXPR
#endif // __cplusplus >= 201103L
#endif // __cplusplus >= 201103L
#if __cplusplus >= 201703L
#define PB_INLINE_CONSTEXPR inline constexpr
#else // __cplusplus >= 201703L
#else // __cplusplus >= 201703L
#define PB_INLINE_CONSTEXPR PB_CONSTEXPR
#endif // __cplusplus >= 201703L
#endif // __cplusplus >= 201703L
namespace nanopb {
namespace nanopb
{
// Each type will be partially specialized by the generator.
template <typename GenMessageT> struct MessageDescriptor;
} // namespace nanopb
#endif /* __cplusplus */
} // namespace nanopb
#endif /* __cplusplus */
#endif

View File

@@ -76,6 +76,7 @@ lib_deps =
Wire ; explicitly needed here because the AXP202 library forgets to add it
SPI
https://github.com/geeksville/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3
PubSubClient
; Common settings for conventional (non Portduino) Ardino targets
[arduino_base]
@@ -202,7 +203,7 @@ build_flags =
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.7
;-DCFG_DEBUG=3
src_filter =
${arduino_base.src_filter} -<esp32/> -<nimble/> -<mesh/wifi/> -<mesh/http/> -<plugins/esp32>
${arduino_base.src_filter} -<esp32/> -<nimble/> -<mesh/wifi/> -<mesh/http/> -<plugins/esp32> -<mqtt/>
lib_ignore =
BluetoothOTA
monitor_port = /dev/ttyACM1
@@ -396,4 +397,4 @@ lib_deps =
;extends = esp32_base
;board = genieblocks_lora
;build_flags =
; ${esp32_base.build_flags} -D GENIEBLOCKS
; ${esp32_base.build_flags} -D GENIEBLOCKS

2
proto

Submodule proto updated: 2aa1439214...3252ed0f13

View File

@@ -32,8 +32,9 @@
#include "nimble/BluetoothUtil.h"
#endif
#ifdef PORTDUINO
#if defined(HAS_WIFI) || defined(PORTDUINO)
#include "mesh/wifi/WiFiServerAPI.h"
#include "mqtt/MQTT.h"
#endif
#include "RF95Interface.h"
@@ -541,6 +542,10 @@ void setup()
initApiServer();
#endif
#if defined(PORTDUINO) || defined(HAS_WIFI)
mqttInit();
#endif
// Start airtime logger thread.
airTime = new AirTime();

View File

@@ -29,7 +29,6 @@ class Channels
int16_t hashes[MAX_NUM_CHANNELS];
public:
/// Well known channel names
static const char *adminChannel, *gpioChannel, *serialChannel;
@@ -43,8 +42,15 @@ class Channels
*/
void setChannel(const Channel &c);
/** Return a human friendly name for this channel (and expand any short strings as needed)
*/
const char *getName(size_t chIndex);
/**
* Return a globally unique channel ID usable with MQTT.
*/
const char *getGlobalId(size_t chIndex) { return getName(chIndex); } // FIXME, not correct
/** The index of the primary channel */
ChannelIndex getPrimaryIndex() const { return primaryIndex; }

View File

@@ -70,7 +70,8 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
// DEBUG_MSG("In call plugins\n");
bool pluginFound = false;
assert(mp.which_payloadVariant == MeshPacket_decoded_tag); // I think we are guarnteed the packet is decoded by this point?
// We now allow **encrypted** packets to pass through the plugins
bool isDecoded = mp.which_payloadVariant == MeshPacket_decoded_tag;
currentReply = NULL; // No reply yet
@@ -82,19 +83,21 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
pi.currentRequest = &mp;
/// received channel
auto ch = channels.getByIndex(mp.channel);
assert(ch.has_settings);
/// We only call plugins that are interested in the packet (and the message is destined to us or we are promiscious)
bool wantsPacket = (pi.isPromiscuous || toUs) && pi.wantPacket(&mp);
bool wantsPacket = (isDecoded || pi.encryptedOk) && (pi.isPromiscuous || toUs) && pi.wantPacket(&mp);
DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket);
assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time
if (wantsPacket) {
// DEBUG_MSG("Plugin %s wantsPacket=%d\n", pi.name, wantsPacket);
pluginFound = true;
/// received channel (or NULL if not decoded)
Channel *ch = isDecoded ? &channels.getByIndex(mp.channel) : NULL;
/// Is the channel this packet arrived on acceptable? (security check)
bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (strcmp(ch.settings.name, pi.boundChannel) == 0);
/// Note: we can't know channel names for encrypted packets, so those are NEVER sent to boundChannel plugins
bool rxChannelOk = !pi.boundChannel || (ch && ((mp.from == 0) || (strcmp(ch->settings.name, pi.boundChannel) == 0)));
if (!rxChannelOk) {
// no one should have already replied!
@@ -123,6 +126,14 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
} else {
DEBUG_MSG("Plugin %s considered\n", pi.name);
}
// If the requester didn't ask for a response we might need to discard unused replies to prevent memory leaks
if (pi.myReply) {
DEBUG_MSG("Discarding an unneeded response\n");
packetPool.release(pi.myReply);
pi.myReply = NULL;
}
if (handled) {
DEBUG_MSG("Plugin %s handled and skipped other processing\n", pi.name);
break;
@@ -135,12 +146,16 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
if (mp.decoded.want_response && toUs) {
if (currentReply) {
DEBUG_MSG("Sending response\n");
printPacket("Sending response", currentReply);
service.sendToMesh(currentReply);
currentReply = NULL;
} else {
// No one wanted to reply to this requst, tell the requster that happened
DEBUG_MSG("No one responded, send a nak\n");
// SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded)
// but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs
// bad.
routingPlugin->sendAckNak(Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel);
}
}
@@ -149,6 +164,13 @@ void MeshPlugin::callPlugins(const MeshPacket &mp)
DEBUG_MSG("No plugins interested in portnum=%d\n", mp.decoded.portnum);
}
MeshPacket *MeshPlugin::allocReply()
{
auto r = myReply;
myReply = NULL; // Only use each reply once
return r;
}
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. Implementing this method
* is optional
@@ -171,7 +193,7 @@ void MeshPlugin::sendResponse(const MeshPacket &req)
void setReplyTo(MeshPacket *p, const MeshPacket &to)
{
assert(p->which_payloadVariant == MeshPacket_decoded_tag); // Should already be set by now
p->to = getFrom(&to);
p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0
p->channel = to.channel; // Use the same channel that the request came in on
// No need for an ack if we are just delivering locally (it just generates an ignored ack)

View File

@@ -42,12 +42,16 @@ class MeshPlugin
protected:
const char *name;
/* Most plugins only care about packets that are destined for their node (i.e. broadcasts or has their node as the specific
/** Most plugins only care about packets that are destined for their node (i.e. broadcasts or has their node as the specific
recipient) But some plugs might want to 'sniff' packets that are merely being routed (passing through the current node). Those
plugins can set this to true and their handleReceived() will be called for every packet.
*/
bool isPromiscuous = false;
/** Most plugins only understand decrypted packets. For plugins that also want to see encrypted packets, they should set this
* flag */
bool encryptedOk = false;
/** If a bound channel name is set, we will only accept received packets that come in on that channel.
* A special exception (FIXME, not sure if this is a good idea) - packets that arrive on the local interface
* are allowed on any channel (this lets the local user do anything).
@@ -65,6 +69,11 @@ class MeshPlugin
*/
static const MeshPacket *currentRequest;
/**
* If your handler wants to send a response, simply set currentReply and it will be sent at the end of response handling.
*/
MeshPacket *myReply = NULL;
/**
* Initialize your plugin. This setup function is called once after all hardware and mesh protocol layers have
* been initialized
@@ -83,8 +92,12 @@ class MeshPlugin
virtual bool handleReceived(const MeshPacket &mp) { return false; }
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual MeshPacket *allocReply() { return NULL; }
* so that subclasses can (optionally) send a response back to the original sender.
*
* Note: most implementers don't need to override this, instead: If while handling a request you have a reply, just set
* the protected reply field in this instance.
* */
virtual MeshPacket *allocReply();
/***
* @return true if you want to be alloced a UI screen frame
@@ -102,6 +115,7 @@ class MeshPlugin
* the RoutingPlugin to avoid sending redundant acks
*/
static MeshPacket *currentReply;
friend class ReliableRouter;
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked

View File

@@ -12,10 +12,8 @@ typedef uint32_t PacketId; // A packet sequence number
#define NODENUM_BROADCAST UINT32_MAX
#define ERRNO_OK 0
#define ERRNO_NO_INTERFACES 33
#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER
#define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER
#define ERRNO_DISABLED 34 // the itnerface is disabled
#define ERRNO_TOO_LARGE 35
#define ERRNO_NO_CHANNEL 36
/**
* the max number of hops a message can pass through, used as the default max for hop_limit in MeshPacket.
@@ -35,7 +33,7 @@ typedef int ErrorCode;
extern Allocator<MeshPacket> &packetPool;
/**
* Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on the local node.
* If from is zero this function returns our node number instead
* Most (but not always) of the time we want to treat packets 'from' the local phone (where from == 0), as if they originated on
* the local node. If from is zero this function returns our node number instead
*/
NodeNum getFrom(const MeshPacket *p);

View File

@@ -22,10 +22,9 @@ template <class T> class ProtobufPlugin : protected SinglePortPlugin
}
protected:
/**
* Handle a received message, the data field in the message is already decoded and is provided
*
*
* In general decoded will always be !NULL. But in some special applications (where you have handling packets
* for multiple port numbers, decoding will ONLY be attempted for packets where the portnum matches our expected ourPortNum.
*/
@@ -58,11 +57,12 @@ template <class T> class ProtobufPlugin : protected SinglePortPlugin
// it would be better to update even if the message was destined to others.
auto &p = mp.decoded;
DEBUG_MSG("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d\n", name, mp.from, mp.id, p.portnum, p.payload.size);
DEBUG_MSG("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d\n", name, mp.from, mp.id, p.portnum,
p.payload.size);
T scratch;
T *decoded = NULL;
if(mp.decoded.portnum == ourPortNum) {
T *decoded = NULL;
if (mp.which_payloadVariant == MeshPacket_decoded_tag && mp.decoded.portnum == ourPortNum) {
memset(&scratch, 0, sizeof(scratch));
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch))
decoded = &scratch;

View File

@@ -361,12 +361,8 @@ ErrorCode SimRadio::send(MeshPacket *p)
void RadioInterface::deliverToReceiver(MeshPacket *p)
{
assert(rxDest);
assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages
// Nasty hack because our threading is primitive. interfaces shouldn't need to know about routers FIXME
if (router)
router->setReceivedMessage();
router->enqueueReceivedMessage(p);
}
/***

View File

@@ -42,7 +42,6 @@ typedef struct {
class RadioInterface
{
friend class MeshRadio; // for debugging we let that class touch pool
PointerQueue<MeshPacket> *rxDest = NULL;
CallbackObserver<RadioInterface, void *> configChangedObserver =
CallbackObserver<RadioInterface, void *>(this, &RadioInterface::reloadConfig);
@@ -82,17 +81,11 @@ class RadioInterface
float freq = 915.0;
/** pool is the pool we will alloc our rx packets from
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
*/
RadioInterface();
virtual ~RadioInterface() {}
/**
* Set where to deliver received packets. This method should only be used by the Router class
*/
void setReceiver(PointerQueue<MeshPacket> *_rxDest) { rxDest = _rxDest; }
/**
* Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving)
*

View File

@@ -8,6 +8,10 @@
#include "mesh-pb-constants.h"
#include "plugins/RoutingPlugin.h"
#if defined(HAS_WIFI) || defined(PORTDUINO)
#include "mqtt/MQTT.h"
#endif
/**
* Router todo
*
@@ -64,6 +68,22 @@ int32_t Router::runOnce()
return INT32_MAX; // Wait a long time - until we get woken for the message queue
}
/**
* RadioInterface calls this to queue up packets that have been received from the radio. The router is now responsible for
* freeing the packet
*/
void Router::enqueueReceivedMessage(MeshPacket *p)
{
if (fromRadioQueue.enqueue(p, 0)) { // NOWAIT - fixme, if queue is full, delete older messages
// Nasty hack because our threading is primitive. interfaces shouldn't need to know about routers FIXME
setReceivedMessage();
} else {
printPacket("BUG! fromRadioQueue is full! Discarding!", p);
packetPool.release(p);
}
}
/// Generate a unique packet id
// FIXME, move this someplace better
PacketId generatePacketId()
@@ -129,13 +149,8 @@ ErrorCode Router::sendLocal(MeshPacket *p)
{
// No need to deliver externally if the destination is the local node
if (p->to == nodeDB.getNodeNum()) {
if (fromRadioQueue.enqueue(p, 0)) {
printPacket("Enqueued local", p);
setReceivedMessage();
} else {
printPacket("BUG! fromRadioQueue is full! Discarding!", p);
packetPool.release(p);
}
printPacket("Enqueued local", p);
enqueueReceivedMessage(p);
return ERRNO_OK;
} else if (!iface) {
// We must be sending to remote nodes also, fail if no interface found
@@ -187,36 +202,20 @@ ErrorCode Router::send(MeshPacket *p)
assert(p->which_payloadVariant == MeshPacket_encrypted_tag ||
p->which_payloadVariant == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now
// First convert from protobufs to raw bytes
// If the packet is not yet encrypted, do so now
if (p->which_payloadVariant == MeshPacket_decoded_tag) {
static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
// printPacket("pre encrypt", p); // portnum valid here
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded);
if (numbytes > MAX_RHPACKETLEN) {
abortSendAndNak(Routing_Error_TOO_LARGE, p);
return ERRNO_TOO_LARGE;
auto encodeResult = perhapsEncode(p);
if (encodeResult != Routing_Error_NONE) {
abortSendAndNak(encodeResult, p);
return encodeResult; // FIXME - this isn't a valid ErrorCode
}
// printBytes("plaintext", bytes, numbytes);
auto hash = channels.setActiveByIndex(p->channel);
if (hash < 0) {
// No suitable channel could be found for sending
abortSendAndNak(Routing_Error_NO_CHANNEL, p);
return ERRNO_NO_CHANNEL;
}
// Now that we are encrypting the packet channel should be the hash (no longer the index)
p->channel = hash;
crypto->encrypt(getFrom(p), p->id, numbytes, bytes);
// Copy back into the packet and set the variant type
memcpy(p->encrypted.bytes, bytes, numbytes);
p->encrypted.size = numbytes;
p->which_payloadVariant = MeshPacket_encrypted_tag;
#if defined(HAS_WIFI) || defined(PORTDUINO)
if (mqtt)
mqtt->onSend(*p, chIndex);
#endif
}
assert(iface); // This should have been detected already in sendLocal (or we just received a packet from outside)
@@ -239,7 +238,7 @@ void Router::sniffReceived(const MeshPacket *p, const Routing *c)
// FIXME, update nodedb here for any packet that passes through us
}
bool Router::perhapsDecode(MeshPacket *p)
bool perhapsDecode(MeshPacket *p)
{
if (p->which_payloadVariant == MeshPacket_decoded_tag)
return true; // If packet was already decoded just return
@@ -280,6 +279,42 @@ bool Router::perhapsDecode(MeshPacket *p)
return false;
}
/** Return 0 for success or a Routing_Errror code for failure
*/
Routing_Error perhapsEncode(MeshPacket *p)
{
// If the packet is not yet encrypted, do so now
if (p->which_payloadVariant == MeshPacket_decoded_tag) {
static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union
// printPacket("pre encrypt", p); // portnum valid here
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded);
if (numbytes > MAX_RHPACKETLEN)
return Routing_Error_TOO_LARGE;
// printBytes("plaintext", bytes, numbytes);
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
auto hash = channels.setActiveByIndex(chIndex);
if (hash < 0)
// No suitable channel could be found for sending
return Routing_Error_NO_CHANNEL;
// Now that we are encrypting the packet channel should be the hash (no longer the index)
p->channel = hash;
crypto->encrypt(getFrom(p), p->id, numbytes, bytes);
// Copy back into the packet and set the variant type
memcpy(p->encrypted.bytes, bytes, numbytes);
p->encrypted.size = numbytes;
p->which_payloadVariant = MeshPacket_encrypted_tag;
}
return Routing_Error_NONE;
}
NodeNum Router::getNodeNum()
{
return nodeDB.getNodeNum();
@@ -299,13 +334,12 @@ void Router::handleReceived(MeshPacket *p)
if (decoded) {
// parsing was successful, queue for our recipient
printPacket("handleReceived", p);
// call any promiscious plugins here, make a (non promisiocous) plugin for forwarding messages to phone api
// sniffReceived(p);
MeshPlugin::callPlugins(*p);
} else {
DEBUG_MSG("packet decoding failed\n");
printPacket("packet decoding failed (no PSK?)", p);
}
// call plugins here
MeshPlugin::callPlugins(*p);
}
void Router::perhapsHandleReceived(MeshPacket *p)

View File

@@ -1,12 +1,12 @@
#pragma once
#include "Channels.h"
#include "MemoryPool.h"
#include "MeshTypes.h"
#include "Observer.h"
#include "PointerQueue.h"
#include "RadioInterface.h"
#include "concurrency/OSThread.h"
#include "Channels.h"
/**
* A mesh aware router that supports multiple interfaces.
@@ -22,7 +22,6 @@ class Router : protected concurrency::OSThread
RadioInterface *iface = NULL;
public:
/**
* Constructor
*
@@ -32,11 +31,7 @@ class Router : protected concurrency::OSThread
/**
* Currently we only allow one interface, that may change in the future
*/
void addInterface(RadioInterface *_iface)
{
iface = _iface;
iface->setReceiver(&fromRadioQueue);
}
void addInterface(RadioInterface *_iface) { iface = _iface; }
/**
* do idle processing
@@ -53,7 +48,7 @@ class Router : protected concurrency::OSThread
ErrorCode sendLocal(MeshPacket *p);
/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */
bool cancelSending(NodeNum from, PacketId id);
bool cancelSending(NodeNum from, PacketId id);
/** Allocate and return a meshpacket which defaults as send to broadcast from the current node.
* The returned packet is guaranteed to have a unique packet ID already assigned
@@ -69,6 +64,12 @@ class Router : protected concurrency::OSThread
*/
void setReceivedMessage();
/**
* RadioInterface calls this to queue up packets that have been received from the radio. The router is now responsible for
* freeing the packet
*/
void enqueueReceivedMessage(MeshPacket *p);
protected:
friend class RoutingPlugin;
@@ -83,7 +84,7 @@ class Router : protected concurrency::OSThread
/**
* Should this incoming filter be dropped?
*
*
* FIXME, move this into the new RoutingPlugin and do the filtering there using the regular plugin logic
*
* Called immedately on receiption, before any further processing.
@@ -97,18 +98,11 @@ class Router : protected concurrency::OSThread
*/
virtual void sniffReceived(const MeshPacket *p, const Routing *c);
/**
* Remove any encryption and decode the protobufs inside this packet (if necessary).
*
* @return true for success, false for corrupt packet.
*/
bool perhapsDecode(MeshPacket *p);
/**
* Send an ack or a nak packet back towards whoever sent idFrom
*/
void sendAckNak(Routing_Error err, NodeNum to, PacketId idFrom, ChannelIndex chIndex);
private:
/**
* Called from loop()
@@ -134,6 +128,17 @@ class Router : protected concurrency::OSThread
void abortSendAndNak(Routing_Error err, MeshPacket *p);
};
/** FIXME - move this into a mesh packet class
* Remove any encryption and decode the protobufs inside this packet (if necessary).
*
* @return true for success, false for corrupt packet.
*/
bool perhapsDecode(MeshPacket *p);
/** Return 0 for success or a Routing_Errror code for failure
*/
Routing_Error perhapsEncode(MeshPacket *p);
extern Router *router;
/// Generate a unique packet id

View File

@@ -79,7 +79,7 @@ extern const pb_msgdesc_t AdminMessage_msg;
#define AdminMessage_fields &AdminMessage_msg
/* Maximum encoded size of messages (where known) */
#define AdminMessage_size 360
#define AdminMessage_size 397
#ifdef __cplusplus
} /* extern "C" */

View File

@@ -6,9 +6,6 @@
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(ServiceEnvelope, ServiceEnvelope, 2)
PB_BIND(ChannelSet, ChannelSet, AUTO)

View File

@@ -4,7 +4,6 @@
#ifndef PB_APPONLY_PB_H_INCLUDED
#define PB_APPONLY_PB_H_INCLUDED
#include <pb.h>
#include "mesh.pb.h"
#include "channel.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
@@ -16,54 +15,31 @@ typedef struct _ChannelSet {
pb_callback_t settings;
} ChannelSet;
typedef struct _ServiceEnvelope {
bool has_packet;
MeshPacket packet;
pb_callback_t channel_id;
pb_callback_t gateway_id;
} ServiceEnvelope;
#ifdef __cplusplus
extern "C" {
#endif
/* Initializer values for message structs */
#define ServiceEnvelope_init_default {false, MeshPacket_init_default, {{NULL}, NULL}, {{NULL}, NULL}}
#define ChannelSet_init_default {{{NULL}, NULL}}
#define ServiceEnvelope_init_zero {false, MeshPacket_init_zero, {{NULL}, NULL}, {{NULL}, NULL}}
#define ChannelSet_init_zero {{{NULL}, NULL}}
/* Field tags (for use in manual encoding/decoding) */
#define ChannelSet_settings_tag 1
#define ServiceEnvelope_packet_tag 1
#define ServiceEnvelope_channel_id_tag 2
#define ServiceEnvelope_gateway_id_tag 3
/* Struct field encoding specification for nanopb */
#define ServiceEnvelope_FIELDLIST(X, a) \
X(a, STATIC, OPTIONAL, MESSAGE, packet, 1) \
X(a, CALLBACK, SINGULAR, STRING, channel_id, 2) \
X(a, CALLBACK, SINGULAR, STRING, gateway_id, 3)
#define ServiceEnvelope_CALLBACK pb_default_field_callback
#define ServiceEnvelope_DEFAULT NULL
#define ServiceEnvelope_packet_MSGTYPE MeshPacket
#define ChannelSet_FIELDLIST(X, a) \
X(a, CALLBACK, REPEATED, MESSAGE, settings, 1)
#define ChannelSet_CALLBACK pb_default_field_callback
#define ChannelSet_DEFAULT NULL
#define ChannelSet_settings_MSGTYPE ChannelSettings
extern const pb_msgdesc_t ServiceEnvelope_msg;
extern const pb_msgdesc_t ChannelSet_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define ServiceEnvelope_fields &ServiceEnvelope_msg
#define ChannelSet_fields &ChannelSet_msg
/* Maximum encoded size of messages (where known) */
/* ServiceEnvelope_size depends on runtime parameters */
/* ChannelSet_size depends on runtime parameters */
#ifdef __cplusplus

View File

@@ -0,0 +1,12 @@
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.4.4 */
#include "mqtt.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(ServiceEnvelope, ServiceEnvelope, AUTO)

View File

@@ -0,0 +1,55 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-0.4.4 */
#ifndef PB_MQTT_PB_H_INCLUDED
#define PB_MQTT_PB_H_INCLUDED
#include <pb.h>
#include "mesh.pb.h"
#if PB_PROTO_HEADER_VERSION != 40
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Struct definitions */
typedef struct _ServiceEnvelope {
struct _MeshPacket *packet;
char *channel_id;
char *gateway_id;
} ServiceEnvelope;
#ifdef __cplusplus
extern "C" {
#endif
/* Initializer values for message structs */
#define ServiceEnvelope_init_default {NULL, NULL, NULL}
#define ServiceEnvelope_init_zero {NULL, NULL, NULL}
/* Field tags (for use in manual encoding/decoding) */
#define ServiceEnvelope_packet_tag 1
#define ServiceEnvelope_channel_id_tag 2
#define ServiceEnvelope_gateway_id_tag 3
/* Struct field encoding specification for nanopb */
#define ServiceEnvelope_FIELDLIST(X, a) \
X(a, POINTER, OPTIONAL, MESSAGE, packet, 1) \
X(a, POINTER, SINGULAR, STRING, channel_id, 2) \
X(a, POINTER, SINGULAR, STRING, gateway_id, 3)
#define ServiceEnvelope_CALLBACK NULL
#define ServiceEnvelope_DEFAULT NULL
#define ServiceEnvelope_packet_MSGTYPE MeshPacket
extern const pb_msgdesc_t ServiceEnvelope_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define ServiceEnvelope_fields &ServiceEnvelope_msg
/* Maximum encoded size of messages (where known) */
/* ServiceEnvelope_size depends on runtime parameters */
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif

View File

@@ -87,6 +87,8 @@ typedef struct _RadioConfig_UserPreferences {
bool fixed_position;
bool serial_disabled;
float frequency_offset;
char mqtt_server[32];
bool mqtt_disabled;
bool factory_reset;
bool debug_log_enabled;
pb_size_t ignore_incoming_count;
@@ -152,9 +154,9 @@ extern "C" {
/* Initializer values for message structs */
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default}
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero}
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _RadioConfig_UserPreferences_EnvironmentalMeasurementSensorType_MIN, 0, 0}
/* Field tags (for use in manual encoding/decoding) */
#define RadioConfig_UserPreferences_position_broadcast_secs_tag 1
@@ -181,6 +183,8 @@ extern "C" {
#define RadioConfig_UserPreferences_fixed_position_tag 39
#define RadioConfig_UserPreferences_serial_disabled_tag 40
#define RadioConfig_UserPreferences_frequency_offset_tag 41
#define RadioConfig_UserPreferences_mqtt_server_tag 42
#define RadioConfig_UserPreferences_mqtt_disabled_tag 43
#define RadioConfig_UserPreferences_factory_reset_tag 100
#define RadioConfig_UserPreferences_debug_log_enabled_tag 101
#define RadioConfig_UserPreferences_ignore_incoming_tag 103
@@ -243,6 +247,8 @@ X(a, STATIC, SINGULAR, BOOL, is_low_power, 38) \
X(a, STATIC, SINGULAR, BOOL, fixed_position, 39) \
X(a, STATIC, SINGULAR, BOOL, serial_disabled, 40) \
X(a, STATIC, SINGULAR, FLOAT, frequency_offset, 41) \
X(a, STATIC, SINGULAR, STRING, mqtt_server, 42) \
X(a, STATIC, SINGULAR, BOOL, mqtt_disabled, 43) \
X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \
X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 101) \
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \
@@ -282,8 +288,8 @@ extern const pb_msgdesc_t RadioConfig_UserPreferences_msg;
#define RadioConfig_UserPreferences_fields &RadioConfig_UserPreferences_msg
/* Maximum encoded size of messages (where known) */
#define RadioConfig_size 357
#define RadioConfig_UserPreferences_size 354
#define RadioConfig_size 394
#define RadioConfig_UserPreferences_size 391
#ifdef __cplusplus
} /* extern "C" */

167
src/mqtt/MQTT.cpp Normal file
View File

@@ -0,0 +1,167 @@
#include "MQTT.h"
#include "NodeDB.h"
#include "main.h"
#include "mesh/Channels.h"
#include "mesh/Router.h"
#include "mesh/generated/mqtt.pb.h"
#include <WiFi.h>
#include <assert.h>
MQTT *mqtt;
String statusTopic = "mesh/stat/";
String cryptTopic = "mesh/crypt/"; // mesh/crypt/CHANNELID/NODEID
void MQTT::mqttCallback(char *topic, byte *payload, unsigned int length)
{
mqtt->onPublish(topic, payload, length);
}
void MQTT::onPublish(char *topic, byte *payload, unsigned int length)
{
// parsing ServiceEnvelope
ServiceEnvelope e = ServiceEnvelope_init_default;
if (!pb_decode_from_bytes(payload, length, ServiceEnvelope_fields, &e)) {
DEBUG_MSG("Invalid MQTT service envelope, topic %s, len %u!\n", topic, length);
} else {
DEBUG_MSG("Received MQTT topic %s, len=%u\n", topic, length);
if (e.packet) {
MeshPacket *p = packetPool.allocCopy(*e.packet);
// ignore messages sent by us or if we don't have the channel key
if (router && p->from != nodeDB.getNodeNum() && perhapsDecode(p))
router->enqueueReceivedMessage(p);
else
packetPool.release(p);
}
// make sure to free both strings and the MeshPacket (passing in NULL is acceptable)
free(e.channel_id);
free(e.gateway_id);
free(e.packet);
}
}
void mqttInit()
{
new MQTT();
}
MQTT::MQTT() : concurrency::OSThread("mqtt"), pubSub(mqttClient)
{
assert(!mqtt);
mqtt = this;
pubSub.setCallback(mqttCallback);
}
void MQTT::reconnect()
{
// pubSub.setServer("devsrv.ezdevice.net", 1883); or 192.168.10.188
const char *serverAddr = "test.mosquitto.org"; // "mqtt.meshtastic.org"; // default hostname
if (*radioConfig.preferences.mqtt_server)
serverAddr = radioConfig.preferences.mqtt_server; // Override the default
pubSub.setServer(serverAddr, 1883);
DEBUG_MSG("Connecting to MQTT server\n", serverAddr);
auto myStatus = (statusTopic + owner.id);
// bool connected = pubSub.connect(nodeId.c_str(), "meshdev", "apes4cats", myStatus.c_str(), 1, true, "offline");
bool connected = pubSub.connect(owner.id, myStatus.c_str(), 1, true, "offline");
if (connected) {
DEBUG_MSG("MQTT connected\n");
enabled = true; // Start running background process again
runASAP = true;
/// FIXME, include more information in the status text
bool ok = pubSub.publish(myStatus.c_str(), "online", true);
DEBUG_MSG("published %d\n", ok);
sendSubscriptions();
} else
DEBUG_MSG("Failed to contact MQTT server...\n");
}
void MQTT::sendSubscriptions()
{
size_t numChan = channels.getNumChannels();
for (size_t i = 0; i < numChan; i++) {
auto &ch = channels.getByIndex(i);
if (ch.settings.uplink_enabled) {
String topic = cryptTopic + channels.getGlobalId(i) + "/#";
DEBUG_MSG("Subscribing to %s\n", topic.c_str());
pubSub.subscribe(topic.c_str(), 1); // FIXME, is QOS 1 right?
}
}
}
bool MQTT::wantsLink() const
{
bool hasChannel = false;
if (radioConfig.preferences.mqtt_disabled) {
// DEBUG_MSG("MQTT disabled...\n");
} else {
// No need for link if no channel needed it
size_t numChan = channels.getNumChannels();
for (size_t i = 0; i < numChan; i++) {
auto &ch = channels.getByIndex(i);
if (ch.settings.uplink_enabled || ch.settings.downlink_enabled) {
hasChannel = true;
break;
}
}
}
return hasChannel && WiFi.isConnected();
}
int32_t MQTT::runOnce()
{
bool wantConnection = wantsLink();
// If connected poll rapidly, otherwise only occasionally check for a wifi connection change and ability to contact server
if (!pubSub.loop()) {
if (wantConnection) {
reconnect();
// If we succeeded, start reading rapidly, else try again in 30 seconds (TCP connections are EXPENSIVE so try rarely)
return pubSub.connected() ? 20 : 30000;
} else
return 5000; // If we don't want connection now, check again in 5 secs
} else {
// we are connected to server, check often for new requests on the TCP port
if (!wantConnection) {
DEBUG_MSG("MQTT link not needed, dropping\n");
pubSub.disconnect();
}
return 20;
}
}
void MQTT::onSend(const MeshPacket &mp, ChannelIndex chIndex)
{
auto &ch = channels.getByIndex(chIndex);
// don't bother sending if not connected...
if (pubSub.connected() && ch.settings.uplink_enabled) {
const char *channelId = channels.getGlobalId(chIndex); // FIXME, for now we just use the human name for the channel
ServiceEnvelope env = ServiceEnvelope_init_default;
env.channel_id = (char *)channelId;
env.gateway_id = owner.id;
env.packet = (MeshPacket *)&mp;
// FIXME - this size calculation is super sloppy, but it will go away once we dynamically alloc meshpackets
static uint8_t bytes[MeshPacket_size + 64];
size_t numBytes = pb_encode_to_bytes(bytes, sizeof(bytes), ServiceEnvelope_fields, &env);
String topic = cryptTopic + channelId + "/" + owner.id;
DEBUG_MSG("publish %s, %u bytes\n", topic.c_str(), numBytes);
pubSub.publish(topic.c_str(), bytes, numBytes, false);
}
}

60
src/mqtt/MQTT.h Normal file
View File

@@ -0,0 +1,60 @@
#pragma once
#include "configuration.h"
#include "concurrency/OSThread.h"
#include "mesh/Channels.h"
#include <PubSubClient.h>
#include <WiFiClient.h>
/**
* Our wrapper/singleton for sending/receiving MQTT "udp" packets. This object isolates the MQTT protocol implementation from
* the two components that use it: MQTTPlugin and MQTTSimInterface.
*/
class MQTT : private concurrency::OSThread
{
// supposedly the current version is busted:
// http://www.iotsharing.com/2017/08/how-to-use-esp32-mqtts-with-mqtts-mosquitto-broker-tls-ssl.html
// WiFiClientSecure wifiClient;
WiFiClient mqttClient;
PubSubClient pubSub;
public:
MQTT();
/**
* Publish a packet on the glboal MQTT server.
* This hook must be called **after** the packet is encrypted (including the channel being changed to a hash).
* @param chIndex the index of the channel for this message
*
* Note: for messages we are forwarding on the mesh that we can't find the channel for (because we don't have the keys), we
* can not forward those messages to the cloud - becuase no way to find a global channel ID.
*/
void onSend(const MeshPacket &mp, ChannelIndex chIndex);
protected:
virtual int32_t runOnce();
private:
/** return true if we have a channel that wants uplink/downlink
*/
bool wantsLink() const;
/** Attempt to connect to server if necessary
*/
void reconnect();
/** Tell the server what subscriptions we want (based on channels.downlink_enabled)
*/
void sendSubscriptions();
/// Just C glue to call onPublish
static void mqttCallback(char *topic, byte *payload, unsigned int length);
/// Called when a new publish arrives from the MQTT server
void onPublish(char *topic, byte *payload, unsigned int length);
};
void mqttInit();
extern MQTT *mqtt;

View File

@@ -19,7 +19,7 @@ void AdminPlugin::handleGetChannel(const MeshPacket &req, uint32_t channelIndex)
AdminMessage r = AdminMessage_init_default;
r.get_channel_response = channels.getByIndex(channelIndex);
r.which_variant = AdminMessage_get_channel_response_tag;
reply = allocDataProtobuf(r);
myReply = allocDataProtobuf(r);
}
}
@@ -36,7 +36,7 @@ void AdminPlugin::handleGetRadio(const MeshPacket &req)
r.get_radio_response.preferences.ls_secs = getPref_ls_secs();
r.which_variant = AdminMessage_get_radio_response_tag;
reply = allocDataProtobuf(r);
myReply = allocDataProtobuf(r);
}
}
@@ -56,8 +56,8 @@ bool AdminPlugin::handleReceivedProtobuf(const MeshPacket &mp, const AdminMessag
case AdminMessage_set_channel_tag:
DEBUG_MSG("Client is setting channel %d\n", r->set_channel.index);
if (r->set_channel.index < 0 || r->set_channel.index >= MAX_NUM_CHANNELS)
reply = allocErrorResponse(Routing_Error_BAD_REQUEST, &mp);
if (r->set_channel.index < 0 || r->set_channel.index >= (int)MAX_NUM_CHANNELS)
myReply = allocErrorResponse(Routing_Error_BAD_REQUEST, &mp);
else
handleSetChannel(r->set_channel);
break;
@@ -66,7 +66,7 @@ bool AdminPlugin::handleReceivedProtobuf(const MeshPacket &mp, const AdminMessag
uint32_t i = r->get_channel_request - 1;
DEBUG_MSG("Client is getting channel %u\n", i);
if (i >= MAX_NUM_CHANNELS)
reply = allocErrorResponse(Routing_Error_BAD_REQUEST, &mp);
myReply = allocErrorResponse(Routing_Error_BAD_REQUEST, &mp);
else
handleGetChannel(mp, i);
break;
@@ -141,13 +141,6 @@ void AdminPlugin::handleSetRadio(const RadioConfig &r)
service.reloadConfig();
}
MeshPacket *AdminPlugin::allocReply()
{
auto r = reply;
reply = NULL; // Only use each reply once
return r;
}
AdminPlugin::AdminPlugin() : ProtobufPlugin("Admin", PortNum_ADMIN_APP, AdminMessage_fields)
{
// restrict to the admin channel for rx

View File

@@ -6,8 +6,6 @@
*/
class AdminPlugin : public ProtobufPlugin<AdminMessage>
{
MeshPacket *reply = NULL;
public:
/** Constructor
* name is for debugging output
@@ -21,10 +19,6 @@ class AdminPlugin : public ProtobufPlugin<AdminMessage>
*/
virtual bool handleReceivedProtobuf(const MeshPacket &mp, const AdminMessage *p);
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual MeshPacket *allocReply();
private:
void handleSetOwner(const User &o);
void handleSetChannel(const Channel &cc);

View File

@@ -10,12 +10,13 @@
// Because (FIXME) we currently don't tell API clients status on sent messages
// we need to throttle our sending, so that if a gpio is bouncing up and down we
// don't generate more messages than the net can send. So we limit watch messages to
// don't generate more messages than the net can send. So we limit watch messages to
// a max of one change per 30 seconds
#define WATCH_INTERVAL_MSEC (30 * 1000)
/// Set pin modes for every set bit in a mask
static void pinModes(uint64_t mask, uint8_t mode) {
static void pinModes(uint64_t mask, uint8_t mode)
{
for (uint8_t i = 0; i < NUM_GPIOS; i++) {
if (mask & (1 << i)) {
pinMode(i, mode);
@@ -24,7 +25,8 @@ static void pinModes(uint64_t mask, uint8_t mode) {
}
/// Read all the pins mentioned in a mask
static uint64_t digitalReads(uint64_t mask) {
static uint64_t digitalReads(uint64_t mask)
{
uint64_t res = 0;
pinModes(mask, INPUT_PULLUP);
@@ -40,10 +42,9 @@ static uint64_t digitalReads(uint64_t mask) {
return res;
}
RemoteHardwarePlugin::RemoteHardwarePlugin()
: ProtobufPlugin("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields),
concurrency::OSThread("remotehardware")
: ProtobufPlugin("remotehardware", PortNum_REMOTE_HARDWARE_APP, HardwareMessage_fields), concurrency::OSThread(
"remotehardware")
{
}
@@ -69,26 +70,26 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H
case HardwareMessage_Type_READ_GPIOS: {
// Print notification to LCD screen
if(screen)
if (screen)
screen->print("Read GPIOs\n");
uint64_t res = digitalReads(p.gpio_mask);
// Send the reply
HardwareMessage reply = HardwareMessage_init_default;
reply.typ = HardwareMessage_Type_READ_GPIOS_REPLY;
reply.gpio_value = res;
MeshPacket *p = allocDataProtobuf(reply);
HardwareMessage r = HardwareMessage_init_default;
r.typ = HardwareMessage_Type_READ_GPIOS_REPLY;
r.gpio_value = res;
MeshPacket *p = allocDataProtobuf(r);
setReplyTo(p, req);
service.sendToMesh(p);
myReply = p;
break;
}
case HardwareMessage_Type_WATCH_GPIOS: {
watchGpios = p.gpio_mask;
lastWatchMsec = 0; // Force a new publish soon
lastWatchMsec = 0; // Force a new publish soon
previousWatch = ~watchGpios; // generate a 'previous' value which is guaranteed to not match (to force an initial publish)
enabled = true; // Let our thread run at least once
enabled = true; // Let our thread run at least once
DEBUG_MSG("Now watching GPIOs 0x%llx\n", watchGpios);
break;
}
@@ -101,31 +102,31 @@ bool RemoteHardwarePlugin::handleReceivedProtobuf(const MeshPacket &req, const H
DEBUG_MSG("Hardware operation %d not yet implemented! FIXME\n", p.typ);
break;
}
return true; // handled
return false;
}
int32_t RemoteHardwarePlugin::runOnce() {
if(watchGpios) {
int32_t RemoteHardwarePlugin::runOnce()
{
if (watchGpios) {
uint32_t now = millis();
if(now - lastWatchMsec >= WATCH_INTERVAL_MSEC) {
if (now - lastWatchMsec >= WATCH_INTERVAL_MSEC) {
uint64_t curVal = digitalReads(watchGpios);
if(curVal != previousWatch) {
if (curVal != previousWatch) {
previousWatch = curVal;
DEBUG_MSG("Broadcasting GPIOS 0x%llx changed!\n", curVal);
// Something changed! Tell the world with a broadcast message
HardwareMessage reply = HardwareMessage_init_default;
reply.typ = HardwareMessage_Type_GPIOS_CHANGED;
reply.gpio_value = curVal;
MeshPacket *p = allocDataProtobuf(reply);
HardwareMessage r = HardwareMessage_init_default;
r.typ = HardwareMessage_Type_GPIOS_CHANGED;
r.gpio_value = curVal;
MeshPacket *p = allocDataProtobuf(r);
service.sendToMesh(p);
}
}
}
else {
} else {
// No longer watching anything - stop using CPU
enabled = false;
}

View File

@@ -1,4 +1,4 @@
[VERSION]
major = 1
minor = 2
build = 20
build = 23