├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── examples └── SimpleExample │ └── SimpleExample.ino ├── keywords.txt ├── library.json ├── library.properties └── src ├── AWSClient.cpp ├── AWSClient.h ├── AWSClient2.cpp ├── AWSClient2.h ├── AWSClient4.cpp ├── AWSClient4.h ├── AWSFoundationalTypes.cpp ├── AWSFoundationalTypes.h ├── AmazonDynamoDBClient.cpp ├── AmazonDynamoDBClient.h ├── AmazonIOTClient.cpp ├── AmazonIOTClient.h ├── AmazonKinesisClient.cpp ├── AmazonKinesisClient.h ├── AmazonS3Client.cpp ├── AmazonS3Client.h ├── AmazonSNSClient.cpp ├── AmazonSNSClient.h ├── DeviceIndependentInterfaces.cpp ├── DeviceIndependentInterfaces.h ├── ESPAWSImplementations.cpp ├── ESPAWSImplementations.h ├── Utils.cpp ├── Utils.h ├── jsmn.c ├── jsmn.h ├── sha256.cpp └── sha256.h /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | 4 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 5 | 6 | 1. Definitions. 7 | 8 | "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. 9 | 10 | "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. 11 | 12 | "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. 13 | 14 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. 15 | 16 | "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. 17 | 18 | "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. 19 | 20 | "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). 21 | 22 | "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. 23 | 24 | "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." 25 | 26 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 27 | 28 | 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 29 | 30 | 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 31 | 32 | 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: 33 | 34 | 1. You must give any other recipients of the Work or Derivative Works a copy of this License; and 35 | 2. You must cause any modified files to carry prominent notices stating that You changed the files; and 36 | 3. You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and 37 | 4. If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. 38 | 39 | You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 40 | 41 | 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 42 | 43 | 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 44 | 45 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 46 | 47 | 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 48 | 49 | 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. 50 | 51 | END OF TERMS AND CONDITIONS 52 | 53 | 54 | Note: Other license terms may apply to certain, identified software files contained within or distributed with the accompanying software if such terms are included in the directory containing the accompanying software. Such other license terms will then apply in lieu of the terms of the software license above. 55 | 56 | JSMN code subject to the MIT License: 57 | 58 | Copyright (c) 2010 Serge A. Zaitsev 59 | 60 | Permission is hereby granted, free of charge, to any person obtaining a copy 61 | of this software and associated documentation files (the "Software"), to deal 62 | in the Software without restriction, including without limitation the rights 63 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 64 | copies of the Software, and to permit persons to whom the Software is 65 | furnished to do so, subject to the following conditions: 66 | 67 | The above copyright notice and this permission notice shall be included in 68 | all copies or substantial portions of the Software. 69 | 70 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 71 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 72 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 73 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 74 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 75 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 76 | THE SOFTWARE. 77 | 78 | Portable C++ Hashing Library (SHA256 files only) subject to this zlib License: 79 | All source code published on http://create.stephan-brumme.com and its sub-pages is licensed similar to the zlib license: 80 | 81 | This software is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software. 82 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 83 | 84 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. 85 | 2. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 86 | 3. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 87 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Version 1 of Experimental AWS SDK for Arduino, and Samples 2 | 3 | Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.  4 | 5 | This product includes software developed by Amazon Technologies, Inc (http://www.amazon.com/).  6 | 7 | Licensed under the Apache License Version 2.0 8 | 9 | See the License for the specific language governing permissions and limitations under the License. 10 | 11 | ********************** 12 | THIRD PARTY COMPONENTS 13 | ********************** 14 | This software includes third party software subject to the following copyrights: 15 | 16 | - jsmn, minimalistic JSON parser in C - Copyright (c) 2010 Serge A. Zaitsev. Includes minor modifications. 17 | - Portable C++ Hashing Library (SHA256 files only) - Copyright (c) 2014 Stephan Brumme. Includes minor modifications. 18 | 19 | 20 | The licenses for these third party components are included in LICENSE 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Experimental AWS SDK for Arduino 2 | 3 | An experimental SDK for contacting AWS Services on Arduino-compatible devices. Currently it supports Amazon DynamoDB, Amazon Kinesis, Amazon IOT and Amazon SNS. More services coming soon. 4 | 5 | All Amazon DynamoDB operations are supported. The code for creating, serializing, and deserializing Kinesis input and output objects is included, but the devices that the experimental SDK has been tested on do not have readily available HTTPS support. This code has been included so it can be used by those who want to do further experimenting with Kinesis and HTTPS. 6 | 7 | The SDK is extensible to non-Arduino-compatible devices by implementing the interfaces in `DeviceIndependentInterfaces.cpp`/`.h`. See `SparkAWSImplementations.cpp`/`.h` and `EdisonAWSImplementations.cpp`/`.h` for examples of this. 8 | 9 | ## Folder Structure 10 | 11 | * /src contains all the common source code 12 | 13 | Happy experimenting! 14 | 15 | ## Getting Started with the Samples 16 | 17 | Trying the samples is a good way to get started with using the SDK. 18 | 19 | Getting the samples working has the following steps: setting up the DynamoDB table, importing the SDK and copying over the sample, creating the `keys.h` and `keys.cpp` files, and setting up the hardware. These steps are outlined for the samples for both the Spark core and Intel Galileo, so be sure you are following only the directions corresponding to the device and sample you are using. 20 | 21 | If you are using a device other than Spark or Galileo, you may want to read through these steps anyway before implementing the interfaces in `DeviceIndependentInterfaces.cpp`/`.h` for your device. 22 | 23 | ### Step 1: Setting up the DynamoDB Table 24 | 25 | For either device you will need to set up a DynamoDB table with the same name, hash key, and range key as in the sample you are using. These values are defined as constants in the sample, i.e. `HASH_KEY_NAME` and `TABLE_NAME`. 26 | 27 | You can follow the steps below to get the tables set up with the right values, chosing the right set of instructions based on which sample you are using. 28 | 29 | #### Table used by SparkGetItemSample and GalileoSample: 30 | 31 | * Log into the [AWS Console](http://console.aws.amazon.com/) and navigate to DynamoDB. 32 | * Click on the "Create Table" button. 33 | * Enter "AWSArduinoSDKDemo" as the *Table Name*, "DemoName" as the *Hash Attribute Name*, and "id" as the *Range Attribute Name*. Be sure to mark *DemoName* as *String* and *id* as *Number*. 34 | * For this example, we won't add indexes, so just press continue on the *Add Indexes (optional)* page. 35 | * Just one *Read Capacity Unit* and one *Write Capacity Unit* will be enough for this demo. Press continue with these values. 36 | * Uncheck *Use Basic Alarms* and continue again. 37 | * Check that the information is correct on the *Review* page, then create the table! 38 | * After the table has finished creating, double click on it to explore it. Here you should press the *New Item* button and create an item with the following values: 39 | * "DemoName": *String*, "Colors" 40 | * "id": *Number*, "1" 41 | * "R", *Number* "255" 42 | * "G", *Number* "255" 43 | * "B", *Number* "255" 44 | 45 | #### Table used by SparkPutItemSample: 46 | 47 | * Log into the [AWS Console](http://console.aws.amazon.com/) and navigate to DynamoDB. 48 | * Click on the "Create Table" button. 49 | * Enter "AWSArduinoSDKTests" as the *Table Name*, "device" as the *Hash Attribute Name*, and "Time" as the *Range Attribute Name*. Be sure to mark both as *String*. 50 | * For this example, we won't add indexes, so just press continue on the *Add Indexes (optional)* page. 51 | * Just one *Read Capacity Unit* and one *Write Capacity Unit* will be enough for this demo. Press continue with these values. 52 | * Uncheck *Use Basic Alarms* and continue again. 53 | * Check that the information is correct on the *Review* page, then create the table! 54 | 55 | ### Step 2: Importing SDK and Copying Sample 56 | 57 | This step is different for the Spark Core and Intel Galileo. 58 | 59 | #### Connected Maraca Sample (Edison/SparkCore/MediaTek) 60 | 61 | follow the step by step guide: http://bit.ly/aws-iot-hackseries 62 | 63 | #### Intel Galileo/Edison Sample 64 | 65 | With Galileo or Edison, you should be using the Arduino IDE from Intel as it includes Galileo and Edison libraries. [Link to Intel-Arduino IDE](https://communities.intel.com/docs/DOC-22226). 66 | 67 | Make an `AWSArduinoSDK` directory in the Arduino IDE's `libraries` directory (e.g. `~/Arduino/libraries/AWSArduinoSDK`). 68 | 69 | Move all of the files from the SDK's `src/common` directory into the `AWSArduinoSDK` directory. Import the library. 70 | 71 | Create a new sketch with the Arduino IDE and copy and paste the sample code into it. 72 | 73 | 74 | #### Spark IO Core Sample 75 | 76 | This assumes you already have your Spark set up and are able to program it with Spark Build. If you do not, head over to [Spark's website](http://docs.spark.io/). 77 | 78 | Open up the Spark Build web page and create a new app. Name it whatever you would like. 79 | 80 | Copy the contents of the sample you are using into the `.ino` file of your new app. 81 | 82 | Next you need to import the SDK. Because the Spark Build IDE isn't local to your machine, you can't just `cp` the files over. Instead use the "+" tab in the top right corner of the Spark Build page to create a new file for each `.cpp`/`.h` file in the `src/` directory, except `GalileoAWSImplementations` and `AmazonKinesisClient`. Then copy and paste the contents of each file. 83 | 84 | 85 | 86 | ### Step 3: Creating `keys.h` and `keys.cpp` 87 | 88 | You will need to create and add `keys.h` and `keys.cpp` into the `AWSArduinoSDK` directory you made. These files define the `awsKeyID` and `awsSecKey` values used by the sketch, the files may be structured as following: 89 | 90 | ``` 91 | // keys.h 92 | #ifndef KEYS_H_ 93 | #define KEYS_H_ 94 | 95 | extern const char* awsKeyID; // Declare these variables to 96 | extern const char* awsSecKey; // be accessible by the sketch 97 | 98 | #endif 99 | ``` 100 | 101 | ``` 102 | // keys.cpp 103 | #include "keys.h" 104 | 105 | const char* awsKeyID = "YOUR AWS KEY ID HERE"; 106 | const char* awsSecKey = "YOUR AWS SECRET KEY HERE"; 107 | ``` 108 | 109 | Add these files as you added the source files. That is: with Spark, use the "+" button, and with Galileo, move them to the `AWSArduinoSDK` directory under Arduino's `libraries` directory. 110 | 111 | ### Step 4: Setting up Hardware 112 | 113 | To use the samples you must have the correct breadboard wiring. The samples use different wiring, but use the following rules to create them: 114 | 115 | Buttons: Connect buttons by wiring one leg of the button to the 3v or 5v pin. Connect one leg to ground with a resistor, and also wire it to the pin that is reading the value. 116 | 117 | RGB LED: For the multicolored LED, wire the cathode (the longest leg) to ground, then connect the remaining 3 legs to the corresponding input pins with a resistor. 118 | 119 | #### Spark Core 120 | 121 | Both spark samples use just one button connected to the D2 pin. 122 | 123 | #### Intel Galileo 124 | 125 | This sample uses five buttons and a RGB LED. 126 | 127 | The RGB LED has the red leg connected to pin 6, the green leg connected to pin 9, and the blue leg connected to pin 10. 128 | 129 | Buttons: 130 | 131 | * Button for performing PutItem should be connected to pin 2 132 | * Button for performing GetItem should be connected to pin 4 133 | * Button for changing red color value should be connected to pin 7 134 | * Button for changing green color value should be connected to pin 8 135 | * Button for changing blue color value should be connected to pin 12 136 | 137 | For Galileo/Edison, after the wiring is finished, you should be able to connect it to power, connect it to your computer via usb, and compile and upload the code with the Arduino IDE. Be sure to refer to the comments in the samples for help. 138 | 139 | For Spark, after the wiring is finished, you should be able to connect it to your computer via USB, and *Flash* the code. Be sure to refer to the comments in the samples for help. 140 | 141 | #### ESP8266 and ESP32 142 | 143 | You can use these libraries with the [Arduino ESP8266](https://github.com/esp8266/arduino) or ESP32: or ARDUINO_ARCH_SAM or ARDUINO_ARCH_SAMD. 144 | 145 | ``` 146 | #include 147 | #include //ESP32 case 148 | #include 149 | #include "EspAWSImplementations.h" 150 | 151 | EspHttpClient httpClient; 152 | EspDateTimeProvider dateTimeProvider; 153 | 154 | AmazonIOTClient iotClient; 155 | ActionError actionError; 156 | 157 | void setup() { 158 | Serial.begin(115200); 159 | delay(10); 160 | 161 | // Connect to WAP 162 | Serial.print("Connecting to "); 163 | Serial.println(ssid); 164 | WiFi.begin(ssid, password); 165 | 166 | while (WiFi.status() != WL_CONNECTED) { 167 | delay(500); 168 | Serial.print("."); 169 | } 170 | Serial.println(""); 171 | Serial.println("WiFi connected"); 172 | Serial.println("IP address: "); 173 | Serial.println(WiFi.localIP()); 174 | 175 | iotClient.setAWSRegion("eu-west-1"); 176 | iotClient.setAWSEndpoint("amazonaws.com"); 177 | iotClient.setAWSDomain("foobar.iot.eu-west-1.amazonaws.com"); 178 | iotClient.setAWSPath("/things/example-1/shadow"); 179 | iotClient.setAWSKeyID("ID"); 180 | iotClient.setAWSSecretKey("SECRET"); 181 | iotClient.setHttpClient(&httpClient); 182 | iotClient.setDateTimeProvider(&dateTimeProvider); 183 | } 184 | 185 | void loop(){ 186 | char* shadow = "{\"state\":{\"reported\": {\"foobar\": \"bar\"}}}"; 187 | 188 | char* result = iotClient.update_shadow(shadow, actionError); 189 | Serial.print(result); 190 | 191 | delay(60000); 192 | } 193 | 194 | ``` 195 | -------------------------------------------------------------------------------- /examples/SimpleExample/SimpleExample.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ESPAWSImplementations.h" 3 | 4 | EspHttpClient httpClient; 5 | EspDateTimeProvider dateTimeProvider; 6 | 7 | AmazonIOTClient iotClient; 8 | ActionError actionError; 9 | 10 | const char *ssid="MySSID"; 11 | const char *password="MyPASS"; 12 | 13 | void setup() { 14 | Serial.begin(115200); 15 | delay(10); 16 | 17 | // Connect to WAP 18 | Serial.print("Connecting to "); 19 | Serial.println(ssid); 20 | WiFi.begin(ssid, password); 21 | 22 | while (WiFi.status() != WL_CONNECTED) { 23 | delay(500); 24 | Serial.print("."); 25 | } 26 | Serial.println(""); 27 | Serial.println("WiFi connected"); 28 | Serial.println("IP address: "); 29 | Serial.println(WiFi.localIP()); 30 | 31 | iotClient.setAWSRegion("eu-west-1"); 32 | iotClient.setAWSEndpoint("amazonaws.com"); 33 | iotClient.setAWSDomain("foobar.iot.eu-west-1.amazonaws.com"); 34 | iotClient.setAWSPath("/things/example-1/shadow"); 35 | iotClient.setAWSKeyID("ID"); 36 | iotClient.setAWSSecretKey("SECRET"); 37 | iotClient.setHttpClient(&httpClient); 38 | iotClient.setDateTimeProvider(&dateTimeProvider); 39 | } 40 | 41 | void loop(){ 42 | 43 | const char* shadow = "{\"state\":{\"reported\": {\"foobar\": \"bar\"}}}"; 44 | const char* result = iotClient.update_shadow(shadow, actionError); 45 | 46 | Serial.print(result); 47 | 48 | delay(60000); 49 | } 50 | 51 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | Amazon KEYWORD1 2 | AWS KEYWORD2 3 | IoT KEYWORD3 4 | ESP8266 KEYWORD4 5 | ESP32 KEYWORD5 6 | SAM KEYWORD6 7 | SAMD KEYWORD7 8 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AWS-SDK-ESP by Schm1tz1 modified by Burt-Silverman", 3 | "keywords": "AWS, SDK, ESP8266, ESP32, IoT, SAM, SAMD", 4 | "description": "SDK for accessing AWS IoT services with many devices.", 5 | "repository": 6 | { 7 | "type": "git", 8 | "url": "https://github.com/Schm1tz1/aws-sdk-arduino-esp8266" 9 | }, 10 | "frameworks": "arduino", 11 | "platforms": "*" 12 | } 13 | 14 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=AWS-SDK-ESP8266 2 | version=0.9.1-beta 3 | author=Roman Schmitz , Burt Silverman 4 | maintainer=Roman Schmitz 5 | sentence=An SDK for AWS using ESP8266, ESP32, SAM, or SAMD SOCs. 6 | paragraph=This library is based on the code by awslabs, svdgraaf and fuzzyhandle. It enables you to easily use AWS IoT Services from an ESP8266 or ESP32 or SAM or SAMD device with Arduino. There are some great tutorials and projects on the web; also check YoutTube and my GitHub-Pages for examples. As I will be using this system quite often, I will try to keep it up-to-date and contribute to the arduino libraries. Feel free to contribute to this code - fork, add your stuff, change things and create pull requests. 7 | category=Communication 8 | url=https://github.com/Schm1tz1/aws-sdk-arduino-esp8266 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/AWSClient.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AWSClient.cpp 3 | * 4 | * See AWSClient.h for description. 5 | * 6 | * Created on: Jul 8, 2014 7 | * Author: hoffmaj 8 | */ 9 | 10 | #include "AWSClient.h" 11 | #include "Utils.h" 12 | #include "DeviceIndependentInterfaces.h" 13 | #include "AWSFoundationalTypes.h" 14 | #include "sha256.h" 15 | #include 16 | #include 17 | #include 18 | 19 | /* Constants string, formats, and lengths. */ 20 | static const char* CANONICAL_FORM_POST_LINE = "POST\n/\n\n"; 21 | static const int CANONICAL_FORM_POST_LINE_LEN = 8; 22 | static const char* HTTPS_REQUEST_POST_LINE = 23 | "POST https://%s.%s.%s/ HTTP/1.1\n"; 24 | static const int HTTPS_REQUEST_POST_LINE_LEN = 28; 25 | static const char* HTTP_REQUEST_POST_LINE = "POST http://%s.%s.%s/ HTTP/1.1\n"; 26 | static const int HTTP_REQUEST_POST_LINE_LEN = 27; 27 | static const char* CURL_START = "curl --silent -X POST "; 28 | static const int CURL_START_LEN = 22; 29 | static const char* HTTPS_CURL_END = "https://%s.%s.%s"; 30 | static const int HTTPS_CURL_END_LEN = 10; 31 | static const char* HTTP_CURL_END = "http://%s.%s.%s"; 32 | static const int HTTP_CURL_END_LEN = 9; 33 | static const char* TO_SIGN_TEMPLATE = 34 | "AWS4-HMAC-SHA256\n%sT%sZ\n%s/%s/%s/aws4_request\n%s"; 35 | static const int TO_SIGN_TEMPLATE_LEN = 36; 36 | static const char* CONTENT_LENGTH_HEADER = "content-length:%d"; 37 | static const int CONTENT_LENGTH_HEADER_LEN = 15; 38 | static const char* HOST_HEADER = "host:%s.%s.%s"; 39 | static const int HOST_HEADER_LEN = 7; 40 | static const char* CONNECTION_HEADER = "Connection:close"; 41 | static const int CONNECTION_HEADER_LEN = 16; 42 | static const char* CONTENT_TYPE_HEADER = "content-type:%s"; 43 | static const int CONTENT_TYPE_HEADER_LEN = 13; 44 | static const char* X_AMZ_DATE_HEADER = "x-amz-date:%sT%sZ"; 45 | static const int X_AMZ_DATE_HEADER_LEN = 13; 46 | static const char* X_AMZ_TARGET_HEADER = "x-amz-target:%s"; 47 | static const int X_AMZ_TARGET_HEADER_LEN = 13; 48 | static const char* AUTHORIZATION_HEADER = 49 | "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request, SignedHeaders=%s, Signature=%s"; 50 | static const int AUTHORIZATION_HEADER_LEN = 87; 51 | static const char* SIGNED_HEADERS = 52 | "content-length;content-type;host;x-amz-date;x-amz-target"; 53 | static const int SIGNED_HEADERS_LEN = 56; 54 | 55 | AWSClient::AWSClient() { 56 | /* Null until set in init method. */ 57 | awsRegion = 0; 58 | awsEndpoint = 0; 59 | awsSecKey = 0; 60 | awsKeyID = 0; 61 | httpClient = 0; 62 | dateTimeProvider = 0; 63 | } 64 | 65 | void AWSClient::setAWSRegion(const char * awsRegion) { 66 | int len = strlen(awsRegion) + 1; 67 | this->awsRegion = new char[len](); 68 | strcpy(this->awsRegion, awsRegion); 69 | } 70 | void AWSClient::setAWSEndpoint(const char * awsEndpoint) { 71 | int len = strlen(awsEndpoint) + 1; 72 | this->awsEndpoint = new char[len](); 73 | strcpy(this->awsEndpoint, awsEndpoint); 74 | } 75 | void AWSClient::setAWSSecretKey(const char * awsSecKey) { 76 | int len = strlen(awsSecKey) + 1; 77 | this->awsSecKey = new char[len](); 78 | strcpy(this->awsSecKey, awsSecKey); 79 | } 80 | void AWSClient::setAWSKeyID(const char * awsKeyID) { 81 | int len = strlen(awsKeyID) + 1; 82 | this->awsKeyID = new char[len](); 83 | strcpy(this->awsKeyID, awsKeyID); 84 | } 85 | void AWSClient::setHttpClient(IHttpClient* httpClient) { 86 | this->httpClient = httpClient; 87 | } 88 | void AWSClient::setDateTimeProvider(IDateTimeProvider* dateTimeProvider) { 89 | this->dateTimeProvider = dateTimeProvider; 90 | } 91 | 92 | AWSClient::~AWSClient() { 93 | if (awsRegion != 0) 94 | delete[] awsRegion; 95 | if (awsEndpoint != 0) 96 | delete[] awsEndpoint; 97 | if (awsSecKey != 0) 98 | delete[] awsSecKey; 99 | if (awsKeyID != 0) 100 | delete[] awsKeyID; 101 | } 102 | 103 | void AWSClient::initSignedHeaders() { 104 | /* For each of the formats for unsigned headers, determine the size of the 105 | * formatted string, allocate that much space in the next available element 106 | * in the headers array, create the string, and add it's length to the 107 | * headerLens array. */ 108 | 109 | int contentLen = payload.length(); 110 | int len = CONTENT_LENGTH_HEADER_LEN + digitCount(contentLen); 111 | headers[headersCreated] = new char[len + 1](); 112 | sprintf(headers[headersCreated], CONTENT_LENGTH_HEADER, contentLen); 113 | headerLens[headersCreated++] = len; 114 | 115 | len = CONTENT_TYPE_HEADER_LEN + strlen(contentType); 116 | headers[headersCreated] = new char[len + 1](); 117 | sprintf(headers[headersCreated], CONTENT_TYPE_HEADER, contentType); 118 | headerLens[headersCreated++] = len; 119 | 120 | len = HOST_HEADER_LEN + strlen(awsService) + strlen(awsRegion) 121 | + strlen(awsEndpoint); 122 | headers[headersCreated] = new char[len + 1](); 123 | sprintf(headers[headersCreated], HOST_HEADER, awsService, awsRegion, 124 | awsEndpoint); 125 | headerLens[headersCreated++] = len; 126 | 127 | len = X_AMZ_DATE_HEADER_LEN + AWS_DATE_LEN + AWS_TIME_LEN; 128 | headers[headersCreated] = new char[len + 1](); 129 | sprintf(headers[headersCreated], X_AMZ_DATE_HEADER, awsDate, awsTime); 130 | headerLens[headersCreated++] = len; 131 | 132 | len = X_AMZ_TARGET_HEADER_LEN + strlen(target); 133 | headers[headersCreated] = new char[len + 1](); 134 | sprintf(headers[headersCreated], X_AMZ_TARGET_HEADER, target); 135 | headerLens[headersCreated++] = len; 136 | } 137 | 138 | char* AWSClient::createStringToSign(void) { 139 | SHA256* sha256 = new SHA256(); 140 | char* hashed; 141 | /* Calculate length of canonicalForm string. */ 142 | int canonicalFormLen = CANONICAL_FORM_POST_LINE_LEN; 143 | for (int i = 0; i < headersCreated; i++) { 144 | /* +1 for newlines */ 145 | canonicalFormLen += *(headerLens + i) + 1; 146 | } 147 | /* +2 for newlines. */ 148 | canonicalFormLen += SIGNED_HEADERS_LEN + HASH_HEX_LEN + 2; 149 | 150 | char* canonicalForm = new char[canonicalFormLen + 1](); 151 | 152 | /* Write the cannonicalForm string. */ 153 | int canonicalFormWritten = 0; 154 | canonicalFormWritten += strlen( 155 | strcpy(canonicalForm + canonicalFormWritten, 156 | CANONICAL_FORM_POST_LINE)); 157 | for (int i = 0; i < headersCreated; i++) { 158 | canonicalFormWritten += sprintf(canonicalForm + canonicalFormWritten, 159 | "%s\n", *(headers + i)); 160 | } 161 | canonicalFormWritten += sprintf(canonicalForm + canonicalFormWritten, 162 | "\n%s\n", SIGNED_HEADERS); 163 | hashed = (*sha256)(payload.getCStr(), payload.length()); 164 | strcpy(canonicalForm + canonicalFormWritten, hashed); 165 | delete[] hashed; 166 | canonicalFormWritten += HASH_HEX_LEN; 167 | 168 | /* Hash the canonicalForm string. */ 169 | hashed = (*sha256)(canonicalForm, canonicalFormWritten); 170 | delete sha256; 171 | 172 | delete[] canonicalForm; 173 | 174 | /* Determine the size to the string to sign. */ 175 | int toSignLen = TO_SIGN_TEMPLATE_LEN + 2 * AWS_DATE_LEN + AWS_TIME_LEN 176 | + strlen(awsRegion) + strlen(awsService) + HASH_HEX_LEN; 177 | 178 | /* Create and return the string to sign. */ 179 | char* toSign = new char[toSignLen + 1](); 180 | sprintf(toSign, TO_SIGN_TEMPLATE, awsDate, awsTime, awsDate, awsRegion, 181 | awsService, hashed); 182 | delete[] hashed; 183 | return toSign; 184 | 185 | } 186 | char* AWSClient::createSignature(const char* toSign) { 187 | 188 | /* Allocate memory for the signature */ 189 | char* signature = new char[HASH_HEX_LEN + 1](); 190 | 191 | /* Create the signature key */ 192 | /* + 4 for "AWS4" */ 193 | int keyLen = strlen(awsSecKey) + 4; 194 | char* key = new char[keyLen + 1](); 195 | sprintf(key, "AWS4%s", awsSecKey); 196 | 197 | /* repeatedly apply hmac with the appropriate values. See 198 | * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html 199 | * for algorithm. */ 200 | char* k1 = hmacSha256(key, keyLen, awsDate, strlen(awsDate)); 201 | delete[] key; 202 | char* k2 = hmacSha256(k1, SHA256_DEC_HASH_LEN, awsRegion, 203 | strlen(awsRegion)); 204 | delete[] k1; 205 | char* k3 = hmacSha256(k2, SHA256_DEC_HASH_LEN, awsService, 206 | strlen(awsService)); 207 | delete[] k2; 208 | char* k4 = hmacSha256(k3, SHA256_DEC_HASH_LEN, "aws4_request", 12); 209 | delete[] k3; 210 | char* k5 = hmacSha256(k4, SHA256_DEC_HASH_LEN, toSign, strlen(toSign)); 211 | delete[] k4; 212 | 213 | /* Convert the chars in hash to hex for signature. */ 214 | for (int i = 0; i < SHA256_DEC_HASH_LEN; ++i) { 215 | sprintf(signature + 2 * i, "%02lx", 0xff & (unsigned long) k5[i]); 216 | } 217 | delete[] k5; 218 | return signature; 219 | } 220 | 221 | void AWSClient::initUnsignedHeaders(const char* signature) { 222 | int len = AUTHORIZATION_HEADER_LEN + strlen(awsKeyID) + AWS_DATE_LEN 223 | + strlen(awsRegion) + strlen(awsService) + SIGNED_HEADERS_LEN 224 | + HASH_HEX_LEN; 225 | headers[headersCreated] = new char[len + 1](); 226 | sprintf(headers[headersCreated], AUTHORIZATION_HEADER, awsKeyID, awsDate, 227 | awsRegion, awsService, SIGNED_HEADERS, signature); 228 | headerLens[headersCreated++] = len; 229 | 230 | len = CONNECTION_HEADER_LEN; 231 | headers[headersCreated] = new char[len + 1](); 232 | strcpy(headers[headersCreated], CONNECTION_HEADER); 233 | headerLens[headersCreated++] = len; 234 | } 235 | 236 | void AWSClient::createRequestInit(MinimalString &reqPayload) { 237 | //initialize object-scoped variables 238 | const char* dateTime = dateTimeProvider->getDateTime(); 239 | sprintf(awsDate, "%.8s", dateTime); 240 | sprintf(awsTime, "%.6s", dateTime + 8); 241 | payload = reqPayload; 242 | headersCreated = 0; 243 | 244 | //Create signature and headers 245 | initSignedHeaders(); 246 | char* toSign = createStringToSign(); 247 | char* signature = createSignature(toSign); 248 | delete[] toSign; 249 | initUnsignedHeaders(signature); 250 | delete[] signature; 251 | } 252 | 253 | void AWSClient::createRequestCleanup() { 254 | /* Free each header */ 255 | for (int i = 0; i < headersCreated; i++) { 256 | delete[] headers[i]; 257 | } 258 | } 259 | 260 | char* AWSClient::headersToRequest() { 261 | /* Determine whether to use https or http postLine values. */ 262 | int postLineLen = 263 | httpS ? HTTPS_REQUEST_POST_LINE_LEN : HTTP_REQUEST_POST_LINE_LEN; 264 | const char* postLine = 265 | httpS ? HTTPS_REQUEST_POST_LINE : HTTP_REQUEST_POST_LINE; 266 | 267 | /* Calculate length of httpRequest string. */ 268 | int httpRequestLen = postLineLen + strlen(awsService) + strlen(awsRegion) 269 | + strlen(awsEndpoint); 270 | for (int i = 0; i < headersCreated; i++) { 271 | /* +1 for newline. */ 272 | httpRequestLen += *(headerLens + i) + 1; 273 | } 274 | /* +1 for newline. */ 275 | httpRequestLen += payload.length() + 1; 276 | 277 | /* Create and write to the httpRequest string. */ 278 | char* httpRequest = new char[httpRequestLen + 1](); 279 | int httpRequestWritten = 0; 280 | httpRequestWritten += sprintf(httpRequest + httpRequestWritten, postLine, 281 | awsService, awsRegion, awsEndpoint); 282 | for (int i = 0; i < headersCreated; i++) { 283 | httpRequestWritten += sprintf(httpRequest + httpRequestWritten, "%s\n", 284 | *(headers + i)); 285 | } 286 | httpRequestWritten += sprintf(httpRequest + httpRequestWritten, "\n%s", 287 | payload.getCStr()); 288 | 289 | return httpRequest; 290 | } 291 | 292 | char* AWSClient::headersToCurlRequest() { 293 | /* Add backslashes before quotes in json string */ 294 | char* escapedPayload = escapeQuotes(payload.getCStr()); 295 | 296 | /* Determine whether to use https or http curlEnd values. */ 297 | int curlEndLen = httpS ? HTTPS_CURL_END_LEN : HTTP_CURL_END_LEN; 298 | const char* curlEnd = httpS ? HTTPS_CURL_END : HTTP_CURL_END; 299 | 300 | /* Calculate length of curl command. +6 for "-d", 2 spaces, 2 quotes. */ 301 | int httpRequestLen = CURL_START_LEN + curlEndLen + strlen(awsService) 302 | + strlen(awsRegion) + strlen(awsEndpoint) + strlen(escapedPayload) 303 | + 6; 304 | for (int i = 0; i < headersCreated; i++) { 305 | /* +6 for "-H", 2 spaces, 2 quotes */ 306 | httpRequestLen += *(headerLens + i) + 6; 307 | } 308 | 309 | /* Create and write to the httpRequest string. */ 310 | char* httpRequest = new char[httpRequestLen + 1](); 311 | int httpRequestWritten = 0; 312 | httpRequestWritten += strlen(strcpy(httpRequest, CURL_START)); 313 | for (int i = 0; i < headersCreated; i++) { 314 | httpRequestWritten += sprintf(httpRequest + httpRequestWritten, 315 | "-H \"%s\" ", *(headers + i)); 316 | } 317 | httpRequestWritten += sprintf(httpRequest + httpRequestWritten, 318 | "-d \"%s\" ", escapedPayload); 319 | delete[] escapedPayload; 320 | httpRequestWritten += sprintf(httpRequest + httpRequestWritten, curlEnd, 321 | awsService, awsRegion, awsEndpoint); 322 | 323 | return httpRequest; 324 | } 325 | 326 | char* AWSClient::createRequest(MinimalString &reqPayload) { 327 | /* Check that all values have been initialized. */ 328 | if (awsRegion == 0 || awsEndpoint == 0 || awsSecKey == 0 || awsKeyID == 0 329 | || httpClient == 0 || dateTimeProvider == 0) 330 | return 0; 331 | createRequestInit(reqPayload); 332 | char* request = headersToRequest(); 333 | createRequestCleanup(); 334 | return request; 335 | } 336 | 337 | const char* AWSClient::createCurlRequest(MinimalString &reqPayload) { 338 | /* Check that all values have been initialized. */ 339 | if (awsRegion == 0 || awsEndpoint == 0 || awsSecKey == 0 || awsKeyID == 0 340 | || httpClient == 0 || dateTimeProvider == 0) 341 | return 0; 342 | createRequestInit(reqPayload); 343 | char* request = headersToCurlRequest(); 344 | createRequestCleanup(); 345 | return request; 346 | } 347 | 348 | const char* AWSClient::sendData(const char* data) { 349 | char* server = new char[strlen(awsService) + strlen(awsRegion) 350 | + strlen(awsEndpoint) + 4](); 351 | sprintf(server, "%s.%s.%s", awsService, awsRegion, awsEndpoint); 352 | int port = httpS ? 443 : 80; 353 | const char* response = httpClient->send(data, server, port); 354 | delete[] server; 355 | return response; 356 | } 357 | -------------------------------------------------------------------------------- /src/AWSClient.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AWSClient.h 3 | * 4 | * Base classes for services to create an HTTP request. 5 | * 6 | * Created on: Jul 8, 2014 7 | * Author: hoffmaj 8 | */ 9 | 10 | #ifndef AWSCLIENT_H_ 11 | #define AWSCLIENT_H_ 12 | 13 | #include "DeviceIndependentInterfaces.h" 14 | #include "AWSFoundationalTypes.h" 15 | 16 | /* Total number of headers. */ 17 | static const int HEADER_COUNT = 7; 18 | /* Size of the awsDate string. */ 19 | static const int AWS_DATE_LEN = 8; 20 | /* Size of the awsTime string. */ 21 | static const int AWS_TIME_LEN = 6; 22 | /* Size of sha hashes and signatures in hexidecimal. */ 23 | static const int HASH_HEX_LEN = 64; 24 | 25 | /* Base class for an AWS Service Client. Creates http and https request in raw 26 | * http format or as a curl command. */ 27 | class AWSClient { 28 | /* Name of region, eg. "us-east-1" in "kinesis.us-east-1.amazonaws.com". */ 29 | char* awsRegion; 30 | /* Endpoint, eg. "amazonaws.com" in "kinesis.us-east-1.amazonaws.com". */ 31 | char* awsEndpoint; 32 | /* The user's AWS Secret Key for accessing the AWS Resource. */ 33 | char* awsSecKey; 34 | /* The user's AWS Access Key ID for accessing the AWS Resource. */ 35 | char* awsKeyID; 36 | /* GMT date in yyyyMMdd format. */ 37 | char awsDate[AWS_DATE_LEN + 1]; 38 | /* GMT time in HHmmss format. */ 39 | char awsTime[AWS_TIME_LEN + 1]; 40 | /* Number of headers created. */ 41 | int headersCreated; 42 | /* Array of the created http headers. */ 43 | char* headers[HEADER_COUNT]; 44 | /* Array of string lengths of the headers in the "headers" array. */ 45 | int headerLens[HEADER_COUNT]; 46 | /* The payload of the httprequest to be created */ 47 | MinimalString payload; 48 | 49 | /* Add the headers that will be signed to the headers array. Called before 50 | * createStringToSign. */ 51 | void initSignedHeaders(); 52 | /* Create the canonical request and the string to sign as described. Return 53 | * value must be deleted by caller. */ 54 | char* createStringToSign(void); 55 | /* Given the string to sign, create the signature (a 64-char cstring). 56 | * Return value must be deleted by caller. */ 57 | char* createSignature(const char* toSign); 58 | /* Add the headers that will not be signed to the headers array. Called 59 | * after createSignature. */ 60 | void initUnsignedHeaders(const char* signature); 61 | /* Contains all of the work to be done before headersToRequest or 62 | * headersToCurlRequest are called. Takes the payload to be sent and the 63 | * GMT date in yyyyMMddHHmmss format. */ 64 | void createRequestInit(MinimalString &reqPayload); 65 | /* Clean up after headersToRequest or headersToCurlRequest are called. */ 66 | void createRequestCleanup(); 67 | /* Using the headers array, create a raw http request. */ 68 | char* headersToRequest(void); 69 | /* Using the headers array, create a http request in curl command 70 | * format. */ 71 | char* headersToCurlRequest(void); 72 | 73 | protected: 74 | /* Used to keep track of time. */ 75 | IDateTimeProvider* dateTimeProvider; 76 | /* Used to send http to the server. */ 77 | IHttpClient* httpClient; 78 | /* true if https is to be used, false if http is to be used. */ 79 | bool httpS; 80 | /* Name of service, eg. "kinesis" in "kinesis.us-east-1.amazonaws.com". */ 81 | const char* awsService; 82 | /* Content type of payload, eg. "application/x-amz-json-1.1". */ 83 | const char* contentType; 84 | /* The request's target, i.e. "Kinesis_20131202.PutRecord". */ 85 | const char* target; 86 | /* Creates a raw http request, given the payload and current GMT date in 87 | * yyyyMMddHHmmss format. Should be exposed to user by extending class. 88 | * Returns 0 if client is unititialized. */ 89 | char* createRequest(MinimalString &payload); 90 | /* Creates a http request in curl format, given the payload and current GMT 91 | * date in yyyyMMddHHmmss format. Should be exposed to user by extending 92 | * class. Returns 0 if client is unititialized. */ 93 | const char* createCurlRequest(MinimalString &payload); 94 | /* Sends http data. Returns http response, or null on error. */ 95 | const char* sendData(const char* data); 96 | /* Empty constructor. Must also be initialized with init. */ 97 | AWSClient(); 98 | 99 | public: 100 | /* Setters for values used by createRequest and createCurlRequest. Must 101 | * be set or create[Curl]Request will return null. */ 102 | void setAWSRegion(const char * awsRegion); 103 | void setAWSEndpoint(const char * awsEndpoint); 104 | void setAWSSecretKey(const char * awsSecKey); 105 | void setAWSKeyID(const char * awsKeyID); 106 | void setHttpClient(IHttpClient* httpClient); 107 | void setDateTimeProvider(IDateTimeProvider* dateTimeProvider); 108 | ~AWSClient(void); 109 | }; 110 | 111 | #endif /* AWSCLIENT_H_ */ 112 | -------------------------------------------------------------------------------- /src/AWSClient2.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AWSClient.cpp 3 | * 4 | * See AWSClient.h for description. 5 | * 6 | */ 7 | 8 | #include "AWSClient2.h" 9 | #include "Utils.h" 10 | #include "DeviceIndependentInterfaces.h" 11 | #include "AWSFoundationalTypes.h" 12 | #include "sha256.h" 13 | #include 14 | #include 15 | #include 16 | 17 | /* Constants string, formats, and lengths. */ 18 | static const char* CANONICAL_FORM_POST_LINE = "POST\n/\n\n"; 19 | static const int CANONICAL_FORM_POST_LINE_LEN = 8; 20 | static const char* HTTPS_REQUEST_POST_LINE = 21 | "POST https://%s.%s.%s/ HTTP/1.1\n"; 22 | static const int HTTPS_REQUEST_POST_LINE_LEN = 28; 23 | static const char* HTTP_REQUEST_POST_LINE = "POST http://%s.%s.%s/ HTTP/1.1\n"; 24 | static const int HTTP_REQUEST_POST_LINE_LEN = 27; 25 | static const char* TO_SIGN_TEMPLATE = 26 | "AWS4-HMAC-SHA256\n%sT%sZ\n%s/%s/%s/aws4_request\n%s"; 27 | static const int TO_SIGN_TEMPLATE_LEN = 36; 28 | static const char* CONTENT_LENGTH_HEADER = "content-length:%d"; 29 | static const int CONTENT_LENGTH_HEADER_LEN = 15; 30 | static const char* HOST_HEADER = "host:%s.%s.%s"; 31 | static const int HOST_HEADER_LEN = 7; 32 | static const int CONNECTION_HEADER_LEN = 16; 33 | static const char* CONTENT_TYPE_HEADER = "content-type:%s"; 34 | static const int CONTENT_TYPE_HEADER_LEN = 13; 35 | static const char* X_AMZ_DATE_HEADER = "x-amz-date:%sT%sZ"; 36 | static const int X_AMZ_DATE_HEADER_LEN = 13; 37 | static const char* AUTHORIZATION_HEADER = 38 | "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request, SignedHeaders=%s, Signature=%s"; 39 | static const int AUTHORIZATION_HEADER_LEN = 87; 40 | static const char* SIGNED_HEADERS = 41 | "content-length;content-type;host;x-amz-date"; 42 | static const int SIGNED_HEADERS_LEN = 43; 43 | 44 | AWSClient2::AWSClient2() { 45 | /* Null until set in init method. */ 46 | awsRegion = 0; 47 | awsEndpoint = 0; 48 | awsSecKey = 0; 49 | awsKeyID = 0; 50 | httpClient = 0; 51 | dateTimeProvider = 0; 52 | } 53 | 54 | void AWSClient2::setAWSRegion(const char * awsRegion) { 55 | int len = strlen(awsRegion) + 1; 56 | this->awsRegion = new char[len](); 57 | strcpy(this->awsRegion, awsRegion); 58 | } 59 | void AWSClient2::setAWSEndpoint(const char * awsEndpoint) { 60 | int len = strlen(awsEndpoint) + 1; 61 | this->awsEndpoint = new char[len](); 62 | strcpy(this->awsEndpoint, awsEndpoint); 63 | } 64 | void AWSClient2::setAWSSecretKey(const char * awsSecKey) { 65 | int len = strlen(awsSecKey) + 1; 66 | this->awsSecKey = new char[len](); 67 | strcpy(this->awsSecKey, awsSecKey); 68 | } 69 | void AWSClient2::setAWSKeyID(const char * awsKeyID) { 70 | int len = strlen(awsKeyID) + 1; 71 | this->awsKeyID = new char[len](); 72 | strcpy(this->awsKeyID, awsKeyID); 73 | } 74 | void AWSClient2::setHttpClient(IHttpClient* httpClient) { 75 | this->httpClient = httpClient; 76 | } 77 | void AWSClient2::setDateTimeProvider(IDateTimeProvider* dateTimeProvider) { 78 | this->dateTimeProvider = dateTimeProvider; 79 | } 80 | 81 | AWSClient2::~AWSClient2() { 82 | if (awsRegion != 0) 83 | delete[] awsRegion; 84 | if (awsEndpoint != 0) 85 | delete[] awsEndpoint; 86 | if (awsSecKey != 0) 87 | delete[] awsSecKey; 88 | if (awsKeyID != 0) 89 | delete[] awsKeyID; 90 | } 91 | 92 | void AWSClient2::initSignedHeaders() { 93 | /* For each of the formats for unsigned headers, determine the size of the 94 | * formatted string, allocate that much space in the next available element 95 | * in the headers array, create the string, and add it's length to the 96 | * headerLens array. */ 97 | 98 | int contentLen = payload.length(); 99 | int len = CONTENT_LENGTH_HEADER_LEN + digitCount(contentLen); 100 | headers[headersCreated] = new char[len + 1](); 101 | sprintf(headers[headersCreated], CONTENT_LENGTH_HEADER, contentLen); 102 | headerLens[headersCreated++] = len; 103 | 104 | len = CONTENT_TYPE_HEADER_LEN + strlen(contentType); 105 | headers[headersCreated] = new char[len + 1](); 106 | sprintf(headers[headersCreated], CONTENT_TYPE_HEADER, contentType); 107 | headerLens[headersCreated++] = len; 108 | 109 | len = HOST_HEADER_LEN + strlen(awsService) + strlen(awsRegion) 110 | + strlen(awsEndpoint); 111 | headers[headersCreated] = new char[len + 1](); 112 | sprintf(headers[headersCreated], HOST_HEADER, awsService, awsRegion, 113 | awsEndpoint); 114 | headerLens[headersCreated++] = len; 115 | 116 | len = X_AMZ_DATE_HEADER_LEN + AWS_DATE_LEN2 + AWS_TIME_LEN2; 117 | headers[headersCreated] = new char[len + 1](); 118 | sprintf(headers[headersCreated], X_AMZ_DATE_HEADER, awsDate, awsTime); 119 | headerLens[headersCreated++] = len; 120 | } 121 | 122 | char* AWSClient2::createStringToSign(void) { 123 | SHA256* sha256 = new SHA256(); 124 | char* hashed; 125 | /* Calculate length of canonicalForm string. */ 126 | int canonicalFormLen = CANONICAL_FORM_POST_LINE_LEN; 127 | for (int i = 0; i < headersCreated; i++) { 128 | /* +1 for newlines */ 129 | canonicalFormLen += *(headerLens + i) + 1; 130 | } 131 | /* +2 for newlines. */ 132 | canonicalFormLen += SIGNED_HEADERS_LEN + HASH_HEX_LEN2 + 2; 133 | 134 | char* canonicalForm = new char[canonicalFormLen + 1](); 135 | 136 | /* Write the cannonicalForm string. */ 137 | int canonicalFormWritten = 0; 138 | canonicalFormWritten += strlen( 139 | strcpy(canonicalForm + canonicalFormWritten, 140 | CANONICAL_FORM_POST_LINE)); 141 | for (int i = 0; i < headersCreated; i++) { 142 | canonicalFormWritten += sprintf(canonicalForm + canonicalFormWritten, 143 | "%s\n", *(headers + i)); 144 | } 145 | canonicalFormWritten += sprintf(canonicalForm + canonicalFormWritten, 146 | "\n%s\n", SIGNED_HEADERS); 147 | hashed = (*sha256)(payload.getCStr(), payload.length()); 148 | strcpy(canonicalForm + canonicalFormWritten, hashed); 149 | delete[] hashed; 150 | canonicalFormWritten += HASH_HEX_LEN2; 151 | 152 | /* Hash the canonicalForm string. */ 153 | hashed = (*sha256)(canonicalForm, canonicalFormWritten); 154 | delete sha256; 155 | 156 | delete[] canonicalForm; 157 | 158 | /* Determine the size to the string to sign. */ 159 | int toSignLen = TO_SIGN_TEMPLATE_LEN + 2 * AWS_DATE_LEN2 + AWS_TIME_LEN2 160 | + strlen(awsRegion) + strlen(awsService) + HASH_HEX_LEN2; 161 | 162 | /* Create and return the string to sign. */ 163 | char* toSign = new char[toSignLen + 1](); 164 | sprintf(toSign, TO_SIGN_TEMPLATE, awsDate, awsTime, awsDate, awsRegion, 165 | awsService, hashed); 166 | delete[] hashed; 167 | return toSign; 168 | 169 | } 170 | char* AWSClient2::createSignature(const char* toSign) { 171 | 172 | /* Allocate memory for the signature */ 173 | char* signature = new char[HASH_HEX_LEN2 + 1](); 174 | 175 | /* Create the signature key */ 176 | /* + 4 for "AWS4" */ 177 | int keyLen = strlen(awsSecKey) + 4; 178 | char* key = new char[keyLen + 1](); 179 | sprintf(key, "AWS4%s", awsSecKey); 180 | 181 | /* repeatedly apply hmac with the appropriate values. See 182 | * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html 183 | * for algorithm. */ 184 | char* k1 = hmacSha256(key, keyLen, awsDate, strlen(awsDate)); 185 | delete[] key; 186 | char* k2 = hmacSha256(k1, SHA256_DEC_HASH_LEN, awsRegion, 187 | strlen(awsRegion)); 188 | delete[] k1; 189 | char* k3 = hmacSha256(k2, SHA256_DEC_HASH_LEN, awsService, 190 | strlen(awsService)); 191 | delete[] k2; 192 | char* k4 = hmacSha256(k3, SHA256_DEC_HASH_LEN, "aws4_request", 12); 193 | delete[] k3; 194 | char* k5 = hmacSha256(k4, SHA256_DEC_HASH_LEN, toSign, strlen(toSign)); 195 | delete[] k4; 196 | 197 | /* Convert the chars in hash to hex for signature. */ 198 | for (int i = 0; i < SHA256_DEC_HASH_LEN; ++i) { 199 | sprintf(signature + 2 * i, "%02lx", 0xff & (unsigned long) k5[i]); 200 | } 201 | delete[] k5; 202 | return signature; 203 | } 204 | 205 | void AWSClient2::initUnsignedHeaders(const char* signature) { 206 | int len = AUTHORIZATION_HEADER_LEN + strlen(awsKeyID) + AWS_DATE_LEN2 207 | + strlen(awsRegion) + strlen(awsService) + SIGNED_HEADERS_LEN 208 | + HASH_HEX_LEN2; 209 | headers[headersCreated] = new char[len + 1](); 210 | sprintf(headers[headersCreated], AUTHORIZATION_HEADER, awsKeyID, awsDate, 211 | awsRegion, awsService, SIGNED_HEADERS, signature); 212 | headerLens[headersCreated++] = len; 213 | /* 214 | len = CONNECTION_HEADER_LEN; 215 | headers[headersCreated] = new char[len + 1](); 216 | strcpy(headers[headersCreated], CONNECTION_HEADER); 217 | headerLens[headersCreated++] = len; 218 | */ 219 | } 220 | 221 | void AWSClient2::createRequestInit(MinimalString &reqPayload) { 222 | //initialize object-scoped variables 223 | const char* dateTime = dateTimeProvider->getDateTime(); 224 | sprintf(awsDate, "%.8s", dateTime); 225 | sprintf(awsTime, "%.6s", dateTime + 8); 226 | payload = reqPayload; 227 | headersCreated = 0; 228 | 229 | //Create signature and headers 230 | initSignedHeaders(); 231 | char* toSign = createStringToSign(); 232 | char* signature = createSignature(toSign); 233 | delete[] toSign; 234 | initUnsignedHeaders(signature); 235 | delete[] signature; 236 | } 237 | 238 | void AWSClient2::createRequestCleanup() { 239 | /* Free each header */ 240 | for (int i = 0; i < headersCreated; i++) { 241 | delete[] headers[i]; 242 | } 243 | } 244 | 245 | char* AWSClient2::headersToRequest() { 246 | /* Determine whether to use https or http postLine values. */ 247 | int postLineLen = 248 | httpS ? HTTPS_REQUEST_POST_LINE_LEN : HTTP_REQUEST_POST_LINE_LEN; 249 | const char* postLine = 250 | httpS ? HTTPS_REQUEST_POST_LINE : HTTP_REQUEST_POST_LINE; 251 | 252 | /* Calculate length of httpRequest string. */ 253 | int httpRequestLen = postLineLen + strlen(awsService) + strlen(awsRegion) 254 | + strlen(awsEndpoint); 255 | for (int i = 0; i < headersCreated; i++) { 256 | /* +1 for newline. */ 257 | httpRequestLen += *(headerLens + i) + 1; 258 | } 259 | /* +1 for newline. */ 260 | httpRequestLen += payload.length() + 1; 261 | 262 | /* Create and write to the httpRequest string. */ 263 | char* httpRequest = new char[httpRequestLen + 1](); 264 | int httpRequestWritten = 0; 265 | httpRequestWritten += sprintf(httpRequest + httpRequestWritten, postLine, 266 | awsService, awsRegion, awsEndpoint); 267 | for (int i = 0; i < headersCreated; i++) { 268 | httpRequestWritten += sprintf(httpRequest + httpRequestWritten, "%s\n", 269 | *(headers + i)); 270 | } 271 | httpRequestWritten += sprintf(httpRequest + httpRequestWritten, "\n%s", 272 | payload.getCStr()); 273 | 274 | return httpRequest; 275 | } 276 | 277 | char* AWSClient2::createRequest(MinimalString &reqPayload) { 278 | /* Check that all values have been initialized. */ 279 | if (awsRegion == 0 || awsEndpoint == 0 || awsSecKey == 0 || awsKeyID == 0 280 | || httpClient == 0 || dateTimeProvider == 0) 281 | return 0; 282 | 283 | createRequestInit(reqPayload); 284 | char* request = headersToRequest(); 285 | createRequestCleanup(); 286 | return request; 287 | } 288 | 289 | const char* AWSClient2::sendData(const char* data) { 290 | char* server = new char[strlen(awsService) + strlen(awsRegion) 291 | + strlen(awsEndpoint) + 4](); 292 | sprintf(server, "%s.%s.%s", awsService, awsRegion, awsEndpoint); 293 | int port = httpS ? 443 : 80; 294 | const char* response = httpClient->send(data, server, port); 295 | delete[] server; 296 | return response; 297 | } 298 | -------------------------------------------------------------------------------- /src/AWSClient2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AWSClient2.h 3 | * 4 | * Base classes for services to create an HTTP request. 5 | * 6 | */ 7 | 8 | #ifndef AWSCLIENT2_H_ 9 | #define AWSCLIENT2_H_ 10 | 11 | #include "DeviceIndependentInterfaces.h" 12 | #include "AWSFoundationalTypes.h" 13 | 14 | /* Total number of headers. */ 15 | static const int HEADER_COUNT2 = 7; 16 | /* Size of the awsDate string. */ 17 | static const int AWS_DATE_LEN2 = 8; 18 | /* Size of the awsTime string. */ 19 | static const int AWS_TIME_LEN2 = 6; 20 | /* Size of sha hashes and signatures in hexidecimal. */ 21 | static const int HASH_HEX_LEN2 = 64; 22 | 23 | /* Base class for an AWS Service Client. Creates http and https request in raw 24 | * http format or as a curl command. */ 25 | class AWSClient2 { 26 | /* Name of region, eg. "us-east-1" in "kinesis.us-east-1.amazonaws.com". */ 27 | char* awsRegion; 28 | /* Endpoint, eg. "amazonaws.com" in "kinesis.us-east-1.amazonaws.com". */ 29 | char* awsEndpoint; 30 | /* The user's AWS Secret Key for accessing the AWS Resource. */ 31 | char* awsSecKey; 32 | /* The user's AWS Access Key ID for accessing the AWS Resource. */ 33 | char* awsKeyID; 34 | /* GMT date in yyyyMMdd format. */ 35 | char awsDate[AWS_DATE_LEN2 + 1]; 36 | /* GMT time in HHmmss format. */ 37 | char awsTime[AWS_TIME_LEN2 + 1]; 38 | /* Number of headers created. */ 39 | int headersCreated; 40 | /* Array of the created http headers. */ 41 | char* headers[HEADER_COUNT2]; 42 | /* Array of string lengths of the headers in the "headers" array. */ 43 | int headerLens[HEADER_COUNT2]; 44 | /* The payload of the httprequest to be created */ 45 | MinimalString payload; 46 | 47 | /* Add the headers that will be signed to the headers array. Called before 48 | * createStringToSign. */ 49 | void initSignedHeaders(); 50 | /* Create the canonical request and the string to sign as described. Return 51 | * value must be deleted by caller. */ 52 | char* createStringToSign(void); 53 | /* Given the string to sign, create the signature (a 64-char cstring). 54 | * Return value must be deleted by caller. */ 55 | char* createSignature(const char* toSign); 56 | /* Add the headers that will not be signed to the headers array. Called 57 | * after createSignature. */ 58 | void initUnsignedHeaders(const char* signature); 59 | /* Contains all of the work to be done before headersToRequest or 60 | * headersToCurlRequest are called. Takes the payload to be sent and the 61 | * GMT date in yyyyMMddHHmmss format. */ 62 | void createRequestInit(MinimalString &reqPayload); 63 | /* Clean up after headersToRequest or headersToCurlRequest are called. */ 64 | void createRequestCleanup(); 65 | /* Using the headers array, create a raw http request. */ 66 | char* headersToRequest(void); 67 | 68 | protected: 69 | /* Used to keep track of time. */ 70 | IDateTimeProvider* dateTimeProvider; 71 | /* Used to send http to the server. */ 72 | IHttpClient* httpClient; 73 | /* true if https is to be used, false if http is to be used. */ 74 | bool httpS; 75 | /* Name of service, eg. "kinesis" in "kinesis.us-east-1.amazonaws.com". */ 76 | const char* awsService; 77 | /* Content type of payload, eg. "application/x-amz-json-1.1". */ 78 | const char* contentType; 79 | /* Creates a raw http request, given the payload and current GMT date in 80 | * yyyyMMddHHmmss format. Should be exposed to user by extending class. 81 | * Returns 0 if client is unititialized. */ 82 | char* createRequest(MinimalString &payload); 83 | /* Sends http data. Returns http response, or null on error. */ 84 | const char* sendData(const char* data); 85 | /* Empty constructor. Must also be initialized with init. */ 86 | AWSClient2(); 87 | 88 | public: 89 | /* Setters for values used by createRequest and createCurlRequest. Must 90 | * be set or create[Curl]Request will return null. */ 91 | void setAWSRegion(const char * awsRegion); 92 | void setAWSEndpoint(const char * awsEndpoint); 93 | void setAWSSecretKey(const char * awsSecKey); 94 | void setAWSKeyID(const char * awsKeyID); 95 | void setHttpClient(IHttpClient* httpClient); 96 | void setDateTimeProvider(IDateTimeProvider* dateTimeProvider); 97 | ~AWSClient2(void); 98 | }; 99 | 100 | #endif /* AWSCLIENT2_H_ */ 101 | -------------------------------------------------------------------------------- /src/AWSClient4.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AWSClient4.cpp 3 | * 4 | * See AWSClient4.h for description. 5 | * See http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html 6 | * 7 | */ 8 | 9 | #include "AWSClient4.h" 10 | #include "Utils.h" 11 | #include "DeviceIndependentInterfaces.h" 12 | #include "AWSFoundationalTypes.h" 13 | #include "sha256.h" 14 | #include 15 | #include 16 | #include 17 | 18 | AWSClient4::AWSClient4() 19 | { 20 | /* Null until set in init method or derived class function. */ 21 | awsRegion = 0; 22 | awsEndpoint = 0; 23 | awsSecKey = 0; 24 | awsKeyID = 0; 25 | httpClient = 0; 26 | dateTimeProvider = 0; 27 | method = 0; 28 | uri = 0; 29 | payload = ""; 30 | } 31 | 32 | void 33 | AWSClient4::setAWSRegion(const char * awsRegion) 34 | { 35 | int len = strlen(awsRegion) + 1; 36 | this->awsRegion = new char[len](); 37 | strcpy(this->awsRegion, awsRegion); 38 | } 39 | 40 | void 41 | AWSClient4::setAWSEndpoint(const char * awsEndpoint) 42 | { 43 | int len = strlen(awsEndpoint) + 1; 44 | this->awsEndpoint = new char[len](); 45 | strcpy(this->awsEndpoint, awsEndpoint); 46 | } 47 | 48 | void 49 | AWSClient4::setAWSDomain(const char * awsDomain) 50 | { 51 | int len = strlen(awsDomain) + 1; 52 | this->awsDomain = new char[len](); 53 | strcpy(this->awsDomain, awsDomain); 54 | } 55 | 56 | void 57 | AWSClient4::setAWSPath(const char * awsPath) 58 | { 59 | int len = strlen(awsPath) + 1; 60 | this->awsPath = new char[len](); 61 | strcpy(this->awsPath, awsPath); 62 | } 63 | 64 | void 65 | AWSClient4::setAWSSecretKey(const char * awsSecKey) 66 | { 67 | int len = strlen(awsSecKey) + 1; 68 | this->awsSecKey = new char[len](); 69 | strcpy(this->awsSecKey, awsSecKey); 70 | } 71 | 72 | void 73 | AWSClient4::setAWSKeyID(const char * awsKeyID) 74 | { 75 | int len = strlen(awsKeyID) + 1; 76 | this->awsKeyID = new char[len](); 77 | strcpy(this->awsKeyID, awsKeyID); 78 | } 79 | 80 | void 81 | AWSClient4::setHttpClient(IHttpClient* httpClient) 82 | { 83 | this->httpClient = httpClient; 84 | } 85 | 86 | void 87 | AWSClient4::setDateTimeProvider(IDateTimeProvider* dateTimeProvider) 88 | { 89 | this->dateTimeProvider = dateTimeProvider; 90 | } 91 | 92 | AWSClient4::~AWSClient4() 93 | { 94 | if (awsRegion != 0) 95 | delete[] awsRegion; 96 | if (awsEndpoint != 0) 97 | delete[] awsEndpoint; 98 | if (awsSecKey != 0) 99 | delete[] awsSecKey; 100 | if (awsKeyID != 0) 101 | delete[] awsKeyID; 102 | if (awsDomain != 0) 103 | delete[] awsDomain; 104 | if (awsPath != 0) 105 | delete[] awsPath; 106 | } 107 | 108 | 109 | void 110 | AWSClient4::createCanonicalHeaders() 111 | { 112 | // headers, alphabetically sorted, lowercase, eg: key:value 113 | // content-length:l 114 | // content-type:x 115 | // host:host 116 | // x-amz-content-sha256:hash 117 | // x-amz-date:date 118 | canonical_headers[0] = 0; 119 | sprintf(canonical_headers, "%scontent-length:%d\n", canonical_headers, 120 | strlen(payload.getCStr())); 121 | sprintf(canonical_headers, "%scontent-type:%s\n", canonical_headers, 122 | contentType); 123 | sprintf(canonical_headers, "%shost:%s\n", canonical_headers, awsDomain); 124 | // sprintf(canonical_headers, "%srange:bytes=0-9\n", canonical_headers); // s3 125 | sprintf(canonical_headers, "%sx-amz-content-sha256:%s\n", 126 | canonical_headers, payloadHash); 127 | sprintf(canonical_headers, "%sx-amz-date:%sT%sZ\n\n", canonical_headers, 128 | awsDate, awsTime); 129 | return; 130 | } 131 | 132 | void 133 | AWSClient4::createRequestHeaders(char* signature) 134 | { 135 | headers[0] = 0; 136 | sprintf(headers, "%sContent-Type: %s\r\n", headers, contentType); 137 | sprintf(headers, "%sContent-Length: %d\r\n", headers, 138 | strlen(payload.getCStr())); 139 | sprintf(headers, "%sHost: %s\r\n", headers, awsDomain); 140 | sprintf(headers, "%sX-Amz-Content-Sha256: %s\r\n", headers, payloadHash); 141 | sprintf(headers, "%sX-Amz-Date: %sT%sZ\r\n", headers, awsDate, awsTime); 142 | sprintf(headers, "%sAuthorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/" 143 | "aws4_request, SignedHeaders=%s, Signature=%s\r\n", headers, 144 | awsKeyID, awsDate, awsRegion, awsService, signedHeaders, signature); 145 | return; 146 | } 147 | 148 | void 149 | AWSClient4::createStringToSign(char* canonical_request) 150 | { 151 | SHA256* sha256 = new SHA256(); 152 | char* hashed = (*sha256)(canonical_request, strlen(canonical_request)); 153 | delete sha256; 154 | 155 | string_to_sign[0] = 0; 156 | sprintf(string_to_sign, "%sAWS4-HMAC-SHA256\n", string_to_sign); 157 | sprintf(string_to_sign, "%s%sT%sZ\n", string_to_sign, awsDate, awsTime); 158 | sprintf(string_to_sign, "%s%s/%s/%s/aws4_request\n", string_to_sign, 159 | awsDate, awsRegion, awsService); 160 | 161 | sprintf(string_to_sign, "%s%s", string_to_sign, hashed); 162 | 163 | return; 164 | } 165 | 166 | void 167 | AWSClient4::createCanonicalRequest() 168 | { 169 | canonical_request[0] = 0; 170 | sprintf(canonical_request, "%s%s\n", canonical_request, method); // VERB 171 | sprintf(canonical_request, "%s%s\n", canonical_request, awsPath); // URI 172 | sprintf(canonical_request, "%s%s\n", canonical_request, 173 | queryString); // queryString 174 | 175 | createCanonicalHeaders(); 176 | 177 | sprintf(canonical_request, "%s%s", canonical_request, canonical_headers); 178 | // headers 179 | sprintf(canonical_request, "%s%s\n", canonical_request, signedHeaders); 180 | // signed_headers 181 | sprintf(canonical_request, "%s%s", canonical_request, payloadHash); 182 | // payload 183 | 184 | return; 185 | } 186 | 187 | 188 | char* 189 | AWSClient4::createSignature(const char* toSign) 190 | { 191 | /* Allocate memory for the signature */ 192 | char* signature = new char[HASH_HEX_LEN4 + 1](); 193 | 194 | /* Create the signature key */ 195 | /* + 4 for "AWS4" */ 196 | int keyLen = strlen(awsSecKey) + 4; 197 | char* key = new char[keyLen + 1](); 198 | sprintf(key, "AWS4%s", awsSecKey); 199 | 200 | /* repeatedly apply hmac with the appropriate values. See 201 | * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html 202 | * for algorithm. */ 203 | char* k1 = hmacSha256(key, keyLen, awsDate, strlen(awsDate)); 204 | delete[] key; 205 | char* k2 = hmacSha256(k1, SHA256_DEC_HASH_LEN, awsRegion, 206 | strlen(awsRegion)); 207 | delete[] k1; 208 | char* k3 = hmacSha256(k2, SHA256_DEC_HASH_LEN, awsService, 209 | strlen(awsService)); 210 | delete[] k2; 211 | char* k4 = hmacSha256(k3, SHA256_DEC_HASH_LEN, "aws4_request", 12); 212 | delete[] k3; 213 | char* k5 = hmacSha256(k4, SHA256_DEC_HASH_LEN, toSign, strlen(toSign)); 214 | delete[] k4; 215 | 216 | /* Convert the chars in hash to hex for signature. */ 217 | for (int i = 0; i < SHA256_DEC_HASH_LEN; ++i) { 218 | sprintf(signature + 2 * i, "%02lx", 0xff & (unsigned long) k5[i]); 219 | } 220 | delete[] k5; 221 | return signature; 222 | } 223 | 224 | 225 | char* 226 | AWSClient4::createRequest(MinimalString &reqPayload) 227 | { 228 | /* Check that all values have been initialized. */ 229 | if (awsRegion == 0 || awsEndpoint == 0 || awsSecKey == 0 || awsKeyID == 0 230 | || httpClient == 0 || dateTimeProvider == 0) 231 | return 0; 232 | 233 | // set date and time 234 | // @TODO: find out why sprintf doesn't work 235 | const char* dateTime = dateTimeProvider->getDateTime(); 236 | strncpy(awsDate, dateTime, 8); 237 | awsDate[AWS_DATE_LEN4] = '\0'; 238 | strncpy(awsTime, dateTime + 8, 6); 239 | awsTime[AWS_TIME_LEN4] = '\0'; 240 | 241 | SHA256* sha256 = new SHA256(); 242 | payloadHash = (*sha256)(reqPayload.getCStr(), reqPayload.length()); 243 | delete sha256; 244 | 245 | payload = reqPayload; 246 | 247 | // create the canonical request 248 | createCanonicalRequest(); 249 | 250 | // create the signing string 251 | createStringToSign(canonical_request); 252 | 253 | // create the signature 254 | char *signature = createSignature(string_to_sign); 255 | 256 | // create the headers 257 | createRequestHeaders(signature); 258 | delete signature; 259 | 260 | // get the host/domain 261 | // char *host = createHost(); 262 | 263 | // create the request with all the vars 264 | char* request = new char[strlen(method) + strlen(awsDomain) + 265 | strlen(awsPath) + strlen(headers) + 266 | strlen(reqPayload.getCStr()) + 16](); 267 | sprintf(request, "%s %s HTTP/1.1\r\n%s\r\n%s\r\n\r\n", method, awsPath, 268 | headers, reqPayload.getCStr()); 269 | 270 | return request; 271 | } 272 | 273 | const char* 274 | AWSClient4::sendData(const char* data) 275 | { 276 | // char* server = createHost(); 277 | int port = httpS ? 443 : 80; 278 | const char* response = httpClient->send(data, awsDomain, port); 279 | // delete[] server; 280 | return response; 281 | } 282 | -------------------------------------------------------------------------------- /src/AWSClient4.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AWSClient2.h 3 | * 4 | * Base classes for services to create an HTTP request. 5 | * 6 | */ 7 | 8 | #ifndef AWSCLIENT4_H_ 9 | #define AWSCLIENT4_H_ 10 | 11 | #include "DeviceIndependentInterfaces.h" 12 | #include "AWSFoundationalTypes.h" 13 | 14 | /* Total number of headers. */ 15 | static const int HEADER_COUNT4 = 7; 16 | /* Size of the awsDate string. */ 17 | static const int AWS_DATE_LEN4 = 8; 18 | /* Size of the awsTime string. */ 19 | static const int AWS_TIME_LEN4 = 6; 20 | /* Size of sha hashes and signatures in hexidecimal. */ 21 | static const int HASH_HEX_LEN4 = 64; 22 | 23 | /* Base class for an AWS Service Client. Creates http and https request in raw 24 | * http format or as a curl command. */ 25 | class AWSClient4 { 26 | /* Name of region, eg. "us-east-1" in "kinesis.us-east-1.amazonaws.com". */ 27 | char* awsRegion; 28 | /* Endpoint, eg. "amazonaws.com" in "kinesis.us-east-1.amazonaws.com". */ 29 | char* awsEndpoint; 30 | /* Domain, optional, eg. "A2MBBEONHC9LUG.iot.us-east-1.amazonaws.com". */ 31 | char* awsDomain; 32 | /* Path, optional eg. "/things/foobar/shadow", eg for iot-data. */ 33 | char* awsPath; 34 | /* The user's AWS Secret Key for accessing the AWS Resource. */ 35 | char* awsSecKey; 36 | /* The user's AWS Access Key ID for accessing the AWS Resource. */ 37 | char* awsKeyID; 38 | /* GMT date in yyyyMMdd format. */ 39 | char awsDate[AWS_DATE_LEN4 + 1]; 40 | /* GMT time in HHmmss format. */ 41 | char awsTime[AWS_TIME_LEN4 + 1]; 42 | 43 | /* The payload of the httprequest to be created */ 44 | MinimalString payload; 45 | 46 | // /* Add the headers that will be signed to the headers array. Called before 47 | // * createStringToSign. */ 48 | // void initSignedHeaders(); 49 | // /* Create the canonical request and the string to sign as described. Return 50 | // * value must be deleted by caller. */ 51 | void createStringToSign(char* canonical_request); 52 | // /* Given the string to sign, create the signature (a 64-char cstring). 53 | // * Return value must be deleted by caller. */ 54 | char* createSignature(const char* toSign); 55 | void createRequestHeaders(char* signature); 56 | // /* Add the headers that will not be signed to the headers array. Called 57 | // * after createSignature. */ 58 | // void initUnsignedHeaders(const char* signature); 59 | // /* Contains all of the work to be done before headersToRequest or 60 | // * headersToCurlRequest are called. Takes the payload to be sent and the 61 | // * GMT date in yyyyMMddHHmmss format. */ 62 | // void createRequestInit(MinimalString &reqPayload); 63 | // /* Clean up after headersToRequest or headersToCurlRequest are called. */ 64 | // void createRequestCleanup(); 65 | // /* Using the headers array, create a raw http request. */ 66 | // char* headersToRequest(void); 67 | 68 | protected: 69 | const char* method; 70 | const char* uri; 71 | const char* queryString; 72 | char headers[1000]; 73 | char canonical_headers[500]; 74 | char string_to_sign[700]; 75 | char canonical_request[1000]; 76 | const char* signedHeaders; 77 | char* payloadHash; 78 | 79 | /* Used to keep track of time. */ 80 | IDateTimeProvider* dateTimeProvider; 81 | /* Used to send http to the server. */ 82 | IHttpClient* httpClient; 83 | /* true if https is to be used, false if http is to be used. */ 84 | bool httpS; 85 | /* Name of service, eg. "kinesis" in "kinesis.us-east-1.amazonaws.com". */ 86 | const char* awsService; 87 | /* Content type of payload, eg. "application/x-amz-json-1.1". */ 88 | const char* contentType; 89 | // /* Generates the host based on subdomain, service, etc */ 90 | char* createHost(void); 91 | /* Creates a raw http request, given the payload and current GMT date in 92 | * yyyyMMddHHmmss format. Should be exposed to user by extending class. 93 | * Returns 0 if client is unititialized. */ 94 | char* createRequest(MinimalString &payload); 95 | void createCanonicalRequest(); 96 | void createCanonicalHeaders(); 97 | 98 | /* Sends http data. Returns http response, or null on error. */ 99 | const char* sendData(const char* data); 100 | /* Empty constructor. Must also be initialized with init. */ 101 | AWSClient4(); 102 | 103 | public: 104 | /* Setters for values used by createRequest and createCurlRequest. Must 105 | * be set or create[Curl]Request will return null. */ 106 | /* Generates the host based on subdomain, service, etc */ 107 | char* createHostString(void); 108 | void setAWSRegion(const char * awsRegion); 109 | void setAWSEndpoint(const char * awsEndpoint); 110 | void setAWSDomain(const char * awsDomain); 111 | void setAWSPath(const char * awsPath); 112 | void setAWSSecretKey(const char * awsSecKey); 113 | void setAWSKeyID(const char * awsKeyID); 114 | void setHttpClient(IHttpClient* httpClient); 115 | void setDateTimeProvider(IDateTimeProvider* dateTimeProvider); 116 | ~AWSClient4(void); 117 | }; 118 | 119 | #endif /* AWSCLIENT4_H_ */ 120 | -------------------------------------------------------------------------------- /src/AWSFoundationalTypes.cpp: -------------------------------------------------------------------------------- 1 | #include "AWSFoundationalTypes.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | static const char* FALSE_AS_JSON = "\"false\""; 8 | static const char* TRUE_AS_JSON = "\"true\""; 9 | static const int DOUBLE_STR_LEN = 10; 10 | static const int LONG_STR_LEN = 10; 11 | static const int INT_STR_LEN = 10; 12 | 13 | void MinimalString::innerCopy(const MinimalString &toCopy) { 14 | alreadySerialized = toCopy.getAlreadySerialized(); 15 | const char* toCopyCStr = toCopy.getCStr(); 16 | if (toCopyCStr == NULL) { 17 | cStr = NULL; 18 | } else { 19 | cStr = new char[strlen(toCopyCStr) + 1](); 20 | strcpy(cStr, toCopyCStr); 21 | } 22 | } 23 | 24 | void MinimalString::innerDelete() { 25 | if (cStr != NULL) { 26 | delete[] cStr; 27 | } 28 | } 29 | 30 | MinimalString::MinimalString(const char* cStr) { 31 | alreadySerialized = false; 32 | this->cStr = new char[strlen(cStr) + 1](); 33 | strcpy(this->cStr, cStr); 34 | } 35 | 36 | MinimalString::MinimalString(const char* cStr, int len) { 37 | alreadySerialized = false; 38 | this->cStr = new char[len + 1](); 39 | strncpy(this->cStr, cStr, len); 40 | } 41 | 42 | MinimalString::MinimalString() { 43 | alreadySerialized = false; 44 | this->cStr = NULL; 45 | } 46 | 47 | MinimalString::MinimalString(const MinimalString &toCopy) { 48 | innerCopy(toCopy); 49 | } 50 | 51 | const char* MinimalString::getCStr() const { 52 | return cStr; 53 | } 54 | 55 | MinimalString& MinimalString::operator=(const MinimalString &toCopy) { 56 | /* Make sure we are not leaking memory by overwriting a value. */ 57 | innerDelete(); 58 | innerCopy(toCopy); 59 | return *this; 60 | } 61 | 62 | void MinimalString::setAlreadySerialized(bool alreadySerialized) { 63 | this->alreadySerialized = alreadySerialized; 64 | } 65 | 66 | bool MinimalString::getAlreadySerialized() const { 67 | return alreadySerialized; 68 | } 69 | 70 | MinimalString MinimalString::jsonSerialize() const { 71 | if (alreadySerialized) { 72 | return *this; 73 | } 74 | if (cStr == NULL) { 75 | /* Empty quoted string. */ 76 | return MinimalString((char*) "\"\""); 77 | } else { 78 | /* +3 for the 2 quotes and the null termination character. */ 79 | char* json = new char[strlen(cStr) + 3](); 80 | sprintf(json, "\"%s\"", cStr); 81 | MinimalString jsonMinStr(json); 82 | delete json; 83 | return jsonMinStr; 84 | } 85 | } 86 | 87 | bool MinimalString::jsonDeserialize(MinimalString json) { 88 | innerDelete(); 89 | const char* jsonCStr = json.getCStr(); 90 | if (jsonCStr == NULL) { 91 | return false; 92 | } 93 | int jsonLen = json.length(); 94 | /* Only deserialize if the first and last characters are quotes. */ 95 | if ((jsonCStr[0] == '"') && (jsonCStr[jsonLen - 1] == '"')) { 96 | /* -2 for the removed quote, but +1 for the null termination 97 | * character. */ 98 | cStr = new char[jsonLen - 1](); 99 | /* Copy all but the first and last characters */ 100 | strncpy(cStr, jsonCStr + 1, jsonLen - 2); 101 | } else { 102 | return false; 103 | } 104 | return true; 105 | } 106 | 107 | int MinimalString::length() const { 108 | if (cStr == NULL) { 109 | return 0; 110 | } else { 111 | return strlen(cStr); 112 | } 113 | } 114 | 115 | MinimalString::~MinimalString() { 116 | innerDelete(); 117 | } 118 | 119 | SerializableDouble::SerializableDouble(double d) { 120 | this->d = d; 121 | } 122 | SerializableDouble::SerializableDouble() { 123 | } 124 | 125 | double SerializableDouble::getValue() const { 126 | return d; 127 | } 128 | 129 | MinimalString SerializableDouble::jsonSerialize() const { 130 | return jsonSerializeDouble(d); 131 | } 132 | 133 | bool SerializableDouble::jsonDeserialize(MinimalString json) { 134 | d = atof(json.getCStr()); 135 | /* No error checking for this deserialize method, always return success. */ 136 | return true; 137 | } 138 | 139 | MinimalString jsonCommaConcatenate(MinimalList list, 140 | char openChar, char closeChar) { 141 | int arrayLen = list.getLength(); 142 | const MinimalString* array = list.getArray(); 143 | MinimalString* jsonSerializationsArray = new MinimalString[arrayLen](); 144 | /* Count the length of the string to allocate and create array of 145 | * serialized char*s: 146 | * There are arrayLen - 1 separating commas and 1 openChar and 1 closeChar. 147 | * fullSerializationLen = arrayLen - 1 + 2 + (lengths of all 148 | * json-serialized elements) = arraylen + 1 + (lengths of all 149 | * json-serialized elements). */ 150 | int fullSerializationLen = arrayLen + 1; 151 | for (int i = 0; i < arrayLen; i++) { 152 | /* If quote, use teh jsonSerialized string, which simply puts quotes 153 | * around it. Otherwise use the string as is. */ 154 | jsonSerializationsArray[i] = array[i]; 155 | fullSerializationLen += jsonSerializationsArray[i].length(); 156 | } 157 | /* Create the array-syntax serialization of all inner serializations. */ 158 | char* fullSerialization = new char[fullSerializationLen + 1](); 159 | int fullSerializationWritten = 0; 160 | fullSerialization[fullSerializationWritten++] = openChar; 161 | for (int i = 0; i < arrayLen; i++) { 162 | /* Write the inner serialization to the full serialization and delete 163 | * the no longer needed inner serialization. */ 164 | fullSerializationWritten += sprintf( 165 | fullSerialization + fullSerializationWritten, "%s", 166 | jsonSerializationsArray[i].getCStr()); 167 | /* Comma after all but last element. */ 168 | if (i != arrayLen - 1) { 169 | fullSerialization[fullSerializationWritten++] = ','; 170 | } 171 | } 172 | delete[] jsonSerializationsArray; 173 | fullSerialization[fullSerializationWritten++] = closeChar; 174 | MinimalString jsonString(fullSerialization); 175 | delete[] fullSerialization; 176 | return jsonString; 177 | } 178 | 179 | MinimalList jsonCommaSeparate(MinimalString jsonList, 180 | char openChar, char closeChar) { 181 | /* Incemented for every opening bracket and decremented for every closing 182 | * bracket. */ 183 | int bracketLevel = 0; 184 | /* Incemented for every opening brace and decremented for every closing 185 | * brace. */ 186 | int braceLevel = 0; 187 | /* True when looking at characters within quotes. */ 188 | bool inQuotes = false; 189 | int jsonLen = jsonList.length(); 190 | 191 | if ((jsonLen <= 2) || (jsonList.getCStr()[0] != openChar) 192 | || (jsonList.getCStr()[jsonLen - 1] != closeChar)) { 193 | /* Missing open/close character or empty list, return empty list. */ 194 | MinimalList emptyList(0, 0); 195 | return emptyList; 196 | } 197 | 198 | /* Number of elements in the list, determined as the number of 199 | * unquoted/unbraced/unbracketed commas found plus one. */ 200 | int elementCount = 1; 201 | /* This for loop determines elementCount. */ 202 | for (int i = 1; i < jsonLen - 1; i++) { 203 | switch (jsonList.getCStr()[i]) { 204 | case '"': 205 | /* Toggle inQuotes. */ 206 | inQuotes = !inQuotes; 207 | break; 208 | /* increment and decrement for unquoted brackets and braces. */ 209 | case '[': 210 | if (!inQuotes) { 211 | bracketLevel++; 212 | } 213 | break; 214 | case ']': 215 | if (!inQuotes) { 216 | bracketLevel--; 217 | } 218 | break; 219 | case '{': 220 | if (!inQuotes) { 221 | braceLevel++; 222 | } 223 | break; 224 | case '}': 225 | if (!inQuotes) { 226 | braceLevel--; 227 | } 228 | break; 229 | case ',': 230 | /* If we are at the opening level, and unquoted, and have 231 | * encountered a comma, it is a element separator. */ 232 | if ((braceLevel == 0) && (bracketLevel == 0) && !inQuotes) { 233 | elementCount++; 234 | } 235 | } 236 | } 237 | /* Array of elements. */ 238 | MinimalString* elements = new MinimalString[elementCount](); 239 | /* Number of elements created / next index of elements array to write 240 | * to. */ 241 | int elementsCreated = 0; 242 | /* The start index for the current element being coppied. */ 243 | int elementStartIdx = 1; 244 | /* This for loop fills the elements array. */ 245 | for (int i = 1; i < jsonLen - 1; i++) { 246 | switch (jsonList.getCStr()[i]) { 247 | case '"': 248 | /* Toggle inQuotes. */ 249 | inQuotes = !inQuotes; 250 | break; 251 | /* increment and decrement for unquoted brackets and braces. */ 252 | case '[': 253 | if (!inQuotes) { 254 | bracketLevel++; 255 | } 256 | break; 257 | case ']': 258 | if (!inQuotes) { 259 | bracketLevel--; 260 | } 261 | break; 262 | case '{': 263 | if (!inQuotes) { 264 | braceLevel++; 265 | } 266 | break; 267 | case '}': 268 | if (!inQuotes) { 269 | braceLevel--; 270 | } 271 | break; 272 | case ',': 273 | /* If we are at the opening level, and unquoted, and have 274 | * encountered a comma, it is a element separator. */ 275 | if ((braceLevel == 0) && (bracketLevel == 0) && !inQuotes) { 276 | /* Copy over the string from the elementStartIdx (inclusive) to 277 | * i (exclusive). */ 278 | elements[elementsCreated] = MinimalString( 279 | jsonList.getCStr() + elementStartIdx, 280 | i - elementStartIdx); 281 | 282 | elementsCreated++; 283 | /* The start of the next element is the index after the index 284 | * of this comma. */ 285 | elementStartIdx = i + 1; 286 | } 287 | } 288 | } 289 | /* Copy over the last element, which does not end with a comma. */ 290 | elements[elementsCreated] = MinimalString( 291 | jsonList.getCStr() + elementStartIdx, 292 | (jsonList.length() - 1) - elementStartIdx); 293 | 294 | MinimalList elementsList(elements, elementCount); 295 | delete[] elements; 296 | return elementsList; 297 | } 298 | 299 | MinimalString jsonSerializeBool(bool b) { 300 | return b ? MinimalString(TRUE_AS_JSON) : MinimalString(FALSE_AS_JSON); 301 | } 302 | 303 | bool jsonDeserializeBool(MinimalString json) { 304 | return !strcmp("true", json.getCStr()); 305 | } 306 | 307 | int jsonDeserializeEnum(const char** enumLookup, int enumSize, 308 | MinimalString json) { 309 | MinimalString serialized = json.jsonSerialize(); 310 | const char* expected = serialized.getCStr(); 311 | for (int i = 0; i < enumSize; i++) { 312 | if (!strcmp(enumLookup[i], expected)) { 313 | return i; 314 | } 315 | } 316 | return -1; 317 | } 318 | 319 | MinimalString jsonSerializeDouble(double d) { 320 | char buffer[DOUBLE_STR_LEN + 1]; 321 | snprintf(buffer, DOUBLE_STR_LEN, "%f", d); 322 | return MinimalString(buffer); 323 | } 324 | 325 | MinimalString jsonSerializeLong(long l) { 326 | char buffer[LONG_STR_LEN + 1]; 327 | snprintf(buffer, LONG_STR_LEN, "%ld", l); 328 | return MinimalString(buffer); 329 | } 330 | 331 | MinimalString jsonSerializeInt(int i) { 332 | char buffer[INT_STR_LEN + 1]; 333 | snprintf(buffer, INT_STR_LEN, "%d", i); 334 | return MinimalString(buffer); 335 | } 336 | 337 | -------------------------------------------------------------------------------- /src/AWSFoundationalTypes.h: -------------------------------------------------------------------------------- 1 | /* Arduino compatible c++ does not have any built-in map or list types. It 2 | * does have a string type that is ONLY available on Arduino. The types in this 3 | * file are designed to be substitutes for map/list/string that function on 4 | * any device. They are simple and do not cover some edge conditions and should 5 | * be used carefully. Any types provided to the generics here should have deep 6 | * copying for their assignment operator and copy constructor, e.g. no 7 | * "char*". Many also require jsonSerialize and jsonDeserialize methods. */ 8 | 9 | #ifndef AWSFOUNDATIONALTYPES_H_ 10 | #define AWSFOUNDATIONALTYPES_H_ 11 | 12 | #include 13 | #include 14 | 15 | /* Common enum for AWS actions. Are set/returned by actions depending on the 16 | * action's success. */ 17 | enum ActionError { 18 | NONE_ACTIONERROR, 19 | RESPONSE_PARSING_ACTIONERROR, 20 | INVALID_REQUEST_ACTIONERROR, 21 | MISSING_REQUIRED_ARGS_ACTIONERROR, 22 | CONNECTION_ACTIONERROR 23 | }; 24 | 25 | /* A wrapper for c_strings that has a deep-copying equality operator and 26 | * constructor, as well as json serialization and deserialization 27 | * capabilities. */ 28 | class MinimalString { 29 | /* The wrapped c_string. */ 30 | char* cStr; 31 | /* True if this string represents an object that is already serialized and 32 | * therefore does not need to be enclosed in quotes when jsonSerialize() 33 | * is called. */ 34 | bool alreadySerialized; 35 | /* Copy the other MinimalString's c_string. */ 36 | void innerCopy(const MinimalString &toCopy); 37 | /* Delete the c_string if it exists. */ 38 | void innerDelete(); 39 | public: 40 | /* Copy the passed c_string. */ 41 | MinimalString(const char* cStr); 42 | /* Copy the passed c_string. */ 43 | MinimalString(const char* cStr, int len); 44 | /* Empty constructor, c_string is NULL. */ 45 | MinimalString(); 46 | /* Coppying constructor. */ 47 | MinimalString(const MinimalString &toCopy); 48 | /* c_string getter. */ 49 | const char* getCStr() const; 50 | /* Overload = for deep copying. */ 51 | MinimalString& operator=(const MinimalString &toCopy); 52 | void setAlreadySerialized(bool alreadySerialized); 53 | bool getAlreadySerialized() const; 54 | /* Returns a quoted copy of the string. */ 55 | MinimalString jsonSerialize() const; 56 | /* Unquotes the string if it has surrounding quotes. */ 57 | bool jsonDeserialize(MinimalString json); 58 | /* Calculate the length of the string. */ 59 | int length() const; 60 | ~MinimalString(); 61 | }; 62 | 63 | /* A wrapper for double objects that can has serialize/deserialize methods */ 64 | class SerializableDouble { 65 | /* The wrapped double. */ 66 | double d; 67 | public: 68 | SerializableDouble(double d); 69 | SerializableDouble(); 70 | /* c_string getter. */ 71 | double getValue() const; 72 | /* Returns the value's serialization. */ 73 | MinimalString jsonSerialize() const; 74 | /* Sets the value to the json's deserialization. */ 75 | bool jsonDeserialize(MinimalString json); 76 | }; 77 | 78 | /* Contains an array of elements and has a deep-copying equality operator and 79 | * constructor. Element_Type must also have a deep-copying equality operator 80 | * and constructor for this to work properly. Also has json serialization and 81 | * deserialization capabilities*/ 82 | template 83 | class MinimalList { 84 | /* The underylying array. */ 85 | Element_Type* array; 86 | /* Number of elements in the array. */ 87 | int length; 88 | /* Deep-copy an array and length. */ 89 | void copyArray(const Element_Type* array, int length); 90 | /* Delete the array is it exists. */ 91 | void innerDelete(); 92 | public: 93 | /* Getter for array. */ 94 | const Element_Type* getArray() const; 95 | /* Getter for length. */ 96 | int getLength() const; 97 | /* Constructor taking the array and length. */ 98 | MinimalList(const Element_Type* array, int length); 99 | /* Deep-copying constructor. */ 100 | MinimalList(const MinimalList& toCopy); 101 | /* Empty constructor with null array. */ 102 | MinimalList(); 103 | /* Deep-copying equality operator. */ 104 | MinimalList& operator=( 105 | const MinimalList& toCopy); 106 | /* Given a json syntax list of Element_Type, populate the MinimalList. 107 | * Element_Type must have a jsonDeserialize() method. */ 108 | bool jsonDeserialize(MinimalString json); 109 | /* Create a json syntax list of the elements. Element_Type must have a 110 | * jsonSerialize() method. */ 111 | MinimalString jsonSerialize() const; 112 | ~MinimalList(); 113 | }; 114 | 115 | /* Conatiner for a key-value pair, with json serialization/deserialization 116 | * capabilities. */ 117 | template 118 | class MinimalKeyValuePair { 119 | Key_Type key; 120 | Value_Type value; 121 | public: 122 | /* Given a string of the json form for a key-value pair (i.e. key:value), 123 | * set key and value. Key_Type and Value_Type must each have a 124 | * jsonDeserialize() method. */ 125 | bool jsonDeserialize(MinimalString json); 126 | /* Create a json key-value pair. Key_Type and Value_Type must each have a 127 | * jsonSerialize() method. */ 128 | MinimalString jsonSerialize() const; 129 | /* Get the key. */ 130 | Key_Type getKey() const; 131 | /* Get the value. */ 132 | Value_Type getValue() const; 133 | MinimalKeyValuePair(Key_Type key, Value_Type value); 134 | MinimalKeyValuePair(); 135 | }; 136 | 137 | /* A MinimalString to Value_Type map. This is designed to take a small amount 138 | * of code and memory, not to be fast. It is json 139 | * serializable/deserializable. */ 140 | template 141 | class MinimalMap { 142 | MinimalList > pairList; 143 | public: 144 | /* Json serialize a map. Value_Type MUST have a jsonSerialize() method. */ 145 | MinimalString jsonSerialize() const; 146 | /* Json deserialize a map. Value_Type MUST have a jsonDeserialize() method. */ 147 | bool jsonDeserialize(MinimalString json); 148 | /* Constructor taking a list of MinimalString, Value_Type pairs. */ 149 | MinimalMap( 150 | MinimalList > pairList); 151 | /* Constructor taking a array of MinimalString, Value_Type pairs and it's 152 | * length. */ 153 | MinimalMap(MinimalKeyValuePair* pairArray, 154 | int length); 155 | /* Return the underlying list of KeyValuePairs. */ 156 | MinimalList > getPairList() const; 157 | MinimalMap(); 158 | /* Get the value (passed by reference) for the given key (as a c_string 159 | * for usability purposes). */ 160 | bool get(const char* key, Value_Type &value); 161 | }; 162 | 163 | /* Takes an List of MinimalStrings, places a comma between each element and 164 | * surrounds all in brackets. */ 165 | MinimalString jsonCommaConcatenate(MinimalList list, 166 | char openChar, char closeChar); 167 | 168 | /* Create a list of strings for a string that starts openChar, ends with 169 | * closedChar, and has comma separated elements. */ 170 | MinimalList jsonCommaSeparate(MinimalString jsonList, 171 | char openChar, char closeChar); 172 | 173 | /* Get the json representation of a boolean */ 174 | MinimalString jsonSerializeBool(bool b); 175 | 176 | /* Get the json representation of a long */ 177 | MinimalString jsonSerializeLong(long l); 178 | 179 | /* Get the json representation of a int */ 180 | MinimalString jsonSerializeInt(int l); 181 | 182 | /* Get the json representation of a boolean */ 183 | bool jsonDeserializeBool(MinimalString json); 184 | 185 | /* Return the integer value of the enum deserialization of the passed json 186 | * string. enumLookup is an array of char* where each element is the json 187 | * serialization of a an enum, where the index of the lookup string is the same 188 | * as the integer value of the corresponding enum. */ 189 | int jsonDeserializeEnum(const char** enumLookup, int enumSize, 190 | MinimalString json); 191 | 192 | /* Get the json representation of a double */ 193 | MinimalString jsonSerializeDouble(double d); 194 | 195 | /* 196 | * 197 | * The following are the implementations for the template classes. Apparently 198 | * these are not allowed in the cpp file for templates. 199 | * 200 | */ 201 | 202 | template 203 | void MinimalList::copyArray(const Element_Type* array, 204 | int length) { 205 | this->length = length; 206 | this->array = new Element_Type[length](); 207 | for (int i = 0; i < length; i++) { 208 | this->array[i] = array[i]; 209 | } 210 | } 211 | 212 | template 213 | void MinimalList::innerDelete() { 214 | if (array != NULL) { 215 | delete[] array; 216 | } 217 | } 218 | 219 | template 220 | const Element_Type* MinimalList::getArray() const { 221 | return array; 222 | } 223 | 224 | template 225 | int MinimalList::getLength() const { 226 | return length; 227 | } 228 | 229 | template 230 | MinimalList::MinimalList(const Element_Type* array, int length) { 231 | copyArray(array, length); 232 | } 233 | 234 | template 235 | MinimalList::MinimalList( 236 | const MinimalList& toCopy) { 237 | copyArray(toCopy.getArray(), toCopy.getLength()); 238 | } 239 | 240 | template 241 | MinimalList::MinimalList() { 242 | this->array = NULL; 243 | this->length = 0; 244 | } 245 | 246 | template 247 | MinimalList& MinimalList::operator=( 248 | const MinimalList& toCopy) { 249 | innerDelete(); 250 | copyArray(toCopy.getArray(), toCopy.getLength()); 251 | return *this; 252 | } 253 | 254 | template 255 | bool MinimalList::jsonDeserialize(MinimalString json) { 256 | innerDelete(); 257 | /* Create a list of strings of the elements and get the underlying 258 | * array. */ 259 | MinimalList elementStrs = jsonCommaSeparate(json, '[', ']'); 260 | const MinimalString* elementStrsArray = elementStrs.getArray(); 261 | length = elementStrs.getLength(); 262 | /* Set this list's underlying array to be the same size as the length of 263 | * array of elementStings, and serialize each element with the 264 | * elementString of the same index. */ 265 | array = new Element_Type[length](); 266 | for (int i = 0; i < length; i++) { 267 | if (!array[i].jsonDeserialize(elementStrsArray[i])) { 268 | /* Propogate error. */ 269 | return false; 270 | } 271 | } 272 | return true; 273 | } 274 | 275 | template 276 | MinimalString MinimalList::jsonSerialize() const { 277 | /* Create a list where each element is the serialization of the 278 | * Element_Type. */ 279 | MinimalString* elementStrs = new MinimalString[length](); 280 | for (int i = 0; i < length; i++) { 281 | elementStrs[i] = array[i].jsonSerialize(); 282 | } 283 | MinimalList strList(elementStrs, length); 284 | delete[] elementStrs; 285 | /* Concatenate each string into a json-format list. */ 286 | return jsonCommaConcatenate(strList, '[', ']'); 287 | } 288 | 289 | template 290 | MinimalList::~MinimalList() { 291 | innerDelete(); 292 | } 293 | 294 | template 295 | bool MinimalKeyValuePair::jsonDeserialize( 296 | MinimalString json) { 297 | int jsonLen = json.length(); 298 | const char* jsonCStr = json.getCStr(); 299 | const char* colonLocation = strchr(jsonCStr, ':'); 300 | /* If not containing a colon, use empty constructor. */ 301 | if (colonLocation == NULL) { 302 | key = Key_Type(); 303 | value = Value_Type(); 304 | } 305 | int colonIdx = colonLocation - jsonCStr; 306 | /* Deserialize each substring on either side of the ':' in the json 307 | * string. */ 308 | if (!key.jsonDeserialize(MinimalString(jsonCStr, colonIdx)) 309 | || !value.jsonDeserialize( 310 | MinimalString(colonLocation + 1, jsonLen - colonIdx))) { 311 | /* Propogate error. */ 312 | return false; 313 | } 314 | return true; 315 | } 316 | 317 | template 318 | MinimalString MinimalKeyValuePair::jsonSerialize() const { 319 | MinimalString keyStr = key.jsonSerialize(); 320 | MinimalString valueStr = value.jsonSerialize(); 321 | /* +1 for the ':' between them. */ 322 | int serializedLen = keyStr.length() + valueStr.length() + 1; 323 | char* serialized = new char[serializedLen + 1](); 324 | sprintf(serialized, "%s:%s", keyStr.getCStr(), valueStr.getCStr()); 325 | MinimalString jsonString(serialized); 326 | delete[] serialized; 327 | return jsonString; 328 | } 329 | 330 | template 331 | Key_Type MinimalKeyValuePair::getKey() const { 332 | return key; 333 | } 334 | 335 | template 336 | Value_Type MinimalKeyValuePair::getValue() const { 337 | return value; 338 | } 339 | 340 | template 341 | MinimalKeyValuePair::MinimalKeyValuePair(Key_Type key, 342 | Value_Type value) { 343 | this->key = key; 344 | this->value = value; 345 | } 346 | 347 | template 348 | MinimalKeyValuePair::MinimalKeyValuePair() { 349 | } 350 | 351 | template 352 | MinimalString MinimalMap::jsonSerialize() const { 353 | const MinimalKeyValuePair* pairArray = 354 | pairList.getArray(); 355 | int arrayLen = pairList.getLength(); 356 | /* Create a string array and assign each element to the serialized form of 357 | * the element at the same index in pairArray. */ 358 | MinimalString* serializedPairsArray = new MinimalString[arrayLen](); 359 | for (int i = 0; i < arrayLen; i++) { 360 | serializedPairsArray[i] = pairArray[i].jsonSerialize(); 361 | } 362 | /* Create a list out of the serializedPairsArray, and create a braced, 363 | * comma-separated string out of the list. */ 364 | MinimalList serializedPairsList(serializedPairsArray, 365 | arrayLen); 366 | delete[] serializedPairsArray; 367 | return jsonCommaConcatenate(serializedPairsList, '{', '}'); 368 | } 369 | 370 | template 371 | bool MinimalMap::jsonDeserialize(MinimalString json) { 372 | /* Create the list of serialized pairs. */ 373 | MinimalList strList = jsonCommaSeparate(json, '{', '}'); 374 | const MinimalString* strArray = strList.getArray(); 375 | int arrayLen = strList.getLength(); 376 | /* Create a list of pairs the same size as the list of serialized pair 377 | * strings, then deserialize each element. */ 378 | MinimalKeyValuePair* deserializedPairsArray = 379 | new MinimalKeyValuePair [arrayLen](); 380 | for (int i = 0; i < arrayLen; i++) { 381 | if (!deserializedPairsArray[i].jsonDeserialize(strArray[i])) { 382 | delete[] deserializedPairsArray; 383 | /* Propogate error. */ 384 | return false; 385 | } 386 | } 387 | pairList = MinimalList >( 388 | deserializedPairsArray, arrayLen); 389 | delete[] deserializedPairsArray; 390 | return true; 391 | } 392 | 393 | template 394 | MinimalMap::MinimalMap( 395 | MinimalList > pairList) { 396 | this->pairList = pairList; 397 | } 398 | 399 | template 400 | MinimalMap::MinimalMap( 401 | MinimalKeyValuePair* pairArray, int length) { 402 | /* Create a MinimalList out of the array. */ 403 | this->pairList = 404 | MinimalList >( 405 | pairArray, length); 406 | } 407 | 408 | template 409 | MinimalList > MinimalMap< 410 | Value_Type>::getPairList() const { 411 | return pairList; 412 | } 413 | 414 | template 415 | MinimalMap::MinimalMap() { 416 | } 417 | 418 | template 419 | bool MinimalMap::get(const char* key, Value_Type &value) { 420 | /* Using a linear search, not hashing for this map. Program size and memory 421 | * usage are more important than speed on these microcontrollers. */ 422 | for (int i = 0; i < pairList.getLength(); i++) { 423 | if (!strcmp(key, pairList.getArray()[i].getKey().getCStr())) { 424 | value = pairList.getArray()[i].getValue(); 425 | return true; 426 | } 427 | } 428 | return false; 429 | } 430 | 431 | #endif /* AWSFOUNDATIONALTYPES_H_ */ 432 | -------------------------------------------------------------------------------- /src/AmazonIOTClient.cpp: -------------------------------------------------------------------------------- 1 | #include "AmazonIOTClient.h" 2 | #include "AWSFoundationalTypes.h" 3 | #include 4 | #include "Utils.h" 5 | 6 | AmazonIOTClient::AmazonIOTClient() : AWSClient4() { 7 | this->awsService = "iotdata"; 8 | this->contentType = "application/json"; 9 | this->signedHeaders = "content-length;content-type;host;x-amz-content-sha256;x-amz-date"; 10 | this->uri = "/"; 11 | this->queryString = ""; 12 | this->httpS = true; 13 | } 14 | 15 | const char* AmazonIOTClient::update_shadow(MinimalString shadow, ActionError& actionError) { 16 | actionError = NONE_ACTIONERROR; 17 | 18 | this->method = "POST"; 19 | char* request = createRequest(shadow); 20 | // return request; 21 | const char* response = sendData(request); 22 | return response; 23 | } 24 | 25 | const char* AmazonIOTClient::get_shadow(ActionError& actionError) { 26 | actionError = NONE_ACTIONERROR; 27 | 28 | this->method = "GET"; 29 | MinimalString shadow = ""; 30 | char* request = createRequest(shadow); 31 | // return request; 32 | const char* response = sendData(request); 33 | return response; 34 | } 35 | -------------------------------------------------------------------------------- /src/AmazonIOTClient.h: -------------------------------------------------------------------------------- 1 | #ifndef AMAZONIOTCLIENT_H_ 2 | #define AMAZONIOTCLIENT_H_ 3 | #include "AWSClient4.h" 4 | 5 | 6 | // class Shadow { 7 | // MinimalString shadow; 8 | // void reset(); 9 | // public: 10 | // void setShadow(shadow) const; 11 | // }; 12 | 13 | 14 | 15 | class AmazonIOTClient : public AWSClient4 { 16 | public: 17 | AmazonIOTClient(); 18 | 19 | const char* update_shadow(MinimalString shadow, ActionError& actionError); 20 | const char* get_shadow(ActionError& actionError); 21 | }; 22 | 23 | #endif /* AMAZONIOTCLIENT_H_ */ 24 | -------------------------------------------------------------------------------- /src/AmazonKinesisClient.h: -------------------------------------------------------------------------------- 1 | #ifndef AMAZONKINESISCLIENT_H_ 2 | #define AMAZONKINESISCLIENT_H_ 3 | #include "AWSFoundationalTypes.h" 4 | #include "AWSClient.h" 5 | 6 | enum ShardIteratorType{ 7 | AT_SEQUENCE_NUMBER_SHARDITERATORTYPE, 8 | AFTER_SEQUENCE_NUMBER_SHARDITERATORTYPE, 9 | TRIM_HORIZON_SHARDITERATORTYPE, 10 | LATEST_SHARDITERATORTYPE 11 | }; 12 | 13 | enum StreamStatus{ 14 | CREATING_STREAMSTATUS, 15 | DELETING_STREAMSTATUS, 16 | ACTIVE_STREAMSTATUS, 17 | UPDATING_STREAMSTATUS 18 | }; 19 | 20 | /*

The range of possible hash key values for the shard, which is a set of ordered contiguous positive integers.

*/ 21 | class HashKeyRange{ 22 | MinimalString startingHashKey; 23 | MinimalString endingHashKey; 24 | bool startingHashKeyBeenSet; 25 | bool endingHashKeyBeenSet; 26 | void reset(); 27 | public: 28 | HashKeyRange(); 29 | bool jsonDeserialize(MinimalString json); 30 | MinimalString jsonSerialize() const; 31 | void setStartingHashKey(MinimalString startingHashKey); 32 | void setEndingHashKey(MinimalString endingHashKey); 33 | MinimalString getStartingHashKey() const; 34 | MinimalString getEndingHashKey() const; 35 | }; 36 | 37 | /*

The range of possible sequence numbers for the shard.

*/ 38 | class SequenceNumberRange{ 39 | MinimalString startingSequenceNumber; 40 | MinimalString endingSequenceNumber; 41 | bool startingSequenceNumberBeenSet; 42 | bool endingSequenceNumberBeenSet; 43 | void reset(); 44 | public: 45 | SequenceNumberRange(); 46 | bool jsonDeserialize(MinimalString json); 47 | MinimalString jsonSerialize() const; 48 | void setStartingSequenceNumber(MinimalString startingSequenceNumber); 49 | void setEndingSequenceNumber(MinimalString endingSequenceNumber); 50 | MinimalString getStartingSequenceNumber() const; 51 | MinimalString getEndingSequenceNumber() const; 52 | }; 53 | 54 | /*

A uniquely identified group of data records in an Amazon Kinesis stream.

*/ 55 | class Shard{ 56 | MinimalString parentShardId; 57 | SequenceNumberRange sequenceNumberRange; 58 | MinimalString shardId; 59 | HashKeyRange hashKeyRange; 60 | MinimalString adjacentParentShardId; 61 | bool parentShardIdBeenSet; 62 | bool sequenceNumberRangeBeenSet; 63 | bool shardIdBeenSet; 64 | bool hashKeyRangeBeenSet; 65 | bool adjacentParentShardIdBeenSet; 66 | void reset(); 67 | public: 68 | Shard(); 69 | bool jsonDeserialize(MinimalString json); 70 | MinimalString jsonSerialize() const; 71 | void setParentShardId(MinimalString parentShardId); 72 | void setSequenceNumberRange(SequenceNumberRange sequenceNumberRange); 73 | void setShardId(MinimalString shardId); 74 | void setHashKeyRange(HashKeyRange hashKeyRange); 75 | void setAdjacentParentShardId(MinimalString adjacentParentShardId); 76 | MinimalString getParentShardId() const; 77 | SequenceNumberRange getSequenceNumberRange() const; 78 | MinimalString getShardId() const; 79 | HashKeyRange getHashKeyRange() const; 80 | MinimalString getAdjacentParentShardId() const; 81 | }; 82 | 83 | /*

The unit of data of the Amazon Kinesis stream, which is composed of a sequence number, a partition key, and a data blob.

*/ 84 | class Record{ 85 | MinimalString data; 86 | MinimalString partitionKey; 87 | MinimalString sequenceNumber; 88 | bool dataBeenSet; 89 | bool partitionKeyBeenSet; 90 | bool sequenceNumberBeenSet; 91 | void reset(); 92 | public: 93 | Record(); 94 | bool jsonDeserialize(MinimalString json); 95 | MinimalString jsonSerialize() const; 96 | void setData(MinimalString data); 97 | void setPartitionKey(MinimalString partitionKey); 98 | void setSequenceNumber(MinimalString sequenceNumber); 99 | MinimalString getData() const; 100 | MinimalString getPartitionKey() const; 101 | MinimalString getSequenceNumber() const; 102 | }; 103 | 104 | /*

Represents the output of a DescribeStream operation.

*/ 105 | class StreamDescription{ 106 | bool hasMoreShards; 107 | MinimalString streamName; 108 | MinimalString streamARN; 109 | MinimalList shards; 110 | StreamStatus streamStatus; 111 | bool hasMoreShardsBeenSet; 112 | bool streamNameBeenSet; 113 | bool streamARNBeenSet; 114 | bool shardsBeenSet; 115 | bool streamStatusBeenSet; 116 | void reset(); 117 | public: 118 | StreamDescription(); 119 | bool jsonDeserialize(MinimalString json); 120 | MinimalString jsonSerialize() const; 121 | void setHasMoreShards(bool hasMoreShards); 122 | void setStreamName(MinimalString streamName); 123 | void setStreamARN(MinimalString streamARN); 124 | void setShards(MinimalList shards); 125 | void setStreamStatus(StreamStatus streamStatus); 126 | bool getHasMoreShards() const; 127 | MinimalString getStreamName() const; 128 | MinimalString getStreamARN() const; 129 | MinimalList getShards() const; 130 | StreamStatus getStreamStatus() const; 131 | }; 132 | 133 | /*

Represents the input of a GetShardIterator operation.

*/ 134 | class GetShardIteratorInput{ 135 | MinimalString startingSequenceNumber; 136 | MinimalString streamName; 137 | ShardIteratorType shardIteratorType; 138 | MinimalString shardId; 139 | bool startingSequenceNumberBeenSet; 140 | bool streamNameBeenSet; 141 | bool shardIteratorTypeBeenSet; 142 | bool shardIdBeenSet; 143 | void reset(); 144 | public: 145 | GetShardIteratorInput(); 146 | bool requiredAreSet() const; 147 | MinimalString jsonSerialize() const; 148 | void setStartingSequenceNumber(MinimalString startingSequenceNumber); 149 | void setStreamName(MinimalString streamName); 150 | void setShardIteratorType(ShardIteratorType shardIteratorType); 151 | void setShardId(MinimalString shardId); 152 | MinimalString getStartingSequenceNumber() const; 153 | MinimalString getStreamName() const; 154 | ShardIteratorType getShardIteratorType() const; 155 | MinimalString getShardId() const; 156 | }; 157 | 158 | /*

Represents the input of a GetRecords operation.

*/ 159 | class GetRecordsInput{ 160 | MinimalString shardIterator; 161 | int limit; 162 | bool shardIteratorBeenSet; 163 | bool limitBeenSet; 164 | void reset(); 165 | public: 166 | GetRecordsInput(); 167 | bool requiredAreSet() const; 168 | MinimalString jsonSerialize() const; 169 | void setShardIterator(MinimalString shardIterator); 170 | void setLimit(int limit); 171 | MinimalString getShardIterator() const; 172 | int getLimit() const; 173 | }; 174 | 175 | /*

Represents the input of a MergeShards operation.

*/ 176 | class MergeShardsInput{ 177 | MinimalString adjacentShardToMerge; 178 | MinimalString streamName; 179 | MinimalString shardToMerge; 180 | bool adjacentShardToMergeBeenSet; 181 | bool streamNameBeenSet; 182 | bool shardToMergeBeenSet; 183 | void reset(); 184 | public: 185 | MergeShardsInput(); 186 | bool requiredAreSet() const; 187 | MinimalString jsonSerialize() const; 188 | void setAdjacentShardToMerge(MinimalString adjacentShardToMerge); 189 | void setStreamName(MinimalString streamName); 190 | void setShardToMerge(MinimalString shardToMerge); 191 | MinimalString getAdjacentShardToMerge() const; 192 | MinimalString getStreamName() const; 193 | MinimalString getShardToMerge() const; 194 | }; 195 | 196 | /*

Represents the input of a DescribeStream operation.

*/ 197 | class DescribeStreamInput{ 198 | MinimalString exclusiveStartShardId; 199 | MinimalString streamName; 200 | int limit; 201 | bool exclusiveStartShardIdBeenSet; 202 | bool streamNameBeenSet; 203 | bool limitBeenSet; 204 | void reset(); 205 | public: 206 | DescribeStreamInput(); 207 | bool requiredAreSet() const; 208 | MinimalString jsonSerialize() const; 209 | void setExclusiveStartShardId(MinimalString exclusiveStartShardId); 210 | void setStreamName(MinimalString streamName); 211 | void setLimit(int limit); 212 | MinimalString getExclusiveStartShardId() const; 213 | MinimalString getStreamName() const; 214 | int getLimit() const; 215 | }; 216 | 217 | /*

Represents the input of a DeleteStream operation.

*/ 218 | class DeleteStreamInput{ 219 | MinimalString streamName; 220 | bool streamNameBeenSet; 221 | void reset(); 222 | public: 223 | DeleteStreamInput(); 224 | bool requiredAreSet() const; 225 | MinimalString jsonSerialize() const; 226 | void setStreamName(MinimalString streamName); 227 | MinimalString getStreamName() const; 228 | }; 229 | 230 | /*

Represents the output of a GetShardIterator operation.

*/ 231 | class GetShardIteratorOutput{ 232 | MinimalString shardIterator; 233 | bool shardIteratorBeenSet; 234 | MinimalString errorType; 235 | MinimalString errorMessage; 236 | void reset(); 237 | public: 238 | GetShardIteratorOutput(); 239 | bool jsonDeserialize(MinimalString json); 240 | MinimalString getErrorType() const; 241 | MinimalString getErrorMessage() const; 242 | void setShardIterator(MinimalString shardIterator); 243 | MinimalString getShardIterator() const; 244 | }; 245 | 246 | /*

Represents the input of a CreateStream operation.

*/ 247 | class CreateStreamInput{ 248 | int shardCount; 249 | MinimalString streamName; 250 | bool shardCountBeenSet; 251 | bool streamNameBeenSet; 252 | void reset(); 253 | public: 254 | CreateStreamInput(); 255 | bool requiredAreSet() const; 256 | MinimalString jsonSerialize() const; 257 | void setShardCount(int shardCount); 258 | void setStreamName(MinimalString streamName); 259 | int getShardCount() const; 260 | MinimalString getStreamName() const; 261 | }; 262 | 263 | /*

Represents the input of a SplitShard operation.

*/ 264 | class SplitShardInput{ 265 | MinimalString newStartingHashKey; 266 | MinimalString streamName; 267 | MinimalString shardToSplit; 268 | bool newStartingHashKeyBeenSet; 269 | bool streamNameBeenSet; 270 | bool shardToSplitBeenSet; 271 | void reset(); 272 | public: 273 | SplitShardInput(); 274 | bool requiredAreSet() const; 275 | MinimalString jsonSerialize() const; 276 | void setNewStartingHashKey(MinimalString newStartingHashKey); 277 | void setStreamName(MinimalString streamName); 278 | void setShardToSplit(MinimalString shardToSplit); 279 | MinimalString getNewStartingHashKey() const; 280 | MinimalString getStreamName() const; 281 | MinimalString getShardToSplit() const; 282 | }; 283 | 284 | /*

Represents the output of a ListStreams operation.

*/ 285 | class ListStreamsOutput{ 286 | MinimalList streamNames; 287 | bool hasMoreStreams; 288 | bool streamNamesBeenSet; 289 | bool hasMoreStreamsBeenSet; 290 | MinimalString errorType; 291 | MinimalString errorMessage; 292 | void reset(); 293 | public: 294 | ListStreamsOutput(); 295 | bool jsonDeserialize(MinimalString json); 296 | MinimalString getErrorType() const; 297 | MinimalString getErrorMessage() const; 298 | void setStreamNames(MinimalList streamNames); 299 | void setHasMoreStreams(bool hasMoreStreams); 300 | MinimalList getStreamNames() const; 301 | bool getHasMoreStreams() const; 302 | }; 303 | 304 | /*

Represents the output of a GetRecords operation.

*/ 305 | class GetRecordsOutput{ 306 | MinimalString nextShardIterator; 307 | MinimalList records; 308 | bool nextShardIteratorBeenSet; 309 | bool recordsBeenSet; 310 | MinimalString errorType; 311 | MinimalString errorMessage; 312 | void reset(); 313 | public: 314 | GetRecordsOutput(); 315 | bool jsonDeserialize(MinimalString json); 316 | MinimalString getErrorType() const; 317 | MinimalString getErrorMessage() const; 318 | void setNextShardIterator(MinimalString nextShardIterator); 319 | void setRecords(MinimalList records); 320 | MinimalString getNextShardIterator() const; 321 | MinimalList getRecords() const; 322 | }; 323 | 324 | /*

Represents the input of a ListStreams operation.

*/ 325 | class ListStreamsInput{ 326 | MinimalString exclusiveStartStreamName; 327 | int limit; 328 | bool exclusiveStartStreamNameBeenSet; 329 | bool limitBeenSet; 330 | void reset(); 331 | public: 332 | ListStreamsInput(); 333 | bool requiredAreSet() const; 334 | MinimalString jsonSerialize() const; 335 | void setExclusiveStartStreamName(MinimalString exclusiveStartStreamName); 336 | void setLimit(int limit); 337 | MinimalString getExclusiveStartStreamName() const; 338 | int getLimit() const; 339 | }; 340 | 341 | /*

Represents the input of a PutRecord operation.

*/ 342 | class PutRecordInput{ 343 | MinimalString data; 344 | MinimalString explicitHashKey; 345 | MinimalString sequenceNumberForOrdering; 346 | MinimalString streamName; 347 | MinimalString partitionKey; 348 | bool dataBeenSet; 349 | bool explicitHashKeyBeenSet; 350 | bool sequenceNumberForOrderingBeenSet; 351 | bool streamNameBeenSet; 352 | bool partitionKeyBeenSet; 353 | void reset(); 354 | public: 355 | PutRecordInput(); 356 | bool requiredAreSet() const; 357 | MinimalString jsonSerialize() const; 358 | void setData(MinimalString data); 359 | void setExplicitHashKey(MinimalString explicitHashKey); 360 | void setSequenceNumberForOrdering(MinimalString sequenceNumberForOrdering); 361 | void setStreamName(MinimalString streamName); 362 | void setPartitionKey(MinimalString partitionKey); 363 | MinimalString getData() const; 364 | MinimalString getExplicitHashKey() const; 365 | MinimalString getSequenceNumberForOrdering() const; 366 | MinimalString getStreamName() const; 367 | MinimalString getPartitionKey() const; 368 | }; 369 | 370 | class KinesisErrorCheckingOnlyOutput{ 371 | MinimalString errorType; 372 | MinimalString errorMessage; 373 | void reset(); 374 | public: 375 | KinesisErrorCheckingOnlyOutput(); 376 | bool jsonDeserialize(MinimalString json); 377 | MinimalString getErrorType() const; 378 | MinimalString getErrorMessage() const; 379 | }; 380 | 381 | /*

Represents the output of a DescribeStream operation.

*/ 382 | class DescribeStreamOutput{ 383 | StreamDescription streamDescription; 384 | bool streamDescriptionBeenSet; 385 | MinimalString errorType; 386 | MinimalString errorMessage; 387 | void reset(); 388 | public: 389 | DescribeStreamOutput(); 390 | bool jsonDeserialize(MinimalString json); 391 | MinimalString getErrorType() const; 392 | MinimalString getErrorMessage() const; 393 | void setStreamDescription(StreamDescription streamDescription); 394 | StreamDescription getStreamDescription() const; 395 | }; 396 | 397 | /*

Represents the output of a PutRecord operation.

*/ 398 | class PutRecordOutput{ 399 | MinimalString shardId; 400 | MinimalString sequenceNumber; 401 | bool shardIdBeenSet; 402 | bool sequenceNumberBeenSet; 403 | MinimalString errorType; 404 | MinimalString errorMessage; 405 | void reset(); 406 | public: 407 | PutRecordOutput(); 408 | bool jsonDeserialize(MinimalString json); 409 | MinimalString getErrorType() const; 410 | MinimalString getErrorMessage() const; 411 | void setShardId(MinimalString shardId); 412 | void setSequenceNumber(MinimalString sequenceNumber); 413 | MinimalString getShardId() const; 414 | MinimalString getSequenceNumber() const; 415 | }; 416 | 417 | class AmazonKinesisClient : public AWSClient { 418 | public: 419 | AmazonKinesisClient(); 420 | /*

This operation adds a new Amazon Kinesis stream to your AWS account. A stream captures and transports data records that are continuously emitted from different data sources or producers. Scale-out within an Amazon Kinesis stream is explicitly supported by means of shards, which are uniquely identified groups of data records in an Amazon Kinesis stream.

You specify and control the number of shards that a stream is composed of. Each open shard can support up to 5 read transactions per second, up to a maximum total of 2 MB of data read per second. Each shard can support up to 1000 write transactions per second, up to a maximum total of 1 MB data written per second. You can add shards to a stream if the amount of data input increases and you can remove shards if the amount of data input decreases.

The stream name identifies the stream. The name is scoped to the AWS account used by the application. It is also scoped by region. That is, two streams in two different accounts can have the same name, and two streams in the same account, but in two different regions, can have the same name.

CreateStream is an asynchronous operation. Upon receiving a CreateStream request, Amazon Kinesis immediately returns and sets the stream status to CREATING. After the stream is created, Amazon Kinesis sets the stream status to ACTIVE. You should perform read and write operations only on an ACTIVE stream.

You receive a LimitExceededException when making a CreateStream request if you try to do one of the following:

  • Have more than five streams in the CREATING state at any point in time.
  • Create more shards than are authorized for your account.

Note: The default limit for an AWS account is 10 shards per stream. If you need to create a stream with more than 10 shards, contact AWS Support to increase the limit on your account.

You can use the DescribeStream operation to check the stream status, which is returned in StreamStatus.

CreateStream has a limit of 5 transactions per second per account.

*/ 421 | KinesisErrorCheckingOnlyOutput createStream(CreateStreamInput createStreamInput, ActionError& actionError, bool retry = true, int* httpStatusCode = NULL); 422 | /*

This operation deletes a stream and all of its shards and data. You must shut down any applications that are operating on the stream before you delete the stream. If an application attempts to operate on a deleted stream, it will receive the exception ResourceNotFoundException.

If the stream is in the ACTIVE state, you can delete it. After a DeleteStream request, the specified stream is in the DELETING state until Amazon Kinesis completes the deletion.

Note: Amazon Kinesis might continue to accept data read and write operations, such as PutRecord and GetRecords, on a stream in the DELETING state until the stream deletion is complete.

When you delete a stream, any shards in that stream are also deleted.

You can use the DescribeStream operation to check the state of the stream, which is returned in StreamStatus.

DeleteStream has a limit of 5 transactions per second per account.

*/ 423 | KinesisErrorCheckingOnlyOutput deleteStream(DeleteStreamInput deleteStreamInput, ActionError& actionError, bool retry = true, int* httpStatusCode = NULL); 424 | /*

This operation returns the following information about the stream: the current status of the stream, the stream Amazon Resource Name (ARN), and an array of shard objects that comprise the stream. For each shard object there is information about the hash key and sequence number ranges that the shard spans, and the IDs of any earlier shards that played in a role in a MergeShards or SplitShard operation that created the shard. A sequence number is the identifier associated with every record ingested in the Amazon Kinesis stream. The sequence number is assigned by the Amazon Kinesis service when a record is put into the stream.

You can limit the number of returned shards using the Limit parameter. The number of shards in a stream may be too large to return from a single call to DescribeStream. You can detect this by using the HasMoreShards flag in the returned output. HasMoreShards is set to true when there is more data available.

If there are more shards available, you can request more shards by using the shard ID of the last shard returned by the DescribeStream request, in the ExclusiveStartShardId parameter in a subsequent request to DescribeStream. DescribeStream is a paginated operation.

DescribeStream has a limit of 10 transactions per second per account.

*/ 425 | DescribeStreamOutput describeStream(DescribeStreamInput describeStreamInput, ActionError& actionError, bool retry = true, int* httpStatusCode = NULL); 426 | /*

This operation returns one or more data records from a shard. A GetRecords operation request can retrieve up to 10 MB of data.

You specify a shard iterator for the shard that you want to read data from in the ShardIterator parameter. The shard iterator specifies the position in the shard from which you want to start reading data records sequentially. A shard iterator specifies this position using the sequence number of a data record in the shard. For more information about the shard iterator, see GetShardIterator.

GetRecords may return a partial result if the response size limit is exceeded. You will get an error, but not a partial result if the shard's provisioned throughput is exceeded, the shard iterator has expired, or an internal processing failure has occurred. Clients can request a smaller amount of data by specifying a maximum number of returned records using the Limit parameter. The Limit parameter can be set to an integer value of up to 10,000. If you set the value to an integer greater than 10,000, you will receive InvalidArgumentException.

A new shard iterator is returned by every GetRecords request in NextShardIterator, which you use in the ShardIterator parameter of the next GetRecords request. When you repeatedly read from an Amazon Kinesis stream use a GetShardIterator request to get the first shard iterator to use in your first GetRecords request and then use the shard iterator returned in NextShardIterator for subsequent reads.

GetRecords can return null for the NextShardIterator to reflect that the shard has been closed and that the requested shard iterator would never have returned more data.

If no items can be processed because of insufficient provisioned throughput on the shard involved in the request, GetRecords throws ProvisionedThroughputExceededException.

*/ 427 | GetRecordsOutput getRecords(GetRecordsInput getRecordsInput, ActionError& actionError, bool retry = true, int* httpStatusCode = NULL); 428 | /*

This operation returns a shard iterator in ShardIterator. The shard iterator specifies the position in the shard from which you want to start reading data records sequentially. A shard iterator specifies this position using the sequence number of a data record in a shard. A sequence number is the identifier associated with every record ingested in the Amazon Kinesis stream. The sequence number is assigned by the Amazon Kinesis service when a record is put into the stream.

You must specify the shard iterator type in the GetShardIterator request. For example, you can set the ShardIteratorType parameter to read exactly from the position denoted by a specific sequence number by using the AT_SEQUENCE_NUMBER shard iterator type, or right after the sequence number by using the AFTER_SEQUENCE_NUMBER shard iterator type, using sequence numbers returned by earlier PutRecord, GetRecords or DescribeStream requests. You can specify the shard iterator type TRIM_HORIZON in the request to cause ShardIterator to point to the last untrimmed record in the shard in the system, which is the oldest data record in the shard. Or you can point to just after the most recent record in the shard, by using the shard iterator type LATEST, so that you always read the most recent data in the shard.

Note: Each shard iterator expires five minutes after it is returned to the requester.

When you repeatedly read from an Amazon Kinesis stream use a GetShardIterator request to get the first shard iterator to to use in your first GetRecords request and then use the shard iterator returned by the GetRecords request in NextShardIterator for subsequent reads. A new shard iterator is returned by every GetRecords request in NextShardIterator, which you use in the ShardIterator parameter of the next GetRecords request.

If a GetShardIterator request is made too often, you will receive a ProvisionedThroughputExceededException. For more information about throughput limits, see the Amazon Kinesis Developer Guide.

GetShardIterator can return null for its ShardIterator to indicate that the shard has been closed and that the requested iterator will return no more data. A shard can be closed by a SplitShard or MergeShards operation.

GetShardIterator has a limit of 5 transactions per second per account per open shard.

*/ 429 | GetShardIteratorOutput getShardIterator(GetShardIteratorInput getShardIteratorInput, ActionError& actionError, bool retry = true, int* httpStatusCode = NULL); 430 | /*

This operation returns an array of the names of all the streams that are associated with the AWS account making the ListStreams request. A given AWS account can have many streams active at one time.

The number of streams may be too large to return from a single call to ListStreams. You can limit the number of returned streams using the Limit parameter. If you do not specify a value for the Limit parameter, Amazon Kinesis uses the default limit, which is currently 10.

You can detect if there are more streams available to list by using the HasMoreStreams flag from the returned output. If there are more streams available, you can request more streams by using the name of the last stream returned by the ListStreams request in the ExclusiveStartStreamName parameter in a subsequent request to ListStreams. The group of stream names returned by the subsequent request is then added to the list. You can continue this process until all the stream names have been collected in the list.

ListStreams has a limit of 5 transactions per second per account.

*/ 431 | ListStreamsOutput listStreams(ListStreamsInput listStreamsInput, ActionError& actionError, bool retry = true, int* httpStatusCode = NULL); 432 | /*

This operation merges two adjacent shards in a stream and combines them into a single shard to reduce the stream's capacity to ingest and transport data. Two shards are considered adjacent if the union of the hash key ranges for the two shards form a contiguous set with no gaps. For example, if you have two shards, one with a hash key range of 276...381 and the other with a hash key range of 382...454, then you could merge these two shards into a single shard that would have a hash key range of 276...454. After the merge, the single child shard receives data for all hash key values covered by the two parent shards.

MergeShards is called when there is a need to reduce the overall capacity of a stream because of excess capacity that is not being used. The operation requires that you specify the shard to be merged and the adjacent shard for a given stream. For more information about merging shards, see the Amazon Kinesis Developer Guide.

If the stream is in the ACTIVE state, you can call MergeShards. If a stream is in CREATING or UPDATING or DELETING states, then Amazon Kinesis returns a ResourceInUseException. If the specified stream does not exist, Amazon Kinesis returns a ResourceNotFoundException.

You can use the DescribeStream operation to check the state of the stream, which is returned in StreamStatus.

MergeShards is an asynchronous operation. Upon receiving a MergeShards request, Amazon Kinesis immediately returns a response and sets the StreamStatus to UPDATING. After the operation is completed, Amazon Kinesis sets the StreamStatus to ACTIVE. Read and write operations continue to work while the stream is in the UPDATING state.

You use the DescribeStream operation to determine the shard IDs that are specified in the MergeShards request.

If you try to operate on too many streams in parallel using CreateStream, DeleteStream, MergeShards or SplitShard, you will receive a LimitExceededException.

MergeShards has limit of 5 transactions per second per account.

*/ 433 | KinesisErrorCheckingOnlyOutput mergeShards(MergeShardsInput mergeShardsInput, ActionError& actionError, bool retry = true, int* httpStatusCode = NULL); 434 | /*

This operation puts a data record into an Amazon Kinesis stream from a producer. This operation must be called to send data from the producer into the Amazon Kinesis stream for real-time ingestion and subsequent processing. The PutRecord operation requires the name of the stream that captures, stores, and transports the data; a partition key; and the data blob itself. The data blob could be a segment from a log file, geographic/location data, website clickstream data, or any other data type.

The partition key is used to distribute data across shards. Amazon Kinesis segregates the data records that belong to a data stream into multiple shards, using the partition key associated with each data record to determine which shard a given data record belongs to.

Partition keys are Unicode strings, with a maximum length limit of 256 bytes. An MD5 hash function is used to map partition keys to 128-bit integer values and to map associated data records to shards using the hash key ranges of the shards. You can override hashing the partition key to determine the shard by explicitly specifying a hash value using the ExplicitHashKey parameter. For more information, see the Amazon Kinesis Developer Guide.

PutRecord returns the shard ID of where the data record was placed and the sequence number that was assigned to the data record.

Sequence numbers generally increase over time. To guarantee strictly increasing ordering, use the SequenceNumberForOrdering parameter. For more information, see the Amazon Kinesis Developer Guide.

If a PutRecord request cannot be processed because of insufficient provisioned throughput on the shard involved in the request, PutRecord throws ProvisionedThroughputExceededException.

Data records are accessible for only 24 hours from the time that they are added to an Amazon Kinesis stream.

*/ 435 | PutRecordOutput putRecord(PutRecordInput putRecordInput, ActionError& actionError, bool retry = true, int* httpStatusCode = NULL); 436 | /*

This operation splits a shard into two new shards in the stream, to increase the stream's capacity to ingest and transport data. SplitShard is called when there is a need to increase the overall capacity of stream because of an expected increase in the volume of data records being ingested.

SplitShard can also be used when a given shard appears to be approaching its maximum utilization, for example, when the set of producers sending data into the specific shard are suddenly sending more than previously anticipated. You can also call the SplitShard operation to increase stream capacity, so that more Amazon Kinesis applications can simultaneously read data from the stream for real-time processing.

The SplitShard operation requires that you specify the shard to be split and the new hash key, which is the position in the shard where the shard gets split in two. In many cases, the new hash key might simply be the average of the beginning and ending hash key, but it can be any hash key value in the range being mapped into the shard. For more information about splitting shards, see the Amazon Kinesis Developer Guide.

You can use the DescribeStream operation to determine the shard ID and hash key values for the ShardToSplit and NewStartingHashKey parameters that are specified in the SplitShard request.

SplitShard is an asynchronous operation. Upon receiving a SplitShard request, Amazon Kinesis immediately returns a response and sets the stream status to UPDATING. After the operation is completed, Amazon Kinesis sets the stream status to ACTIVE. Read and write operations continue to work while the stream is in the UPDATING state.

You can use DescribeStream to check the status of the stream, which is returned in StreamStatus. If the stream is in the ACTIVE state, you can call SplitShard. If a stream is in CREATING or UPDATING or DELETING states, then Amazon Kinesis returns a ResourceInUseException.

If the specified stream does not exist, Amazon Kinesis returns a ResourceNotFoundException. If you try to create more shards than are authorized for your account, you receive a LimitExceededException.

Note: The default limit for an AWS account is 10 shards per stream. If you need to create a stream with more than 10 shards, contact AWS Support to increase the limit on your account.

If you try to operate on too many streams in parallel using CreateStream, DeleteStream, MergeShards or SplitShard, you will receive a LimitExceededException.

SplitShard has limit of 5 transactions per second per account.

*/ 437 | KinesisErrorCheckingOnlyOutput splitShard(SplitShardInput splitShardInput, ActionError& actionError, bool retry = true, int* httpStatusCode = NULL); 438 | }; 439 | 440 | #endif /* AMAZONKINESISCLIENT_H_ */ 441 | -------------------------------------------------------------------------------- /src/AmazonS3Client.cpp: -------------------------------------------------------------------------------- 1 | #include "AmazonS3Client.h" 2 | #include "AWSFoundationalTypes.h" 3 | #include 4 | #include "Utils.h" 5 | 6 | // stub for verifying if the signature actually works, needs to get some more love :) 7 | 8 | AmazonS3Client::AmazonS3Client() : AWSClient4() { 9 | this->awsService = "s3"; 10 | this->signedHeaders = "host;range;x-amz-content-sha256;x-amz-date"; 11 | this->queryString = ""; 12 | this->httpS = true; 13 | } 14 | 15 | char* AmazonS3Client::get(MinimalString uri, ActionError& actionError) { 16 | actionError = NONE_ACTIONERROR; 17 | 18 | this->method = "GET"; 19 | setAWSPath(uri.getCStr()); 20 | MinimalString foo = ""; 21 | char* request = createRequest(foo); 22 | // char* response = sendData(request); 23 | return request; 24 | } 25 | -------------------------------------------------------------------------------- /src/AmazonS3Client.h: -------------------------------------------------------------------------------- 1 | #ifndef AMAZONS3CLIENT_H_ 2 | #define AMAZONS3CLIENT_H_ 3 | #include "AWSClient4.h" 4 | 5 | class AmazonS3Client : public AWSClient4 { 6 | public: 7 | AmazonS3Client(); 8 | char* get(MinimalString uri, ActionError& actionError); 9 | }; 10 | 11 | #endif /* AMAZONS3CLIENT_H_ */ 12 | -------------------------------------------------------------------------------- /src/AmazonSNSClient.cpp: -------------------------------------------------------------------------------- 1 | #include "AmazonSNSClient.h" 2 | #include "AWSFoundationalTypes.h" 3 | #include 4 | #include "Utils.h" 5 | 6 | static const char* SERVICE = "sns"; 7 | static const char* FORM_TYPE = "application/x-www-form-urlencoded"; 8 | static const char* PAYLOAD_TEMPLATE = "Action=Publish&TargetArn=%s&Message=%s&Version=2010-03-31\n"; 9 | int PAYLOAD_TEMPLATE_LENGTH = 54; 10 | int MESSAGEID_BUFFER_LENGTH = 37; 11 | int EXTRACTED_TIMESTAMP_BUFFER_LENGTH = 17; 12 | int FORMATTED_TIMESTAMP_BUFFER_LENGTH = 15; 13 | 14 | PublishInput::PublishInput() { 15 | reset(); 16 | } 17 | 18 | void PublishInput::reset() { 19 | targetArnBeenSet = false; 20 | messageBeenSet = false; 21 | } 22 | 23 | bool PublishInput::requiredAreSet() const { 24 | return targetArnBeenSet && messageBeenSet; 25 | } 26 | 27 | void PublishInput::setTargetArn(MinimalString targetArn) { 28 | targetArnBeenSet = true; 29 | this->targetArn = targetArn; 30 | } 31 | 32 | void PublishInput::setMessage(MinimalString message) { 33 | messageBeenSet = true; 34 | this->message = message; 35 | } 36 | 37 | MinimalString PublishInput::getTargetArn() const { 38 | return this->targetArn; 39 | } 40 | 41 | MinimalString PublishInput::getMessage() const { 42 | return this->message; 43 | } 44 | 45 | MinimalString PublishInput::serialize() const { 46 | char* payload = new char[PAYLOAD_TEMPLATE_LENGTH + message.length() + targetArn.length() + 1](); 47 | 48 | sprintf(payload, PAYLOAD_TEMPLATE, targetArn.getCStr(), message.getCStr()); 49 | 50 | return MinimalString(payload); 51 | } 52 | 53 | PublishOutput::PublishOutput() { 54 | reset(); 55 | } 56 | 57 | void PublishOutput::reset() { 58 | messageIdBeenSet = false; 59 | 60 | errorType = MinimalString(); 61 | errorMessage = MinimalString(); 62 | } 63 | 64 | void PublishOutput::setMessageId(MinimalString messageId) { 65 | messageIdBeenSet = true; 66 | this->messageId = messageId; 67 | } 68 | 69 | MinimalString PublishOutput::getMessageId() const { 70 | return this->messageId; 71 | } 72 | 73 | MinimalString PublishOutput::getErrorType() const { 74 | return errorType; 75 | } 76 | 77 | MinimalString PublishOutput::getErrorMessage() const { 78 | return errorMessage; 79 | } 80 | 81 | AmazonSNSClient::AmazonSNSClient() : AWSClient2() { 82 | awsService = SERVICE; 83 | } 84 | 85 | PublishOutput AmazonSNSClient::publish(PublishInput publishInput, ActionError& actionError) { 86 | actionError = NONE_ACTIONERROR; 87 | 88 | PublishOutput publishOutput; 89 | 90 | if (!publishInput.requiredAreSet()) { 91 | actionError = MISSING_REQUIRED_ARGS_ACTIONERROR; 92 | return publishOutput; 93 | } 94 | 95 | contentType = FORM_TYPE; 96 | MinimalString payload = publishInput.serialize(); 97 | 98 | char* request = createRequest(payload); 99 | const char* response = sendData(request); 100 | delete[] request; 101 | 102 | if (response == NULL) { 103 | actionError = CONNECTION_ACTIONERROR; 104 | return publishOutput; 105 | } 106 | 107 | int httpStatusCode = findHttpStatusCode(response); 108 | 109 | if (httpStatusCode == 200) { 110 | char* msgidIdx = strstr(response, ""); 111 | int msgidPos = msgidIdx - response; 112 | 113 | char* msgid = new char[MESSAGEID_BUFFER_LENGTH](); 114 | strncpy(msgid, response + msgidPos + 11, MESSAGEID_BUFFER_LENGTH - 1); 115 | msgid[37] = '\0'; 116 | 117 | publishOutput.setMessageId(msgid); 118 | return publishOutput; 119 | } 120 | 121 | if (httpStatusCode == 403) { 122 | char* ts = strstr(response, "earlier than "); 123 | int pos = ts - response; 124 | 125 | char* newts = new char[EXTRACTED_TIMESTAMP_BUFFER_LENGTH](); 126 | strncpy(newts, response + pos + 31, EXTRACTED_TIMESTAMP_BUFFER_LENGTH - 1); 127 | newts[16] = '\0'; 128 | 129 | char* time = new char[FORMATTED_TIMESTAMP_BUFFER_LENGTH](); 130 | sprintf(time, "%.8s%.6s", newts, newts + 9); 131 | dateTimeProvider->sync(time); 132 | } 133 | 134 | return publishOutput; 135 | } 136 | -------------------------------------------------------------------------------- /src/AmazonSNSClient.h: -------------------------------------------------------------------------------- 1 | #ifndef AMAZONSNSCLIENT_H_ 2 | #define AMAZONSNSCLIENT_H_ 3 | #include "AWSFoundationalTypes.h" 4 | #include "AWSClient2.h" 5 | 6 | class PublishInput { 7 | MinimalString targetArn; 8 | MinimalString message; 9 | bool targetArnBeenSet; 10 | bool messageBeenSet; 11 | void reset(); 12 | public: 13 | PublishInput(); 14 | bool requiredAreSet() const; 15 | void setMessage(MinimalString message); 16 | void setTargetArn(MinimalString targetArn); 17 | MinimalString getMessage() const; 18 | MinimalString getTargetArn() const; 19 | MinimalString serialize() const; 20 | }; 21 | 22 | class PublishOutput { 23 | MinimalString messageId; 24 | bool messageIdBeenSet; 25 | MinimalString errorType; 26 | MinimalString errorMessage; 27 | void reset(); 28 | public: 29 | PublishOutput(); 30 | MinimalString getErrorType() const; 31 | MinimalString getErrorMessage() const; 32 | void setMessageId(MinimalString messageId); 33 | MinimalString getMessageId() const; 34 | }; 35 | 36 | class AmazonSNSClient : public AWSClient2 { 37 | public: 38 | AmazonSNSClient(); 39 | PublishOutput publish(PublishInput input, ActionError& actionError); 40 | }; 41 | 42 | #endif /* AMAZONSNSCLIENT_H_ */ 43 | -------------------------------------------------------------------------------- /src/DeviceIndependentInterfaces.cpp: -------------------------------------------------------------------------------- 1 | #include "DeviceIndependentInterfaces.h" 2 | 3 | IHttpClient::~IHttpClient() { 4 | } 5 | IDateTimeProvider::~IDateTimeProvider() { 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/DeviceIndependentInterfaces.h: -------------------------------------------------------------------------------- 1 | #ifndef AWSDEVICEINDEPENDENTINTERFACES_H_ 2 | #define AWSDEVICEINDEPENDENTINTERFACES_H_ 3 | 4 | /* HTTPClient Interface. Wraps the functionality of the existing TCPClient, and 5 | * EthernetClient for Spark Core and Intel Galileo Respectively. */ 6 | class IHttpClient { 7 | public: 8 | virtual ~IHttpClient(); 9 | /* Send http request and return the response. */ 10 | virtual const char* send(const char *request, const char* serverUrl, 11 | int port) = 0; 12 | /* Returns true if the client uses a curl command, false if the client uses 13 | * raw http/https. */ 14 | virtual bool usesCurl(void) = 0; 15 | }; 16 | 17 | /* Interface for setting and retrieving the current time. Required for sigv4 18 | * signing. */ 19 | class IDateTimeProvider { 20 | public: 21 | virtual ~IDateTimeProvider(); 22 | /* Retrieve the current GMT date and time in yyyyMMddHHmmss format. */ 23 | virtual const char* getDateTime(void) = 0; 24 | /* Return true if the sync function requires the current time as in 25 | * argument. */ 26 | virtual bool syncTakesArg(void) = 0; 27 | /* Called if AWS Service reports in accurate time. Sets the provider to 28 | * current time. If syncTakesArg() returns true, this argument takes the 29 | * current GMT date and time in yyyyMMddHHmmss format. Else, the nput value 30 | * is ignored and may be null. */ 31 | virtual void sync(const char* dateTime) = 0; 32 | }; 33 | 34 | #endif /* AWSDEVICEINDEPENDENTINTERFACES_H_ */ 35 | -------------------------------------------------------------------------------- /src/ESPAWSImplementations.cpp: -------------------------------------------------------------------------------- 1 | #include "ESPAWSImplementations.h" 2 | #include "DeviceIndependentInterfaces.h" 3 | 4 | #include 5 | #include 6 | 7 | int delayTime = 500; 8 | const char* fingerprint = "20 E4 92 1C D4 B6 39 57 8C EB 41 8B 23 15 9E A4 69 F0 8B F5"; 9 | char* updateCurTime(void); 10 | 11 | EspHttpClient::EspHttpClient() { 12 | } 13 | 14 | const char* 15 | EspHttpClient::send(const char* request, const char* serverUrl, int port) 16 | { 17 | //port = 443; 18 | #if !defined ARDUINO_ARCH_SAM && !defined ARDUINO_ARCH_SAMD 19 | WiFiClientSecure sclient; 20 | #else 21 | WiFiSSLClient sclient; 22 | #endif 23 | Serial.println(serverUrl); 24 | Serial.println(port); 25 | Serial.println(request); 26 | Serial.println(""); 27 | Serial.println(""); 28 | 29 | String response = ""; 30 | 31 | if (sclient.connect(serverUrl, port)) { 32 | #ifdef ESP8266 33 | if(sclient.verify(fingerprint,serverUrl)){ 34 | Serial.println("Certificate Matches"); 35 | } else { 36 | Serial.println("Certificate Does Not Match"); 37 | } 38 | #endif 39 | 40 | // Send the request 41 | sclient.print(request); 42 | // keep reading the response until it's finished 43 | while(sclient.connected()) { 44 | bool availableSeen = false; 45 | while(sclient.available()){ 46 | availableSeen = true; 47 | char c = sclient.read(); 48 | response.concat(c); 49 | } 50 | if(availableSeen) 51 | sclient.stop(); // disconnect any open connections 52 | } 53 | 54 | } else { 55 | Serial.println("Connection Unsuccessful"); 56 | // connection was unsuccessful 57 | sclient.stop(); 58 | return "can't setup SSL connection"; 59 | } 60 | 61 | // convert the string into a char and return 62 | int len = response.length(); 63 | char* response_char = new char[len + 1](); 64 | response.toCharArray(response_char, len + 1); 65 | return response_char; 66 | } 67 | 68 | bool EspHttpClient::usesCurl() { 69 | /* Does not use curl command. */ 70 | return false; 71 | } 72 | 73 | EspDateTimeProvider::EspDateTimeProvider() { 74 | } 75 | 76 | const char* EspDateTimeProvider::getDateTime() { 77 | return updateCurTime(); 78 | } 79 | bool EspDateTimeProvider::syncTakesArg(void) { 80 | return true; 81 | } 82 | 83 | void EspDateTimeProvider::sync(const char* dateTime) { 84 | // should have no need for an implementation 85 | } 86 | 87 | //////////////////////////////////// 88 | // convert month to digits 89 | //////////////////////////////////// 90 | String getMonth(String sM) { 91 | if(sM=="Jan") return "01"; 92 | if(sM=="Feb") return "02"; 93 | if(sM=="Mar") return "03"; 94 | if(sM=="Apr") return "04"; 95 | if(sM=="May") return "05"; 96 | if(sM=="Jun") return "06"; 97 | if(sM=="Jul") return "07"; 98 | if(sM=="Aug") return "08"; 99 | if(sM=="Sep") return "09"; 100 | if(sM=="Oct") return "10"; 101 | if(sM=="Nov") return "11"; 102 | if(sM=="Dec") return "12"; 103 | return "01"; 104 | } 105 | 106 | //////////////////////////////////// 107 | // Scrape UTC Time from server 108 | //////////////////////////////////// 109 | char* updateCurTime(void) { 110 | static int timeout_busy=0; 111 | int ipos; 112 | timeout_busy=0; //reset 113 | 114 | const char* timeServer = "aws.amazon.com"; 115 | 116 | // send a bad header on purpose, so we get a 400 with a DATE: timestamp 117 | const char* timeServerGet = "GET example.com/ HTTP/1.1"; 118 | String utctime; 119 | String GmtDate; 120 | static char dateStamp[20]; 121 | 122 | WiFiClient client; 123 | if (client.connect(timeServer, 80)) { 124 | //Send Request 125 | client.println(timeServerGet); 126 | client.println(); 127 | while((!client.available())&&(timeout_busy++<5000)){ 128 | // Wait until the client sends some data 129 | delay(1); 130 | } 131 | 132 | // kill client if timeout 133 | if(timeout_busy>=5000) { 134 | client.flush(); 135 | client.stop(); 136 | Serial.println("timeout receiving timeserver data\n"); 137 | return dateStamp; 138 | } 139 | 140 | // read the http GET Response 141 | const int buflen = 1024; 142 | uint8_t buffer[buflen]; 143 | client.read(buffer, buflen); 144 | String req2 = (char*)buffer; 145 | // Serial.println(""); 146 | // Serial.println(""); 147 | // Serial.print(req2); 148 | // Serial.println(""); 149 | // Serial.println(""); 150 | 151 | // close connection 152 | delay(1); 153 | client.flush(); 154 | client.stop(); 155 | 156 | ipos = req2.indexOf("Date:"); 157 | if(ipos>0) { 158 | GmtDate = req2.substring(ipos,ipos+35); 159 | Serial.println(GmtDate); 160 | utctime = GmtDate.substring(18,22) + getMonth(GmtDate.substring(14,17)) + GmtDate.substring(11,13) + GmtDate.substring(23,25) + GmtDate.substring(26,28) + GmtDate.substring(29,31); 161 | Serial.println(utctime.substring(0,14)); 162 | utctime.substring(0,14).toCharArray(dateStamp, 20); 163 | } 164 | } 165 | else { 166 | Serial.println("did not connect to timeserver\n"); 167 | } 168 | timeout_busy=0; // reset timeout 169 | return dateStamp; // Return latest or last good dateStamp 170 | } 171 | -------------------------------------------------------------------------------- /src/ESPAWSImplementations.h: -------------------------------------------------------------------------------- 1 | #ifndef AWSESPIMPLEMENTATIONS_H_ 2 | #define AWSESPIMPLEMENTATIONS_H_ 3 | 4 | #include "DeviceIndependentInterfaces.h" 5 | 6 | #if !defined ARDUINO_ARCH_SAM && !defined ARDUINO_ARCH_SAMD 7 | #include 8 | #else 9 | #include 10 | #endif 11 | 12 | #if defined ARDUINO_ARCH_SAM || defined ARDUINO_ARCH_SAMD 13 | #ifdef SERIALUSB 14 | #define Serial SerialUSB 15 | #endif 16 | #endif 17 | 18 | /* HttpClient implementation to be used on the Esp Core device. */ 19 | class EspHttpClient: public IHttpClient { 20 | #if !defined ARDUINO_ARCH_SAM && !defined ARDUINO_ARCH_SAMD 21 | WiFiClientSecure sclient; 22 | #else 23 | WiFiSSLClient sclient; 24 | #endif 25 | public: 26 | EspHttpClient(); 27 | /* Send http request and return the response. */ 28 | const char* send(const char *request, const char* serverUrl, int port); 29 | /* Returns false. Client uses raw http/https. */ 30 | bool usesCurl(void); 31 | }; 32 | 33 | class EspDateTimeProvider: public IDateTimeProvider { 34 | /* The time as a cstring in yyyyMMddHHmmss format. Is written to within and 35 | * returned by getDateTime(). */ 36 | WiFiClient client; 37 | //char dateTime[15]; 38 | public: 39 | char dateTime[15]; 40 | EspDateTimeProvider(); 41 | /* Retrieve the current GMT date and time in yyyyMMddHHmmss format. */ 42 | const char* getDateTime(void); 43 | /* Returns false because Esp has it's own mechanism for syncing that does 44 | * not require an argument. */ 45 | bool syncTakesArg(void); 46 | /* Synchronizes Esp's date and time with Esp's servers. The dateTime 47 | * argument is ignored. */ 48 | void sync(const char* dateTime); 49 | }; 50 | 51 | #endif /* AWSESPIMPLEMENTATIONS_H_ */ 52 | -------------------------------------------------------------------------------- /src/Utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Utils.cpp 3 | * 4 | * See Utils.h for description. 5 | * 6 | * Created on: Jun 30, 2014 7 | * Author: hoffmaj 8 | */ 9 | 10 | #include "Utils.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "jsmn.h" 16 | #include "sha256.h" 17 | 18 | /* Constants for base64Encode. */ 19 | static const char ENCODE_CHARS[] = 20 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 21 | static const char PAD_CHAR = '='; 22 | 23 | /* Constants for findHttpStatusCode. */ 24 | static const char HTTP_STATUS_CODE_PREFIX[] = "HTTP/1.1 "; 25 | static const int HTTP_STATUS_CODE_LEN = 3; 26 | 27 | /* Constants for hmacSha256. */ 28 | static const int BLOCK_SIZE = 64; 29 | static const char OPAD = 0x5c; 30 | static const char IPAD = 0x36; 31 | const int SHA256_DEC_HASH_LEN = 32; 32 | 33 | char *base64Encode(const char *toEncode) { 34 | int inLen = strlen(toEncode); 35 | /* For every three input chars there are 4 output chars, plus an extra 4 36 | * output chars for the possible 1 or 2 remaining input chars, plus an 37 | * extra byte for null termination. */ 38 | size_t encodedLen = (((inLen / 3) + (inLen % 3 > 0)) * 4) + 1; 39 | 40 | char *encoded = new char[encodedLen](); 41 | int chunkIdx; 42 | char inChar1; 43 | char inChar2; 44 | char inChar3; 45 | int outIdx1; 46 | int outIdx2; 47 | int outIdx3; 48 | int outIdx4; 49 | 50 | for (chunkIdx = 0; chunkIdx < inLen / 3; chunkIdx++) { 51 | /* This approach of treating each character individually instead of 52 | * containing all bits in a long type allows the encoding to work on 8, 53 | * 16, 32, and 64 bit systems. */ 54 | inChar1 = *toEncode++; 55 | inChar2 = *toEncode++; 56 | inChar3 = *toEncode++; 57 | 58 | outIdx1 = (inChar1 & 0xFC) >> 2; 59 | outIdx2 = ((inChar1 & 0x03) << 4) + ((inChar2 & 0xF0) >> 4); 60 | outIdx3 = ((inChar2 & 0x0F) << 2) + ((inChar3 & 0xC0) >> 6); 61 | outIdx4 = inChar3 & 0x3F; 62 | 63 | encoded[chunkIdx * 4] = ENCODE_CHARS[outIdx1]; 64 | encoded[chunkIdx * 4 + 1] = ENCODE_CHARS[outIdx2]; 65 | encoded[chunkIdx * 4 + 2] = ENCODE_CHARS[outIdx3]; 66 | encoded[chunkIdx * 4 + 3] = ENCODE_CHARS[outIdx4]; 67 | } 68 | 69 | switch (inLen % 3) { 70 | case 1: 71 | /* 1 extra input char -> 2 output chars and 2 padding chars. */ 72 | 73 | inChar1 = *toEncode++; 74 | 75 | outIdx1 = (inChar1 & 0xFC) >> 2; 76 | outIdx2 = (inChar1 & 0x03) << 4; 77 | 78 | encoded[chunkIdx * 4] = ENCODE_CHARS[outIdx1]; 79 | encoded[chunkIdx * 4 + 1] = ENCODE_CHARS[outIdx2]; 80 | encoded[chunkIdx * 4 + 2] = PAD_CHAR; 81 | encoded[chunkIdx * 4 + 3] = PAD_CHAR; 82 | chunkIdx++; 83 | break; 84 | case 2: 85 | /* 2 extra input chars -> 3 output chars and 1 padding char. */ 86 | 87 | inChar1 = *toEncode++; 88 | inChar2 = *toEncode++; 89 | outIdx1 = (inChar1 & 0xFC) >> 2; 90 | outIdx2 = ((inChar1 & 0x03) << 4) + ((inChar2 & 0xF0) >> 4); 91 | outIdx3 = ((inChar2 & 0x0F) << 2); 92 | encoded[chunkIdx * 4] = ENCODE_CHARS[outIdx1]; 93 | encoded[chunkIdx * 4 + 1] = ENCODE_CHARS[outIdx2]; 94 | encoded[chunkIdx * 4 + 2] = ENCODE_CHARS[outIdx3]; 95 | encoded[chunkIdx * 4 + 3] = PAD_CHAR; 96 | chunkIdx++; 97 | break; 98 | } 99 | /* Ensure null termination. */ 100 | encoded[chunkIdx * 4] = 0; 101 | 102 | return encoded; 103 | } 104 | 105 | int digitCount(int i) { 106 | int digits; 107 | for (digits = 0; i != 0; digits++) 108 | i /= 10; 109 | return digits; 110 | } 111 | 112 | char* escapeQuotes(const char* unescaped) { 113 | int unescapedLen = strlen(unescaped); 114 | 115 | /* Count quotes so that the amount of memory to be allocated can be 116 | * determined */ 117 | int quoteCount = 0; 118 | for (int i = 0; i < unescapedLen; i++) { 119 | if (unescaped[i] == '\"') { 120 | quoteCount++; 121 | } 122 | } 123 | 124 | /* Copy ever character over, including a backslash before every quote. */ 125 | char* escaped = new char[unescapedLen + quoteCount + 1](); 126 | int escapedWritten = 0; 127 | for (int i = 0; i < unescapedLen; i++) { 128 | if (unescaped[i] == '\"') { 129 | escaped[escapedWritten] = '\\'; 130 | escapedWritten++; 131 | } 132 | escaped[escapedWritten] = unescaped[i]; 133 | escapedWritten++; 134 | } 135 | return escaped; 136 | } 137 | 138 | bool findJsonStartEnd(const char* str, int* start, int* end) { 139 | /* Ignore everything before the first unquoted bracket and after the 140 | * unquoted bracket matching the first unquoted bracket, eg. headers and 141 | * newlines. */ 142 | int strLen = strlen(str); 143 | /* Incrememented for every unquoted '{' and decremented for every 144 | * unquoted '}' */ 145 | int braceBalance = 0; 146 | /* -1 is invalid start and end indices. */ 147 | int s = -1; 148 | int e = -1; 149 | bool inQuotes = false; 150 | for (int i = 0; i < strLen; i++) { 151 | /* Toggle inQuotes. */ 152 | if (str[i] == '\"') { 153 | inQuotes = !inQuotes; 154 | } 155 | /* Only consider brackets outside of quotes. */ 156 | if (!inQuotes) { 157 | if (str[i] == '{') { 158 | if (s == -1) { 159 | s = i; 160 | } 161 | braceBalance++; 162 | } else if (str[i] == '}') { 163 | braceBalance--; 164 | if (braceBalance == 0) { 165 | e = i; 166 | break; 167 | } 168 | } 169 | } 170 | } 171 | *start = s; 172 | *end = e; 173 | /* If not a full json string no success. */ 174 | if ((s == -1) || (e == -1)) { 175 | return false; 176 | } 177 | return true; 178 | } 179 | 180 | int findHttpStatusCode(const char* str) { 181 | /* If the input is null OR the input is not long enough to contain the 182 | * error code OR the first characters of the input are not 183 | * HTTP_STATUS_CODE_PREFIX, return 0; */ 184 | if (str == NULL 185 | || strlen(str) 186 | < strlen(HTTP_STATUS_CODE_PREFIX) + HTTP_STATUS_CODE_LEN 187 | || strncmp(HTTP_STATUS_CODE_PREFIX, str, 188 | strlen(HTTP_STATUS_CODE_PREFIX))) { 189 | return 0; 190 | } 191 | /* copy the error code string and convert it to an int. */ 192 | char errorCodeStr[HTTP_STATUS_CODE_LEN + 1]; 193 | strncpy(errorCodeStr, str + strlen(HTTP_STATUS_CODE_PREFIX), 194 | HTTP_STATUS_CODE_LEN); 195 | return atoi(errorCodeStr); 196 | } 197 | 198 | int jsonArraySize(const char* jsonArrayStr, int jsonArrayStrLen) { 199 | int elementCount = 0; 200 | bool inQuotes = false; 201 | if (jsonArrayStr[0] != '[' || jsonArrayStr[jsonArrayStrLen - 1] != ']') { 202 | /* Invalid syntax. */ 203 | return -1; 204 | } 205 | for (int i = 1; i < jsonArrayStrLen - 1; i++) { 206 | if (jsonArrayStr[i] == '"' && jsonArrayStr[i - 1] != '\\') { 207 | if (inQuotes) { 208 | elementCount++; 209 | } 210 | inQuotes = !inQuotes; 211 | } 212 | } 213 | return elementCount; 214 | } 215 | 216 | char** jsonArrayToStringArray(int numOfElements, const char* jsonArrayStr, 217 | int jsonArrayStrLen) { 218 | char** strArray = new char*[numOfElements](); 219 | int start = -1; 220 | int elementCount = 0; 221 | bool inQuotes = false; 222 | if (jsonArrayStr[0] != '[' || jsonArrayStr[jsonArrayStrLen - 1] != ']') { 223 | /* Invalid syntax. */ 224 | return 0; 225 | } 226 | for (int i = 1; i < jsonArrayStrLen - 1; i++) { 227 | if (jsonArrayStr[i] == '"' && jsonArrayStr[i - 1] != '\\') { 228 | if (inQuotes) { 229 | /* Delete values and return null if we find more elements than 230 | * expected. */ 231 | if (elementCount == numOfElements) { 232 | for (int j = 0; j < numOfElements; j++) { 233 | delete[] strArray[j]; 234 | } 235 | delete[] strArray; 236 | return 0; 237 | } 238 | 239 | char* str = new char[(i - start) + 1](); 240 | strncpy(str, jsonArrayStr + start, i - start); 241 | strArray[elementCount] = str; 242 | elementCount++; 243 | } else { 244 | start = i + 1; 245 | } 246 | inQuotes = !inQuotes; 247 | } 248 | } 249 | return strArray; 250 | } 251 | 252 | bool isKey(const char * json, int thisEnd, int nextStart) { 253 | /* Check the characters in between the two tokens. */ 254 | for (int i = thisEnd; i < nextStart; i++) { 255 | if (json[i] == ':') { 256 | /* A ':' means the token on the left is a key. */ 257 | return true; 258 | } else if (json[i] == ',') { 259 | /* A ',' means the token on the left is a value. */ 260 | return false; 261 | } 262 | } 263 | return false; 264 | } 265 | 266 | bool isOuterKey(const char * json, int thisEnd, int nextStart) { 267 | /* Check if token is a key at all before checking if it is an inner key. */ 268 | if (!isKey(json, thisEnd, nextStart)) { 269 | return false; 270 | } 271 | /* True when we are in a quoted section of a string. Ignore braces in a 272 | * quoted section. */ 273 | bool inQuote = false; 274 | /* The number of opening braces encountered minus the number of closing 275 | * braces encountered. braceLevel greater than 1 means we are looking at 276 | * inner keys. */ 277 | int braceLevel = 0; 278 | for (int i = 0; i < thisEnd; i++) { 279 | switch (json[i]) { 280 | case '"': 281 | /* Toggle inQuote. */ 282 | inQuote = !inQuote; 283 | break; 284 | case '{': 285 | /* If not in quotes, increase braceLevel. */ 286 | if (!inQuote) { 287 | braceLevel++; 288 | } 289 | break; 290 | case '}': 291 | /* If not in quotes, decrease braceLevel. */ 292 | if (!inQuote) { 293 | braceLevel--; 294 | } 295 | break; 296 | } 297 | } 298 | /* if we are at brace level 1 upon reaching, the key is in the outermost 299 | * json object */ 300 | return (braceLevel == 1); 301 | 302 | } 303 | 304 | char* jsmnGetVal(const char* key, const char* json, jsmntok_t* tokens, 305 | int tokenCount) { 306 | /* Look at all json tokens. */ 307 | for (int i = 0; i < tokenCount - 1; i++) { 308 | /* Check if token is an outer key. */ 309 | if (isOuterKey(json, tokens[i].end, tokens[i + 1].start)) { 310 | int currentKeyLen = tokens[i].end - tokens[i].start; 311 | int valueLen = tokens[i + 1].end - tokens[i + 1].start; 312 | /* Check if the key we are looking at is the key we are looking 313 | * for. */ 314 | if (((int) strlen(key) == currentKeyLen) 315 | && (strncmp(json + tokens[i].start, key, currentKeyLen) == 0)) { 316 | /* Copy and return the value */ 317 | char* value = new char[valueLen + 1](); 318 | strncpy(value, json + tokens[i + 1].start, valueLen); 319 | return value; 320 | } 321 | } 322 | } 323 | /* Key was not found. */ 324 | return NULL; 325 | } 326 | 327 | char* getTimeFromInvalidSignatureMessage(const char* message) { 328 | int messageLen = strlen(message); 329 | /* Iterate through each character in the string. */ 330 | for (int i = 0; i < messageLen; i++) { 331 | /* If an opening parenthesis is found, copy the following 15 332 | * characters, excluding the 9th character which is a 'T'.*/ 333 | if (message[i] == '(') { 334 | char* time = new char[15](); 335 | sprintf(time, "%.8s%.6s", message + i + 1, message + i + 10); 336 | return time; 337 | } 338 | } 339 | return 0; 340 | } 341 | 342 | char* hmacSha256(const char* key, int keyLen, const char* message, 343 | int messageLen) { 344 | SHA256* sha256 = new SHA256(); 345 | /* The corrected key should be BLOCK_SIZE long. */ 346 | char* correctedKey = new char[BLOCK_SIZE + 1](); 347 | /* If the key is greater than BLOCK_SIZE long, copy over its sha256 hash of 348 | * SHA256_DEC_HASH_LEN, leaving 0-padding to fill the entire BLOCK_SIZE. */ 349 | if ((int) keyLen > BLOCK_SIZE) { 350 | sha256->reset(); 351 | sha256->add(key, keyLen); 352 | char* hashedKey = sha256->getHashDec(); 353 | memcpy(correctedKey, hashedKey, SHA256_DEC_HASH_LEN); 354 | delete[] hashedKey; 355 | } 356 | /* if the key is less than BLOCK_SIZE long, simply copy it over, leaving 357 | * 0-padding to fill the entire BLOCK_SIZE. */ 358 | else { 359 | memcpy(correctedKey, key, keyLen); 360 | } 361 | 362 | /* Using an exclusive or with these OPAD and IPAD values to create the 363 | * iPadded and oPadded values specified by the HMAC algorithm. */ 364 | char* oPadded = new char[BLOCK_SIZE + 1](); 365 | char* iPadded = new char[BLOCK_SIZE + 1](); 366 | for (int i = 0; i < BLOCK_SIZE; i++) { 367 | oPadded[i] = correctedKey[i] ^ OPAD; 368 | iPadded[i] = correctedKey[i] ^ IPAD; 369 | } 370 | 371 | delete[] correctedKey; 372 | 373 | /* Create the inner hash with the concatenation of iPadded and message. */ 374 | sha256->reset(); 375 | sha256->add(iPadded, BLOCK_SIZE); 376 | delete[] iPadded; 377 | sha256->add(message, messageLen); 378 | char* innerHash = sha256->getHashDec(); 379 | 380 | /* Create the outer hash with the concatenation of oPadded and 381 | * innerhash. */ 382 | sha256->reset(); 383 | sha256->add(oPadded, BLOCK_SIZE); 384 | delete[] oPadded; 385 | sha256->add(innerHash, SHA256_DEC_HASH_LEN); 386 | delete[] innerHash; 387 | char* final = sha256->getHashDec(); 388 | delete sha256; 389 | return final; 390 | } 391 | 392 | -------------------------------------------------------------------------------- /src/Utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Utils.h 3 | * 4 | * Utility functions for the Arduino AWS SDK. 5 | * 6 | * Created on: Jun 30, 2014 7 | * Author: hoffmaj 8 | */ 9 | 10 | #ifndef UTILS_H_ 11 | #define UTILS_H_ 12 | 13 | #include 14 | #include "jsmn.h" 15 | 16 | extern const int SHA256_DEC_HASH_LEN; 17 | 18 | /* Base encode 64 an array of characters. Returned array must be deleted by 19 | * caller. */ 20 | char *base64Encode(const char *inputBuffer); 21 | 22 | /* Determine the number of digits in the base 10 representation a given 23 | * number. */ 24 | int digitCount(int i); 25 | 26 | /* Create a string identical to the given string except with backslashes before 27 | * quotes. Caller must delete the returned string. */ 28 | char* escapeQuotes(const char* unescaped); 29 | 30 | /* Determines the index of the opening and closing of a json string that is 31 | * surrounded by non-json, eg. http headers and newlines. Returns true if json 32 | * was found, false otherwise. */ 33 | bool findJsonStartEnd(const char* str, int* start, int* end); 34 | 35 | /* Find and return the status code of an http response. */ 36 | int findHttpStatusCode(const char* str); 37 | 38 | /* Given a char* of json array syntax ( e.g. ["a", "b", "c"]), count the number 39 | * of elements. */ 40 | int jsonArraySize(const char* jsonArrayStr, int jsonArrayStrLen); 41 | 42 | /* Given a char* of json array syntax ( e.g. ["a", "b", "c"]) and the number of 43 | * elements, create a matching array of char*'s. Caller must delete the created 44 | * array AND each element. */ 45 | char** jsonArrayToStringArray(int numOfElements, const char* jsonArrayStr, 46 | int jsonArrayStrLen); 47 | 48 | /* Determines whether a token is a json key, given the json string, the token's 49 | * end index, and the next token's start index. This includes keys of inner 50 | * json objects, i.e. both 'a' and 'b' would be a key in '{"a":{"b":1}}'. */ 51 | bool isKey(const char * json, int thisEnd, int nextStart); 52 | 53 | /* Determines whether a token is a json key, given the json string, the token's 54 | * end index, and the next token's start index. This does not include keys of 55 | * inner json objects, i.e. 'a' would be a key in '{"a":{"b":1}}', but 'b' 56 | * would not. */ 57 | bool isOuterKey(const char * json, int thisEnd, int nextStart); 58 | 59 | /* Given a json key, get a corresponding key. Takes the key, the json object 60 | * string, and the jsmn tokens. Returns 0 if key does not exist. If key does 61 | * exist, user must free the memory allocated to store the value. */ 62 | char* jsmnGetVal(const char* key, const char* json, jsmntok_t* tokens, 63 | int tokenCount); 64 | 65 | /* Very minimal implementation for finding the current time from the message 66 | * returned by a signature failure. This works with at least Kinesis and 67 | * DynamoDB, in which the time is located within the 15 characters following 68 | * the first opening parenthesis. (e.g. "...(20140721T184435Z ..." )*/ 69 | char* getTimeFromInvalidSignatureMessage(const char* message); 70 | 71 | /* Apply hmac to the key and message. */ 72 | char* hmacSha256(const char* key, int keyLen, const char* message, 73 | int messageLen); 74 | 75 | #endif /* UTILS_H_ */ 76 | -------------------------------------------------------------------------------- /src/jsmn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Serge A. Zaitsev 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | 25 | #include "jsmn.h" 26 | 27 | /** 28 | * Allocates a fresh unused token from the token pull. 29 | */ 30 | static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, jsmntok_t *tokens, 31 | size_t num_tokens) { 32 | jsmntok_t *tok; 33 | if (parser->toknext >= num_tokens) { 34 | return NULL; 35 | } 36 | tok = &tokens[parser->toknext++]; 37 | tok->start = tok->end = -1; 38 | tok->size = 0; 39 | #ifdef JSMN_PARENT_LINKS 40 | tok->parent = -1; 41 | #endif 42 | return tok; 43 | } 44 | 45 | /** 46 | * Fills token type and boundaries. 47 | */ 48 | static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, int start, 49 | int end) { 50 | token->type = type; 51 | token->start = start; 52 | token->end = end; 53 | token->size = 0; 54 | } 55 | 56 | /** 57 | * Fills next available token with JSON primitive. 58 | */ 59 | static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, 60 | size_t len, jsmntok_t *tokens, size_t num_tokens) { 61 | jsmntok_t *token; 62 | int start; 63 | 64 | start = parser->pos; 65 | 66 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 67 | switch (js[parser->pos]) { 68 | #ifndef JSMN_STRICT 69 | /* In strict mode primitive must be followed by "," or "}" or "]" */ 70 | case ':': 71 | #endif 72 | case '\t': 73 | case '\r': 74 | case '\n': 75 | case ' ': 76 | case ',': 77 | case ']': 78 | case '}': 79 | goto found; 80 | } 81 | if (js[parser->pos] < 32 || js[parser->pos] >= 127) { 82 | parser->pos = start; 83 | return JSMN_ERROR_INVAL; 84 | } 85 | } 86 | #ifdef JSMN_STRICT 87 | /* In strict mode primitive must be followed by a comma/object/array */ 88 | parser->pos = start; 89 | return JSMN_ERROR_PART; 90 | #endif 91 | 92 | found: if (tokens == NULL) { 93 | parser->pos--; 94 | return (jsmnerr_t) 0; 95 | } 96 | token = jsmn_alloc_token(parser, tokens, num_tokens); 97 | if (token == NULL) { 98 | parser->pos = start; 99 | return JSMN_ERROR_NOMEM; 100 | } 101 | jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); 102 | #ifdef JSMN_PARENT_LINKS 103 | token->parent = parser->toksuper; 104 | #endif 105 | parser->pos--; 106 | return (jsmnerr_t) 0; 107 | } 108 | 109 | /** 110 | * Filsl next token with JSON string. 111 | */ 112 | static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, 113 | size_t len, jsmntok_t *tokens, size_t num_tokens) { 114 | jsmntok_t *token; 115 | 116 | int start = parser->pos; 117 | 118 | parser->pos++; 119 | 120 | /* Skip starting quote */ 121 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 122 | char c = js[parser->pos]; 123 | 124 | /* Quote: end of string */ 125 | if (c == '\"') { 126 | if (tokens == NULL) { 127 | return (jsmnerr_t) 0; 128 | } 129 | token = jsmn_alloc_token(parser, tokens, num_tokens); 130 | if (token == NULL) { 131 | parser->pos = start; 132 | return JSMN_ERROR_NOMEM; 133 | } 134 | jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos); 135 | #ifdef JSMN_PARENT_LINKS 136 | token->parent = parser->toksuper; 137 | #endif 138 | return (jsmnerr_t) 0; 139 | } 140 | 141 | /* Backslash: Quoted symbol expected */ 142 | if (c == '\\') { 143 | parser->pos++; 144 | switch (js[parser->pos]) { 145 | /* Allowed escaped symbols */ 146 | case '\"': 147 | case '/': 148 | case '\\': 149 | case 'b': 150 | case 'f': 151 | case 'r': 152 | case 'n': 153 | case 't': 154 | break; 155 | /* Allows escaped symbol \uXXXX */ 156 | case 'u': 157 | parser->pos++; 158 | int i; 159 | for (i = 0; i < 4 && js[parser->pos] != '\0'; i++) { 160 | /* If it isn't a hex character we have an error */ 161 | if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */ 162 | (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */ 163 | (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */ 164 | parser->pos = start; 165 | return JSMN_ERROR_INVAL; 166 | } 167 | parser->pos++; 168 | } 169 | parser->pos--; 170 | break; 171 | /* Unexpected symbol */ 172 | default: 173 | parser->pos = start; 174 | return JSMN_ERROR_INVAL; 175 | } 176 | } 177 | } 178 | parser->pos = start; 179 | return JSMN_ERROR_PART; 180 | } 181 | 182 | /** 183 | * Parse JSON string and fill tokens. 184 | */ 185 | jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, 186 | jsmntok_t *tokens, unsigned int num_tokens) { 187 | jsmnerr_t r; 188 | int i; 189 | jsmntok_t *token; 190 | int count = 0; 191 | 192 | for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) { 193 | char c; 194 | jsmntype_t type; 195 | 196 | c = js[parser->pos]; 197 | switch (c) { 198 | case '{': 199 | case '[': 200 | count++; 201 | if (tokens == NULL) { 202 | break; 203 | } 204 | token = jsmn_alloc_token(parser, tokens, num_tokens); 205 | if (token == NULL) 206 | return JSMN_ERROR_NOMEM; 207 | if (parser->toksuper != -1) { 208 | tokens[parser->toksuper].size++; 209 | #ifdef JSMN_PARENT_LINKS 210 | token->parent = parser->toksuper; 211 | #endif 212 | } 213 | token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); 214 | token->start = parser->pos; 215 | parser->toksuper = parser->toknext - 1; 216 | break; 217 | case '}': 218 | case ']': 219 | if (tokens == NULL) 220 | break; 221 | type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); 222 | #ifdef JSMN_PARENT_LINKS 223 | if (parser->toknext < 1) { 224 | return JSMN_ERROR_INVAL; 225 | } 226 | token = &tokens[parser->toknext - 1]; 227 | for (;;) { 228 | if (token->start != -1 && token->end == -1) { 229 | if (token->type != type) { 230 | return JSMN_ERROR_INVAL; 231 | } 232 | token->end = parser->pos + 1; 233 | parser->toksuper = token->parent; 234 | break; 235 | } 236 | if (token->parent == -1) { 237 | break; 238 | } 239 | token = &tokens[token->parent]; 240 | } 241 | #else 242 | for (i = parser->toknext - 1; i >= 0; i--) { 243 | token = &tokens[i]; 244 | if (token->start != -1 && token->end == -1) { 245 | if (token->type != type) { 246 | return JSMN_ERROR_INVAL; 247 | } 248 | parser->toksuper = -1; 249 | token->end = parser->pos + 1; 250 | break; 251 | } 252 | } 253 | /* Error if unmatched closing bracket */ 254 | if (i == -1) 255 | return JSMN_ERROR_INVAL; 256 | for (; i >= 0; i--) { 257 | token = &tokens[i]; 258 | if (token->start != -1 && token->end == -1) { 259 | parser->toksuper = i; 260 | break; 261 | } 262 | } 263 | #endif 264 | break; 265 | case '\"': 266 | r = jsmn_parse_string(parser, js, len, tokens, num_tokens); 267 | if (r < 0) 268 | return r; 269 | count++; 270 | if (parser->toksuper != -1 && tokens != NULL) 271 | tokens[parser->toksuper].size++; 272 | break; 273 | case '\t': 274 | case '\r': 275 | case '\n': 276 | case ':': 277 | case ',': 278 | case ' ': 279 | break; 280 | #ifdef JSMN_STRICT 281 | /* In strict mode primitives are: numbers and booleans */ 282 | case '-': 283 | case '0': 284 | case '1': 285 | case '2': 286 | case '3': 287 | case '4': 288 | case '5': 289 | case '6': 290 | case '7': 291 | case '8': 292 | case '9': 293 | case 't': 294 | case 'f': 295 | case 'n': 296 | #else 297 | /* In non-strict mode every unquoted value is a primitive */ 298 | default: 299 | #endif 300 | r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens); 301 | if (r < 0) 302 | return r; 303 | count++; 304 | if (parser->toksuper != -1 && tokens != NULL) 305 | tokens[parser->toksuper].size++; 306 | break; 307 | 308 | #ifdef JSMN_STRICT 309 | /* Unexpected char in strict mode */ 310 | default: 311 | return JSMN_ERROR_INVAL; 312 | #endif 313 | } 314 | } 315 | 316 | for (i = parser->toknext - 1; i >= 0; i--) { 317 | /* Unmatched opened object or array */ 318 | if (tokens[i].start != -1 && tokens[i].end == -1) { 319 | return JSMN_ERROR_PART; 320 | } 321 | } 322 | 323 | return (jsmnerr_t) count; 324 | } 325 | 326 | /** 327 | * Creates a new parser based over a given buffer with an array of tokens 328 | * available. 329 | */ 330 | void jsmn_init(jsmn_parser *parser) { 331 | parser->pos = 0; 332 | parser->toknext = 0; 333 | parser->toksuper = -1; 334 | } 335 | 336 | -------------------------------------------------------------------------------- /src/jsmn.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Serge A. Zaitsev 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #ifndef __JSMN_H_ 24 | #define __JSMN_H_ 25 | #include 26 | #define JSMN_STRICT 27 | #ifdef __cplusplus 28 | extern "C" { 29 | #endif 30 | 31 | /** 32 | * JSON type identifier. Basic types are: 33 | * o Object 34 | * o Array 35 | * o String 36 | * o Other primitive: number, boolean (true/false) or null 37 | */ 38 | typedef enum { 39 | JSMN_PRIMITIVE = 0, JSMN_OBJECT = 1, JSMN_ARRAY = 2, JSMN_STRING = 3 40 | } jsmntype_t; 41 | 42 | typedef enum { 43 | /* Not enough tokens were provided */ 44 | JSMN_ERROR_NOMEM = -1, 45 | /* Invalid character inside JSON string */ 46 | JSMN_ERROR_INVAL = -2, 47 | /* The string is not a full JSON packet, more bytes expected */ 48 | JSMN_ERROR_PART = -3, 49 | } jsmnerr_t; 50 | 51 | /** 52 | * JSON token description. 53 | * @param type type (object, array, string etc.) 54 | * @param start start position in JSON data string 55 | * @param end end position in JSON data string 56 | */ 57 | typedef struct { 58 | jsmntype_t type; 59 | int start; 60 | int end; 61 | int size; 62 | #ifdef JSMN_PARENT_LINKS 63 | int parent; 64 | #endif 65 | } jsmntok_t; 66 | 67 | /** 68 | * JSON parser. Contains an array of token blocks available. Also stores 69 | * the string being parsed now and current position in that string 70 | */ 71 | typedef struct { 72 | unsigned int pos; /* offset in the JSON string */ 73 | unsigned int toknext; /* next token to allocate */ 74 | int toksuper; /* superior token node, e.g parent object or array */ 75 | } jsmn_parser; 76 | 77 | /** 78 | * Create JSON parser over an array of tokens 79 | */ 80 | void jsmn_init(jsmn_parser *parser); 81 | 82 | /** 83 | * Run JSON parser. It parses a JSON data string into and array of tokens, each describing 84 | * a single JSON object. 85 | */ 86 | jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, 87 | jsmntok_t *tokens, unsigned int num_tokens); 88 | 89 | #ifdef __cplusplus 90 | } 91 | #endif 92 | 93 | #endif /* __JSMN_H_ */ 94 | -------------------------------------------------------------------------------- /src/sha256.cpp: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // sha256.cpp 3 | // Copyright (c) 2014 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #include "sha256.h" 8 | 9 | /* These lines have been discarded from the original source code. endian.h is 10 | * not necessarilly included on the devices that will use this SDK. Little 11 | * Endian is assumed instead. */ 12 | // // big endian architectures need #define __BYTE_ORDER __BIG_ENDIAN 13 | // #ifndef _MSC_VER 14 | // #include 15 | // #endif 16 | /// same as reset() 17 | SHA256::SHA256() { 18 | reset(); 19 | } 20 | 21 | /// restart 22 | void SHA256::reset() { 23 | m_numBytes = 0; 24 | m_bufferSize = 0; 25 | 26 | // according to RFC 1321 27 | m_hash[0] = 0x6a09e667; 28 | m_hash[1] = 0xbb67ae85; 29 | m_hash[2] = 0x3c6ef372; 30 | m_hash[3] = 0xa54ff53a; 31 | m_hash[4] = 0x510e527f; 32 | m_hash[5] = 0x9b05688c; 33 | m_hash[6] = 0x1f83d9ab; 34 | m_hash[7] = 0x5be0cd19; 35 | } 36 | 37 | namespace { 38 | inline uint32_t rotate(uint32_t a, uint32_t c) { 39 | return (a >> c) | (a << (32 - c)); 40 | } 41 | 42 | inline uint32_t swap(uint32_t x) { 43 | #if defined(__GNUC__) || defined(__clang__) 44 | return __builtin_bswap32(x); 45 | #endif 46 | #ifdef MSC_VER 47 | return _byteswap_ulong(x); 48 | #endif 49 | 50 | return (x >> 24) | ((x >> 8) & 0x0000FF00) | ((x << 8) & 0x00FF0000) 51 | | (x << 24); 52 | } 53 | 54 | // mix functions for processBlock() 55 | inline uint32_t f1(uint32_t e, uint32_t f, uint32_t g) { 56 | uint32_t term1 = rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25); 57 | uint32_t term2 = (e & f) ^ (~e & g); //(g ^ (e & (f ^ g))) 58 | return term1 + term2; 59 | } 60 | 61 | inline uint32_t f2(uint32_t a, uint32_t b, uint32_t c) { 62 | uint32_t term1 = rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22); 63 | uint32_t term2 = ((a | b) & c) | (a & b); //(a & (b ^ c)) ^ (b & c); 64 | return term1 + term2; 65 | } 66 | } 67 | 68 | /// process 64 bytes 69 | void SHA256::processBlock(const void* data) { 70 | // get last hash 71 | uint32_t a = m_hash[0]; 72 | uint32_t b = m_hash[1]; 73 | uint32_t c = m_hash[2]; 74 | uint32_t d = m_hash[3]; 75 | uint32_t e = m_hash[4]; 76 | uint32_t f = m_hash[5]; 77 | uint32_t g = m_hash[6]; 78 | uint32_t h = m_hash[7]; 79 | 80 | // data represented as 16x 32-bit words 81 | const uint32_t* input = (uint32_t*) data; 82 | // convert to big endian 83 | uint32_t words[64]; 84 | int i; 85 | for (i = 0; i < 16; i++) 86 | #if defined(__BYTE_ORDER) && (__BYTE_ORDER != 0) && (__BYTE_ORDER == __BIG_ENDIAN) 87 | words[i] = input[i]; 88 | #else 89 | words[i] = swap(input[i]); 90 | #endif 91 | 92 | uint32_t x, y; // temporaries 93 | 94 | // first round 95 | x = h + f1(e, f, g) + 0x428a2f98 + words[0]; 96 | y = f2(a, b, c); 97 | d += x; 98 | h = x + y; 99 | x = g + f1(d, e, f) + 0x71374491 + words[1]; 100 | y = f2(h, a, b); 101 | c += x; 102 | g = x + y; 103 | x = f + f1(c, d, e) + 0xb5c0fbcf + words[2]; 104 | y = f2(g, h, a); 105 | b += x; 106 | f = x + y; 107 | x = e + f1(b, c, d) + 0xe9b5dba5 + words[3]; 108 | y = f2(f, g, h); 109 | a += x; 110 | e = x + y; 111 | x = d + f1(a, b, c) + 0x3956c25b + words[4]; 112 | y = f2(e, f, g); 113 | h += x; 114 | d = x + y; 115 | x = c + f1(h, a, b) + 0x59f111f1 + words[5]; 116 | y = f2(d, e, f); 117 | g += x; 118 | c = x + y; 119 | x = b + f1(g, h, a) + 0x923f82a4 + words[6]; 120 | y = f2(c, d, e); 121 | f += x; 122 | b = x + y; 123 | x = a + f1(f, g, h) + 0xab1c5ed5 + words[7]; 124 | y = f2(b, c, d); 125 | e += x; 126 | a = x + y; 127 | 128 | // secound round 129 | x = h + f1(e, f, g) + 0xd807aa98 + words[8]; 130 | y = f2(a, b, c); 131 | d += x; 132 | h = x + y; 133 | x = g + f1(d, e, f) + 0x12835b01 + words[9]; 134 | y = f2(h, a, b); 135 | c += x; 136 | g = x + y; 137 | x = f + f1(c, d, e) + 0x243185be + words[10]; 138 | y = f2(g, h, a); 139 | b += x; 140 | f = x + y; 141 | x = e + f1(b, c, d) + 0x550c7dc3 + words[11]; 142 | y = f2(f, g, h); 143 | a += x; 144 | e = x + y; 145 | x = d + f1(a, b, c) + 0x72be5d74 + words[12]; 146 | y = f2(e, f, g); 147 | h += x; 148 | d = x + y; 149 | x = c + f1(h, a, b) + 0x80deb1fe + words[13]; 150 | y = f2(d, e, f); 151 | g += x; 152 | c = x + y; 153 | x = b + f1(g, h, a) + 0x9bdc06a7 + words[14]; 154 | y = f2(c, d, e); 155 | f += x; 156 | b = x + y; 157 | x = a + f1(f, g, h) + 0xc19bf174 + words[15]; 158 | y = f2(b, c, d); 159 | e += x; 160 | a = x + y; 161 | 162 | // extend to 24 words 163 | for (; i < 24; i++) 164 | words[i] = words[i - 16] 165 | + (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) 166 | ^ (words[i - 15] >> 3)) + words[i - 7] 167 | + (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) 168 | ^ (words[i - 2] >> 10)); 169 | 170 | // third round 171 | x = h + f1(e, f, g) + 0xe49b69c1 + words[16]; 172 | y = f2(a, b, c); 173 | d += x; 174 | h = x + y; 175 | x = g + f1(d, e, f) + 0xefbe4786 + words[17]; 176 | y = f2(h, a, b); 177 | c += x; 178 | g = x + y; 179 | x = f + f1(c, d, e) + 0x0fc19dc6 + words[18]; 180 | y = f2(g, h, a); 181 | b += x; 182 | f = x + y; 183 | x = e + f1(b, c, d) + 0x240ca1cc + words[19]; 184 | y = f2(f, g, h); 185 | a += x; 186 | e = x + y; 187 | x = d + f1(a, b, c) + 0x2de92c6f + words[20]; 188 | y = f2(e, f, g); 189 | h += x; 190 | d = x + y; 191 | x = c + f1(h, a, b) + 0x4a7484aa + words[21]; 192 | y = f2(d, e, f); 193 | g += x; 194 | c = x + y; 195 | x = b + f1(g, h, a) + 0x5cb0a9dc + words[22]; 196 | y = f2(c, d, e); 197 | f += x; 198 | b = x + y; 199 | x = a + f1(f, g, h) + 0x76f988da + words[23]; 200 | y = f2(b, c, d); 201 | e += x; 202 | a = x + y; 203 | 204 | // extend to 32 words 205 | for (; i < 32; i++) 206 | words[i] = words[i - 16] 207 | + (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) 208 | ^ (words[i - 15] >> 3)) + words[i - 7] 209 | + (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) 210 | ^ (words[i - 2] >> 10)); 211 | 212 | // fourth round 213 | x = h + f1(e, f, g) + 0x983e5152 + words[24]; 214 | y = f2(a, b, c); 215 | d += x; 216 | h = x + y; 217 | x = g + f1(d, e, f) + 0xa831c66d + words[25]; 218 | y = f2(h, a, b); 219 | c += x; 220 | g = x + y; 221 | x = f + f1(c, d, e) + 0xb00327c8 + words[26]; 222 | y = f2(g, h, a); 223 | b += x; 224 | f = x + y; 225 | x = e + f1(b, c, d) + 0xbf597fc7 + words[27]; 226 | y = f2(f, g, h); 227 | a += x; 228 | e = x + y; 229 | x = d + f1(a, b, c) + 0xc6e00bf3 + words[28]; 230 | y = f2(e, f, g); 231 | h += x; 232 | d = x + y; 233 | x = c + f1(h, a, b) + 0xd5a79147 + words[29]; 234 | y = f2(d, e, f); 235 | g += x; 236 | c = x + y; 237 | x = b + f1(g, h, a) + 0x06ca6351 + words[30]; 238 | y = f2(c, d, e); 239 | f += x; 240 | b = x + y; 241 | x = a + f1(f, g, h) + 0x14292967 + words[31]; 242 | y = f2(b, c, d); 243 | e += x; 244 | a = x + y; 245 | 246 | // extend to 40 words 247 | for (; i < 40; i++) 248 | words[i] = words[i - 16] 249 | + (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) 250 | ^ (words[i - 15] >> 3)) + words[i - 7] 251 | + (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) 252 | ^ (words[i - 2] >> 10)); 253 | 254 | // fifth round 255 | x = h + f1(e, f, g) + 0x27b70a85 + words[32]; 256 | y = f2(a, b, c); 257 | d += x; 258 | h = x + y; 259 | x = g + f1(d, e, f) + 0x2e1b2138 + words[33]; 260 | y = f2(h, a, b); 261 | c += x; 262 | g = x + y; 263 | x = f + f1(c, d, e) + 0x4d2c6dfc + words[34]; 264 | y = f2(g, h, a); 265 | b += x; 266 | f = x + y; 267 | x = e + f1(b, c, d) + 0x53380d13 + words[35]; 268 | y = f2(f, g, h); 269 | a += x; 270 | e = x + y; 271 | x = d + f1(a, b, c) + 0x650a7354 + words[36]; 272 | y = f2(e, f, g); 273 | h += x; 274 | d = x + y; 275 | x = c + f1(h, a, b) + 0x766a0abb + words[37]; 276 | y = f2(d, e, f); 277 | g += x; 278 | c = x + y; 279 | x = b + f1(g, h, a) + 0x81c2c92e + words[38]; 280 | y = f2(c, d, e); 281 | f += x; 282 | b = x + y; 283 | x = a + f1(f, g, h) + 0x92722c85 + words[39]; 284 | y = f2(b, c, d); 285 | e += x; 286 | a = x + y; 287 | 288 | // extend to 48 words 289 | for (; i < 48; i++) 290 | words[i] = words[i - 16] 291 | + (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) 292 | ^ (words[i - 15] >> 3)) + words[i - 7] 293 | + (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) 294 | ^ (words[i - 2] >> 10)); 295 | 296 | // sixth round 297 | x = h + f1(e, f, g) + 0xa2bfe8a1 + words[40]; 298 | y = f2(a, b, c); 299 | d += x; 300 | h = x + y; 301 | x = g + f1(d, e, f) + 0xa81a664b + words[41]; 302 | y = f2(h, a, b); 303 | c += x; 304 | g = x + y; 305 | x = f + f1(c, d, e) + 0xc24b8b70 + words[42]; 306 | y = f2(g, h, a); 307 | b += x; 308 | f = x + y; 309 | x = e + f1(b, c, d) + 0xc76c51a3 + words[43]; 310 | y = f2(f, g, h); 311 | a += x; 312 | e = x + y; 313 | x = d + f1(a, b, c) + 0xd192e819 + words[44]; 314 | y = f2(e, f, g); 315 | h += x; 316 | d = x + y; 317 | x = c + f1(h, a, b) + 0xd6990624 + words[45]; 318 | y = f2(d, e, f); 319 | g += x; 320 | c = x + y; 321 | x = b + f1(g, h, a) + 0xf40e3585 + words[46]; 322 | y = f2(c, d, e); 323 | f += x; 324 | b = x + y; 325 | x = a + f1(f, g, h) + 0x106aa070 + words[47]; 326 | y = f2(b, c, d); 327 | e += x; 328 | a = x + y; 329 | 330 | // extend to 56 words 331 | for (; i < 56; i++) 332 | words[i] = words[i - 16] 333 | + (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) 334 | ^ (words[i - 15] >> 3)) + words[i - 7] 335 | + (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) 336 | ^ (words[i - 2] >> 10)); 337 | 338 | // seventh round 339 | x = h + f1(e, f, g) + 0x19a4c116 + words[48]; 340 | y = f2(a, b, c); 341 | d += x; 342 | h = x + y; 343 | x = g + f1(d, e, f) + 0x1e376c08 + words[49]; 344 | y = f2(h, a, b); 345 | c += x; 346 | g = x + y; 347 | x = f + f1(c, d, e) + 0x2748774c + words[50]; 348 | y = f2(g, h, a); 349 | b += x; 350 | f = x + y; 351 | x = e + f1(b, c, d) + 0x34b0bcb5 + words[51]; 352 | y = f2(f, g, h); 353 | a += x; 354 | e = x + y; 355 | x = d + f1(a, b, c) + 0x391c0cb3 + words[52]; 356 | y = f2(e, f, g); 357 | h += x; 358 | d = x + y; 359 | x = c + f1(h, a, b) + 0x4ed8aa4a + words[53]; 360 | y = f2(d, e, f); 361 | g += x; 362 | c = x + y; 363 | x = b + f1(g, h, a) + 0x5b9cca4f + words[54]; 364 | y = f2(c, d, e); 365 | f += x; 366 | b = x + y; 367 | x = a + f1(f, g, h) + 0x682e6ff3 + words[55]; 368 | y = f2(b, c, d); 369 | e += x; 370 | a = x + y; 371 | 372 | // extend to 64 words 373 | for (; i < 64; i++) 374 | words[i] = words[i - 16] 375 | + (rotate(words[i - 15], 7) ^ rotate(words[i - 15], 18) 376 | ^ (words[i - 15] >> 3)) + words[i - 7] 377 | + (rotate(words[i - 2], 17) ^ rotate(words[i - 2], 19) 378 | ^ (words[i - 2] >> 10)); 379 | 380 | // eigth round 381 | x = h + f1(e, f, g) + 0x748f82ee + words[56]; 382 | y = f2(a, b, c); 383 | d += x; 384 | h = x + y; 385 | x = g + f1(d, e, f) + 0x78a5636f + words[57]; 386 | y = f2(h, a, b); 387 | c += x; 388 | g = x + y; 389 | x = f + f1(c, d, e) + 0x84c87814 + words[58]; 390 | y = f2(g, h, a); 391 | b += x; 392 | f = x + y; 393 | x = e + f1(b, c, d) + 0x8cc70208 + words[59]; 394 | y = f2(f, g, h); 395 | a += x; 396 | e = x + y; 397 | x = d + f1(a, b, c) + 0x90befffa + words[60]; 398 | y = f2(e, f, g); 399 | h += x; 400 | d = x + y; 401 | x = c + f1(h, a, b) + 0xa4506ceb + words[61]; 402 | y = f2(d, e, f); 403 | g += x; 404 | c = x + y; 405 | x = b + f1(g, h, a) + 0xbef9a3f7 + words[62]; 406 | y = f2(c, d, e); 407 | f += x; 408 | b = x + y; 409 | x = a + f1(f, g, h) + 0xc67178f2 + words[63]; 410 | y = f2(b, c, d); 411 | e += x; 412 | a = x + y; 413 | 414 | // update hash 415 | m_hash[0] += a; 416 | m_hash[1] += b; 417 | m_hash[2] += c; 418 | m_hash[3] += d; 419 | m_hash[4] += e; 420 | m_hash[5] += f; 421 | m_hash[6] += g; 422 | m_hash[7] += h; 423 | } 424 | 425 | /// add arbitrary number of bytes 426 | void SHA256::add(const void* data, size_t numBytes) { 427 | const uint8_t* current = (const uint8_t*) data; 428 | 429 | if (m_bufferSize > 0) { 430 | while (numBytes > 0 && m_bufferSize < BlockSize) { 431 | m_buffer[m_bufferSize++] = *current++; 432 | numBytes--; 433 | } 434 | } 435 | 436 | // full buffer 437 | if (m_bufferSize == BlockSize) { 438 | processBlock(m_buffer); 439 | m_numBytes += BlockSize; 440 | m_bufferSize = 0; 441 | } 442 | 443 | // no more data ? 444 | if (numBytes == 0) 445 | return; 446 | 447 | // process full blocks 448 | while (numBytes >= BlockSize) { 449 | processBlock(current); 450 | current += BlockSize; 451 | m_numBytes += BlockSize; 452 | numBytes -= BlockSize; 453 | } 454 | 455 | // keep remaining bytes in buffer 456 | while (numBytes > 0) { 457 | m_buffer[m_bufferSize++] = *current++; 458 | numBytes--; 459 | } 460 | } 461 | 462 | /// process final block, less than 64 bytes 463 | void SHA256::processBuffer() { 464 | // the input bytes are considered as bits strings, where the first bit is the most significant bit of the byte 465 | 466 | // - append "1" bit to message 467 | // - append "0" bits until message length in bit mod 512 is 448 468 | // - append length as 64 bit integer 469 | 470 | // number of bits 471 | size_t paddedLength = m_bufferSize * 8; 472 | 473 | // plus one bit set to 1 (always appended) 474 | paddedLength++; 475 | 476 | // number of bits must be (numBits % 512) = 448 477 | size_t lower11Bits = paddedLength & 511; 478 | if (lower11Bits <= 448) 479 | paddedLength += 448 - lower11Bits; 480 | else 481 | paddedLength += 512 + 448 - lower11Bits; 482 | // convert from bits to bytes 483 | paddedLength /= 8; 484 | 485 | // only needed if additional data flows over into a second block 486 | unsigned char extra[BlockSize]; 487 | 488 | // append a "1" bit, 128 => binary 10000000 489 | if (m_bufferSize < BlockSize) 490 | m_buffer[m_bufferSize] = 128; 491 | else 492 | extra[0] = 128; 493 | 494 | size_t i; 495 | for (i = m_bufferSize + 1; i < BlockSize; i++) 496 | m_buffer[i] = 0; 497 | for (; i < paddedLength; i++) 498 | extra[i - BlockSize] = 0; 499 | 500 | // add message length in bits as 64 bit number 501 | uint64_t msgBits = 8 * (m_numBytes + m_bufferSize); 502 | // find right position 503 | unsigned char* addLength; 504 | if (paddedLength < BlockSize) 505 | addLength = m_buffer + paddedLength; 506 | else 507 | addLength = extra + paddedLength - BlockSize; 508 | 509 | // must be big endian 510 | *addLength++ = (msgBits >> 56) & 0xFF; 511 | *addLength++ = (msgBits >> 48) & 0xFF; 512 | *addLength++ = (msgBits >> 40) & 0xFF; 513 | *addLength++ = (msgBits >> 32) & 0xFF; 514 | *addLength++ = (msgBits >> 24) & 0xFF; 515 | *addLength++ = (msgBits >> 16) & 0xFF; 516 | *addLength++ = (msgBits >> 8) & 0xFF; 517 | *addLength = msgBits & 0xFF; 518 | 519 | // process blocks 520 | processBlock(m_buffer); 521 | // flowed over into a second block ? 522 | if (paddedLength > BlockSize) 523 | processBlock(extra); 524 | } 525 | 526 | /// return latest hash as 16 hex characters 527 | /* Modified from original source code. Using char* instead of string. */ 528 | char* SHA256::getHash() { 529 | // convert hash to string 530 | static const char dec2hex[16 + 1] = "0123456789abcdef"; 531 | 532 | // save old hash if buffer is partially filled 533 | uint32_t oldHash[HashValues]; 534 | for (int i = 0; i < HashValues; i++) 535 | oldHash[i] = m_hash[i]; 536 | 537 | // process remaining bytes 538 | processBuffer(); 539 | 540 | // create hash string 541 | /* Modified from original source code. Creating char* instead of string. */ 542 | char* hashBuffer = new char[HashValues * 8 + 1](); 543 | size_t offset = 0; 544 | for (int i = 0; i < HashValues; i++) { 545 | hashBuffer[offset++] = dec2hex[(m_hash[i] >> 28) & 15]; 546 | hashBuffer[offset++] = dec2hex[(m_hash[i] >> 24) & 15]; 547 | hashBuffer[offset++] = dec2hex[(m_hash[i] >> 20) & 15]; 548 | hashBuffer[offset++] = dec2hex[(m_hash[i] >> 16) & 15]; 549 | hashBuffer[offset++] = dec2hex[(m_hash[i] >> 12) & 15]; 550 | hashBuffer[offset++] = dec2hex[(m_hash[i] >> 8) & 15]; 551 | hashBuffer[offset++] = dec2hex[(m_hash[i] >> 4) & 15]; 552 | hashBuffer[offset++] = dec2hex[m_hash[i] & 15]; 553 | 554 | // restore old hash 555 | m_hash[i] = oldHash[i]; 556 | } 557 | // zero-terminated string 558 | hashBuffer[offset] = 0; 559 | 560 | return hashBuffer; 561 | } 562 | 563 | /* This function added to original source. It is a modified version of 564 | * getHash() from above. */ 565 | /// return latest hash raw (not as hex) 566 | char* SHA256::getHashDec() { 567 | // save old hash if buffer is partially filled 568 | uint32_t oldHash[HashValues]; 569 | for (int i = 0; i < HashValues; i++) 570 | oldHash[i] = m_hash[i]; 571 | 572 | // process remaining bytes 573 | processBuffer(); 574 | 575 | // create hash string 576 | /* Modified from original source code. Creating char* instead of string. */ 577 | char* hashBuffer = new char[HashValues * 4 + 1](); 578 | size_t offset = 0; 579 | for (int i = 0; i < HashValues; i++) { 580 | hashBuffer[offset++] = (m_hash[i] >> 24) & 0xff; 581 | hashBuffer[offset++] = (m_hash[i] >> 16) & 0xff; 582 | hashBuffer[offset++] = (m_hash[i] >> 8) & 0xff; 583 | hashBuffer[offset++] = m_hash[i] & 0xff; 584 | // restore old hash 585 | m_hash[i] = oldHash[i]; 586 | } 587 | // zero-terminated string 588 | hashBuffer[offset] = 0; 589 | 590 | return hashBuffer; 591 | } 592 | 593 | /// compute SHA256 of a memory block 594 | /* Modified from original source code. Using char* instead of string. */ 595 | char* SHA256::operator()(const void* data, size_t numBytes) { 596 | reset(); 597 | add(data, numBytes); 598 | return getHash(); 599 | } 600 | 601 | /* These lines have been discarded from the original source code. std::string object 602 | * is not necessarilly included on the devices that will use this SDK.*/ 603 | ///// compute SHA256 of a string, excluding final zero 604 | //std::string SHA256::operator()(const std::string& text) 605 | //{ 606 | // reset(); 607 | // add(text.c_str(), text.size()); 608 | // return getHash(); 609 | //} 610 | -------------------------------------------------------------------------------- /src/sha256.h: -------------------------------------------------------------------------------- 1 | // ////////////////////////////////////////////////////////// 2 | // sha256.h 3 | // Copyright (c) 2014 Stephan Brumme. All rights reserved. 4 | // see http://create.stephan-brumme.com/disclaimer.html 5 | // 6 | 7 | #pragma once 8 | 9 | //#include "hash.h" 10 | 11 | /* This include statement has been discarded from the original source code. Not 12 | * using string object as it is not necessarilly included on the devices that 13 | * will use this SDK. */ 14 | //#include 15 | // define fixed size integer types 16 | #ifdef _MSC_VER 17 | // Windows 18 | typedef unsigned __int8 uint8_t; 19 | typedef unsigned __int32 uint32_t; 20 | typedef unsigned __int64 uint64_t; 21 | #else 22 | // GCC 23 | #include 24 | #endif 25 | // This line added to original source code. defines size_t. 26 | #include "stddef.h" 27 | 28 | /// compute SHA256 hash 29 | /** Usage: 30 | SHA256 sha256; 31 | std::string myHash = sha256("Hello World"); // std::string 32 | std::string myHash2 = sha256("How are you", 11); // arbitrary data, 11 bytes 33 | 34 | // or in a streaming fashion: 35 | 36 | SHA256 sha256; 37 | while (more data available) 38 | sha256.add(pointer to fresh data, number of new bytes); 39 | std::string myHash3 = sha256.getHash(); 40 | */ 41 | class SHA256 //: public Hash 42 | { 43 | public: 44 | /// same as reset() 45 | SHA256(); 46 | 47 | /// compute SHA256 of a memory block 48 | /* Modified from original source code. Using char* instead of string. */ 49 | char* operator()(const void* data, size_t numBytes); 50 | /// compute SHA256 of a string, excluding final zero 51 | /* Modified from original source code. Using char* instead of string. */ 52 | char* operator()(const char* text); 53 | 54 | /// add arbitrary number of bytes 55 | void add(const void* data, size_t numBytes); 56 | 57 | /// return latest hash as 16 hex characters 58 | /* Modified from original source code. Using char* instead of string. */ 59 | char* getHash(); 60 | 61 | /* This function added to original source. */ 62 | /// return latest hash raw (not as hex) 63 | char* getHashDec(); 64 | 65 | /// restart 66 | void reset(); 67 | 68 | private: 69 | /// process 64 bytes 70 | void processBlock(const void* data); 71 | /// process everything left in the internal buffer 72 | void processBuffer(); 73 | 74 | /// split into 64 byte blocks (=> 512 bits) 75 | enum { 76 | BlockSize = 512 / 8, HashValues = 8 77 | }; 78 | 79 | /// size of processed data in bytes 80 | uint64_t m_numBytes; 81 | /// valid bytes in m_buffer 82 | size_t m_bufferSize; 83 | /// bytes not processed yet 84 | uint8_t m_buffer[BlockSize]; 85 | /// hash, stored as integers 86 | uint32_t m_hash[8]; 87 | }; 88 | --------------------------------------------------------------------------------