├── .gitignore
├── CMakeLists.txt
├── README.md
├── cmake
└── modules
│ └── FindWiringPi.cmake
├── install
├── intel_edison
│ ├── install.sh
│ └── thinger.service
└── raspberry
│ ├── install.sh
│ └── thinger
├── run.sh
└── src
├── main.cpp
└── thinger
├── core
├── pson.h
├── thinger.h
├── thinger_decoder.hpp
├── thinger_encoder.hpp
├── thinger_io.hpp
├── thinger_map.hpp
├── thinger_message.hpp
└── thinger_resource.hpp
├── thinger.h
├── thinger_client.h
└── thinger_tls_client.h
/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 2.8.9)
2 | project(thinger)
3 |
4 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/")
5 |
6 | # check c++11 support
7 | include(CheckCXXCompilerFlag)
8 | CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
9 | CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
10 | if(COMPILER_SUPPORTS_CXX11)
11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
12 | elseif(COMPILER_SUPPORTS_CXX0X)
13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
14 | else()
15 | message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
16 | endif()
17 |
18 | OPTION(ENABLE_OPENSSL "Enable use of OpenSSL" ON)
19 | OPTION(DAEMON "Build thinger client as daemon" OFF)
20 | OPTION(EDISON "Enable build and install for Intel Edison" OFF)
21 | OPTION(RASPBERRY "Enable build and isntall for Raspberry Pi" OFF)
22 |
23 | # Find OpenSSL
24 | IF(ENABLE_OPENSSL)
25 | if(APPLE) # point to the correct OpenSSL path (installed by homebrew)
26 | SET(OPEN_SSL 1)
27 | include_directories(/usr/local/opt/openssl/include)
28 | list(APPEND ADDITIONAL_LIBS /usr/local/opt/openssl/lib/libssl.a)
29 | list(APPEND ADDITIONAL_LIBS /usr/local/opt/openssl/lib/libcrypto.a)
30 | else()
31 | find_package(OpenSSL REQUIRED)
32 | if(OPENSSL_FOUND)
33 | SET(OPEN_SSL 1)
34 | include_directories(${OPENSSL_INCLUDE_DIR})
35 | list(APPEND ADDITIONAL_LIBS ${OPENSSL_LIBRARIES})
36 | message(STATUS "OpenSSL Version: ${OPENSSL_VERSION}")
37 | else()
38 | SET(OPEN_SSL 0)
39 | endif()
40 | endif()
41 | ELSE()
42 | SET(OPEN_SSL 0)
43 | ENDIF()
44 |
45 | set(SOURCE_FILES src/main.cpp)
46 |
47 | # set OpenSSL if available
48 | add_definitions( -DOPEN_SSL=${OPEN_SSL} )
49 |
50 | # Support for WiringPi on Raspberry
51 | if(RASPBERRY)
52 | find_package(WiringPi)
53 | if(WiringPi_FOUND)
54 | include_directories(${WiringPi_INCLUDE_DIR})
55 | list(APPEND ADDITIONAL_LIBS ${WiringPi_LIBRARIES})
56 | endif()
57 | endif()
58 |
59 | if(DAEMON)
60 | # daemon service that can be installed as a linux service
61 | add_executable(thinger ${SOURCE_FILES})
62 | target_link_libraries(thinger ${ADDITIONAL_LIBS})
63 | set_target_properties(thinger PROPERTIES COMPILE_DEFINITIONS "DAEMON=1")
64 |
65 | if(EDISON)
66 | install(TARGETS thinger RUNTIME DESTINATION bin)
67 | install(FILES "${CMAKE_SOURCE_DIR}/install/intel_edison/thinger.service" DESTINATION /lib/systemd/system)
68 | elseif(RASPBERRY)
69 | install(TARGETS thinger RUNTIME DESTINATION bin)
70 | install(FILES "${CMAKE_SOURCE_DIR}/install/raspberry/thinger" DESTINATION /etc/init.d)
71 | elseif(MAC)
72 |
73 | endif()
74 | else()
75 | add_executable(thinger ${SOURCE_FILES})
76 | target_link_libraries(thinger ${ADDITIONAL_LIBS})
77 | set_target_properties(thinger PROPERTIES COMPILE_DEFINITIONS "DAEMON=0")
78 | endif()
79 |
80 |
81 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Thinger Linux client is a library that allows connecting your Linux IoT devices to the [thinger.io](http://thinger.io "thinger.io IoT Cloud Platform") cloud platform, or even your private server deployment. This is a library specifically designed for the Linux Operating Systems, so you can easily connect devices like a Raspberry Pi, Intel Edison, Ubuntu, Mac, etc.
2 |
3 | ## In progress
4 |
5 | This library is currently being developed and should not be integrated in any production device until it is more mature and tested. The library interface may change as it evolve to a stable version. Some things that requires attention now are:
6 |
7 | - Document code
8 | - Provide more examples and use cases
9 | - Provide a proper guide for installing the client as a service and support more devices like beaglebone
10 | - Check the reconnection mechanism and its reliability
11 | - Handle signals for proper daemon shutdown
12 | - Support OTA updates?
13 | - Load credentials from external config files?
14 | - Distribute the code as library?
15 |
16 | ## Quickstart
17 |
18 | The simplest way to start using thinger.io platform in Linux is by modifying the ```src/main.cpp``` to add your username, device, and device credentials.
19 | Then you can type in your terminal ```./run.sh```, that will compile, start and run the thinger.io client.
20 |
21 | ## Notes for compiling on Raspberry Pi
22 |
23 | - Check the compiler version and install a newer compiler if necessary. At least GCC 4.8.2.
24 | - Install CMake ```sudo apt-get install cmake```
25 | - Install Open SSL Libraries (not required but useful for secured connection) ```sudo apt-get install libssl-dev```
26 |
27 | ## License
28 |
29 |
30 |
31 | The class is licensed under the [MIT License](http://opensource.org/licenses/MIT):
32 |
33 | Copyright © 2017 [THINK BIG LABS S.L.](http://thinger.io)
34 |
35 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
36 |
37 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
38 |
39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/cmake/modules/FindWiringPi.cmake:
--------------------------------------------------------------------------------
1 | # Try to find the wiringPi library.
2 | # This will define:
3 | #
4 | # WiringPi_FOUND - wiringPi library is available
5 | # WiringPi_INCLUDE_DIR - Where the wiringPi.h header file is
6 | # WiringPi_LIBRARIES - The libraries to link in.
7 |
8 | find_library(WiringPi_LIBRARIES NAMES wiringPi PATHS
9 | /usr
10 | /usr/local
11 | /opt
12 | )
13 |
14 | find_path(WiringPi_INCLUDE_DIR wiringPi.h PATHS
15 | /usr
16 | /usr/local
17 | /opt
18 | PATH_SUFFIXES
19 | )
20 |
21 | include(FindPackageHandleStandardArgs)
22 | find_package_handle_standard_args(WiringPi DEFAULT_MSG
23 | WiringPi_LIBRARIES
24 | WiringPi_INCLUDE_DIR
25 | )
26 |
27 | mark_as_advanced(
28 | WiringPi_LIBRARIES
29 | WiringPi_INCLUDE_DIR
30 | )
--------------------------------------------------------------------------------
/install/intel_edison/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | mkdir -p build
3 | cd build
4 | cmake -DCMAKE_BUILD_TYPE=Release -DDAEMON=ON -DEDISON=ON ../../../
5 | systemctl stop thinger.service
6 | make thinger
7 | make install
8 | systemctl start thinger.service
9 | systemctl enable thinger.service
--------------------------------------------------------------------------------
/install/intel_edison/thinger.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Thinger.io client for the Internet of Things
3 | After=network.target
4 |
5 | [Service]
6 | Type=forking
7 | ExecStart=/usr/local/bin/thinger
8 | ExecReload=/bin/kill -HUP $MAINPID
9 | Restart=on-failure
10 |
11 | [Install]
12 | WantedBy=multi-user.target
--------------------------------------------------------------------------------
/install/raspberry/install.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | mkdir -p build
3 | cd build
4 | cmake -DCMAKE_BUILD_TYPE=Release -DDAEMON=ON -DRASPBERRY=ON ../../../
5 | sudo service thinger stop
6 | make thinger
7 | sudo make install
8 | sudo chmod +x /etc/init.d/thinger
9 | sudo update-rc.d thinger defaults
10 | sudo service thinger start
--------------------------------------------------------------------------------
/install/raspberry/thinger:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # /etc/init.d/thinger
3 |
4 | ### BEGIN INIT INFO
5 | # Provides: thinger
6 | # Required-Start: $remote_fs $syslog
7 | # Required-Stop: $remote_fs $syslog
8 | # Default-Start: 2 3 4 5
9 | # Default-Stop: 0 1 6
10 | # Short-Description: Example initscript
11 | # Description: Thinger client service
12 | ### END INIT INFO
13 |
14 | case "$1" in
15 | start)
16 | echo "Starting thinger.io client"
17 | /usr/local/bin/thinger
18 | ;;
19 | stop)
20 | echo "Stopping thinger.io client"
21 | killall thinger
22 | ;;
23 | *)
24 | echo "Usage: /etc/init.d/thinger start|stop"
25 | exit 1
26 | ;;
27 | esac
28 |
29 | exit 0
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | mkdir -p build
3 | cd build
4 | cmake -DCMAKE_BUILD_TYPE=Release -DDAEMON=OFF ../
5 | make thinger
6 | ./thinger
--------------------------------------------------------------------------------
/src/main.cpp:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2015 THINGER LTD
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #include "thinger/thinger.h"
25 |
26 | #define USER_ID "YOUR_USER_ID"
27 | #define DEVICE_ID "YOUR_DEVICE_ID"
28 | #define DEVICE_CREDENTIAL "YOUR_DEVICE_CREDENTIAL"
29 |
30 | int main(int argc, char *argv[])
31 | {
32 | thinger_device thing(USER_ID, DEVICE_ID, DEVICE_CREDENTIAL);
33 |
34 | // define thing resources here. i.e, this is a sum example
35 | thing["sum"] = [](pson& in, pson& out){
36 | out["result"] = (int) in["value1"] + (int) in["value2"];
37 | };
38 |
39 | thing.start();
40 | return 0;
41 | }
--------------------------------------------------------------------------------
/src/thinger/core/pson.h:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS S.L.
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #ifndef PSON_HPP
25 | #define PSON_HPP
26 |
27 | #include
28 | #include
29 | #include
30 | #include
31 |
32 | #ifndef ARDUINO
33 | #include
34 | #endif
35 |
36 | #ifndef UINT32_MAX
37 | #define UINT32_MAX 4294967295U
38 | #endif
39 |
40 | /*
41 | * Dummy placement new operator to support old Arduino compilers where this operator is not defined
42 | * (and cannot be used from inside a class), and also to not overwrite global operator from modern
43 | * toolchains like Espressif SDKs
44 | */
45 | inline void * operator new(size_t sz, void * here, void* dummy) { return here; }
46 |
47 | namespace protoson {
48 |
49 | class memory_allocator{
50 | public:
51 |
52 | // allocate
53 | virtual void *allocate(size_t size) = 0;
54 | virtual void deallocate(void *) = 0;
55 |
56 | template
57 | T* allocate(){
58 | /*
59 | * placement new has UB if memory is NULL, so check there is memory
60 | * before trying to call the operator
61 | */
62 | if(void * memory = allocate(sizeof(T))){
63 | return new (memory, NULL) T();
64 | }
65 | return NULL;
66 | }
67 |
68 | template
69 | void destroy(T* p){
70 | if(p){
71 | p->~T();
72 | deallocate(p);
73 | }
74 | }
75 | };
76 |
77 | template
78 | class circular_memory_allocator : public memory_allocator{
79 | private:
80 | uint8_t buffer_[buffer_size];
81 | size_t index_;
82 | public:
83 | circular_memory_allocator() : index_(0) {
84 | }
85 |
86 | virtual void *allocate(size_t size) {
87 | if(size>buffer_size){
88 | return NULL;
89 | }
90 | if (index_ + size > buffer_size) {
91 | index_ = 0;
92 | }
93 | void *position = &buffer_[index_];
94 | index_ += size;
95 | return position;
96 | }
97 |
98 | virtual void deallocate(void *) {}
99 | };
100 |
101 | class dynamic_memory_allocator : public memory_allocator{
102 | public:
103 | virtual void *allocate(size_t size) {
104 | return malloc(size);
105 | }
106 |
107 | virtual void deallocate(void *ptr) {
108 | free(ptr);
109 | }
110 | };
111 |
112 | extern memory_allocator& pool;
113 | }
114 |
115 | namespace protoson {
116 |
117 | enum pb_wire_type{
118 | varint = 0,
119 | fixed_64 = 1,
120 | length_delimited = 2,
121 | fixed_32 = 5,
122 | pson_type = 6
123 | };
124 |
125 | template
126 | class pson_container {
127 |
128 | class list_item{
129 | public:
130 | list_item() : next_(NULL), previous_(NULL) {}
131 | ~list_item(){}
132 |
133 | T item_;
134 | list_item* next_;
135 | list_item* previous_;
136 | };
137 |
138 | public:
139 |
140 | class iterator{
141 | public:
142 | iterator(list_item *item) : current_(item) {
143 | }
144 |
145 | private:
146 | list_item * current_;
147 |
148 | public:
149 |
150 | bool next(){
151 | if(current_==NULL) return false;
152 | current_ = current_->next_;
153 | return true;
154 | }
155 |
156 | bool has_next(){
157 | return current_!=NULL && current_->next_!=NULL;
158 | }
159 |
160 | bool valid(){
161 | return current_!=NULL;
162 | }
163 |
164 | T& item(){
165 | return current_->item_;
166 | }
167 | };
168 |
169 | private:
170 | list_item* item_;
171 | list_item* last_;
172 |
173 | public:
174 | iterator begin() const{
175 | return iterator(item_);
176 | }
177 |
178 | iterator end() const{
179 | return iterator(last_);
180 | }
181 |
182 | pson_container() : item_(NULL), last_(NULL) {
183 | }
184 |
185 | ~pson_container(){
186 | clear();
187 | }
188 |
189 | size_t size() const{
190 | size_t size = 0;
191 | list_item* current = item_;
192 | while(current!=NULL){
193 | current = current->next_;
194 | size++;
195 | }
196 | return size;
197 | }
198 |
199 | T* operator[](size_t index){
200 | list_item* current = item_;
201 | size_t current_index = 0;
202 | while(current!=NULL){
203 | if(current_index==index){
204 | return ¤t->item_;
205 | }
206 | current = current->next_;
207 | current_index++;
208 | }
209 | return NULL;
210 | }
211 |
212 | void clear(){
213 | while(last_!=NULL){
214 | list_item* previous = last_->previous_;
215 | pool.destroy(last_);
216 | last_ = previous;
217 | }
218 | }
219 |
220 | T* create_item(){
221 | list_item* new_list_item = pool.allocate();
222 | if(new_list_item==NULL) return NULL;
223 | if(item_==NULL){
224 | item_ = new_list_item;
225 | }else{
226 | last_->next_ = new_list_item;
227 | new_list_item->previous_ = last_;
228 | }
229 | last_ = new_list_item;
230 | return &(new_list_item->item_);
231 | }
232 | };
233 |
234 | class pson_object;
235 | class pson_array;
236 |
237 | class pson {
238 | public:
239 | enum field_type {
240 | null_field = 0,
241 | varint_field = 1,
242 | svarint_field = 2,
243 | float_field = 3,
244 | double_field = 4,
245 | true_field = 5,
246 | false_field = 6,
247 | zero_field = 7,
248 | one_field = 8,
249 | string_field = 9,
250 | empty_string = 10,
251 | bytes_field = 11,
252 | empty_bytes = 12,
253 | object_field = 13,
254 | array_field = 14,
255 | empty = 15
256 | // a message tag is encoded in a 128-base varint [1-bit][3-bit wire type][4-bit field]
257 | // we have up to 4 bits (0-15) for encoding fields in the first byte
258 | };
259 |
260 | // interchange two different containers
261 | static void swap(pson& source, pson& destination){
262 | // destroy destination container data (if any)
263 | destination.~pson();
264 | // override fields
265 | destination.value_ = source.value_;
266 | destination.field_type_ = source.field_type_;
267 | // 'clear' source container
268 | source.value_ = NULL;
269 | source.field_type_ = empty;
270 | }
271 |
272 | bool is_boolean() const{
273 | return field_type_ == true_field || field_type_ == false_field;
274 | }
275 |
276 | bool is_string() const{
277 | return field_type_ == string_field;
278 | }
279 |
280 | bool is_bytes() const{
281 | return field_type_ == bytes_field;
282 | }
283 |
284 | bool is_number() const{
285 | return field_type_ == varint_field ||
286 | field_type_ == svarint_field ||
287 | field_type_ == float_field ||
288 | field_type_ == double_field ||
289 | field_type_ == zero_field ||
290 | field_type_ == one_field;
291 | }
292 |
293 | bool is_float() const{
294 | return field_type_ == float_field ||
295 | field_type_ == double_field;
296 | }
297 |
298 | bool is_integer() const{
299 | return field_type_ == varint_field ||
300 | field_type_ == svarint_field ||
301 | field_type_ == zero_field ||
302 | field_type_ == one_field;
303 | }
304 |
305 | bool is_object() const{
306 | return field_type_ == object_field;
307 | }
308 |
309 | bool is_array() const{
310 | return field_type_ == array_field;
311 | }
312 |
313 | bool is_null() const{
314 | return field_type_ == null_field;
315 | }
316 |
317 | bool is_empty() const{
318 | return field_type_ == empty;
319 | }
320 |
321 | pson() : value_(NULL), field_type_(empty) {
322 | }
323 |
324 | template
325 | pson(T value) : value_(NULL), field_type_(empty){
326 | *this = value;
327 | }
328 |
329 | ~pson(){
330 | if(field_type_==object_field){
331 | pool.destroy((pson_object *) value_);
332 | }else if(field_type_==array_field) {
333 | pool.destroy((pson_array *) value_);
334 | }else{
335 | pool.deallocate(value_);
336 | }
337 | value_ = NULL;
338 | field_type_ = empty;
339 | }
340 |
341 | template
342 | void operator=(T value)
343 | {
344 | if(value==0){
345 | field_type_ = zero_field;
346 | }else if(value==1) {
347 | field_type_ = one_field;
348 | }else{
349 | uint64_t uint_value = value>0 ? value : -value;
350 | if(allocate(pson::get_varint_size(uint_value))){
351 | pb_encode_varint(uint_value);
352 | field_type_ = value>0 ? varint_field : svarint_field;
353 | }
354 | }
355 | }
356 |
357 | void operator=(bool value){
358 | field_type_ = value ? true_field : false_field;
359 | }
360 |
361 | void operator=(float value) {
362 | if(value==(int32_t)value){
363 | *this = (int32_t) value;
364 | }else{
365 | field_type_ = float_field;
366 | set(value);
367 | }
368 | }
369 |
370 | void operator=(double value) {
371 | if(value==(int64_t)value) {
372 | *this = (int64_t) value;
373 | }else if(fabs(value-(float)value)<=0.00001){
374 | field_type_ = float_field;
375 | set((float)value);
376 | }else{
377 | field_type_ = double_field;
378 | set(value);
379 | }
380 | }
381 |
382 | void operator=(const char *str) {
383 | size_t str_size = strlen(str);
384 | if(str_size==0){
385 | field_type_ = empty_string;
386 | }else if(allocate(str_size+1)){
387 | memcpy(value_, str, str_size+1);
388 | field_type_ = string_field;
389 | }
390 | }
391 |
392 | void set_bytes(void* bytes, size_t size) {
393 | if(size>0){
394 | size_t varint_size = get_varint_size(size);
395 | if(allocate(varint_size+size)){
396 | pb_encode_varint(size);
397 | memcpy(((uint8_t*)value_)+varint_size, bytes, size);
398 | field_type_ = bytes_field;
399 | }
400 | }else{
401 | field_type_ = empty_bytes;
402 | }
403 | }
404 |
405 | bool get_bytes(const void*& bytes, size_t& size){
406 | switch(field_type_){
407 | case bytes_field:
408 | size = pb_decode_varint();
409 | bytes = (uint8_t*) value_ + get_varint_size(size);
410 | return true;
411 | case empty:
412 | field_type_ = empty_bytes;
413 | return false;
414 | default:
415 | return false;
416 | }
417 | }
418 |
419 | bool allocate(size_t size){
420 | if(value_ == NULL){
421 | value_ = pool.allocate(size);
422 | return value_!=NULL;
423 | }
424 | return false;
425 | }
426 |
427 | template
428 | bool allocate(){
429 | if(value_ == NULL){
430 | value_ = pool.allocate();
431 | return value_!=NULL;
432 | }
433 | return false;
434 | }
435 |
436 | operator pson_object &();
437 | operator pson_array &();
438 | pson & operator[](const char *name);
439 |
440 | operator const char *() {
441 | switch(field_type_){
442 | case string_field:
443 | return (const char*) value_;
444 | case empty:
445 | field_type_ = empty_string;
446 | return "";
447 | default:
448 | return "";
449 | }
450 | }
451 |
452 | operator bool(){
453 | switch(field_type_){
454 | case zero_field:
455 | case false_field:
456 | return false;
457 | case one_field:
458 | case true_field:
459 | return true;
460 | case empty:
461 | field_type_ = false_field;
462 | return false;
463 | default:
464 | return false;
465 | }
466 | }
467 |
468 | operator char(){
469 | return get_value();
470 | }
471 |
472 | operator unsigned char(){
473 | return get_value();
474 | }
475 |
476 | operator short(){
477 | return get_value();
478 | }
479 |
480 | operator unsigned short(){
481 | return get_value();
482 | }
483 |
484 | operator int(){
485 | return get_value();
486 | }
487 |
488 | operator unsigned int(){
489 | return get_value();
490 | }
491 |
492 | operator long(){
493 | return get_value();
494 | }
495 |
496 | operator unsigned long(){
497 | return get_value();
498 | }
499 |
500 | operator float(){
501 | return get_value();
502 | }
503 |
504 | operator double(){
505 | return get_value();
506 | }
507 |
508 | template
509 | operator T() {
510 | return get_value();
511 | }
512 |
513 | template
514 | T get_value(){
515 | switch(field_type_){
516 | case zero_field:
517 | case false_field:
518 | return 0;
519 | case one_field:
520 | case true_field:
521 | return 1;
522 | case float_field:
523 | return *(float*)value_;
524 | case double_field:
525 | return *(double*)value_;
526 | case varint_field:
527 | return pb_decode_varint();
528 | case svarint_field:
529 | return -pb_decode_varint();
530 | case empty:
531 | field_type_ = zero_field;
532 | return 0;
533 | default:
534 | return 0;
535 | }
536 | }
537 |
538 | void* get_value(){
539 | return value_;
540 | }
541 |
542 | field_type get_type() const{
543 | return field_type_;
544 | }
545 |
546 | void set_null(){
547 | field_type_ = null_field;
548 | // TODO free existing value_ (if any)
549 | }
550 |
551 | void set_type(field_type type){
552 | field_type_ = type;
553 | }
554 |
555 | uint8_t get_varint_size(uint64_t value) const{
556 | uint8_t size = 1;
557 | while(value>>=7) size++;
558 | return size;
559 | }
560 |
561 | void pb_encode_varint(uint64_t value) const
562 | {
563 | uint8_t count = 0;
564 | do
565 | {
566 | uint8_t byte = (uint8_t)(value & 0x7F);
567 | value >>= 7;
568 | if(value) byte |= 0x80;
569 | ((uint8_t*)value_)[count] = byte;
570 | count++;
571 | }while(value);
572 | }
573 |
574 | uint64_t pb_decode_varint() const
575 | {
576 | if(value_==NULL) return 0;
577 | uint64_t value = 0;
578 | uint8_t pos = 0;
579 | uint8_t byte = 0;
580 | do{
581 | byte = ((uint8_t*)value_)[pos];
582 | value |= (uint64_t)(byte&0x7F) << pos*7;
583 | pos++;
584 | }while(byte>=0x80);
585 | return value;
586 | }
587 |
588 | #ifdef ARDUINO
589 | void operator=(const String& str) {
590 | (*this) = str.c_str();
591 | }
592 |
593 | operator String(){
594 | return (const char*)(*this);
595 | }
596 | #else
597 | void operator=(const std::string& str) {
598 | (*this) = str.c_str();
599 | }
600 |
601 | operator std::string(){
602 | return (const char*)(*this);
603 | }
604 | #endif
605 |
606 | private:
607 | void* value_;
608 | field_type field_type_;
609 |
610 | template
611 | void set(T value) {
612 | if(allocate(sizeof(T))){
613 | memcpy(value_, &value, sizeof(T));
614 | }
615 | }
616 | };
617 |
618 | class pson_pair{
619 | private:
620 | char* name_;
621 | pson value_;
622 | public:
623 | pson_pair() : name_(NULL){
624 | }
625 |
626 | ~pson_pair(){
627 | pool.deallocate(name_);
628 | }
629 |
630 | void set_name(const char *name) {
631 | size_t name_size = strlen(name) + 1;
632 | if(allocate_name(name_size)){
633 | memcpy(name_, name, name_size);
634 | }
635 | }
636 |
637 | char* allocate_name(size_t size){
638 | name_ = (char*)pool.allocate(size);
639 | return name_;
640 | }
641 |
642 | pson& value(){
643 | return value_;
644 | }
645 |
646 | char* name() const{
647 | return name_;
648 | }
649 | };
650 |
651 | class pson_object : public pson_container {
652 | public:
653 |
654 | pson &operator[](const char *name) {
655 | for(iterator it=begin(); it.valid(); it.next()){
656 | const char* item_name = it.item().name();
657 | if(item_name && strcmp(item_name, name)==0){
658 | return it.item().value();
659 | }
660 | }
661 | if(pson_pair* pair = create_item()){
662 | pair->set_name(name);
663 | return pair->value();
664 | }else{
665 | static pson value;
666 | return value;
667 | }
668 | };
669 | };
670 |
671 | class pson_array : public pson_container {
672 | public:
673 | template
674 | pson_array& add(T item_value){
675 | pson* item = create_item();
676 | if(item!=NULL){
677 | *item = item_value;
678 | }
679 | return *this;
680 | }
681 | };
682 |
683 | inline pson::operator pson_object &() {
684 | if (field_type_ != object_field) {
685 | value_ = pool.allocate();
686 | field_type_ = value_ != NULL ? object_field : empty;
687 | }
688 | if(value_!=NULL && field_type_ == object_field){
689 | return *((pson_object *)value_);
690 | }else{
691 | static pson_object dummy;
692 | return dummy;
693 | }
694 | }
695 |
696 | inline pson::operator pson_array &() {
697 | if (field_type_ != array_field) {
698 | value_ = pool.allocate();
699 | field_type_ = value_!=NULL ? array_field : empty;
700 | }
701 | if(value_!=NULL && field_type_==array_field){
702 | return *((pson_array *)value_);
703 | }else{
704 | static pson_array dummy;
705 | return dummy;
706 | }
707 | }
708 |
709 | inline pson &pson::operator[](const char *name) {
710 | return ((pson_object &) *this)[name];
711 | }
712 |
713 | ////////////////////////////
714 | /////// PSON_DECODER ///////
715 | ////////////////////////////
716 |
717 | class pson_decoder {
718 |
719 | protected:
720 | size_t read_;
721 |
722 | virtual bool read(void* buffer, size_t size){
723 | read_+=size;
724 | return true;
725 | }
726 |
727 | public:
728 |
729 | pson_decoder() : read_(0) {
730 |
731 | }
732 |
733 | void reset(){
734 | read_ = 0;
735 | }
736 |
737 | size_t bytes_read(){
738 | return read_;
739 | }
740 |
741 | bool pb_decode_tag(pb_wire_type& wire_type, uint32_t& field_number)
742 | {
743 | uint32_t temp=0;
744 | if(!pb_decode_varint32(temp)) return false;
745 | wire_type = (pb_wire_type)(temp & 0x07);
746 | field_number = temp >> 3;
747 | return true;
748 | }
749 |
750 | bool pb_decode_varint32(uint32_t& varint){
751 | varint = 0;
752 | uint8_t byte;
753 | uint8_t bit_pos = 0;
754 | do{
755 | if(!read(&byte, 1) || bit_pos>=32){
756 | return false;
757 | }
758 | varint |= (uint32_t)(byte&0x7F) << bit_pos;
759 | bit_pos += 7;
760 | }while(byte>=0x80);
761 | return true;
762 | }
763 |
764 | bool pb_decode_varint64(uint64_t& varint)
765 | {
766 | varint = 0;
767 | uint8_t byte;
768 | uint8_t bit_pos = 0;
769 | do{
770 | if(!read(&byte, 1) || bit_pos>=64){
771 | return false;
772 | }
773 | varint |= (uint32_t)(byte&0x7F) << bit_pos;
774 | bit_pos += 7;
775 | }while(byte>=0x80);
776 | return true;
777 | }
778 |
779 | bool pb_skip(size_t size){
780 | uint8_t byte;
781 | bool success = true;
782 | for(size_t i=0; i0x80 && success);
794 | return success;
795 | }
796 |
797 | bool pb_read_string(char *str, size_t size){
798 | if(str && read(str, size)){
799 | str[size]=0;
800 | return true;
801 | }
802 | return false;
803 | }
804 |
805 | bool pb_read_varint(pson& value)
806 | {
807 | uint8_t temp[10];
808 | uint8_t byte=0;
809 | uint8_t bytes_read=0;
810 | do{
811 | if(bytes_read==10 || !read(&byte, 1)) return false;
812 | temp[bytes_read] = byte;
813 | bytes_read++;
814 | }while(byte>=0x80);
815 | if(value.allocate(bytes_read)){
816 | memcpy(value.get_value(), temp, bytes_read);
817 | return true;
818 | }else{
819 | return false;
820 | }
821 | }
822 |
823 | public:
824 |
825 | bool decode(pson_object & object, size_t size){
826 | size_t start_read = bytes_read();
827 | while(size-(bytes_read()-start_read)>0){
828 | pson_pair* pair = object.create_item();
829 | if(pair==NULL || !decode(*pair)){
830 | return false;
831 | }
832 | }
833 | return true;
834 | }
835 |
836 | bool decode(pson_array & array, size_t size){
837 | size_t start_read = bytes_read();
838 | while(size-(bytes_read()-start_read)>0){
839 | pson* item = array.create_item();
840 | if(item==NULL || !decode(*item)){
841 | return false;
842 | }
843 | }
844 | return true;
845 | }
846 |
847 | bool decode(pson_pair & pair){
848 | uint32_t name_size;
849 | if(pb_decode_varint32(name_size)){
850 | return name_size != UINT32_MAX && pair.allocate_name(name_size + 1) && pb_read_string(pair.name(), name_size) && decode(pair.value());
851 | }
852 | return false;
853 | }
854 |
855 | bool decode(pson& value) {
856 | uint32_t field_number;
857 | pb_wire_type wire_type;
858 | if(!pb_decode_tag(wire_type, field_number)) return false;
859 | value.set_type((pson::field_type)field_number);
860 | if(wire_type==length_delimited){
861 | uint32_t size = 0;
862 | if(!pb_decode_varint32(size)) return false;
863 | switch(field_number){
864 | case pson::string_field:
865 | return size!=UINT32_MAX && value.allocate(size+1) && pb_read_string((char*)value.get_value(), size);
866 | case pson::bytes_field: {
867 | uint8_t varint_size = value.get_varint_size(size);
868 | if(size<=UINT32_MAX-varint_size && value.allocate(size + varint_size)){
869 | if(read((char*)value.get_value() + varint_size, size)){
870 | value.pb_encode_varint(size);
871 | return true;
872 | }
873 | }
874 | return false;
875 | }
876 | case pson::object_field:
877 | if(value.allocate()){
878 | return decode(*(pson_object*) value.get_value(), size);
879 | }
880 | return false;
881 | case pson::array_field:
882 | if(value.allocate()){
883 | return decode(*(pson_array*) value.get_value(), size);
884 | }
885 | return false;
886 | default:
887 | return false;
888 | }
889 | }else {
890 | switch (field_number) {
891 | case pson::svarint_field:
892 | case pson::varint_field:
893 | return pb_read_varint(value);
894 | case pson::float_field:
895 | return value.allocate(4) && read(value.get_value(), 4);
896 | case pson::double_field:
897 | return value.allocate(8) && read(value.get_value(), 8);
898 | case pson::null_field:
899 | case pson::true_field:
900 | case pson::false_field:
901 | case pson::zero_field:
902 | case pson::one_field:
903 | case pson::empty_string:
904 | case pson::empty_bytes:
905 | case pson::empty:
906 | return true;
907 | default:
908 | return false;
909 | }
910 | }
911 | }
912 | };
913 |
914 | ////////////////////////////
915 | /////// PSON_ENCODER ///////
916 | ////////////////////////////
917 |
918 | class pson_encoder {
919 |
920 | protected:
921 | size_t written_;
922 |
923 | virtual bool write(const void* buffer, size_t size){
924 | written_+=size;
925 | return true;
926 | }
927 |
928 | public:
929 |
930 | pson_encoder() : written_(0) {
931 | }
932 |
933 | void reset(){
934 | written_ = 0;
935 | }
936 |
937 | size_t bytes_written(){
938 | return written_;
939 | }
940 |
941 | void pb_encode_tag(pb_wire_type wire_type, uint32_t field_number){
942 | uint64_t tag = ((uint64_t)field_number << 3) | wire_type;
943 | pb_encode_varint(tag);
944 | }
945 |
946 | void pb_encode_varint(uint32_t field, uint64_t value)
947 | {
948 | pb_encode_tag(varint, field);
949 | pb_encode_varint(value);
950 | }
951 |
952 | uint8_t pb_write_varint(void * buffer)
953 | {
954 | uint8_t byte=0;
955 | uint8_t bytes_written=0;
956 | do{
957 | byte = *((uint8_t*)buffer + bytes_written);
958 | bytes_written++;
959 | }while(byte>=0x80);
960 | write(buffer, bytes_written);
961 | return bytes_written;
962 | }
963 |
964 | void pb_encode_varint(uint64_t value)
965 | {
966 | do
967 | {
968 | uint8_t byte = (uint8_t)(value & 0x7F);
969 | value >>= 7;
970 | if(value>0) byte |= 0x80;
971 | write(&byte, 1);
972 | }while(value>0);
973 | }
974 |
975 | void pb_encode_string(const char* str, uint32_t field_number){
976 | pb_encode_tag(length_delimited, field_number);
977 | pb_encode_string(str);
978 | }
979 |
980 | void pb_encode_string(const char* str){
981 | if(str!=NULL){
982 | size_t string_size = strlen(str);
983 | pb_encode_varint(string_size);
984 | write(str, string_size);
985 | }
986 | }
987 |
988 | template
989 | void pb_encode_submessage(T& element, uint32_t field_number)
990 | {
991 | pb_encode_tag(length_delimited, field_number);
992 | pson_encoder sink;
993 | sink.encode(element);
994 | pb_encode_varint(sink.bytes_written());
995 | encode(element);
996 | }
997 |
998 | void pb_encode_fixed32(void* value){
999 | write(value, 4);
1000 | }
1001 |
1002 | void pb_encode_fixed64(void* value){
1003 | write(value, 8);
1004 | }
1005 |
1006 | void pb_encode_fixed32(uint32_t field, void*value)
1007 | {
1008 | pb_encode_tag(fixed_32, field);
1009 | pb_encode_fixed32(value);
1010 | }
1011 |
1012 | void pb_encode_fixed64(uint32_t field, void*value)
1013 | {
1014 | pb_encode_tag(fixed_64, field);
1015 | pb_encode_fixed64(value);
1016 | }
1017 |
1018 | public:
1019 |
1020 | void encode(pson_object & object){
1021 | pson_container::iterator it = object.begin();
1022 | while(it.valid()){
1023 | encode(it.item());
1024 | it.next();
1025 | }
1026 | }
1027 |
1028 | void encode(pson_array & array){
1029 | pson_container::iterator it = array.begin();
1030 | while(it.valid()){
1031 | encode(it.item());
1032 | it.next();
1033 | }
1034 | }
1035 |
1036 | void encode(pson_pair & pair){
1037 | pb_encode_string(pair.name());
1038 | encode(pair.value());
1039 | }
1040 |
1041 | void encode(pson & value) {
1042 | switch (value.get_type()) {
1043 | case pson::string_field:
1044 | pb_encode_string((const char*)value.get_value(), pson::string_field);
1045 | break;
1046 | case pson::bytes_field:
1047 | pb_encode_tag(length_delimited, pson::bytes_field);
1048 | write(((const char *) value.get_value()) + pb_write_varint(value.get_value()), value.pb_decode_varint());
1049 | break;
1050 | case pson::svarint_field:
1051 | case pson::varint_field:
1052 | pb_encode_tag(varint, value.get_type());
1053 | pb_write_varint(value.get_value());
1054 | break;
1055 | case pson::float_field:
1056 | pb_encode_fixed32(pson::float_field, value.get_value());
1057 | break;
1058 | case pson::double_field:
1059 | pb_encode_fixed64(pson::double_field, value.get_value());
1060 | break;
1061 | case pson::object_field:
1062 | pb_encode_submessage(*(pson_object *) value.get_value(), pson::object_field);
1063 | break;
1064 | case pson::array_field:
1065 | pb_encode_submessage(*(pson_array *) value.get_value(), pson::array_field);
1066 | break;
1067 | default:
1068 | pb_encode_tag(varint, value.get_type());
1069 | break;
1070 | }
1071 | }
1072 | };
1073 | }
1074 |
1075 | #endif
--------------------------------------------------------------------------------
/src/thinger/core/thinger.h:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS SL
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #ifndef THINGER_H
25 | #define THINGER_H
26 |
27 | #include "pson.h"
28 | #include "thinger_map.hpp"
29 | #include "thinger_resource.hpp"
30 | #include "thinger_message.hpp"
31 | #include "thinger_encoder.hpp"
32 | #include "thinger_decoder.hpp"
33 | #include "thinger_message.hpp"
34 | #include "thinger_io.hpp"
35 |
36 | #define KEEP_ALIVE_MILLIS 60000
37 |
38 | #ifdef THINGER_MULTITASK
39 | #define th_synchronized(code) \
40 | lock(); \
41 | code \
42 | unlock();
43 | #else
44 | #define th_synchronized(code) \
45 | code
46 | #endif
47 |
48 | namespace thinger{
49 |
50 | using namespace protoson;
51 |
52 | class thinger : public thinger_io{
53 | public:
54 | thinger() :
55 | encoder(*this),
56 | decoder(*this),
57 | last_keep_alive(0),
58 | keep_alive_response(true)
59 | {
60 | #ifdef THINGER_FREE_RTOS_MULTITASK
61 | semaphore_ = xSemaphoreCreateMutex();
62 | #endif
63 | }
64 |
65 | virtual ~thinger(){
66 |
67 | }
68 |
69 | private:
70 | thinger_write_encoder encoder;
71 | thinger_read_decoder decoder;
72 | unsigned long last_keep_alive;
73 | bool keep_alive_response;
74 | thinger_map resources_;
75 |
76 | #if defined(THINGER_FREE_RTOS_MULTITASK)
77 | SemaphoreHandle_t semaphore_;
78 | #elif defined(THINGER_MBED_MULTITASK)
79 | rtos::Mutex mutex_;
80 | #endif
81 |
82 |
83 |
84 | protected:
85 |
86 | /**
87 | * Can be override to start reconnection process
88 | */
89 | virtual void disconnected(){
90 | // stop all streaming resources after disconnect
91 | if(thinger_resource::get_streaming_counter()>0) {
92 | thinger_map::entry* current = resources_.begin();
93 | while(current!=NULL){
94 | current->value_.disable_streaming();
95 | current = current->next_;
96 | }
97 | }
98 | }
99 |
100 | bool connect(const char* username, const char* device_id, const char* credential){
101 | // reset keep alive status for each connection
102 | keep_alive_response = true;
103 | thinger_message message;
104 | message.set_signal_flag(thinger_message::AUTH);
105 | message.resources().add(username).add(device_id).add(credential);
106 |
107 | /** temporal fix for old production server **/
108 | if(!send_message(message)) return false;
109 | thinger_message response;
110 | return read_message(response) && response.get_signal_flag() == thinger_message::REQUEST_OK;
111 |
112 | /*
113 | *
114 | use this in new server
115 | return send_message_with_ack(message);
116 | */
117 | }
118 |
119 | public:
120 |
121 | #ifdef THINGER_MULTITASK
122 | #if defined(THINGER_FREE_RTOS_MULTITASK)
123 | void lock(){
124 | xSemaphoreTake(semaphore_, portMAX_DELAY);
125 | }
126 |
127 | void unlock(){
128 | xSemaphoreGive(semaphore_);
129 | }
130 | #elif defined(THINGER_MBED_MULTITASK)
131 | void lock(){
132 | mutex_.lock();
133 | }
134 |
135 | void unlock(){
136 | mutex_.unlock();
137 | }
138 | #else
139 | #error "Thinger Multitask enabled but not framework defined"
140 | #endif
141 | #endif
142 |
143 | thinger_resource & operator[](const char* res){
144 | return resources_[res];
145 | }
146 |
147 | /**
148 | * Read a property stored in the server
149 | * @param property_identifier property identifier
150 | * @param data pson structure to be filled with the property data received from server
151 | * @return true if the property read was ok
152 | */
153 | bool get_property(const char* property_identifier, protoson::pson& data){
154 | thinger_message request;
155 | request.set_signal_flag(thinger_message::GET_PROPERTY);
156 | request.set_identifier(property_identifier);
157 | return send_message(request, data);
158 | }
159 |
160 | /**
161 | * Set a property in the server
162 | * @param property_identifier property identifier
163 | * @param data pson structure with the data to be stored in the server
164 | * @param confirm_write flag to control whether it is required a write confirmation or not
165 | * @return
166 | */
167 | bool set_property(const char* property_identifier, pson& data, bool confirm_write=false){
168 | thinger_message message;
169 | message.set_signal_flag(thinger_message::SET_PROPERTY);
170 | message.set_identifier(property_identifier);
171 | message.set_data(data);
172 | return send_message_with_ack(message, confirm_write);
173 | }
174 |
175 | /**
176 | * Execute a resource in a remote device (without data)
177 | * @param device_name remote device identifier (must be connected to your account)
178 | * @param resource_name remote resource identifier (must be defined in the remote device)
179 | * @return
180 | */
181 | bool call_device(const char* device_name, const char* resource_name, bool confirm_call=false){
182 | thinger_message message;
183 | message.set_signal_flag(thinger_message::CALL_DEVICE);
184 | message.set_identifier(device_name);
185 | message.resources().add(resource_name);
186 | return send_message_with_ack(message, confirm_call);
187 | }
188 |
189 | /**
190 | * Execute a resource in a remote device (with a custom pson payload)
191 | * @param device_name remote device identifier (must be connected to your account)
192 | * @param resource_name remote resource identifier (must be defined in the remote device)
193 | * @param data pson structure to be sent to the remote resource input
194 | * @return
195 | */
196 | bool call_device(const char* device_name, const char* resource_name, pson& data, bool confirm_call=false){
197 | thinger_message message;
198 | message.set_signal_flag(thinger_message::CALL_DEVICE);
199 | message.set_identifier(device_name);
200 | message.resources().add(resource_name);
201 | message.set_data(data);
202 | return send_message_with_ack(message, confirm_call);
203 | }
204 |
205 | /**
206 | * Execute a resource in a remote device (with a payload from a local resource)
207 | * @param device_name remote device identifier (must be connected to your account)
208 | * @param resource_name remote resource identifier (must be defined in the remote device)
209 | * @param resource local device resource, as defined in code, i.e., thing["location"]
210 | * @return
211 | */
212 | bool call_device(const char* device_name, const char* resource_name, thinger_resource& resource, bool confirm_call=false){
213 | thinger_message message;
214 | message.set_signal_flag(thinger_message::CALL_DEVICE);
215 | message.set_identifier(device_name);
216 | message.resources().add(resource_name);
217 | resource.fill_output(message.get_data());
218 | return send_message_with_ack(message, confirm_call);
219 | }
220 |
221 | /**
222 | * Cal a server endpoint (without any data)
223 | * @param endpoint_name endpoint identifier, as defined in the server
224 | * @return
225 | */
226 | bool call_endpoint(const char* endpoint_name, bool confirm_call=false){
227 | thinger_message message;
228 | message.set_signal_flag(thinger_message::CALL_ENDPOINT);
229 | message.set_identifier(endpoint_name);
230 | return send_message_with_ack(message, confirm_call);
231 | }
232 |
233 | /**
234 | * Call a server endpoint
235 | * @param endpoint_name endpoint identifier, as defined in the server
236 | * @param data data in pson format to be used as data source for the endpoint call
237 | * @return
238 | */
239 | bool call_endpoint(const char* endpoint_name, pson& data, bool confirm_call=false){
240 | thinger_message message;
241 | message.set_signal_flag(thinger_message::CALL_ENDPOINT);
242 | message.set_identifier(endpoint_name);
243 | message.set_data(data);
244 | return send_message_with_ack(message, confirm_call);
245 | }
246 |
247 | /**
248 | * Call a server endpoint
249 | * @param endpoint_name endpoint identifier, as defined in the server
250 | * @param resource resource to be used as data source for the endpoint call, i.e., thing["location"]
251 | * @return
252 | */
253 | bool call_endpoint(const char* endpoint_name, thinger_resource& resource, bool confirm_call=false){
254 | thinger_message message;
255 | message.set_signal_flag(thinger_message::CALL_ENDPOINT);
256 | message.set_identifier(endpoint_name);
257 | resource.fill_output(message.get_data());
258 | return send_message_with_ack(message, confirm_call);
259 | }
260 |
261 | /**
262 | * Call a server endpoint
263 | * @param endpoint_name endpoint identifier, as defined in the server
264 | * @param resource_name name of the resource to be used as data source for the endpoint call, i.e., "location"
265 | * @return
266 | */
267 | bool call_endpoint(const char* endpoint_name, const char* resource_name, bool confirm_call=false){
268 | return call_endpoint(endpoint_name, resources_[resource_name], confirm_call);
269 | }
270 |
271 | /**
272 | * Write arbitrary data to a given bucket identifier
273 | * @param bucket_id bucket identifier
274 | * @param data data to write defined in a pson structure
275 | * @return
276 | */
277 | bool write_bucket(const char* bucket_id, pson& data, bool confirm_write=false){
278 | thinger_message message;
279 | message.set_signal_flag(thinger_message::BUCKET_DATA);
280 | message.set_identifier(bucket_id);
281 | message.set_data(data);
282 | return send_message_with_ack(message, confirm_write);
283 | }
284 |
285 | /**
286 | * Write a resource to a given bucket identifier
287 | * @param bucket_id bucket identifier
288 | * @param resource_name resource defined in the code, i.e., thing["location"]
289 | * @return
290 | */
291 | bool write_bucket(const char* bucket_id, thinger_resource& resource, bool confirm_write=false){
292 | thinger_message message;
293 | message.set_signal_flag(thinger_message::BUCKET_DATA);
294 | message.set_identifier(bucket_id);
295 | resource.fill_output(message.get_data());
296 | return send_message_with_ack(message, confirm_write);
297 | }
298 |
299 | /**
300 | * Write a resource to a given bucket identifier
301 | * @param bucket_id bucket identifier
302 | * @param resource_name resource identifier defined in the code, i.e, "location"
303 | * @return
304 | */
305 | bool write_bucket(const char* bucket_id, const char* resource_name, bool confirm_write=false){
306 | return write_bucket(bucket_id, resources_[resource_name], confirm_write);
307 | }
308 |
309 | /**
310 | * Stream the given resource
311 | * @param resource resource defined in the code, i.e, thing["location"]
312 | * @param type STREAM_EVENT or STREAM_SAMPLE, depending if the stream was an event or a scheduled sampling
313 | */
314 | void stream_resource(thinger_resource& resource, thinger_message::signal_flag type){
315 | thinger_message message;
316 | message.set_stream_id(resource.get_stream_id());
317 | message.set_signal_flag(type);
318 | // TODO modify and update servers to support resource.fill_output(message.get_data());
319 | th_synchronized(resource.fill_api_io(message.get_data());)
320 | send_message(message);
321 | }
322 |
323 | /**
324 | * Stream the given resource. There should be any process listening for such resource, i.e., over a server websocket.
325 | * @param resource resource defined in the code, i.e, thing["location"]
326 | * @return true if there was some external process listening for this resource and the resource was transmitted
327 | */
328 | bool stream(thinger_resource& resource){
329 | if(resource.stream_enabled()){
330 | stream_resource(resource, thinger_message::STREAM_EVENT);
331 | return true;
332 | }
333 | return false;
334 | }
335 |
336 | /**
337 | * Stream the given resource. There should be any process listening for such resource, i.e., over a server websocket.
338 | * @param resource resource identifier defined in the code, i.e, "location"
339 | * @return true if there was some external process listening for this resource and the resource was transmitted
340 | */
341 | bool stream(const char* resource){
342 | return stream(resources_[resource]);
343 | }
344 |
345 | /**
346 | * This method should be called periodically, indicating the current timestamp, and if there are bytes
347 | * available in the connection
348 | * @param current_time in milliseconds, i.e., unix epoch or millis from start.
349 | * @param bytes_available true or false indicating if there is input data available for reading.
350 | */
351 | void handle(unsigned long current_time, bool bytes_available)
352 | {
353 | // handle input
354 | if(bytes_available){
355 | thinger_message message;
356 | th_synchronized(bool result = read_message(message)==MESSAGE;)
357 | if(result) handle_request_received(message);
358 | }
359 |
360 | // handle keep alive (send keep alive to server to prevent disconnection)
361 | if(current_time-last_keep_alive>KEEP_ALIVE_MILLIS){
362 | if(keep_alive_response){
363 | last_keep_alive = current_time;
364 | keep_alive_response = false;
365 | send_keep_alive();
366 | }else{
367 | disconnected();
368 | }
369 | }
370 |
371 | // handle streaming resources
372 | if(thinger_resource::get_streaming_counter()>0){
373 | handle_streaming(resources_, current_time);
374 | }
375 | }
376 |
377 | private:
378 |
379 | /**
380 | * Iterates over all resources and subresources to determine when a streaming is required.
381 | * @param resources
382 | * @param current_time
383 | */
384 | void handle_streaming(thinger_map& resources, unsigned long current_time){
385 | thinger_map::entry* current = resources.begin();
386 | while(current!=NULL){
387 | thinger_resource& resource = current->value_;
388 | if(resource.stream_required(current_time)){
389 | stream_resource(resource, thinger_message::STREAM_SAMPLE);
390 | }
391 | thinger_map& sub_resources = resource.get_resources();
392 | if(!sub_resources.empty()){
393 | handle_streaming(sub_resources, current_time);
394 | }
395 | current = current->next_;
396 | }
397 | }
398 |
399 | /**
400 | * Decode a message from the current connection. It should be called when there are bytes available for reading.
401 | * @param message reference to the message that will be filled with the decoded information
402 | * @return true or false if the message passed in reference was filled with a valid message.
403 | */
404 | message_type read_message(thinger_message& message){
405 | uint32_t type = 0;
406 | if(decoder.pb_decode_varint32(type)){
407 | switch(type){
408 | case MESSAGE: {
409 | // decode message size & message itself
410 | uint32_t size = 0;
411 | return decoder.pb_decode_varint32(size) && decoder.decode(message, size) ? MESSAGE : NONE;
412 | }
413 | case KEEP_ALIVE: {
414 | // update our keep_alive flag (connection active)
415 | keep_alive_response = true;
416 | // skip size bytes in keep alive (always 0)
417 | return decoder.pb_skip_varint() ? KEEP_ALIVE : NONE;
418 | }
419 | }
420 | }
421 | return NONE;
422 | }
423 |
424 | /**
425 | * Wait for a server response, and optionally store the response payload on the provided PSON structure
426 | * @param request source message that will be used forf
427 | * @param payload
428 | * @return true if the response was received and succeed (REQUEST_OK in signal flag)
429 | */
430 | bool wait_response(thinger_message& request, protoson::pson* payload = NULL){
431 | do{
432 | // try to read an incoming message
433 | thinger_message response;
434 | message_type type = read_message(response);
435 | switch(type){
436 | // message received
437 | case MESSAGE:
438 | if(request.get_stream_id() == response.get_stream_id()){
439 | // copy response payload to provided structure
440 | if(payload != NULL && response.has_data()) pson::swap(response.get_data(), *payload);
441 | return response.get_signal_flag()==thinger_message::REQUEST_OK;
442 | }
443 | handle_request_received(response);
444 | break;
445 | // keep alive is handled inside read_message automatically
446 | case KEEP_ALIVE:
447 | break;
448 | // maybe a timeout while reading a message
449 | case NONE:
450 | return false;
451 | }
452 | }while(true);
453 | }
454 |
455 | /**
456 | * Write a message to the socket
457 | * @param message
458 | * @return true if success
459 | */
460 | bool write_message(thinger_message& message){
461 | thinger_encoder sink;
462 | sink.encode(message);
463 | encoder.pb_encode_varint(MESSAGE);
464 | encoder.pb_encode_varint(sink.bytes_written());
465 | encoder.encode(message);
466 | return write(NULL, 0, true);
467 | }
468 |
469 | /**
470 | * Send a message
471 | * @param message message to be sent
472 | * @return true if the message was written to the socket
473 | */
474 | bool send_message(thinger_message& message){
475 | th_synchronized(bool result = write_message(message);)
476 | return result;
477 | }
478 |
479 | /**
480 | * Send a message and optionally wait for server acknowledgement
481 | * @param message message to be sent
482 | * @param wait_ack true if ack is required
483 | * @return true if the message was acknowledged by the server.
484 | */
485 | bool send_message_with_ack(thinger_message& message, bool wait_ack=true){
486 | if(wait_ack) message.set_random_stream_id();
487 | th_synchronized(bool result = write_message(message) && (!wait_ack || wait_response(message));)
488 | return result;
489 | }
490 |
491 | /**
492 | * Send a message and wait for server ack and response payload
493 | * @param message message to be sent
494 | * @param data protoson::pson structure to be filled with the response payload
495 | * @return true if the message was acknowledged by the server.
496 | */
497 | bool send_message(thinger_message& message, protoson::pson& data){
498 | th_synchronized(bool result = write_message(message) && wait_response(message, &data););
499 | return result;
500 | }
501 |
502 | /**
503 | * Send a keep alive to the server
504 | * @return true if the keep alive was written to the socket
505 | */
506 | bool send_keep_alive(){
507 | bool result = false;
508 | th_synchronized(
509 | encoder.pb_encode_varint(KEEP_ALIVE);
510 | encoder.pb_encode_varint(0);
511 | result = write(NULL, 0, true);
512 | )
513 | return result;
514 | }
515 |
516 | /**
517 | * Handle an incoming request from the server
518 | * @param request the message sent by the server
519 | */
520 | void handle_request_received(thinger_message& request)
521 | {
522 | // create a response message to any incoming request
523 | thinger_message response(request);
524 |
525 | // if there is no resource in the message, they are not asking for anything in our device
526 | if(!request.has_resource()){
527 | response.set_signal_flag(thinger_message::REQUEST_ERROR);
528 | }
529 |
530 | /*
531 | * decode the requested resource. A resource is an array of string identifiers, as resources may be
532 | * concatenated, i.e., temperature/degrees; tire1/pressure.
533 | */
534 | else{
535 | // pointer to the requested resource (not initialized by default)
536 | thinger_resource * thing_resource = NULL;
537 |
538 | for(pson_array::iterator it = request.resources().begin(); it.valid(); it.next()){
539 |
540 | // if the resource name is not a string.. stop!
541 | if(!it.item().is_string()){
542 | response.set_signal_flag(thinger_message::REQUEST_ERROR);
543 | break;
544 | }
545 |
546 | // get current resource, and check if there are more remaining resources
547 | const char* resource = it.item();
548 |
549 | // there are more sub resources in the array
550 | if(it.has_next()){
551 |
552 | // search the requested resource in the root, or just in the current resource (kept in thing_resource)
553 | thing_resource = thing_resource == NULL ? resources_.find(resource) : thing_resource->find(resource);
554 |
555 | // the requested resource is not available in the device or the resource... stop!
556 | if(thing_resource==NULL) {
557 | response.set_signal_flag(thinger_message::REQUEST_ERROR);
558 | break;
559 | }
560 |
561 | // the current item is the latest resource name
562 | }else{
563 |
564 | // check if resource name is the special word "api" to fill the current resource state
565 | if(strcmp("api", resource)==0){
566 | // just fill the api over the device root
567 | if(thing_resource==NULL){
568 | thinger_map::entry* current = resources_.begin();
569 | while(current!=NULL){
570 | current->value_.fill_api(response.get_data()[current->key_]);
571 | current = current->next_;
572 | }
573 | // fll the api over the specified resource
574 | }else{
575 | th_synchronized(thing_resource->fill_api_io(response.get_data());)
576 | }
577 |
578 | // just want to interact with the resource itself...
579 | }else{
580 | thing_resource = thing_resource == NULL ? resources_.find(resource) : thing_resource->find(resource);
581 | // the resource is not available.. stop!
582 | if(thing_resource==NULL){
583 | response.set_signal_flag(thinger_message::REQUEST_ERROR);
584 |
585 | // the resource is available, so, handle its i/o.
586 | }else{
587 | th_synchronized(thing_resource->handle_request(request, response);)
588 | // stream enabled over a resource input -> notify the current state
589 | if(thing_resource->stream_enabled() && (thing_resource->get_io_type()==thinger_resource::pson_in || thing_resource->get_io_type()==thinger_resource::pson_in_pson_out)){
590 | // send normal response
591 | send_message(response);
592 | // stream the event to notify the change
593 | return stream_resource(*thing_resource, thinger_message::STREAM_EVENT);
594 | }
595 | }
596 | }
597 | }
598 | }
599 | }
600 | // do not send responses to requests without a stream id as they will not reach any destination!
601 | if(response.get_stream_id()!=0){
602 | send_message(response);
603 | }
604 | }
605 |
606 | };
607 | }
608 |
609 | #endif
--------------------------------------------------------------------------------
/src/thinger/core/thinger_decoder.hpp:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS S.L.
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #ifndef THINGER_DECODER_HPP
25 | #define THINGER_DECODER_HPP
26 |
27 | #include "pson.h"
28 | #include "thinger_message.hpp"
29 |
30 | namespace thinger{
31 |
32 | class thinger_decoder : public protoson::pson_decoder{
33 | public:
34 | bool decode(thinger_message& message, size_t size){
35 | size_t start_read = bytes_read();
36 | while(size-(bytes_read()-start_read)>0) {
37 | protoson::pb_wire_type wire_type;
38 | uint32_t field_number=0;
39 | if(!pb_decode_tag(wire_type, field_number)) return false;
40 | switch (wire_type) {
41 | case protoson::length_delimited:{
42 | uint32_t size = 0;
43 | if(!pb_decode_varint32(size) || !pb_skip(size)) return false;
44 | }
45 | break;
46 | case protoson::varint: {
47 | switch (field_number) {
48 | case thinger_message::SIGNAL_FLAG:
49 | {
50 | uint32_t signal_flag = 0;
51 | if(!pb_decode_varint32(signal_flag)) return false;
52 | message.set_signal_flag((thinger_message::signal_flag)(signal_flag));
53 | }
54 | break;
55 | case thinger_message::STREAM_ID:
56 | {
57 | uint32_t stream_id = 0;
58 | if(!pb_decode_varint32(stream_id)) return false;
59 | message.set_stream_id(stream_id);
60 | }
61 | break;
62 | default:
63 | if(!pb_skip_varint()) return false;
64 | break;
65 | }
66 | break;
67 | }
68 | case protoson::pson_type:
69 | switch(field_number){
70 | case thinger_message::IDENTIFIER:
71 | if(!protoson::pson_decoder::decode(message.get_identifier())) return false;
72 | break;
73 | case thinger_message::RESOURCE:
74 | if(!protoson::pson_decoder::decode(message.get_resources())) return false;
75 | break;
76 | case thinger_message::PAYLOAD:
77 | if(!protoson::pson_decoder::decode(((protoson::pson&) message))) return false;
78 | break;
79 | default:
80 | break;
81 | }
82 | break;
83 | case protoson::fixed_32:
84 | if(!pb_skip(4)) return false;
85 | break;
86 | case protoson::fixed_64:
87 | if(!pb_skip(8)) return false;
88 | break;
89 | default:
90 | break;
91 | }
92 | }
93 | return true;
94 | }
95 | };
96 |
97 | class thinger_read_decoder : public thinger_decoder{
98 | public:
99 | thinger_read_decoder(thinger_io& io) : io_(io)
100 | {}
101 |
102 | protected:
103 | virtual bool read(void* buffer, size_t size){
104 | return io_.read((char*)buffer, size) && protoson::pson_decoder::read(buffer, size);
105 | }
106 |
107 | private:
108 | thinger_io& io_;
109 | };
110 |
111 | class thinger_memory_decoder : public thinger_decoder{
112 |
113 | public:
114 | thinger_memory_decoder(uint8_t* buffer, size_t size) : buffer_(buffer), size_(size){}
115 |
116 | protected:
117 | virtual bool read(void* buffer, size_t size){
118 | if(read_+size<=size_){
119 | memcpy(buffer, buffer_ + read_, size);
120 | return protoson::pson_decoder::read(buffer, size);
121 | }else{
122 | return false;
123 | }
124 | }
125 |
126 | private:
127 | uint8_t* buffer_;
128 | size_t size_;
129 | };
130 |
131 | }
132 |
133 | #endif
--------------------------------------------------------------------------------
/src/thinger/core/thinger_encoder.hpp:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS S.L.
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #ifndef THINGER_ENCODER_HPP
25 | #define THINGER_ENCODER_HPP
26 |
27 | #include "thinger_message.hpp"
28 | #include "thinger_io.hpp"
29 |
30 | namespace thinger{
31 |
32 | class thinger_encoder : public protoson::pson_encoder{
33 |
34 | protected:
35 | virtual bool write(const void *buffer, size_t size){
36 | return protoson::pson_encoder::write(buffer, size);
37 | }
38 |
39 | public:
40 | void encode(thinger_message& message){
41 | if(message.get_stream_id()!=0){
42 | pb_encode_varint(thinger_message::STREAM_ID, message.get_stream_id());
43 | }
44 | if(message.get_signal_flag()!=thinger_message::NONE){
45 | pb_encode_varint(thinger_message::SIGNAL_FLAG, message.get_signal_flag());
46 | }
47 | if(message.has_identifier()){
48 | pb_encode_tag(protoson::pson_type, thinger_message::IDENTIFIER);
49 | protoson::pson_encoder::encode(message.get_identifier());
50 | }
51 | if(message.has_resource()){
52 | pb_encode_tag(protoson::pson_type, thinger_message::RESOURCE);
53 | protoson::pson_encoder::encode(message.get_resources());
54 | }
55 | if(message.has_data()){
56 | pb_encode_tag(protoson::pson_type, thinger_message::PAYLOAD);
57 | protoson::pson_encoder::encode((protoson::pson&) message);
58 | }
59 | }
60 | };
61 |
62 | class thinger_write_encoder : public thinger_encoder{
63 | public:
64 | thinger_write_encoder(thinger_io& io) : io_(io)
65 | {}
66 |
67 | protected:
68 | virtual bool write(const void *buffer, size_t size){
69 | return io_.write((const char*)buffer, size) && protoson::pson_encoder::write(buffer, size);
70 | }
71 |
72 | private:
73 | thinger_io& io_;
74 | };
75 |
76 | class thinger_memory_encoder : public thinger_encoder{
77 |
78 | public:
79 | thinger_memory_encoder(uint8_t* buffer, size_t size) : buffer_(buffer), size_(size){}
80 |
81 | protected:
82 | virtual bool write(const void *buffer, size_t size){
83 | if(written_+size < size_){
84 | memcpy(buffer_ + written_, buffer, size);
85 | return protoson::pson_encoder::write(buffer, size);
86 | }
87 | return false;
88 | }
89 |
90 | private:
91 | uint8_t* buffer_;
92 | size_t size_;
93 | };
94 |
95 | }
96 |
97 | #endif
--------------------------------------------------------------------------------
/src/thinger/core/thinger_io.hpp:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS SL
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #ifndef THINGER_IO_HPP
25 | #define THINGER_IO_HPP
26 |
27 | namespace thinger {
28 |
29 | class thinger_io {
30 | public:
31 | thinger_io(){}
32 | virtual ~thinger_io(){}
33 |
34 | public:
35 | virtual bool read(char *buffer, size_t size) = 0;
36 | virtual bool write(const char *buffer, size_t size, bool flush = false) = 0;
37 | };
38 |
39 | }
40 |
41 | #endif
42 |
--------------------------------------------------------------------------------
/src/thinger/core/thinger_map.hpp:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS SL
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #ifndef THINGER_MAP_H
25 | #define THINGER_MAP_H
26 |
27 | #include
28 |
29 | template
30 | class thinger_map {
31 |
32 | public:
33 | thinger_map() : head_(NULL), last_(NULL) {
34 |
35 | }
36 |
37 | virtual ~thinger_map() {
38 | }
39 |
40 | public:
41 |
42 | struct entry {
43 | entry(const char* key) : key_(key), next_(NULL){
44 |
45 | }
46 |
47 | const char* key_;
48 | struct entry * next_;
49 | T value_;
50 | };
51 |
52 | private:
53 |
54 | entry * head_;
55 | entry * last_;
56 |
57 | public:
58 |
59 | T& operator[](const char* key){
60 | entry * current = head_;
61 | while(current != NULL){
62 | if(strcmp(key, current->key_)==0){
63 | return current->value_;
64 | }
65 | current = current->next_;
66 | }
67 | // TODO replace with memory allocator for allowing static memory/dynamic memory
68 | current = new entry(key);
69 |
70 | if(head_==NULL) head_ = current;
71 | if(last_!=NULL) last_->next_ = current;
72 | last_ = current;
73 | return current->value_;
74 | }
75 |
76 | entry* begin(){
77 | return head_;
78 | }
79 |
80 | entry* end(){
81 | return last_;
82 | }
83 |
84 | bool empty()
85 | {
86 | return head_ == last_;
87 | }
88 |
89 | T* find(const char* key)
90 | {
91 | if(key==NULL) return NULL;
92 | entry * current = head_;
93 | while(current != NULL){
94 | if(strcmp(key, current->key_)==0){
95 | return ¤t->value_;
96 | }
97 | current = current->next_;
98 | }
99 | return NULL;
100 | }
101 |
102 | };
103 |
104 | #endif
--------------------------------------------------------------------------------
/src/thinger/core/thinger_message.hpp:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS SL
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #ifndef THINGER_MESSAGE_HPP
25 | #define THINGER_MESSAGE_HPP
26 |
27 | #include "pson.h"
28 |
29 | namespace thinger{
30 |
31 | enum message_type{
32 | NONE = 0,
33 | MESSAGE = 1,
34 | KEEP_ALIVE = 2
35 | };
36 |
37 | class thinger_message{
38 |
39 | public:
40 |
41 | // fields for a thinger message (encoded as in protocol buffers)
42 | enum fields{
43 | STREAM_ID = 1,
44 | SIGNAL_FLAG = 2,
45 | IDENTIFIER = 3,
46 | RESOURCE = 4,
47 | UNUSED1 = 5,
48 | PAYLOAD = 6
49 | };
50 |
51 | // flags for describing a thinger message
52 | enum signal_flag {
53 | // GENERAL USED FLAGS
54 | REQUEST_OK = 1, // the request with the given stream id was successful
55 | REQUEST_ERROR = 2, // the request with the given stream id failed
56 |
57 | // SENT BY THE SERVER
58 | NONE = 0, // default resource action: just execute the given resource
59 | START_STREAM = 3, // enable a streaming resource (with stream_id, and resource filled, sample interval (in payload) is optional)
60 | STOP_STREAM = 4, // stop the streaming resource (with stream_id, and resource filled)
61 |
62 | // SENT BY DEVICE
63 | AUTH = 5,
64 | STREAM_EVENT = 6, // means that the message data is related to a stream event
65 | STREAM_SAMPLE = 7, // means that the message is related to a periodical streaming sample
66 | CALL_ENDPOINT = 8, // call the endpoint with the provided name (endpoint_id in identifier, value passed in payload)
67 | CALL_DEVICE = 9, // call a given device (device_id in identifier, resource in resource, and value, passed in payload)
68 | BUCKET_DATA = 10, // call the bucket with the provided name (bucket_id in identifier, value passed in payload)
69 | GET_PROPERTY = 11, // call the bucket with the provided name (bucket_id in identifier, value passed in payload)
70 | SET_PROPERTY = 12 // call the bucket with the provided name (bucket_id in identifier, value passed in payload)
71 | };
72 |
73 | public:
74 |
75 | /**
76 | * Initialize a default response message setting the same stream id of the source message,
77 | * and initializing the signal flag to ok. All remaining data or fields are empty
78 | */
79 | thinger_message(thinger_message& other) :
80 | stream_id(other.stream_id),
81 | flag(REQUEST_OK),
82 | identifier(NULL),
83 | resource(NULL),
84 | data(NULL),
85 | data_allocated(false)
86 | {}
87 |
88 | /**
89 | * Initialize a default empty message
90 | */
91 | thinger_message() :
92 | stream_id(0),
93 | flag(NONE),
94 | identifier(NULL),
95 | resource(NULL),
96 | data(NULL),
97 | data_allocated(false)
98 | {}
99 |
100 | ~thinger_message(){
101 | // deallocate identifier
102 | protoson::pool.destroy(identifier);
103 | // deallocate resource
104 | protoson::pool.destroy(resource);
105 | // deallocate paylaod if was allocated here
106 | if(data_allocated){
107 | protoson::pool.destroy(data);
108 | }
109 | }
110 |
111 | private:
112 | /// used for identifying a unique stream
113 | uint16_t stream_id;
114 | /// used for setting a stream signal
115 | signal_flag flag;
116 | /// used to identify a device, an endpoint, or a bucket
117 | protoson::pson* identifier;
118 | /// used to identify an specific resource over the identifier
119 | protoson::pson* resource;
120 | /// used to send a data payload in the message
121 | protoson::pson* data;
122 | /// flag to determine when the payload has been reserved
123 | bool data_allocated;
124 |
125 | public:
126 |
127 | uint16_t get_stream_id(){
128 | return stream_id;
129 | }
130 |
131 | signal_flag get_signal_flag(){
132 | return flag;
133 | }
134 |
135 | bool has_data(){
136 | return data!=NULL;
137 | }
138 |
139 | bool has_identifier(){
140 | return identifier!=NULL;
141 | }
142 |
143 | bool has_resource(){
144 | return resource!=NULL;
145 | }
146 |
147 | public:
148 | void set_stream_id(uint16_t stream_id) {
149 | thinger_message::stream_id = stream_id;
150 | }
151 |
152 | void set_random_stream_id(){
153 | // TODO, random seed
154 | thinger_message::stream_id = rand();
155 | }
156 |
157 | void set_signal_flag(signal_flag const &flag) {
158 | thinger_message::flag = flag;
159 | }
160 |
161 | void set_identifier(const char* id){
162 | if(identifier==NULL){
163 | identifier = protoson::pool.allocate();
164 | }
165 | (*identifier) = id;
166 | }
167 |
168 | void clean_identifier(){
169 | protoson::pool.destroy(identifier);
170 | identifier = NULL;
171 | }
172 |
173 | void clean_resource(){
174 | protoson::pool.destroy(resource);
175 | resource = NULL;
176 | }
177 |
178 | void clean_data(){
179 | if(data_allocated){
180 | protoson::pool.destroy(data);
181 | }
182 | data = NULL;
183 | }
184 |
185 | public:
186 |
187 | void operator=(const char* str){
188 | ((protoson::pson &) * this) = str;
189 | }
190 |
191 | operator protoson::pson&(){
192 | if(data==NULL){
193 | data = protoson::pool.allocate();
194 | data_allocated = true;
195 | }
196 | return *data;
197 | }
198 |
199 | protoson::pson_array& resources(){
200 | return (protoson::pson_array&)get_resources();
201 | }
202 |
203 | protoson::pson& get_resources(){
204 | if(resource==NULL){
205 | resource = protoson::pool.allocate();
206 | }
207 | return *resource;
208 | }
209 |
210 | protoson::pson& get_identifier(){
211 | if(identifier==NULL){
212 | identifier = protoson::pool.allocate();
213 | }
214 | return *identifier;
215 | }
216 |
217 | protoson::pson& get_data(){
218 | return *this;
219 | }
220 |
221 | void set_data(protoson::pson& pson_data){
222 | if(data==NULL){
223 | data = &pson_data;
224 | data_allocated = false;
225 | }
226 | }
227 |
228 | };
229 | }
230 |
231 | #endif
--------------------------------------------------------------------------------
/src/thinger/core/thinger_resource.hpp:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS SL
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #ifndef THINGER_RESOURCE_HPP
25 | #define THINGER_RESOURCE_HPP
26 |
27 | #include "thinger_map.hpp"
28 | #include "pson.h"
29 | #include "thinger_message.hpp"
30 |
31 | #ifdef __has_include
32 | # if __has_include() || defined(ESP8266)
33 | # undef min
34 | # undef max
35 | # include
36 | # define THINGER_USE_FUNCTIONAL
37 | # endif
38 | #endif
39 |
40 | namespace thinger{
41 |
42 |
43 | class thinger_resource {
44 |
45 | public:
46 | enum io_type {
47 | none = 0,
48 | run = 1,
49 | pson_in = 2,
50 | pson_out = 3,
51 | pson_in_pson_out = 4
52 | };
53 |
54 | enum access_type{
55 | PRIVATE = 0,
56 | PROTECTED = 1,
57 | PUBLIC = 2
58 | };
59 |
60 | static unsigned int& get_streaming_counter(){
61 | // used to know the total number of streams
62 | static unsigned int streaming_count_ = 0;
63 | return streaming_count_;
64 | }
65 |
66 | private:
67 |
68 | // calback for function, input, output, or input/output
69 | #ifdef THINGER_USE_FUNCTIONAL
70 |
71 | struct callback{
72 | std::function run;
73 | std::function pson;
74 | std::function pson_in_pson_out;
75 | };
76 |
77 | #else
78 |
79 | union callback{
80 | void (*run)();
81 | void (*pson)(protoson::pson& io);
82 | void (*pson_in_pson_out)(protoson::pson& in, protoson::pson& out);
83 | };
84 |
85 | #endif
86 |
87 | // used for defining the resource
88 | io_type io_type_;
89 | access_type access_type_;
90 | callback callback_;
91 |
92 | // used for allowing resource streaming (both periodically or by events)
93 | uint16_t stream_id_;
94 |
95 | // used for periodic stream events
96 | unsigned long streaming_freq_;
97 | unsigned long last_streaming_;
98 |
99 | // TODO change to pointer so it is not using more than a pointer size if not used?
100 | thinger_map sub_resources_;
101 |
102 | void enable_streaming(uint16_t stream_id, unsigned long streaming_freq){
103 | stream_id_ = stream_id;
104 | if(streaming_freq_==0 && streaming_freq>0){
105 | get_streaming_counter()++;
106 | }else if(streaming_freq_>0 && streaming_freq==0){
107 | get_streaming_counter()--;
108 | }
109 | streaming_freq_ = streaming_freq;
110 | last_streaming_ = 0;
111 | }
112 |
113 | public:
114 | thinger_resource() : io_type_(none), access_type_(PRIVATE), stream_id_(0), streaming_freq_(0), last_streaming_(0)
115 | {}
116 |
117 | void disable_streaming(){
118 | stream_id_ = 0;
119 | if(streaming_freq_>0){
120 | get_streaming_counter()--;
121 | }
122 | streaming_freq_ = 0;
123 | }
124 |
125 | bool stream_enabled(){
126 | return stream_id_ > 0;
127 | }
128 |
129 | uint32_t get_stream_id(){
130 | return stream_id_;
131 | }
132 |
133 | bool stream_required(unsigned long timestamp){
134 | // sample interval is activated
135 | if(streaming_freq_>0){
136 | if(timestamp-last_streaming_>=streaming_freq_){
137 | last_streaming_ = timestamp;
138 | return true;
139 | }
140 | }
141 | return false;
142 | }
143 |
144 | thinger_resource * find(const char* res)
145 | {
146 | return sub_resources_.find(res);
147 | }
148 |
149 | thinger_resource & operator[](const char* res){
150 | return sub_resources_[res];
151 | }
152 |
153 | thinger_resource & operator()(access_type type){
154 | access_type_ = type;
155 | return *this;
156 | }
157 |
158 | io_type get_io_type(){
159 | return io_type_;
160 | }
161 |
162 | access_type get_access_type(){
163 | return access_type_;
164 | }
165 |
166 | void fill_api(protoson::pson_object& content){
167 | if(io_type_!=none){
168 | content["al"] = access_type_;
169 | content["fn"] = io_type_;
170 | }
171 | thinger_map::entry* current = sub_resources_.begin();
172 | if(current!=NULL){
173 | protoson::pson_object& actions = content["/"];
174 | do{
175 | current->value_.fill_api(actions[current->key_]);
176 | current = current->next_;
177 | }while(current!=NULL);
178 | }
179 | }
180 |
181 | void fill_api_io(protoson::pson_object& content){
182 | if(io_type_ == pson_in){
183 | callback_.pson(content["in"]);
184 | }else if(io_type_ == pson_out){
185 | callback_.pson(content["out"]);
186 | }else if(io_type_ == pson_in_pson_out){
187 | callback_.pson_in_pson_out(content["in"], content["out"]);
188 | }
189 | }
190 |
191 | void fill_output(protoson::pson& content){
192 | if(io_type_ == pson_out){
193 | callback_.pson(content);
194 | }
195 | }
196 |
197 | thinger_map& get_resources(){
198 | return sub_resources_;
199 | }
200 |
201 | public:
202 |
203 | #ifdef THINGER_USE_FUNCTIONAL
204 |
205 | /**
206 | * Establish a function without input or output parameters
207 | */
208 | void operator=(std::function run_function){
209 | io_type_ = run;
210 | callback_.run = run_function;
211 | }
212 |
213 | /**
214 | * Establish a function without input or output parameters
215 | */
216 | void set_function(std::function run_function){
217 | io_type_ = run;
218 | callback_.run = run_function;
219 | }
220 |
221 | /**
222 | * Establish a function with input parameters
223 | */
224 | void operator<<(std::function in_function){
225 | io_type_ = pson_in;
226 | callback_.pson = in_function;
227 | }
228 |
229 | /**
230 | * Establish a function with input parameters
231 | */
232 | void set_input(std::function in_function){
233 | io_type_ = pson_in;
234 | callback_.pson = in_function;
235 | }
236 |
237 | /**
238 | * Establish a function that only generates an output
239 | */
240 | void operator>>(std::function out_function){
241 | io_type_ = pson_out;
242 | callback_.pson = out_function;
243 | }
244 |
245 | /**
246 | * Establish a function that only generates an output
247 | */
248 | void set_output(std::function out_function){
249 | io_type_ = pson_out;
250 | callback_.pson = out_function;
251 | }
252 |
253 | /**
254 | * Establish a function that can receive input parameters and generate an output
255 | */
256 | void operator=(std::function pson_in_pson_out_function){
257 | io_type_ = pson_in_pson_out;
258 | callback_.pson_in_pson_out = pson_in_pson_out_function;
259 | }
260 |
261 | /**
262 | * Establish a function that can receive input parameters and generate an output
263 | */
264 | void set_input_output(std::function pson_in_pson_out_function){
265 | io_type_ = pson_in_pson_out;
266 | callback_.pson_in_pson_out = pson_in_pson_out_function;
267 | }
268 |
269 | #else
270 |
271 | /**
272 | * Establish a function without input or output parameters
273 | */
274 | void operator=(void (*run_function)()){
275 | io_type_ = run;
276 | callback_.run = run_function;
277 | }
278 |
279 | /**
280 | * Establish a function without input or output parameters
281 | */
282 | void set_function(void (*run_function)()){
283 | io_type_ = run;
284 | callback_.run = run_function;
285 | }
286 |
287 | /**
288 | * Establish a function with input parameters
289 | */
290 | void operator<<(void (*in_function)(protoson::pson& in)){
291 | io_type_ = pson_in;
292 | callback_.pson = in_function;
293 | }
294 |
295 | /**
296 | * Establish a function with input parameters
297 | */
298 | void set_input(void (*in_function)(protoson::pson& in)){
299 | io_type_ = pson_in;
300 | callback_.pson = in_function;
301 | }
302 |
303 | /**
304 | * Establish a function that only generates an output
305 | */
306 | void operator>>(void (*out_function)(protoson::pson& out)){
307 | io_type_ = pson_out;
308 | callback_.pson = out_function;
309 | }
310 |
311 | /**
312 | * Establish a function that only generates an output
313 | */
314 | void set_output(void (*out_function)(protoson::pson& out)){
315 | io_type_ = pson_out;
316 | callback_.pson = out_function;
317 | }
318 |
319 | /**
320 | * Establish a function that can receive input parameters and generate an output
321 | */
322 | void operator=(void (*pson_in_pson_out_function)(protoson::pson& in, protoson::pson& out)){
323 | io_type_ = pson_in_pson_out;
324 | callback_.pson_in_pson_out = pson_in_pson_out_function;
325 | }
326 |
327 | /**
328 | * Establish a function that can receive input parameters and generate an output
329 | */
330 | void set_input_output(void (*pson_in_pson_out_function)(protoson::pson& in, protoson::pson& out)){
331 | io_type_ = pson_in_pson_out;
332 | callback_.pson_in_pson_out = pson_in_pson_out_function;
333 | }
334 |
335 | #endif
336 |
337 | /**
338 | * Handle a request and fill a possible response
339 | */
340 | void handle_request(thinger_message& request, thinger_message& response){
341 | switch(request.get_signal_flag()){
342 | // default action over the stream (run the resource)
343 | case thinger_message::NONE:
344 | switch (io_type_){
345 | case pson_in:
346 | callback_.pson(request);
347 | break;
348 | case pson_out:
349 | callback_.pson(response);
350 | break;
351 | case run:
352 | callback_.run();
353 | break;
354 | case pson_in_pson_out:
355 | callback_.pson_in_pson_out(request, response);
356 | break;
357 | case none:
358 | break;
359 | }
360 | break;
361 | // flag for starting a resource stream
362 | case thinger_message::START_STREAM:
363 | enable_streaming(request.get_stream_id(), request.get_data());
364 | break;
365 | // flat for stopping a resource stream
366 | case thinger_message::STOP_STREAM:
367 | disable_streaming();
368 | break;
369 | default:
370 | break;
371 | }
372 | }
373 | };
374 |
375 | }
376 |
377 | #endif
378 |
--------------------------------------------------------------------------------
/src/thinger/thinger.h:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS SL
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #if !DAEMON
25 | #define DEBUG
26 | #endif
27 |
28 | #include "thinger_client.h"
29 |
30 | #if OPEN_SSL
31 | #include "thinger_tls_client.h"
32 | typedef thinger_tls_client thinger_device;
33 | #else
34 | typedef thinger_client thinger_device;
35 | #endif
36 |
--------------------------------------------------------------------------------
/src/thinger/thinger_client.h:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS SL
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #ifndef THINGER_CLIENT_H
25 | #define THINGER_CLIENT_H
26 |
27 | #include
28 | #include
29 | #include
30 | #include
31 | #include
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #include
38 |
39 | #include
40 | #include
41 | #include
42 | #include
43 | #include "core/thinger.h"
44 |
45 | using namespace protoson;
46 |
47 | dynamic_memory_allocator alloc;
48 | memory_allocator& protoson::pool = alloc;
49 |
50 | #ifndef THINGER_SERVER
51 | #define THINGER_SERVER "iot.thinger.io"
52 | #endif
53 |
54 | #ifndef THINGER_PORT
55 | #define THINGER_PORT 25200
56 | #endif
57 |
58 | #ifndef RECONNECTION_TIMEOUT_SECONDS
59 | #define RECONNECTION_TIMEOUT_SECONDS 15
60 | #endif
61 |
62 |
63 | class thinger_client : public thinger::thinger {
64 |
65 | public:
66 |
67 | enum THINGER_STATE{
68 | NETWORK_CONNECTING,
69 | NETWORK_CONNECTED,
70 | NETWORK_CONNECT_ERROR,
71 | SOCKET_CONNECTING,
72 | SOCKET_CONNECTED,
73 | SOCKET_CONNECTION_ERROR,
74 | SOCKET_DISCONNECTED,
75 | SOCKET_TIMEOUT,
76 | SOCKET_ERROR,
77 | THINGER_AUTHENTICATING,
78 | THINGER_AUTHENTICATED,
79 | THINGER_AUTH_FAILED,
80 | THINGER_STOP_REQUEST
81 | };
82 |
83 | thinger_client(const char* user, const char* device, const char* device_credential, const char* thinger_server = THINGER_SERVER) :
84 | sockfd(-1), username_(user), device_id_(device), device_password_(device_credential), thinger_server_(thinger_server),
85 | out_buffer_(NULL), out_size_(0), buffer_size_(0)
86 | {
87 | #if DAEMON
88 | daemonize();
89 | #endif
90 | }
91 |
92 | virtual ~thinger_client()
93 | {}
94 |
95 | protected:
96 | virtual const char* get_server(){
97 | return thinger_server_;
98 | }
99 |
100 | virtual unsigned short get_server_port(){
101 | return THINGER_PORT;
102 | }
103 |
104 | // TODO change this to a monotonic clock implementation. Using c++11?
105 | unsigned long millis() {
106 | struct timeval te;
107 | gettimeofday(&te, NULL);
108 | unsigned long milliseconds = te.tv_sec*1000LL + te.tv_usec/1000;
109 | return milliseconds;
110 | }
111 |
112 | virtual bool connected(){
113 | return true;
114 | }
115 |
116 | virtual void disconnected(){
117 | thinger_state_listener(SOCKET_TIMEOUT);
118 | thinger::disconnected();
119 | if(sockfd>=0){
120 | close(sockfd);
121 | thinger_state_listener(SOCKET_DISCONNECTED);
122 | }
123 | sockfd = -1;
124 | }
125 |
126 | virtual bool read(char* buffer, size_t size){
127 | if(sockfd==-1) return false;
128 | ssize_t read_size = ::read(sockfd, buffer, size);
129 | if(read_size!=size){
130 | disconnected();
131 | }
132 | return read_size == size;
133 | }
134 |
135 | virtual bool write(const char* buffer, size_t size, bool flush=false){
136 | if(size>0){
137 | if(size+out_size_>buffer_size_){
138 | buffer_size_ = out_size_ + size;
139 | out_buffer_ = (uint8_t*) realloc(out_buffer_, buffer_size_);
140 | }
141 | memcpy(&out_buffer_[out_size_], buffer, size);
142 | out_size_ += size;
143 | }
144 | if(flush && out_size_>0){
145 | bool success = to_socket(out_buffer_, out_size_);
146 | out_size_ = 0;
147 | if(!success){
148 | disconnected();
149 | }
150 | return success;
151 | }
152 | return true;
153 | }
154 |
155 | virtual void thinger_state_listener(THINGER_STATE state){
156 | #ifdef DEBUG
157 | switch(state){
158 | case NETWORK_CONNECTING:
159 | std::cout << std::fixed << millis()/1000.0 << " ";
160 | std::cout << "[NETWORK] Starting connection..." << std::endl;
161 | break;
162 | case NETWORK_CONNECTED:
163 | std::cout << std::fixed << millis()/1000.0 << " ";
164 | std::cout << "[NETWORK] Connected!" << std::endl;
165 | break;
166 | case NETWORK_CONNECT_ERROR:
167 | std::cout << std::fixed << millis()/1000.0 << " ";
168 | std::cout << "[NETWORK] Cannot connect!" << std::endl;
169 | break;
170 | case SOCKET_CONNECTING:
171 | std::cout << std::fixed << millis()/1000.0 << " ";
172 | std::cout << "[_SOCKET] Connecting to " << thinger_server_ << ":" << get_server_port() << "..." << std::endl;
173 | std::cout << std::fixed << millis()/1000.0 << " ";
174 | std::cout << "[_SOCKET] Using secure TLS/SSL connection: ";
175 | #if OPEN_SSL
176 | std::cout << "yes" << std::endl;
177 | #else
178 | std::cout << "no" << std::endl;
179 | #endif
180 | break;
181 | case SOCKET_CONNECTED:
182 | std::cout << std::fixed << millis()/1000.0 << " ";
183 | std::cout << "[_SOCKET] Connected!" << std::endl;
184 | break;
185 | case SOCKET_CONNECTION_ERROR:
186 | std::cout << std::fixed << millis()/1000.0 << " ";
187 | std::cout << "[_SOCKET] Error while connecting!" << std::endl;
188 | break;
189 | case SOCKET_DISCONNECTED:
190 | std::cout << std::fixed << millis()/1000.0 << " ";
191 | std::cout << "[_SOCKET] Is now closed!" << std::endl;
192 | break;
193 | case SOCKET_ERROR:
194 | std::cout << std::fixed << millis()/1000.0 << " ";
195 | std::cout << "[_SOCKET] Socket Error!" << std::endl;
196 | break;
197 | case SOCKET_TIMEOUT:
198 | std::cout << std::fixed << millis()/1000.0 << " ";
199 | std::cout << "[_SOCKET] Timeout!" << std::endl;
200 | break;
201 | case THINGER_AUTHENTICATING:
202 | std::cout << std::fixed << millis()/1000.0 << " ";
203 | std::cout << "[THINGER] Authenticating. User: " << username_ << " Device: " << device_id_ << std::endl;
204 | break;
205 | case THINGER_AUTHENTICATED:
206 | std::cout << std::fixed << millis()/1000.0 << " ";
207 | std::cout << "[THINGER] Authenticated" << std::endl;
208 | break;
209 | case THINGER_AUTH_FAILED:
210 | std::cout << std::fixed << millis()/1000.0 << " ";
211 | std::cout << "[THINGER] Auth Failed! Check username, device id, or device credentials." << std::endl;
212 | break;
213 | case THINGER_STOP_REQUEST:
214 | std::cout << std::fixed << millis()/1000.0 << " ";
215 | std::cout << "[THINGER] Client was requested to stop." << std::endl;
216 | break;
217 | }
218 | #endif
219 | if(state_listener_) state_listener_(state);
220 | }
221 |
222 | bool handle_connection() {
223 | while(sockfd<0){
224 | thinger_state_listener(NETWORK_CONNECTING);
225 | if(!connect_client()){
226 | thinger_state_listener(NETWORK_CONNECT_ERROR);
227 | sleep(RECONNECTION_TIMEOUT_SECONDS);
228 | } else {
229 | thinger_state_listener(NETWORK_CONNECTED);
230 | }
231 | }
232 | return true;
233 | }
234 |
235 | bool connect_client(){
236 | // resolve server name
237 | struct hostent* server = gethostbyname(get_server());
238 | if (server == NULL) return false;
239 |
240 | // configure IP address and server port
241 | struct sockaddr_in serv_addr;
242 | bzero((char *) &serv_addr, sizeof(serv_addr));
243 | serv_addr.sin_family = AF_INET;
244 | bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
245 | serv_addr.sin_port = htons(get_server_port());
246 |
247 | // create a new socket
248 | sockfd = socket(AF_INET, SOCK_STREAM, 0);
249 | if (sockfd < 0) return false;
250 |
251 | thinger_state_listener(SOCKET_CONNECTING);
252 |
253 | // try connecting the socket to the server address
254 | if (::connect(sockfd,(struct sockaddr *) &serv_addr, sizeof(serv_addr)) == 0 && connected()){
255 |
256 | // set tcp no delay
257 | int flag = 1;
258 | int result = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int));
259 | if (result < 0){
260 | thinger_state_listener(SOCKET_CONNECTION_ERROR);
261 | }
262 |
263 | thinger_state_listener(SOCKET_CONNECTED);
264 | thinger_state_listener(THINGER_AUTHENTICATING);
265 | bool auth = thinger::thinger::connect(username_, device_id_, device_password_);
266 | if(!auth){
267 | thinger_state_listener(THINGER_AUTH_FAILED);
268 | disconnected();
269 | } else {
270 | thinger_state_listener(THINGER_AUTHENTICATED);
271 | }
272 | return auth;
273 | }
274 |
275 | // call disconnected to cleanup socket resources
276 | disconnected();
277 | return false;
278 | }
279 |
280 | public:
281 |
282 | /**
283 | * Static method for allowing run the program as a daemon. Must be called in the main.
284 | */
285 | static void daemonize(){
286 | /* Our process ID and Session ID */
287 | pid_t pid, sid;
288 |
289 | /* Fork off the parent process */
290 | pid = fork();
291 | if (pid < 0) {
292 | exit(EXIT_FAILURE);
293 | }
294 | /* If we got a good PID, then
295 | we can exit the parent process. */
296 | if (pid > 0) {
297 | exit(EXIT_SUCCESS);
298 | }
299 |
300 | /* Change the file mode mask */
301 | umask(0);
302 |
303 | /* Open any logs here */
304 |
305 | /* Create a new SID for the child process */
306 | sid = setsid();
307 | if (sid < 0) {
308 | /* Log the failure */
309 | exit(EXIT_FAILURE);
310 | }
311 |
312 | /* Change the current working directory */
313 | if ((chdir("/")) < 0) {
314 | /* Log the failure */
315 | exit(EXIT_FAILURE);
316 | }
317 |
318 | /* Close out the standard file descriptors */
319 | close(STDIN_FILENO);
320 | close(STDOUT_FILENO);
321 | close(STDERR_FILENO);
322 | }
323 |
324 | void start(){
325 | while(true){
326 | handle();
327 | }
328 | }
329 |
330 | void handle(){
331 | if(handle_connection()){
332 | fd_set rfds;
333 | struct timeval tv;
334 |
335 | FD_ZERO(&rfds);
336 | FD_SET(sockfd, &rfds);
337 |
338 | tv.tv_sec = 1;
339 | tv.tv_usec = 0;
340 |
341 | int retval = select(sockfd+1, &rfds, NULL, NULL, &tv);
342 | if (retval == -1){
343 | disconnected();
344 | } else {
345 | bool data_available = retval>0 && FD_ISSET(sockfd, &rfds);
346 | thinger::thinger::handle(millis(), data_available);
347 | }
348 | }
349 | }
350 |
351 | void set_state_listener(std::function state_listener){
352 | state_listener_ = state_listener;
353 | }
354 |
355 | protected:
356 |
357 | virtual bool to_socket(const uint8_t* buffer, size_t size){
358 | if(sockfd==-1) return false;
359 | ssize_t written = ::write(sockfd, buffer, size);
360 | return size == written;
361 | }
362 |
363 | int sockfd;
364 | const char* thinger_server_;
365 | const char* username_;
366 | const char* device_id_;
367 | const char* device_password_;
368 | std::function state_listener_;
369 | uint8_t* out_buffer_;
370 | size_t out_size_;
371 | size_t buffer_size_;
372 |
373 | };
374 |
375 | #endif
376 |
--------------------------------------------------------------------------------
/src/thinger/thinger_tls_client.h:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 | //
3 | // Copyright (c) 2017 THINK BIG LABS SL
4 | // Author: alvarolb@gmail.com (Alvaro Luis Bustamante)
5 | //
6 | // Permission is hereby granted, free of charge, to any person obtaining a copy
7 | // of this software and associated documentation files (the "Software"), to deal
8 | // in the Software without restriction, including without limitation the rights
9 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | // copies of the Software, and to permit persons to whom the Software is
11 | // furnished to do so, subject to the following conditions:
12 | //
13 | // The above copyright notice and this permission notice shall be included in
14 | // all copies or substantial portions of the Software.
15 | //
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | // THE SOFTWARE.
23 |
24 | #ifndef THINGER_TLS_CLIENT_H
25 | #define THINGER_TLS_CLIENT_H
26 |
27 | #include "thinger_client.h"
28 | #include
29 |
30 | #define THINGER_SSL_PORT 25202
31 |
32 | class thinger_tls_client : public thinger_client {
33 |
34 | public:
35 | thinger_tls_client(const char* user, const char* device, const char* device_credential, const char* thinger_server = THINGER_SERVER) :
36 | thinger_client(user, device, device_credential, thinger_server), sslCtx(NULL), ssl(NULL), thinger_server(thinger_server)
37 | {
38 | SSL_library_init();
39 | }
40 |
41 | virtual ~thinger_tls_client()
42 | {
43 | EVP_cleanup();
44 | }
45 |
46 | protected:
47 | virtual unsigned short get_server_port(){
48 | return THINGER_SSL_PORT;
49 | }
50 |
51 | virtual bool connected(){
52 | // create SSL context
53 | sslCtx = SSL_CTX_new(SSLv23_method());
54 | if(sslCtx==NULL) return false;
55 |
56 | //SSL_CTX_set_verify(sslCtx, SSL_VERIFY_PEER, NULL);
57 | //SSL_CTX_set_verify_depth(sslCtx, 4);
58 |
59 | // force TLS 1.1 or TLS 1.2
60 | const long flags = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
61 | SSL_CTX_set_options(sslCtx, flags);
62 |
63 | // create SSL instance
64 | ssl = SSL_new (sslCtx);
65 | if(ssl==NULL) return false;
66 |
67 | // set preferred ciphers
68 | const char* const PREFERRED_CIPHERS = "HIGH:!aNULL:!kRSA:!PSK:!SRP!MD5:!RC4";
69 | SSL_set_cipher_list(ssl, PREFERRED_CIPHERS);
70 |
71 | // required for SNI resolving
72 | if (!SSL_set_tlsext_host_name(ssl, thinger_server)) return false;
73 |
74 | // set socket file descriptor
75 | if (!SSL_set_fd(ssl, sockfd)) return false;
76 |
77 | // initiate SSL handshake
78 | if (!SSL_connect (ssl)) return false;
79 |
80 | return true;
81 | }
82 |
83 | virtual void disconnected(){
84 | // release SSL
85 | if(ssl!=NULL){
86 | SSL_free(ssl);
87 | ssl = NULL;
88 | }
89 |
90 | // release SSL context
91 | if(sslCtx!=NULL){
92 | SSL_CTX_free(sslCtx);
93 | sslCtx = NULL;
94 | }
95 |
96 | // release socket
97 | thinger_client::disconnected();
98 | }
99 |
100 | virtual bool read(char* buffer, size_t size){
101 | if(ssl==NULL) return false;
102 | int read_size = SSL_read(ssl, buffer, size);
103 | if(read_size!=size){
104 | disconnected();
105 | }
106 | return read_size == size;
107 | }
108 |
109 | protected:
110 |
111 | virtual bool to_socket(const uint8_t* buffer, size_t size){
112 | if(ssl==NULL) return false;
113 | int write_size = SSL_write(ssl, buffer, size);
114 | return write_size == size;
115 | }
116 |
117 | private:
118 | SSL_CTX *sslCtx;
119 | SSL *ssl;
120 | const char* thinger_server;
121 | };
122 |
123 | #endif
124 |
--------------------------------------------------------------------------------