├── LICENSE.txt ├── README.md ├── base64.axi ├── binary.axi ├── cipher.axi ├── codec.axi ├── convert.axi ├── crypto.axi ├── date-time.axi ├── debug.axi ├── dictionary.axi ├── hash.axi ├── hmac.axi ├── http.axi ├── json-rpc.axi ├── json.axi ├── jwt.axi ├── math.axi ├── md5.axi ├── proto.axi ├── sha1.axi ├── sha256.axi ├── string.axi ├── uri.axi ├── websockets.axi └── xml.axi /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 David Vine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # amx-util-library 2 | 3 | --- 4 | 5 | #### The amx-util-library contains various NetLinx include files and modules built to make it easier to perform numerous useful operations which might otherwise be onerous if the amx-util-library did not exist. 6 | 7 | All files are located in the one directory. This is due to the limitations of the NetLinx compiler in that the compiler cannot find a file that is in a sub-directory unless either that file or another file in the same sub-directory is referenced by the system. 8 | 9 | All include files within the amx-util-library are fully commented to make them as easy as possible to use without having to refer to outside documentation. 10 | 11 | Similar to the RMS SDK distributed by AMX many of the include files within the amx-util-library have dependencies of; or are dependencies for; other files within the amx-util-library. These dependencies are outlined as follows: 12 | 13 | --- 14 | 15 | #### `base64.axi` 16 | 17 | Functions relating to the Base64 encoding scheme. 18 | 19 | Usage: 20 | ``` 21 | #include 'base64' 22 | ``` 23 | Dependencies: 24 | * none 25 | 26 | --- 27 | 28 | #### `binary.axi` 29 | 30 | Functions for converting between "typical" data and ASCII strings containing binary representations of the data values. 31 | 32 | Usage: 33 | ``` 34 | #include 'binary' 35 | ``` 36 | Dependencies: 37 | * none 38 | 39 | --- 40 | 41 | #### `cipher.axi` 42 | 43 | **[Not yet implemented]** 44 | 45 | All cryptography libraries relating to encryption/decryption. 46 | 47 | Usage: 48 | ``` 49 | #include 'cipher' 50 | ``` 51 | Dependencies: 52 | * none 53 | 54 | --- 55 | 56 | #### `codec.axi` 57 | 58 | All cryptography libraries relating to encoding/decoding. 59 | 60 | Usage: 61 | ``` 62 | #include 'codec' 63 | ``` 64 | Dependencies: 65 | * `base64.axi` 66 | 67 | --- 68 | 69 | #### `convert.axi` 70 | 71 | Functions for converting between different datatypes or formats. 72 | 73 | Usage: 74 | ``` 75 | #insert 'convert' 76 | ``` 77 | Dependencies: 78 | * none 79 | 80 | --- 81 | 82 | #### `crypto.axi` 83 | 84 | All cryptography libraries. 85 | 86 | Usage: 87 | ``` 88 | #include 'crypto' 89 | ``` 90 | Dependencies: 91 | * `cipher.axi` 92 | * `codec.axi` 93 | * `hash.axi` 94 | 95 | --- 96 | 97 | #### `date-time.axi` 98 | 99 | Functions relating to date/time. 100 | 101 | Usage: 102 | ``` 103 | #include 'date-time' 104 | ``` 105 | Dependencies: 106 | * none 107 | 108 | --- 109 | 110 | #### `debug.axi` 111 | 112 | Functions assisting with debugging. 113 | 114 | Usage: 115 | ``` 116 | #include 'debug.axi' 117 | ``` 118 | Dependencies: 119 | * `debug.axi` 120 | 121 | --- 122 | 123 | #### `dictionary.axi` 124 | 125 | Functions for storing/retrieving data in/from key-value pairs. 126 | 127 | Usage: 128 | ``` 129 | #insert 'dictionary' 130 | ``` 131 | Dependencies: 132 | * none 133 | 134 | --- 135 | 136 | #### `hash.axi` 137 | 138 | All cryptography libraries relating to hashing algorithms. 139 | 140 | Usage: 141 | ``` 142 | #include 'hash' 143 | ``` 144 | Dependencies: 145 | * `md5.axi` 146 | * `sha1.axi` 147 | * `sha256.axi` 148 | 149 | --- 150 | 151 | #### `http.axi` 152 | 153 | Functions to assist with building/parsing HTTP requests/responses. 154 | 155 | Usage: 156 | ``` 157 | #include 'http' 158 | ``` 159 | Dependencies: 160 | * `base64.axi` 161 | * `md5.axi` 162 | * `uri.axi` 163 | 164 | --- 165 | 166 | #### `hmac.axi` 167 | 168 | Function to compute HMAC. 169 | 170 | Usage: 171 | ``` 172 | #include 'hmac' 173 | ``` 174 | Dependencies: 175 | * `md5.axi` 176 | * `sha1.axi` 177 | * `sha256.axi` 178 | 179 | --- 180 | 181 | #### `json.axi` 182 | 183 | Functions to assist with building/parsing JSON strings. 184 | 185 | Usage: 186 | ``` 187 | #include 'json' 188 | ``` 189 | Dependencies: 190 | * `string.axi` 191 | 192 | --- 193 | 194 | #### `json-rpc.axi` 195 | 196 | Functions to assist with building JSON-RPC strings. 197 | 198 | Usage: 199 | ``` 200 | #include 'json-rpc' 201 | ``` 202 | Dependencies: 203 | * `json.axi` 204 | 205 | --- 206 | 207 | #### `jwt.axi` 208 | 209 | Function to assist with building JSON Web Tokens (JWT) 210 | 211 | Usage: 212 | ``` 213 | #include 'jwt' 214 | ``` 215 | Dependencies: 216 | * `json.axi` 217 | * `base64.axi` 218 | * `hmac.axi` 219 | 220 | --- 221 | 222 | #### `math.axi` 223 | 224 | **[Not yet implemented]** 225 | 226 | Functions for various mathematical algorithms. 227 | 228 | Usage: 229 | ``` 230 | #include 'math.axi' 231 | ``` 232 | Dependencies: 233 | * none 234 | 235 | --- 236 | 237 | #### `proto.axi` 238 | 239 | All protocol libraries. 240 | 241 | Usage: 242 | ``` 243 | #include 'proto' 244 | ``` 245 | Dependencies: 246 | * `http.axi` 247 | * `json.axi` 248 | * `uri.axi` 249 | * `websockets.axi` 250 | * `xml.axi` 251 | 252 | --- 253 | 254 | #### `sha1.axi` 255 | 256 | Functions relating to the SHA-1 hashing algorithm. 257 | 258 | Usage: 259 | ``` 260 | #include 'sha1' 261 | ``` 262 | Dependencies: 263 | * `binary.axi` 264 | * `convert.axi` 265 | * `string.axi` 266 | 267 | --- 268 | 269 | #### `sha256.axi` 270 | 271 | Functions relating to the SHA-256 hashing algorithm. 272 | 273 | Usage: 274 | ``` 275 | #include 'sha256' 276 | ``` 277 | Dependencies: 278 | * `binary.axi` 279 | * `convert.axi` 280 | * `string.axi` 281 | 282 | --- 283 | 284 | #### `string.axi` 285 | 286 | Functions extending the built-in string manipulation functions. 287 | 288 | Usage: 289 | ``` 290 | #include 'string' 291 | ``` 292 | Dependencies: 293 | * none 294 | 295 | --- 296 | 297 | #### `uri.axi` 298 | 299 | Functions to assist with building/parsing a URI. 300 | 301 | Usage: 302 | ``` 303 | #include 'uri' 304 | ``` 305 | Dependencies: 306 | * `string.axi` 307 | 308 | --- 309 | 310 | #### `websockets.axi` 311 | 312 | Functions to assist with building/parsing WebSockets frames and manage multiple WebSocket connections. 313 | 314 | Usage: 315 | ``` 316 | #include 'websockets' 317 | ``` 318 | Dependencies: 319 | * `convert.axi` 320 | * `crypto.axi` 321 | * `debug.axi` 322 | * `http.axi` 323 | * `string.axi` 324 | 325 | --- 326 | 327 | #### `xml.axi` 328 | 329 | Functions to assist with building/parsing XML strings. 330 | 331 | Usage: 332 | ``` 333 | #include 'xml' 334 | ``` 335 | Dependencies: 336 | * `string.axi` 337 | 338 | --- 339 | 340 | ## License 341 | 342 | See the [LICENSE](LICENSE.txt) file for license rights and limitations (MIT). -------------------------------------------------------------------------------- /base64.axi: -------------------------------------------------------------------------------- 1 | program_name='base64' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: base64 5 | // 6 | // Description: 7 | // 8 | // - This include file provides a NetLinx implementation of the base64 encoding scheme as defined in RFC 4648 (see 9 | // https://tools.ietf.org/html/rfc4648). 10 | // 11 | // Implementation: 12 | // 13 | // - Any NetLinx program utilising the base64 include file must use either the INCLUDE or #INCLUDE keywords to 14 | // include the base64 include file within the program. While the INCLUDE and #INCLUDE keywords are both 15 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 16 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 17 | // for backwards compatibility). 18 | // 19 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 20 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 21 | // 22 | // E.g: 23 | // 24 | // define_program 'Encoding Demo' 25 | // 26 | // #include 'base64' 27 | // 28 | // Usage: 29 | // 30 | // - The base64Encode function provided within this include file takes a message of unbound length and returns a base64 31 | // encoding of the message. 32 | // 33 | // E.g: 34 | // 35 | // encodedMessage = base64Encode('Hello World'); 36 | // // returns 'SGVsbG8gV29ybGQ=' 37 | // 38 | // 39 | // - The base64Decode function provided within this include file takes a message of unbound length and returns a base64 40 | // decoding of the message. 41 | 42 | // E.g: 43 | // 44 | // decodedMessage = base64Decode('SGVsbG8gV29ybGQ'); 45 | // // returns 'Hello World' 46 | // 47 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 48 | 49 | #if_not_defined __BASE64__ 50 | #define __BASE64__ 51 | 52 | 53 | define_constant 54 | 55 | char BASE64_ALPHABET[64] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 56 | char BASE64URL_ALPHABET[64] = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; 57 | char BASE64_PAD = '='; 58 | 59 | 60 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 61 | // 62 | // Function: base64ReverseAlphabetLookup 63 | // 64 | // Parameters: 65 | // char encodedVal - An 8-bit base64 encoded ASCII value. 66 | // 67 | // Returns: 68 | // char - An 8-bit byte containing a 6-bit value. 69 | // 70 | // Description: 71 | // Converts an 8-bit base64 encoded ASCII value back to the original 6-bit data value. 72 | // 73 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 74 | define_function char base64ReverseAlphabetLookup(char encodedVal) 75 | { 76 | char c; 77 | 78 | for(c=1; c<=64; c++) 79 | { 80 | 81 | if(encodedVal == BASE64_ALPHABET[c]) 82 | { 83 | 84 | return c-1; 85 | } 86 | } 87 | 88 | return 0; 89 | } 90 | 91 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 92 | // 93 | // Function: base64AlphabetLookup 94 | // 95 | // Parameters: 96 | // char val - An 8-bit byte containing a 6-bit value. 97 | // 98 | // Returns: 99 | // char - A base-64 encoded ASCII value. 100 | // 101 | // Description: 102 | // Converts a 6-bit data value to an 8-bit base64 encoded ASCII value. 103 | // 104 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 105 | define_function char base64AlphabetLookup(char val) { 106 | if(val > 63) return type_cast(''); 107 | return BASE64_ALPHABET[val+1]; 108 | } 109 | 110 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 111 | // 112 | // Function: base64UrlAlphabetLookup 113 | // 114 | // Parameters: 115 | // char val - An 8-bit byte containing a 6-bit value. 116 | // 117 | // Returns: 118 | // char - A base-64 URL encoded ASCII value. 119 | // 120 | // Description: 121 | // Converts a 6-bit data value to an 8-bit base64 URL encoded ASCII value. 122 | // 123 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 124 | define_function char base64UrlAlphabetLookup(char val) { 125 | if(val > 63) return type_cast(''); 126 | return BASE64URL_ALPHABET[val+1]; 127 | } 128 | 129 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 130 | // 131 | // Function: base64Encode 132 | // 133 | // Parameters: 134 | // char data[] - A character array of undetermined size containing data to encode. 135 | // 136 | // Returns: 137 | // char[1024] - A character array (string) containing a base64 encoded ASCII string. 138 | // 139 | // Description: 140 | // Encodes the data provided using base64 encoding (as defined in RFC 4648). 141 | // 142 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 143 | define_function char[1024] base64Encode(char data[]) { 144 | char encodedData[1024]; 145 | char val6Bit; 146 | integer i; 147 | 148 | // 1. Split into 6-bit values 149 | // 2. Encode 6-bit values into 8-bit ASCII characters using Base-64 Alphabet 150 | // 3. Pad with '=' to create string length evenly divisible by 4-char blocks 151 | 152 | for(i=1; i<=length_array(data); i++) { 153 | if((i == 1) || (i%3 == 1)) { 154 | val6Bit = type_cast((data[i] BAND $FC) >> 2) 155 | encodedData = "encodedData,base64AlphabetLookup(val6Bit)" 156 | if(i == length_array(data)) { 157 | val6Bit = type_cast((data[i] BAND $3) << 4) 158 | } else { 159 | val6Bit = type_cast(((data[i] BAND $3) << 4) BOR ((data[i+1] BAND $F0) >> 4)) 160 | } 161 | encodedData = "encodedData,base64AlphabetLookup(val6Bit)" 162 | } else if((i == 2) || (i%3 == 2)) { 163 | if(i == length_array(data)) { 164 | val6Bit = type_cast((data[i] BAND $F) << 2) 165 | } else { 166 | val6Bit = type_cast(((data[i] BAND $F) << 2) BOR ((data[i+1] BAND $C0) >> 6)) 167 | } 168 | encodedData = "encodedData,base64AlphabetLookup(val6Bit)" 169 | } else if((i == 3) || (i%3 == 0)) { 170 | val6Bit = type_cast(data[i] BAND $3F) 171 | encodedData = "encodedData,base64AlphabetLookup(val6Bit)" 172 | } 173 | } 174 | 175 | // pad if required 176 | while((length_array(encodedData) % 4) != 0) { 177 | encodedData = "encodedData,BASE64_PAD"; 178 | } 179 | 180 | return encodedData; 181 | } 182 | 183 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 184 | // 185 | // Function: base64UrlEncode 186 | // 187 | // Parameters: 188 | // char data[] - A character array of undetermined size containing data to encode. 189 | // 190 | // Returns: 191 | // char[1024] - A character array (string) containing a base64 URL encoded ASCII string. 192 | // 193 | // Description: 194 | // Encodes the data provided using base64 URL encoding (as defined in RFC 4648). 195 | // 196 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 197 | define_function char[1024] base64UrlEncode(char data[], integer pad) { 198 | char encodedData[1024]; 199 | char val6Bit; 200 | integer i; 201 | 202 | // 1. Split into 6-bit values 203 | // 2. Encode 6-bit values into 8-bit ASCII characters using Base-64 URL Alphabet 204 | // 3. Pad with '=' to create string length evenly divisible by 4-char blocks 205 | 206 | for(i=1; i<=length_array(data); i++) { 207 | if((i == 1) || (i%3 == 1)) { 208 | val6Bit = type_cast((data[i] BAND $FC) >> 2) 209 | encodedData = "encodedData,base64UrlAlphabetLookup(val6Bit)" 210 | if(i == length_array(data)) { 211 | val6Bit = type_cast((data[i] BAND $3) << 4) 212 | } else { 213 | val6Bit = type_cast(((data[i] BAND $3) << 4) BOR ((data[i+1] BAND $F0) >> 4)) 214 | } 215 | encodedData = "encodedData,base64UrlAlphabetLookup(val6Bit)" 216 | } else if((i == 2) || (i%3 == 2)) { 217 | if(i == length_array(data)) { 218 | val6Bit = type_cast((data[i] BAND $F) << 2) 219 | } else { 220 | val6Bit = type_cast(((data[i] BAND $F) << 2) BOR ((data[i+1] BAND $C0) >> 6)) 221 | } 222 | encodedData = "encodedData,base64UrlAlphabetLookup(val6Bit)" 223 | } else if((i == 3) || (i%3 == 0)) { 224 | val6Bit = type_cast(data[i] BAND $3F) 225 | encodedData = "encodedData,base64UrlAlphabetLookup(val6Bit)" 226 | } 227 | } 228 | 229 | // pad if required 230 | if(pad) { 231 | while((length_array(encodedData) % 4) != 0) { 232 | encodedData = "encodedData,BASE64_PAD"; 233 | } 234 | } 235 | 236 | return encodedData; 237 | } 238 | 239 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 240 | // 241 | // Function: base64Decode 242 | // 243 | // Parameters: 244 | // char encodedData[] - A string of base-64 encoded data. 245 | // 246 | // Returns: 247 | // char[1024] - A string containing the unencoded (raw) data. 248 | // 249 | // Description: 250 | // Decodes the data provided using base64 URL decoding (as defined in RFC 4648). 251 | // 252 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 253 | define_function char[1024] base64Decode(char encodedData[]) { 254 | char decodedData[1024]; 255 | char vals6Bit[4]; 256 | integer i; 257 | char bytes[3]; 258 | 259 | 260 | // base64 encoded data consists of 8-bit (byte) ASCII characters 261 | // each of these character represents a 6-bit value 262 | // we need to translate each encoded byte into a 6-bit value 263 | // and "stich" all the 6-bit values together and then "chop" the result 264 | // into 8-bit (byte) values to get the decoded data. 265 | 266 | //send_string 0, "'base64Decode()..start the decode process'"; 267 | for(i=1; i<=length_array(encodedData); i=i+4) 268 | { 269 | 270 | if((encodedData[i+2] == '=') && (encodedData[i+3] == '=')) { 271 | vals6Bit[1] = base64ReverseAlphabetLookup(encodedData[i]); 272 | vals6Bit[2] = base64ReverseAlphabetLookup(encodedData[i+1]); 273 | bytes[1] = type_cast(((vals6Bit[1] BAND $3F) << 2) BOR ((vals6Bit[2] BAND $30) >> 4)); 274 | set_length_array(bytes,1); 275 | decodedData = "decodedData,bytes" 276 | break; 277 | } 278 | else if(encodedData[i+3] == '=') { 279 | vals6Bit[1] = base64ReverseAlphabetLookup(encodedData[i]); 280 | vals6Bit[2] = base64ReverseAlphabetLookup(encodedData[i+1]); 281 | vals6Bit[3] = base64ReverseAlphabetLookup(encodedData[i+2]); 282 | bytes[1] = type_cast(((vals6Bit[1] BAND $3F) << 2) BOR ((vals6Bit[2] BAND $30) >> 4)); 283 | bytes[2] = type_cast(((vals6Bit[2] BAND $F) << 4) BOR ((vals6Bit[3] BAND $3C) >> 2)); 284 | set_length_array(bytes,2); 285 | decodedData = "decodedData,bytes" 286 | break; 287 | } 288 | else { 289 | vals6Bit[1] = base64ReverseAlphabetLookup(encodedData[i]); 290 | vals6Bit[2] = base64ReverseAlphabetLookup(encodedData[i+1]); 291 | vals6Bit[3] = base64ReverseAlphabetLookup(encodedData[i+2]); 292 | vals6Bit[4] = base64ReverseAlphabetLookup(encodedData[i+3]); 293 | bytes[1] = type_cast(((vals6Bit[1] BAND $3F) << 2) BOR ((vals6Bit[2] BAND $30) >> 4)); 294 | bytes[2] = type_cast(((vals6Bit[2] BAND $F) << 4) BOR ((vals6Bit[3] BAND $3C) >> 2)); 295 | bytes[3] = type_cast(((vals6Bit[3] BAND $3) << 6) BOR (vals6Bit[4] BAND $3F)); 296 | set_length_array(bytes,3); 297 | decodedData = "decodedData,bytes" 298 | } 299 | } 300 | 301 | return decodedData; 302 | } 303 | 304 | 305 | #end_if 306 | -------------------------------------------------------------------------------- /binary.axi: -------------------------------------------------------------------------------- 1 | program_name='binary' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: binary 5 | // 6 | // Description: 7 | // 8 | // - This include file provides functions for working with binary values larger than the typical CHAR, INTEGER, or 9 | // LONG data types and for converting between smaller data types and binary formatted ASCII strings (e.g., '10011') 10 | // 11 | // Implementation: 12 | // 13 | // - Any NetLinx program utilising the binary include file must use either the INCLUDE or #INCLUDE keywords to 14 | // include the binary include file within the program. While the INCLUDE and #INCLUDE keywords are both 15 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 16 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 17 | // for backwards compatibility). 18 | // 19 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 20 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 21 | // 22 | // E.g: 23 | // 24 | // define_program 'Binary Demo' 25 | // 26 | // #include 'binary' 27 | // 28 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 29 | 30 | #if_not_defined __BINARY__ 31 | #define __BINARY__ 32 | 33 | 34 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 35 | // 36 | // Function: charToBinary 37 | // 38 | // Parameters: 39 | // char c - A char value 40 | // 41 | // Returns: 42 | // char[8] - An 8-byte character array containing a binary formatted ASCII string representing a char. 43 | // 44 | // Description: 45 | // Converts a char value into a binary formatted ASCII string representing a char. 46 | // 47 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 48 | define_function char[8] charToBinary(char c) { 49 | stack_var char b7, b6, b5, b4, b3, b2, b1, b0; 50 | 51 | b7 = type_cast(itoa((c & $80) >> 7)); 52 | b6 = type_cast(itoa((c & $40) >> 6)); 53 | b5 = type_cast(itoa((c & $20) >> 5)); 54 | b4 = type_cast(itoa((c & $10) >> 4)); 55 | b3 = type_cast(itoa((c & $8) >> 3)); 56 | b2 = type_cast(itoa((c & $4) >> 2)); 57 | b1 = type_cast(itoa((c & $2) >> 1)); 58 | b0 = type_cast(itoa((c & $1))); 59 | 60 | return "b7,b6,b5,b4,b3,b2,b1,b0" 61 | } 62 | 63 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 64 | // 65 | // Function: binaryToChar 66 | // 67 | // Parameters: 68 | // char binary[8] - An 8-byte character array containing a binary formatted ASCII string representing a char. 69 | // 70 | // Returns: 71 | // char - A char value 72 | // 73 | // Description: 74 | // Converts a binary formatted ASCII string representing a char to a char. 75 | // 76 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 77 | define_function char binaryToChar(char binary[8]) { 78 | stack_var char result; 79 | 80 | result = type_cast((atoi("binary[1]") << 7) & $80); 81 | result = (result | type_cast((atoi("binary[2]") << 6) & $40)); 82 | result = (result | type_cast((atoi("binary[3]") << 5) & $20)); 83 | result = (result | type_cast((atoi("binary[4]") << 4) & $10)); 84 | result = (result | type_cast((atoi("binary[5]") << 3) & $8)); 85 | result = (result | type_cast((atoi("binary[6]") << 2) & $4)); 86 | result = (result | type_cast((atoi("binary[7]") << 1) & $2)); 87 | result = (result | type_cast(atoi("binary[8]"))); 88 | 89 | return result; 90 | } 91 | 92 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 93 | // 94 | // Function: longToBinary 95 | // 96 | // Parameters: 97 | // long val - A long value 98 | // 99 | // Returns: 100 | // char[32] - A 32-byte character array containing a binary formatted ASCII string representing a long. 101 | // 102 | // Description: 103 | // Converts a long value into a binary formatted ASCII string representing a long. 104 | // 105 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 106 | define_function char[32] longToBinary(long val) { 107 | stack_var char result[32]; 108 | 109 | result = "charToBinary(type_cast((val & $FF000000) >> 24))"; 110 | result = "result,charToBinary(type_cast((val & $FF0000) >> 16))"; 111 | result = "result,charToBinary(type_cast((val & $FF00) >> 8))"; 112 | result = "result,charToBinary(type_cast((val & $FF)))"; 113 | 114 | return result; 115 | } 116 | 117 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 118 | // 119 | // Function: binaryToLong 120 | // 121 | // Parameters: 122 | // char binary[32] - A 32-byte character array containing a binary formatted ASCII string representing a long. 123 | // 124 | // Returns: 125 | // long - A long value 126 | // 127 | // Description: 128 | // Converts a binary formatted ASCII string representing a long to a long. 129 | // 130 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 131 | define_function long binaryToLong(char binary[32]) { 132 | stack_var long result; 133 | stack_var integer i, j; 134 | 135 | for(i=1, j = 31; i <= length_array(binary); i++, j--) { 136 | result = (result | (atoi("binary[i]") << j)); 137 | } 138 | 139 | return result; 140 | } 141 | 142 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 143 | // 144 | // Function: binaryToString 145 | // 146 | // Parameters: 147 | // char binary[] - A character array of undetermined length containing a binary formatted ASCII string. 148 | // 149 | // Returns: 150 | // char[1048576] - A character array containing the raw-values post conversion. 151 | // 152 | // Description: 153 | // Converts a character array containing a binary formatted ASCII string representing the raw value to a character 154 | // array containing the raw values. 155 | // 156 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 157 | define_function char[1048576] binaryToString(char binary[]) { 158 | stack_var char result[1048576]; 159 | stack_var integer i; 160 | 161 | for(i=1; i < length_array(binary); i=i+8) { 162 | result = "result,binaryToChar(mid_string(binary,i,8))" 163 | } 164 | 165 | return result; 166 | } 167 | 168 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 169 | // 170 | // Function: stringToBinary 171 | // 172 | // Parameters: 173 | // char str[] - A character array of undetermined length. 174 | // 175 | // Returns: 176 | // char[1048576] - A character array containing a binary formatted ASCII string. 177 | // 178 | // Description: 179 | // Converts a character array to a binary formatted ASCII string representing the raw value. 180 | // 181 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 182 | define_function char[1048576] stringToBinary(char str[]) { 183 | stack_var char result[1048576]; 184 | stack_var integer i; 185 | 186 | for(i = 1; i <= length_string(str); i++) { 187 | result = "result,charToBinary(str[i])" 188 | } 189 | 190 | return result; 191 | } 192 | 193 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 194 | // 195 | // Function: leftRotateString 196 | // 197 | // Parameters: 198 | // char msg[] - A character array of undetermined length. 199 | // 200 | // Returns: 201 | // char[1048576] - A character array containing the original message bitwise rotated 1 place to the left 202 | // 203 | // Description: 204 | // Does a bitwise rotation on a character array 1 position to the left. MSB is moved to the LSB and everything else 205 | // it bit-shifted 1 place to the left. 206 | // 207 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 208 | define_function char[1048576] leftRotateString(char msg[]) { 209 | stack_var char result[1048576]; 210 | stack_var long i; 211 | stack_var char leftMostBitVal; 212 | 213 | leftMostBitVal = type_cast((msg[1] & 128) >> 7); 214 | 215 | for(i = 1; i <= length_array(msg); i++) { 216 | if(i != length_array(msg)) { 217 | result[i] = type_cast((msg[i] << 1) | ((msg[i+1] & 128) >> 7)); 218 | } else { 219 | result[i] = type_cast((msg[i] << 1) | leftMostBitVal); 220 | } 221 | } 222 | 223 | set_length_string(result,length_string(msg)); 224 | 225 | return result; 226 | } 227 | 228 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 229 | // 230 | // Function: rightRotateString 231 | // 232 | // Parameters: 233 | // char msg[] - A character array of undetermined length. 234 | // 235 | // Returns: 236 | // char[1048576] - A character array containing the original message bitwise rotated 1 place to the right 237 | // 238 | // Description: 239 | // Does a bitwise rotation on a character array 1 position to the right. LSB is moved to the MSB and everything else 240 | // it bit-shifted 1 place to the right. 241 | // 242 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 243 | define_function char[1048576] rightRotateString(char msg[]) { 244 | stack_var char result[1048576]; 245 | stack_var long i; 246 | stack_var char rightMostBitVal; 247 | 248 | rightMostBitVal = type_cast((msg[length_string(msg)] & 1)); 249 | 250 | for(i = length_array(msg); i >= 1; i--) { 251 | if(i != 1) { 252 | result[i] = type_cast((msg[i] >> 1) | ((msg[i-1] & 1) << 7)); 253 | } else { 254 | result[i] = type_cast((msg[i] >> 1) | (rightMostBitVal << 7)); 255 | } 256 | } 257 | 258 | set_length_string(result,length_string(msg)); 259 | 260 | return result; 261 | } 262 | 263 | 264 | #end_if 265 | -------------------------------------------------------------------------------- /cipher.axi: -------------------------------------------------------------------------------- 1 | program_name='cipher' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: cipher (*NOT IMPLEMENTED AT THIS TIME*) 5 | // 6 | // Description: 7 | // 8 | // - This include file provides generic encrypt/decrypt functions which can be used to encrypt/decrypt data using 9 | // various encryption techniques. 10 | // 11 | // Implementation: 12 | // 13 | // - Any NetLinx program utilising the cipher include file must use either the INCLUDE or #INCLUDE keywords to 14 | // include the cipher include file within the program. While the INCLUDE and #INCLUDE keywords are both 15 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 16 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 17 | // for backwards compatibility). 18 | // 19 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 20 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 21 | // 22 | // E.g: 23 | // 24 | // define_program 'Encryption Demo' 25 | // 26 | // #include 'cipher' 27 | // 28 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 29 | 30 | #if_not_defined __CIPHER__ 31 | #define __CIPHER__ 32 | 33 | 34 | /*#include 'rsa' 35 | #include 'aes' 36 | #include '3des' 37 | #include 'blowfish' 38 | #include 'twofish'*/ 39 | 40 | 41 | /*define_function char[5000] encrypt(char cipher[], char data[]) { 42 | 43 | switch(cipher) { 44 | 45 | case '3DES': { 46 | } 47 | 48 | case 'AES': { 49 | } 50 | 51 | case 'RSA': { 52 | } 53 | 54 | case 'Blowfish': { 55 | } 56 | 57 | case 'Twofish': { 58 | } 59 | } 60 | }*/ 61 | 62 | 63 | /*define_function char[5000] decrypt(char cipher[], char data[]) { 64 | 65 | switch(cipher) { 66 | 67 | case '3DES': { 68 | } 69 | 70 | case 'AES': { 71 | } 72 | 73 | case 'RSA': { 74 | } 75 | 76 | case 'Blowfish': { 77 | } 78 | 79 | case 'Twofish': { 80 | } 81 | } 82 | }*/ 83 | 84 | 85 | #end_if 86 | -------------------------------------------------------------------------------- /codec.axi: -------------------------------------------------------------------------------- 1 | program_name='codec' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: codec 5 | // 6 | // Description: 7 | // 8 | // - This include file provides generic encode/decode functions which can be used to encode/decode data using 9 | // various methods. 10 | // 11 | // Implementation: 12 | // 13 | // - Any NetLinx program utilising the codec include file must use either the INCLUDE or #INCLUDE keywords to include 14 | // the codec include file within the program. While the INCLUDE and #INCLUDE keywords are both functionally 15 | // equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE keyword is 16 | // from the earlier Axcess programming language and is included within the NetLinx programming language for 17 | // backwards compatibility). 18 | // 19 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 20 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 21 | // 22 | // E.g: 23 | // 24 | // define_program 'Encoding Demo' 25 | // 26 | // #include 'codec' 27 | // 28 | // Usage: 29 | // 30 | // - To encode a message simply call the encode function and pass through an ASCII string indicating what encoding 31 | // scheme is to be used followed by the data to be encoded. The encode function will return an encoded version of 32 | // the message. 33 | // 34 | // E.g: 35 | // 36 | // encodedMessage = encode('base64','Hello world'); 37 | // // returns 'SGVsbG8gd29ybGQ=' 38 | // 39 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 40 | 41 | #if_not_defined __CODEC__ 42 | #define __CODEC__ 43 | 44 | 45 | #include 'base64' 46 | 47 | 48 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Function: encode 51 | // 52 | // Parameters: 53 | // char scheme[] - A string representing an encoding scheme. 54 | // char data[] - The data to encode. 55 | // 56 | // Returns: 57 | // char[1024] - The encoded data. 58 | // 59 | // Description: 60 | // Encodes data according to the encoding scheme provided. An empty string is returned if specified encoding scheme 61 | // is unsupported. 62 | // Supported Encoding Schemes: 63 | // - base64 64 | // - base64Url 65 | // 66 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 67 | define_function char[1024] encode(char scheme[], char data[]) { 68 | switch(scheme) { 69 | case 'base64': return base64Encode(data); 70 | 71 | case 'base64Url': return base64UrlEncode(data, true); 72 | } 73 | return ''; 74 | } 75 | 76 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 77 | // 78 | // Function: decode 79 | // 80 | // Parameters: 81 | // char scheme[] - A string representing an encoding scheme ('base64'). 82 | // char data[] - The data to decode. 83 | // 84 | // Returns: 85 | // char[1024] - The decoded data. 86 | // 87 | // Description: 88 | // Decodes data according to the encoding scheme provided. An empty string is returned if specified encoding scheme 89 | // is unsupported. 90 | // Supported Encoding Schemes: 91 | // - base64 92 | // 93 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 94 | define_function char[1024] decode(char scheme[], char data[]) { 95 | switch(scheme) { 96 | case 'base64': return base64Decode(data); 97 | } 98 | return ''; 99 | } 100 | 101 | 102 | #end_if 103 | -------------------------------------------------------------------------------- /convert.axi: -------------------------------------------------------------------------------- 1 | program_name='convert' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: convert 5 | // 6 | // Description: 7 | // 8 | // - This include file provides functions for manipulating data or converting data from one format to another. 9 | // 10 | // Implementation: 11 | // 12 | // - Any NetLinx program utilising the convert include file must use either the INCLUDE or #INCLUDE keywords to 13 | // include the convert include file within the program. While the INCLUDE and #INCLUDE keywords are both 14 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 15 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 16 | // for backwards compatibility). 17 | // 18 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 19 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 20 | // 21 | // E.g: 22 | // 23 | // define_program 'Conversion Demo' 24 | // 25 | // #include 'convert' 26 | // 27 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 28 | 29 | #if_not_defined __CONVERT__ 30 | #define __CONVERT__ 31 | 32 | 33 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 34 | // 35 | // Function: ltoa 36 | // 37 | // Parameters: 38 | // long val - 32-bit value 39 | // 40 | // Returns: 41 | // char[10] - A character array (string) containing the value in numeric ASCII form ('0' - '4294967295') 42 | // 43 | // Description: 44 | // Returns a ASCII numeric string representing the long value parameter. Written as a necessity as the in-built 45 | // itoa function in NetLinx has a bug whereby large long values are returned as negative ASCII numeric strings. 46 | // 47 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 48 | define_function char[10] ltoa(long val) { 49 | if(val <= 100000) { 50 | return itoa(val); 51 | } else { 52 | return "itoa(val/100000),itoa(val%100000)"; 53 | } 54 | } 55 | 56 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 57 | // 58 | // Function: ltba 59 | // 60 | // Parameters: 61 | // long val - 32-bit value 62 | // 63 | // Returns: 64 | // char[4] - A 4-byte character array 65 | // 66 | // Description: 67 | // Returns a the 4-byte long value as a 4-byte char array. 68 | // 69 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 70 | define_function CHAR[4] ltba(long val) { 71 | char result[4]; 72 | 73 | result[1] = type_cast((val & $FF000000) >> 24); 74 | result[2] = type_cast((val & $00FF0000) >> 16); 75 | result[3] = type_cast((val & $0000FF00) >> 8); 76 | result[4] = type_cast((val & $000000FF)); 77 | set_length_array(result,4); 78 | 79 | return result; 80 | } 81 | 82 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 83 | // 84 | // Function: ltle 85 | // 86 | // Parameters: 87 | // long val - 32-bit value. 88 | // 89 | // Returns: 90 | // long - 32-bit value in little-endian format. 91 | // 92 | // Description: 93 | // Takes a long parameter assumed to be in big-endian format and returns a little-endian long. 94 | // 95 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 96 | define_function long ltle(long val) { 97 | return (((val & $FF) << 24) | ((val & $FF00) << 8) | ((val & $FF0000) >> 8) | ((val & $FF000000) >> 24)); 98 | } 99 | 100 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 101 | // 102 | // Function: lcls 103 | // 104 | // Parameters: 105 | // long val - 32-bit value. 106 | // long shift - Number of bits to shift left. 107 | // 108 | // Returns: 109 | // long - 32-bit value. 110 | // 111 | // Description: 112 | // Takes a long parameter, val, and a shift value, s, and returns a value which is a circular left shift of the 113 | // original value, shifted s bits. 114 | // 115 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 116 | define_function long lcls(long val, integer s) { 117 | if(s <= 32) 118 | return ((val << s) | (val >> (32-s))) 119 | else 120 | return 0; 121 | } 122 | 123 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 124 | // 125 | // Function: lcrs 126 | // 127 | // Parameters: 128 | // long val - 32-bit value. 129 | // long shift - Number of bits to shift right. 130 | // 131 | // Returns: 132 | // long - 32-bit value. 133 | // 134 | // Description: 135 | // Takes a long parameter, val, and a shift value, s, and returns a value which is a circular right shift of the 136 | // original value, shifted s bits. 137 | // 138 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 139 | define_function long lcrs(long val, integer s) { 140 | if(s <= 32) 141 | return ((val << (32-s)) | (val >> (s))) 142 | else 143 | return 0; 144 | } 145 | 146 | 147 | #end_if 148 | -------------------------------------------------------------------------------- /crypto.axi: -------------------------------------------------------------------------------- 1 | program_name='crypto' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: crypto 5 | // 6 | // Description: 7 | // 8 | // - This include file includes all high-level cryptography libraries: 9 | // 10 | // codec - for encoding/decoding 11 | // cipher - for encypting/decrypting 12 | // hash - for hashing 13 | // 14 | // Implementation: 15 | // 16 | // - Any NetLinx program utilising the crypto include file must use either the INCLUDE or #INCLUDE keywords to 17 | // include the crypto include file within the program. While the INCLUDE and #INCLUDE keywords are both 18 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 19 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 20 | // for backwards compatibility). 21 | // 22 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 23 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 24 | // 25 | // E.g: 26 | // 27 | // define_program 'Cryptography Demo' 28 | // 29 | // #include 'crypto' 30 | // 31 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 32 | 33 | #if_not_defined __CRYPTO__ 34 | #define __CRYPTO__ 35 | 36 | 37 | #include 'codec' // encoding / decoding 38 | #include 'cipher' // encypting / decypting 39 | #include 'hash' // hashing (message digest) 40 | 41 | 42 | #end_if 43 | -------------------------------------------------------------------------------- /date-time.axi: -------------------------------------------------------------------------------- 1 | program_name='date-time' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: date-time 5 | // 6 | // Description: 7 | // 8 | // - This include file provides functions for working with dates and time. 9 | // 10 | // Implementation: 11 | // 12 | // - Any NetLinx program utilising the date-time include file must use either the INCLUDE or #INCLUDE keywords to 13 | // include the date-time include file within the program. While the INCLUDE and #INCLUDE keywords are both 14 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 15 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 16 | // for backwards compatibility). 17 | // 18 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 19 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 20 | // 21 | // E.g: 22 | // 23 | // define_program 'DateTime Demo' 24 | // 25 | // #include 'date-time' 26 | // 27 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 28 | 29 | 30 | #if_not_defined __DATE_TIME__ 31 | #define __DATE_TIME__ 32 | 33 | 34 | define_type 35 | 36 | struct DateTime { 37 | integer ss; 38 | integer mm; 39 | integer hh; 40 | integer dd; 41 | integer mn; 42 | integer yr; 43 | } 44 | 45 | 46 | define_constant 47 | 48 | integer EPOCH_YEAR = 1970; 49 | integer EPOCH_MONTH = 1; 50 | integer EPOCH_DAY = 1; 51 | integer EPOCH_HOUR = 0; 52 | integer EPOCH_MINUTE = 0; 53 | integer EPOCH_SECOND = 0; 54 | 55 | 56 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 57 | // 58 | // Function: secondsSinceEpoch 59 | // 60 | // Parameters: 61 | // none 62 | // 63 | // Returns: 64 | // slong - Seconds since epoch. 65 | // 66 | // Description: 67 | // Returns the number of seconds that have passed since epoch (00:00:00 1/1/1970). 68 | // 69 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 70 | define_function slong secondsSinceEpoch() { 71 | char currentTime[8]; 72 | DateTime currentDateTime; 73 | 74 | currentTime = TIME; // 'hh:mm:ss' 75 | 76 | currentDateTime.hh = atoi(remove_string(currentTime,':',1)); 77 | currentDateTime.mm = atoi(remove_string(currentTime,':',1)); 78 | currentDateTime.ss = atoi(currentTime); 79 | 80 | return (daysSinceEpoch() * 24 * 60 * 60) + (currentDateTime.hh * 60 * 60) + (currentDateTime.mm * 60) + currentDateTime.ss; 81 | } 82 | 83 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 84 | // 85 | // Function: daysSinceEpoch 86 | // 87 | // Parameters: 88 | // none. 89 | // 90 | // Returns: 91 | // slong - Days since epoch. 92 | // 93 | // Description: 94 | // Returns the number of days that have passed since epoch (00:00:00 1/1/1970). 95 | // 96 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 97 | define_function slong daysSinceEpoch() { 98 | char currentDate[10]; 99 | DateTime currentDateTime; 100 | slong era; 101 | long yoe, doy, doe; 102 | slong y; 103 | 104 | AMX_LOG(AMX_DEBUG, "'date-time::daysSinceEpoch'"); 105 | 106 | currentDate = LDATE; // 'MM/DD/YYYY' 107 | 108 | currentDateTime.mn = atoi(remove_string(currentDate,'/',1)); 109 | currentDateTime.dd = atoi(remove_string(currentDate,'/',1)); 110 | currentDateTime.yr = atoi(currentDate); 111 | 112 | y = (currentDateTime.yr - (currentDateTime.mn <= 2)); 113 | 114 | AMX_LOG(AMX_DEBUG, "'date-time::daysSinceEpoch:y = ',itoa(y)"); 115 | 116 | if(y >= 0) { 117 | era = (y / 400); 118 | } 119 | else { 120 | era = ((y-399) / 400); 121 | } 122 | 123 | AMX_LOG(AMX_DEBUG, "'date-time::daysSinceEpoch:era = ',itoa(era)"); 124 | 125 | yoe = type_cast(y - (era * 400)); 126 | 127 | AMX_LOG(AMX_DEBUG, "'date-time::daysSinceEpoch:yoe = ',itoa(yoe)"); 128 | 129 | if(currentDateTime.mn > 2) { 130 | doy = (((153 * (currentDateTime.mn - 3) + 2) / 5) + currentDateTime.dd - 1); 131 | } 132 | else { 133 | doy = (((153 * (currentDateTime.mn + 9) + 2) / 5) + currentDateTime.dd - 1); 134 | } 135 | 136 | AMX_LOG(AMX_DEBUG, "'date-time::daysSinceEpoch:doy = ',itoa(doy)"); 137 | 138 | doe = ((yoe * 365) + (yoe / 4) - (yoe/100) + doy); 139 | 140 | AMX_LOG(AMX_DEBUG, "'date-time::daysSinceEpoch:doe = ',itoa(doe)"); 141 | 142 | return ((era * type_cast(146097)) + type_cast(doe - 719468)); 143 | 144 | } 145 | 146 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 147 | // 148 | // Function: isLeapYear 149 | // 150 | // Parameters: 151 | // integer year - The year to test. 152 | // 153 | // Returns: 154 | // integer - boolean value (0==false | 1==true). 155 | // 156 | // Description: 157 | // Tests if the specified calendar year is a leap year and returns a boolean result. 158 | // 159 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 160 | define_function integer isLeapYear(integer year) { 161 | if((year % 4) == 0) { 162 | return false; 163 | } 164 | else if((year % 100) == 0) { 165 | if((year % 400) == 0) { 166 | return true; 167 | } 168 | else { 169 | return false; 170 | } 171 | } 172 | else { 173 | return true; 174 | } 175 | } 176 | 177 | 178 | #end_if 179 | -------------------------------------------------------------------------------- /debug.axi: -------------------------------------------------------------------------------- 1 | program_name='debug' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: debug 5 | // 6 | // Description: 7 | // 8 | // - This include file provides useful fuctions for debugging a NetLinx program. 9 | // 10 | // Implementation: 11 | // 12 | // - Any NetLinx program utilising the debug include file must use either the INCLUDE or #INCLUDE keywords to 13 | // include the debug include file within the program. While the INCLUDE and #INCLUDE keywords are both 14 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 15 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 16 | // for backwards compatibility). 17 | // 18 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 19 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 20 | // 21 | // E.g: 22 | // 23 | // define_program 'Debugging Demo' 24 | // 25 | // #include 'debug' 26 | // 27 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 28 | 29 | #if_not_defined __DEBUG__ 30 | #define __DEBUG__ 31 | 32 | 33 | define_variable 34 | 35 | volatile debug = false; 36 | 37 | 38 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 39 | // 40 | // Function: print 41 | // 42 | // Parameters: 43 | // char data[] - A character array of undetermined size containing data to print. 44 | // integer multiLine - An integer containing a true/false value indicating whether the data should be printed 45 | // over multiple lines (if it contains carriage returns or line feeds). 46 | // 47 | // Returns: 48 | // nothing 49 | // 50 | // Description: 51 | // Sends data to the master for debugging purposes. Data can be viewed in the Diagnostics tab of the NetLinx Studio 52 | // application or by opening a telnet session to the master and typing "msg on". 53 | // 54 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 55 | define_function print(char data[], integer multiLine) { 56 | stack_var char temp[20000] 57 | 58 | if(debug) { 59 | if(!multiLine) { 60 | send_string 0, "'DEBUG: ',data" 61 | } else { 62 | temp = data; 63 | while(find_string(temp,"$0D",1) || find_string(temp,"$0A",1)) { 64 | stack_var integer crIdx, lfIdx, crLfIdx; 65 | 66 | crIdx = find_string(temp,"$0D",1); 67 | lfIdx = find_string(temp,"$0A",1); 68 | crLfIdx = find_string(temp,"$0D,$0A",1); 69 | 70 | if((lfIdx) && ((!crIdx) || (lfIdx < crIdx)) && ((!crLfIdx) || (lfIdx < crLfIdx))) { 71 | send_string 0, "'DEBUG: ',remove_string(temp,"$0A",1)" 72 | } else if(crIdx) { 73 | if((crIdx && crLfIdx) && (crIdx == crLfIdx)) { 74 | send_string 0, "'DEBUG: ',remove_string(temp,"$0D,$0A",1)" 75 | } else{ 76 | send_string 0, "'DEBUG: ',remove_string(temp,"$0D",1)" 77 | } 78 | } 79 | } 80 | send_string 0, "'DEBUG: ',remove_string(temp,temp,1)" 81 | } 82 | } 83 | } 84 | 85 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 86 | // 87 | // Function: printHex 88 | // 89 | // Parameters: 90 | // char data[] - A character array of undetermined size containing data to print. 91 | // 92 | // Returns: 93 | // nothing 94 | // 95 | // Description: 96 | // Sends an ASCII Hex representation of the data to the master for debugging purposes. Data can be viewed in the 97 | // Diagnostics tab of the NetLinx Studio application or by opening a telnet session to the master and typing 98 | // "msg on". 99 | // 100 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 101 | define_function printHex(char data[]) { 102 | char result[5000]; 103 | integer i,count; 104 | long len; 105 | 106 | len = length_array(data); 107 | 108 | for(i=1,count=1; i<=length_string(data); i++,count++) { 109 | result = "result,format('%02X',data[i])" 110 | } 111 | print("result",false); 112 | } 113 | 114 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Function: printHexBlock 117 | // 118 | // Parameters: 119 | // char data[] - A character array of undetermined size containing data to print. 120 | // 121 | // Returns: 122 | // nothing 123 | // 124 | // Description: 125 | // Sends an ASCII Hex representation of the data to the master for debugging purposes. A space ' ' character is 126 | // placed between every 4th hex value and a new line is printed after every 16th hex value. Data can be viewed in 127 | // the Diagnostics tab of the NetLinx Studio application or by opening a telnet session to the master and typing 128 | // "msg on". 129 | // 130 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 131 | define_function printHexBlock(char data[]) { 132 | char result[5000]; 133 | integer i,count; 134 | long len; 135 | 136 | len = length_array(data); 137 | 138 | for(i=1,count=1; i<=length_string(data); i++,count++) { 139 | result = "result,format('%02X',data[i])" 140 | 141 | if(count == 16) { 142 | print("result",false); 143 | count = 0; 144 | result = ''; 145 | } else if((count %4) == 0) { 146 | result = "result,' '" 147 | } 148 | } 149 | if(count <16) { 150 | print("result",false); 151 | } 152 | } 153 | 154 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 155 | // 156 | // Function: devToString 157 | // 158 | // Parameters: 159 | // dev device - A NetLinx device 160 | // 161 | // Returns: 162 | // char[17] - A character array containing a string in the form D:P:S 163 | // 164 | // Description: 165 | // Takes a NetLinx device and returns a string containing an ASCII representation of the device number, port, and 166 | // system in the D:P:S form number:port:system (e.g., '10001:1:0') 167 | // 168 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 169 | define_function char[17] devToString(dev device) { 170 | return "itoa(device.number),':',itoa(device.port),':',itoa(device.system)" 171 | } 172 | 173 | 174 | #end_if 175 | -------------------------------------------------------------------------------- /dictionary.axi: -------------------------------------------------------------------------------- 1 | program_name='dictionary' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: dictionary 5 | // 6 | // Description: 7 | // 8 | // - This include file provides structures and functions for keeping data in key/value pairs (i.e., a dictionary). 9 | // 10 | // Implementation: 11 | // 12 | // - Any NetLinx program utilising the dictionary include file must use either the INCLUDE or #INCLUDE keywords to 13 | // include the dictionary include file within the program. While the INCLUDE and #INCLUDE keywords are both 14 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 15 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 16 | // for backwards compatibility). 17 | // 18 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 19 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 20 | // 21 | // E.g: 22 | // 23 | // define_program 'KeyVal Pair Demo' 24 | // 25 | // #include 'dictionary' 26 | // 27 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 28 | 29 | #if_not_defined __DICTIONARY__ 30 | #define __DICTIONARY__ 31 | 32 | 33 | define_constant 34 | 35 | integer DICTIONARY_MAX_KEY_LENGTH = 100; 36 | integer DICTIONARY_MAX_VAL_LENGTH = 500; 37 | integer DICTIONARY_MAX_KEY_VAL_PAIRS = 256; 38 | 39 | 40 | define_type 41 | 42 | struct DictionaryEntry { 43 | char key[DICTIONARY_MAX_KEY_LENGTH]; 44 | char val[DICTIONARY_MAX_VAL_LENGTH]; 45 | } 46 | 47 | struct Dictionary { 48 | DictionaryEntry keyVals[DICTIONARY_MAX_KEY_VAL_PAIRS]; 49 | } 50 | 51 | 52 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 53 | // 54 | // Function: dictionaryAdd 55 | // 56 | // Parameters: 57 | // Dictionary dict - A dictionary used to store key/val pairs. 58 | // char key[] - A character array of undefined length containing a key to add to the dictionary. 59 | // 60 | // Returns: 61 | // integer - An integer containing either true (1) or false(0) indicating successful addition of the key/val 62 | // pair. 63 | // 64 | // Description: 65 | // Adds the key/val pair to the dictionary if the key does not already exist and returns a true (1) result. If the 66 | // key already exists the associated value is simply updated and a true (1) result is returned. Neither the key nor 67 | // value can be empty or the process will be aborted and a false (0) result will be returned. A false (0) result 68 | // will also be returned if the dictionary is already full or the key/val pair already exists within the dictionary. 69 | // 70 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 71 | define_function integer dictionaryAdd (Dictionary dict, char key[], char val[]) { 72 | stack_var integer idx; 73 | 74 | if((key == '') || // empty key 75 | (val == '') || // empty value 76 | (dictionaryGetValue(dict,key) == val)) // same key/val already stored in dictionary 77 | return false; 78 | 79 | idx = dictionaryGetIndex(dict, key); 80 | 81 | if(idx) { // the key exists in the dictionary and since we already know it doesn't have the value we want to add we need to replace it 82 | dict.keyVals[idx].val = val; 83 | } else if(length_array(dict.keyVals) == max_length_array(dict.keyVals)) { // dictionary full 84 | return false; 85 | } else { 86 | set_length_array(dict.keyVals,length_array(dict.keyVals)+1); 87 | dict.keyVals[length_array(dict.keyvals)].key = key; 88 | dict.keyVals[length_array(dict.keyvals)].val = val; 89 | } 90 | 91 | return true; 92 | } 93 | 94 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 95 | // 96 | // Function: dictionaryRemove 97 | // 98 | // Parameters: 99 | // Dictionary dict - A dictionary used to store key/val pairs. 100 | // char key[] - A character array of undefined length containing a key to search with. 101 | // 102 | // Returns: 103 | // integer - An integer containing either true (1) or false(0) indicating successful removal of the key/val 104 | // pair. 105 | // 106 | // Description: 107 | // Searches the dictionary for a matching key and if found deletes the key and associated value from the dictionary 108 | // and returns a true (1) result otherwise returns a false (0) result. 109 | // 110 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 111 | define_function integer dictionaryRemove (Dictionary dict, char key[]) { 112 | stack_var integer idx; 113 | 114 | idx = dictionaryGetIndex(dict, key); 115 | 116 | if((key == '') || // key empty 117 | (!idx)) // key does not exist in dictionary 118 | return false; 119 | 120 | if(idx < length_array(dict.keyVals)) { 121 | dict.keyVals[idx].key = dict.keyVals[length_array(dict.keyVals)].key; 122 | dict.keyVals[idx].val = dict.keyVals[length_array(dict.keyVals)].val; 123 | dict.keyVals[length_array(dict.keyVals)].key = ''; 124 | dict.keyVals[length_array(dict.keyVals)].val = ''; 125 | } 126 | else { 127 | dict.keyVals[idx].key = ''; 128 | dict.keyVals[idx].val = ''; 129 | } 130 | set_length_array(dict.keyVals,length_array(dict.keyVals)-1); 131 | 132 | return true; 133 | } 134 | 135 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 136 | // 137 | // Function: dictionaryGetValue 138 | // 139 | // Parameters: 140 | // Dictionary dict - A dictionary used to store key/val pairs. 141 | // char key[] - A character array of undefined length containing a key to search with. 142 | // 143 | // Returns: 144 | // char[DICTIONARY_MAX_VAL_LENGTH] - A character array containing the value. 145 | // 146 | // Description: 147 | // Searches the dictionary for a matching key and if found returns the associated value otherwise returns an empty 148 | // string (''). 149 | // 150 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 151 | define_function char[DICTIONARY_MAX_VAL_LENGTH] dictionaryGetValue(Dictionary dict, char key[]) { 152 | stack_var integer i; 153 | i = 1; 154 | while(i <= length_array(dict.keyVals)) { 155 | if(dict.keyVals[i].key == key) 156 | return dict.keyVals[i].val; 157 | i++; 158 | } 159 | return ''; 160 | } 161 | 162 | 163 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 164 | // 165 | // Function: dictionaryGetValueIgnoreKeyCase 166 | // 167 | // Parameters: 168 | // Dictionary dict - A dictionary used to store key/val pairs. 169 | // char key[] - A character array of undefined length containing a key to search with 170 | // 171 | // Returns: 172 | // char[DICTIONARY_MAX_VAL_LENGTH] - A character array containing the value. 173 | // 174 | // Description: 175 | // Searches the dictionary for a matching key (case insensitive) and if found returns the associated value otherwise 176 | // returns an empty string (''). 177 | // Note: Because search is for a case-insensitive match and there may be multiple keys with the same spelling (but 178 | // different case (e.g: 'KEYA', 'KeYa') the result will be the first key with matching spelling, regardless of 179 | // case. 180 | // 181 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 182 | define_function char[DICTIONARY_MAX_VAL_LENGTH] dictionaryGetValueIgnoreKeyCase(Dictionary dict, char key[]) { 183 | stack_var integer i; 184 | i = 1; 185 | while(i <= length_array(dict.keyVals)) { 186 | if(upper_string(dict.keyVals[i].key) == upper_string(key)) 187 | return dict.keyVals[i].val; 188 | i++; 189 | } 190 | return ''; 191 | } 192 | 193 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 194 | // 195 | // Function: dictionaryGetIndex 196 | // 197 | // Parameters: 198 | // Dictionary dict - A dictionary used to store key/val pairs. 199 | // char key[] - A character array of undefined length containing a key to search with. 200 | // 201 | // Returns: 202 | // integer - An integer containing the index within the dictionary where the key is stored. 203 | // 204 | // Description: 205 | // Searches the dictionary for a matching key and if found returns the index it is stored at otherwise returns 0. 206 | // 207 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 208 | define_function integer dictionaryGetIndex(Dictionary dict, char key[]) { 209 | stack_var integer i; 210 | i = 1; 211 | while(i <= length_array(dict.keyVals)) { 212 | if(dict.keyVals[i].key == key) 213 | return i; 214 | i++; 215 | } 216 | return 0; 217 | } 218 | 219 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 220 | // 221 | // Function: dictionaryClear 222 | // 223 | // Parameters: 224 | // Dictionary dict - A dictionary used to store key/val pairs. 225 | // 226 | // Returns: 227 | // nothing 228 | // 229 | // Description: 230 | // Clears the dictionary of all key/val pairs 231 | // 232 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 233 | define_function dictionaryClear(Dictionary dict) { 234 | stack_var integer i; 235 | 236 | for(i = 1; i <= max_length_array(dict.keyVals); i++) { 237 | dict.keyVals[i].key = ''; 238 | dict.keyVals[i].val = ''; 239 | } 240 | set_length_array(dict.keyVals,0); 241 | } 242 | 243 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 244 | // 245 | // Function: dictionaryCopy 246 | // 247 | // Parameters: 248 | // Dictionary dictCopyFrom - A dictionary used to store key/val pairs. 249 | // Dictionary dictCopyTo - A dictionary used to store key/val pairs. 250 | // 251 | // Returns: 252 | // nothing 253 | // 254 | // Description: 255 | // Copies one dictionary to another 256 | // 257 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 258 | define_function dictionaryCopy(Dictionary dictCopyFrom,Dictionary dictCopyTo) { 259 | stack_var integer i; 260 | 261 | dictionaryClear(dictCopyTo); 262 | 263 | for(i = 1; i <= length_array(dictCopyFrom.keyVals); i++) { 264 | dictCopyTo.keyVals[i].key = dictCopyFrom.keyVals[i].key; 265 | dictCopyTo.keyVals[i].val = dictCopyFrom.keyVals[i].val; 266 | } 267 | set_length_array(dictCopyTo.keyVals,length_array(dictCopyFrom.keyVals)); 268 | } 269 | 270 | 271 | #end_if 272 | -------------------------------------------------------------------------------- /hash.axi: -------------------------------------------------------------------------------- 1 | program_name='hash' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: hash 5 | // 6 | // Description: 7 | // 8 | // - This include file provides a generic hash function which can be used to create various types of message digests 9 | // 10 | // Implementation: 11 | // 12 | // - Any NetLinx program utilising the hash include file must use either the INCLUDE or #INCLUDE keywords to include 13 | // the hash include file within the program. While the INCLUDE and #INCLUDE keywords are both functionally 14 | // equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE keyword is 15 | // from the earlier Axcess programming language and is included within the NetLinx programming language for 16 | // backwards compatibility). 17 | // 18 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 19 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 20 | // 21 | // E.g: 22 | // 23 | // define_program 'Hash Demo' 24 | // 25 | // #include 'hash' 26 | // 27 | // Usage: 28 | // 29 | // - To create a message digest simply call the hash function and pass through an ASCII string indicating what 30 | // hashing scheme is to be used followed by the data to be hashed. The hash function will return the message 31 | // digest in a char array. 32 | // 33 | // E.g: 34 | // 35 | // hashedPassword = hash('md5','p@55w0rd'); 36 | // // returns "$39,$F1,$3D,$60,$B3,$F6,$FB,$E0,$BA,$16,$36,$B0,$A9,$28,$3C,$50" 37 | // 38 | // hashedPassword = hash('sha1','p@55w0rd'); 39 | // // returns "$CE,$0B,$2B,$77,$1F,$7D,$46,$8C,$01,$41,$91,$8D,$AE,$A7,$04,$E0,$E5,$AD,$45,$DB" 40 | // 41 | // hashedPassword = hash('sha256','p@55w0rd'); 42 | // // returns "$59,$F4,$6B,$B9,$0C,$FF,$B0,$ED,$7C,$7E,$5D,$B5,$8B,$B3,$00,$F3,$BC,$D7,$14,$F5,$1A,$E7,$23,$ED,$91,$B0,$6A,$3E,$13,$D4,$D5,$B6" 43 | // 44 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 45 | 46 | #if_not_defined __HASH__ 47 | #define __HASH__ 48 | 49 | 50 | #include 'md5' 51 | #include 'sha1' 52 | #include 'sha256' 53 | 54 | 55 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 56 | // 57 | // Function: hash 58 | // 59 | // Parameters: 60 | // char scheme[] - String representing the hash function to use. 61 | // char data[] - Data to use in hash function. 62 | // 63 | // Returns: 64 | // char[2048] - The result of the hash function. 65 | // 66 | // Description: 67 | // Produces a hashed value using the hash function specified. An empty string is returned if specified hash function 68 | // is unsupported. 69 | // Supported Hash Functions: 70 | // - md5 71 | // - sha1 | sha-1 72 | // - sha256 | sha-256 73 | // 74 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 75 | define_function char[2048] hash(char scheme[], char data[]) { 76 | 77 | switch(lower_string(scheme)) { 78 | 79 | case 'md5': { 80 | return md5(data); 81 | } 82 | 83 | case 'sha1': 84 | case 'sha-1': { 85 | return sha1(data); 86 | } 87 | 88 | case 'sha256': 89 | case 'sha-256': { 90 | return sha256(data); 91 | } 92 | } 93 | 94 | return ''; 95 | } 96 | 97 | 98 | #end_if 99 | -------------------------------------------------------------------------------- /hmac.axi: -------------------------------------------------------------------------------- 1 | program_name='hmac' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: hmac 5 | // 6 | // Description: 7 | // 8 | // - This include file provides a NetLinx implementation of HMAC (Keyed-Hashing for Message Authentication) as 9 | // defined in RFC 2104 (see https://tools.ietf.org/html/rfc2104). 10 | // 11 | // Implementation: 12 | // 13 | // - Any NetLinx program utilising the hmac include file must use either the INCLUDE or #INCLUDE keywords to 14 | // include the hmac include file within the program. While the INCLUDE and #INCLUDE keywords are both 15 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 16 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 17 | // for backwards compatibility). 18 | // 19 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 20 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 21 | // 22 | // E.g: 23 | // 24 | // define_program 'HMAC Demo' 25 | // 26 | // #include 'hmac' 27 | // 28 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 29 | 30 | #if_not_defined __HMAC__ 31 | #define __HMAC__ 32 | 33 | 34 | #include 'md5' 35 | #include 'sha1' 36 | #include 'sha256' 37 | 38 | 39 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 40 | // 41 | // Function: hmac 42 | // 43 | // Parameters: 44 | // char hashFunction[] - String representing the hash function to use. 45 | // char message[] - Message to use in hash function. 46 | // char key[] - Key used for message authentication. 47 | // 48 | // Returns: 49 | // char[1024] - Result of the keyed-hashing process. 50 | // 51 | // Description: 52 | // Produces a keyed-hash conforming to HMAC as defined in RFX 2014. Can be used for message authentication. An empty 53 | // string is returned if specified hash function is unsupported. 54 | // Supported Hash Functions: 55 | // - md5 56 | // - sha1 | sha-1 57 | // - sha256 | sha-256 58 | // 59 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 60 | define_function char[1024] hmac(char hashFunction[], char message[], char key[]) { 61 | char keyAdjusted[1024]; 62 | char outerKey[1024]; 63 | char innerKey[1024]; 64 | char outerKeyPad; 65 | char innerKeyPad; 66 | integer i; 67 | integer blockSize; 68 | 69 | switch(lower_string(hashFunction)) { 70 | case 'md5': 71 | blockSize = MD5_BLOCK_SIZE_BYTES; 72 | 73 | case 'sha1': 74 | case 'sha-1': 75 | blockSize = SHA_1_BLOCK_SIZE_BYTES; 76 | 77 | case 'sha256': 78 | case 'sha-256': 79 | blockSize = SHA_256_BLOCK_SIZE_BYTES; 80 | 81 | default: return ''; 82 | } 83 | 84 | if(length_array(key) > blockSize) { 85 | switch(lower_string(hashFunction)) { 86 | case 'md5': 87 | keyAdjusted = md5(key); 88 | 89 | case 'sha1': 90 | case 'sha-1': 91 | keyAdjusted = sha1(key); 92 | 93 | case 'sha256': 94 | case 'sha-256': 95 | keyAdjusted = sha256(key); 96 | } 97 | } 98 | else if(length_array(key) < blockSize) { 99 | keyAdjusted = key; 100 | while(length_array(keyAdjusted) < blockSize) { 101 | keyAdjusted = "keyAdjusted,$00"; 102 | } 103 | } else { 104 | keyAdjusted = key; 105 | } 106 | 107 | outerKeyPad = $5c; 108 | innerKeyPad = $36; 109 | for(i=1;i<=blockSize;i++) { 110 | outerKey = "outerKey,(keyAdjusted[i] bxor outerKeyPad)"; 111 | innerKey = "innerKey,(keyAdjusted[i] bxor innerKeyPad)"; 112 | } 113 | 114 | switch(lower_string(hashFunction)) { 115 | case 'md5': return md5("outerKey,md5("innerKey,message")"); 116 | 117 | case 'sha1': 118 | case 'sha-1': return sha1("outerKey,sha1("innerKey,message")"); 119 | 120 | case 'sha256': 121 | case 'sha-256': return sha256("outerKey,sha256("innerKey,message")"); 122 | } 123 | } 124 | 125 | 126 | #end_if -------------------------------------------------------------------------------- /json-rpc.axi: -------------------------------------------------------------------------------- 1 | program_name='json-rpc' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: json-rpc 5 | // 6 | // Description: 7 | // 8 | // - This include file provides a NetLinx implementation of JSON RPC's (JSON Remote Procedure Calls). 9 | // 10 | // Implementation: 11 | // 12 | // - Any NetLinx program utilising the jspn-rpc include file must use either the INCLUDE or #INCLUDE keywords to 13 | // include the json-rpc include file within the program. While the INCLUDE and #INCLUDE keywords are both 14 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 15 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 16 | // for backwards compatibility). 17 | // 18 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 19 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 20 | // 21 | // E.g: 22 | // 23 | // define_program 'JSON RPC Demo' 24 | // 25 | // #include 'json-rpc' 26 | // 27 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 28 | 29 | #if_not_defined __JSON_RPC__ 30 | #define __JSON_RPC__ 31 | 32 | 33 | #include 'json' 34 | 35 | 36 | /* 37 | These functions support JSON-RPC v2.0 only and do not support JSON-RPC 1.0/1.1 38 | */ 39 | 40 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 41 | // 42 | // Function: jsonRpcRequestObjParams 43 | // 44 | // Parameters: 45 | // char method[] - A string with the name of the method to be invoked. 46 | // JsonObj jObj - A JSON Object to be passed as a parameter to the define method. 47 | // char id[] - A string used to match the response with the JSON-RPC request when it is replied to. 48 | // 49 | // Returns: 50 | // char[JSON_MAX_VALUE_DATA_LENGTH] - A JSON-RPC formatted request. 51 | // 52 | // Description: 53 | // Builds a JSON-RPC (JSON encoded remote procedure call). 54 | // 55 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 56 | define_function char[JSON_MAX_VALUE_DATA_LENGTH] jsonRpcRequestObjParams(char method[], JsonObj jObj, char id[]) { 57 | char tempMethod[JSON_MAX_VALUE_DATA_LENGTH]; 58 | char tempId[JSON_MAX_VALUE_DATA_LENGTH]; 59 | integer i; 60 | 61 | for(i=1; i<=length_string(method); i++) { 62 | if(method[i] == '"' && (i==1)) { 63 | tempMethod = "tempMethod,'\"'"; 64 | } 65 | else if(method[i] == '"' && method[i-1] != '\') { 66 | tempMethod = "tempMethod,'\"'"; 67 | } 68 | else { 69 | tempMethod = "tempMethod,method[i]"; 70 | } 71 | } 72 | 73 | 74 | for(i=1; i<=length_string(id); i++) { 75 | if(id[i] == '"' && (i==1)) { 76 | tempId = "tempId,'\"'"; 77 | } 78 | else if(id[i] == '"' && id[i-1] != '\') { 79 | tempId = "tempId,'\"'"; 80 | } 81 | else { 82 | tempId = "tempId,id[i]"; 83 | } 84 | } 85 | 86 | if(id == '') { 87 | return "'{"jsonrpc":"2.0","method":',tempMethod,',"params":',jsonStringifyObject(jObj),'}'"; 88 | } 89 | else { 90 | return "'{"jsonrpc":"2.0","method":',tempMethod,',"params":',jsonStringifyObject(jObj),',"id":"',tempId,'"}'"; 91 | } 92 | } 93 | 94 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 95 | // 96 | // Function: jsonRpcRequestArrayParams 97 | // 98 | // Parameters: 99 | // char method[] - A string with the name of the method to be invoked. 100 | // JsonArray jArr - A JSON Array to be passed as a parameter to the define method. 101 | // char id[] - A string used to match the response with the JSON-RPC request when it is replied to. 102 | // 103 | // Returns: 104 | // char[JSON_MAX_VALUE_DATA_LENGTH] - A JSON-RPC formatted request. 105 | // 106 | // Description: 107 | // Builds a JSON-RPC (JSON encoded remote procedure call). 108 | // 109 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 110 | define_function char[JSON_MAX_VALUE_DATA_LENGTH] jsonRpcRequestArrayParams(char method[], JsonArray jArr, char id[]) { 111 | char tempMethod[JSON_MAX_VALUE_DATA_LENGTH]; 112 | char tempId[JSON_MAX_VALUE_DATA_LENGTH]; 113 | integer i; 114 | 115 | for(i=1; i<=length_string(method); i++) { 116 | if(method[i] == '"' && (i==1)) { 117 | tempMethod = "tempMethod,'\"'"; 118 | } 119 | else if(method[i] == '"' && method[i-1] != '\') { 120 | tempMethod = "tempMethod,'\"'"; 121 | } 122 | else { 123 | tempMethod = "tempMethod,method[i]"; 124 | } 125 | } 126 | 127 | 128 | for(i=1; i<=length_string(id); i++) { 129 | if(id[i] == '"' && (i==1)) { 130 | tempId = "tempId,'\"'"; 131 | } 132 | else if(id[i] == '"' && id[i-1] != '\') { 133 | tempId = "tempId,'\"'"; 134 | } 135 | else { 136 | tempId = "tempId,id[i]"; 137 | } 138 | } 139 | 140 | if(id == '') { 141 | return "'{"jsonrpc":"2.0","method":',tempMethod,',"params":',jsonStringifyArray(jArr),'}'"; 142 | } 143 | else { 144 | return "'{"jsonrpc":"2.0","method":',tempMethod,',"params":',jsonStringifyArray(jArr),',"id":"',tempId,'"}'"; 145 | } 146 | } 147 | 148 | 149 | #end_if 150 | -------------------------------------------------------------------------------- /jwt.axi: -------------------------------------------------------------------------------- 1 | program_name='jwt' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: jwt 5 | // 6 | // Description: 7 | // 8 | // - This include file provides a NetLinx implementation of JSON Web Tokens (JWT). 9 | // - see RFC-7515 JSON Web Signature (JWS) [https://tools.ietf.org/html/rfc7515] 10 | // - see RFC-7519 JSON Web Tokens (JWT) [https://tools.ietf.org/html/rfc7519] 11 | // - see RFC-7797 JSON Web Signature (JWS) Unencoded Payload Option [https://tools.ietf.org/html/rfc7797] 12 | // - see RFC-7516 JSON Web Encryption (JWE) [https://tools.ietf.org/html/rfc7516] 13 | // 14 | // - Note that this implementation is far from complete. 15 | // 16 | // Implementation: 17 | // 18 | // - Any NetLinx program utilising the jwt include file must use either the INCLUDE or #INCLUDE keywords to 19 | // include the jwt include file within the program. While the INCLUDE and #INCLUDE keywords are both 20 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 21 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 22 | // for backwards compatibility). 23 | // 24 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 25 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 26 | // 27 | // E.g: 28 | // 29 | // define_program 'JWT Demo' 30 | // 31 | // #include 'jwt' 32 | // 33 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 34 | 35 | #if_not_defined __JWT__ 36 | #define __JWT__ 37 | 38 | 39 | #include 'json' 40 | #include 'base64' 41 | #include 'hmac' 42 | 43 | 44 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 45 | // 46 | // Function: jwt 47 | // 48 | // Parameters: 49 | // char header[] - A JSON formatted header 50 | // char payload[] - A JSON formatted payload 51 | // char secret[] - A secret 52 | // integer base64EncodeSecret - A boolean value to indicate whether or not to base64 encode the secret 53 | // 54 | // Returns: 55 | // char[64] - SHA-256 message digest in ASCII format 56 | // 57 | // Description: 58 | // Returns a JSON Web Token (JWT) - see RFC7519 / RFC7797 59 | // 60 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 61 | define_function char[1024] jwt(char header[], char payload[], char secret[], integer base64EncodeSecret, integer ignoreWhiteSpace) { 62 | char signature[1024]; 63 | char alg[20]; 64 | char tempHeader[1024]; 65 | 66 | if(ignoreWhiteSpace) { 67 | header = jsonRemoveWhiteSpace(header); 68 | payload = jsonRemoveWhiteSpace(payload); 69 | secret = jsonRemoveWhiteSpace(secret); 70 | } 71 | 72 | tempHeader = header; 73 | if(!find_string(tempHeader,"'"alg"'",1)) { 74 | //send_string 0, "'Unable to build JWT. Algorithm not specified.'" 75 | return ''; 76 | } 77 | remove_string(tempHeader,"'"alg"'",1); 78 | remove_string(tempHeader,"'"'",1); 79 | alg = remove_string(tempHeader,"'"'",1); 80 | alg = left_string(alg,length_string(alg)-1); 81 | 82 | switch(upper_string(alg)) { 83 | case 'HS256': { 84 | signature = hmac('sha256',"base64UrlEncode(header,false), '.', base64UrlEncode(payload,false)", secret); 85 | 86 | return "base64UrlEncode(header,false), '.', base64UrlEncode(payload,false), '.', base64UrlEncode(signature,false)"; 87 | } 88 | default: { 89 | send_string 0, "'Unable to build JWT. Unsupported algorithm: ',alg" 90 | return ''; 91 | } 92 | } 93 | } 94 | 95 | 96 | #end_if 97 | -------------------------------------------------------------------------------- /math.axi: -------------------------------------------------------------------------------- 1 | program_name='math' 2 | -------------------------------------------------------------------------------- /md5.axi: -------------------------------------------------------------------------------- 1 | program_name='md5' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: md5 5 | // 6 | // Description: 7 | // 8 | // - This include file provides a NetLinx implementation of the MD5 message-digest algorithm. A cryptographic hash 9 | // function as defined in RFC 1321 (see https://tools.ietf.org/html/rfc1321). 10 | // 11 | // Implementation: 12 | // 13 | // - Any NetLinx program utilising the md5 include file must use either the INCLUDE or #INCLUDE keywords to 14 | // include the md5 include file within the program. While the INCLUDE and #INCLUDE keywords are both 15 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 16 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 17 | // for backwards compatibility). 18 | // 19 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 20 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 21 | // 22 | // E.g: 23 | // 24 | // define_program 'MD5 Demo' 25 | // 26 | // #include 'md5' 27 | // 28 | // Usage: 29 | // 30 | // - The md5 function provided within this include file takes a message of unbound length and returns a 128-bit 31 | // (16-byte) message digest in a CHAR array. 32 | // 33 | // E.g: 34 | // 35 | // hashedPassword = md5('p@55w0rd'); 36 | // // returns "$39,$F1,$3D,$60,$B3,$F6,$FB,$E0,$BA,$16,$36,$B0,$A9,$28,$3C,$50" 37 | // 38 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 39 | 40 | #if_not_defined __MD5__ 41 | #define __MD5__ 42 | 43 | 44 | #include 'binary' 45 | #include 'convert' 46 | #include 'string' 47 | 48 | 49 | define_constant 50 | 51 | MD5_BYTE_SIZE_BITS = 8 52 | MD5_WORD_SIZE_BITS = 32 53 | MD5_WORD_SIZE_BYTES = 4 54 | MD5_BLOCK_SIZE_BITS = 512 55 | MD5_BLOCK_SIZE_BYTES = 64 56 | 57 | 58 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Function: md5 61 | // 62 | // Parameters: 63 | // char msg[] - Data to hash. 64 | // 65 | // Returns: 66 | // CHAR[16] - 128-bit MD5 message digest. 67 | // 68 | // Description: 69 | // This is a NetLinx implemntation of the MD5 message digest algorithm as described in RFC1321 (see 70 | // https://tools.ietf.org/html/rfc1321). 71 | // 72 | // Takes a message parameter and returns a 128-bit (16-byte) MD5 hash of the message. 73 | // 74 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 75 | define_function CHAR[16] md5(char msg[]) { 76 | long K[64]; 77 | long s[64]; 78 | 79 | long a0, b0, c0, d0; 80 | 81 | char msgPadded[1048576]; 82 | char digest[16]; 83 | 84 | integer chunkIdx; 85 | 86 | long lenMsg; 87 | long lenPadded; 88 | 89 | a0 = $67452301 90 | b0 = $efcdab89 91 | c0 = $98badcfe 92 | d0 = $10325476 93 | 94 | s[1] = 7; s[2] = 12; s[3] = 17; s[4] = 22; 95 | s[5] = 7; s[6] = 12; s[7] = 17; s[8] = 22; 96 | s[9] = 7; s[10] = 12; s[11] = 17; s[12] = 22; 97 | s[13] = 7; s[14] = 12; s[15] = 17; s[16] = 22; 98 | s[17] = 5; s[18] = 9; s[19] = 14; s[20] = 20; 99 | s[21] = 5; s[22] = 9; s[23] = 14; s[24] = 20; 100 | s[25] = 5; s[26] = 9; s[27] = 14; s[28] = 20; 101 | s[29] = 5; s[30] = 9; s[31] = 14; s[32] = 20; 102 | s[33] = 4; s[34] = 11; s[35] = 16; s[36] = 23; 103 | s[37] = 4; s[38] = 11; s[39] = 16; s[40] = 23; 104 | s[41] = 4; s[42] = 11; s[43] = 16; s[44] = 23; 105 | s[45] = 4; s[46] = 11; s[47] = 16; s[48] = 23; 106 | s[49] = 6; s[50] = 10; s[51] = 15; s[52] = 21; 107 | s[53] = 6; s[54] = 10; s[55] = 15; s[56] = 21; 108 | s[57] = 6; s[58] = 10; s[59] = 15; s[60] = 21; 109 | s[61] = 6; s[62] = 10; s[63] = 15; s[64] = 21; 110 | 111 | K[1] = $d76aa478; K[2] = $e8c7b756; K[3] = $242070db; K[4] = $c1bdceee; 112 | K[5] = $f57c0faf; K[6] = $4787c62a; K[7] = $a8304613; K[8] = $fd469501; 113 | K[9] = $698098d8; K[10] = $8b44f7af; K[11] = $ffff5bb1; K[12] = $895cd7be; 114 | K[13] = $6b901122; K[14] = $fd987193; K[15] = $a679438e; K[16] = $49b40821; 115 | K[17] = $f61e2562; K[18] = $c040b340; K[19] = $265e5a51; K[20] = $e9b6c7aa; 116 | K[21] = $d62f105d; K[22] = $02441453; K[23] = $d8a1e681; K[24] = $e7d3fbc8; 117 | K[25] = $21e1cde6; K[26] = $c33707d6; K[27] = $f4d50d87; K[28] = $455a14ed; 118 | K[29] = $a9e3e905; K[30] = $fcefa3f8; K[31] = $676f02d9; K[32] = $8d2a4c8a; 119 | K[33] = $fffa3942; K[34] = $8771f681; K[35] = $6d9d6122; K[36] = $fde5380c; 120 | K[37] = $a4beea44; K[38] = $4bdecfa9; K[39] = $f6bb4b60; K[40] = $bebfbc70; 121 | K[41] = $289b7ec6; K[42] = $eaa127fa; K[43] = $d4ef3085; K[44] = $04881d05; 122 | K[45] = $d9d4d039; K[46] = $e6db99e5; K[47] = $1fa27cf8; K[48] = $c4ac5665; 123 | K[49] = $f4292244; K[50] = $432aff97; K[51] = $ab9423a7; K[52] = $fc93a039; 124 | K[53] = $655b59c3; K[54] = $8f0ccc92; K[55] = $ffeff47d; K[56] = $85845dd1; 125 | K[57] = $6fa87e4f; K[58] = $fe2ce6e0; K[59] = $a3014314; K[60] = $4e0811a1; 126 | K[61] = $f7537e82; K[62] = $bd3af235; K[63] = $2ad7d2bb; K[64] = $eb86d391; 127 | 128 | lenMsg = length_array(msg); 129 | msgPadded = "msg,$80" 130 | lenPadded = length_array(msgPadded); 131 | while(((lenPadded*MD5_BYTE_SIZE_BITS) mod 512) != 448) { 132 | msgPadded = "msgPadded,$00" 133 | lenPadded = length_array(msgPadded); 134 | } 135 | msgPadded = "msgPadded,ltba(ltle(lenMsg*MD5_BYTE_SIZE_BITS)),$00,$00,$00,$00"; 136 | 137 | for(chunkIdx = 1; chunkIdx < length_array(msgPadded); chunkIdx=chunkIdx+MD5_BLOCK_SIZE_BYTES) { 138 | 139 | char chunk[MD5_BLOCK_SIZE_BYTES]; 140 | long M[16]; 141 | integer mIdx; 142 | long A, B, C, D; 143 | long i; 144 | 145 | chunk = mid_string(msgPadded,chunkIdx,MD5_BLOCK_SIZE_BYTES); 146 | 147 | i = 1; 148 | for(mIdx=1; mIdx<=length_array(chunk); mIdx=mIdx+MD5_WORD_SIZE_BYTES) { 149 | char word[MD5_WORD_SIZE_BYTES]; 150 | word = mid_string(chunk,mIdx,MD5_WORD_SIZE_BYTES); 151 | M[i] = ((word[4] << 24) BOR (word[3] << 16) BOR (word[2] << 8) BOR word[1]) 152 | 153 | i++; 154 | } 155 | 156 | A = a0; 157 | B = b0; 158 | C = c0; 159 | D = d0; 160 | 161 | for(i=0; i <= 63; i++) { 162 | long F, g; 163 | 164 | if((i>=0) && (i<=15)) { 165 | F = (B BAND C) BOR ((BNOT B) BAND D); 166 | g = i; 167 | } 168 | else if((i>=16) && (i<=31)) { 169 | F = (D BAND B) BOR ((BNOT D) BAND C); 170 | g = ((5*i) + 1) mod 16; 171 | } 172 | else if((i>=32) && (i<=47)) { 173 | F = B BXOR C BXOR D; 174 | g = ((3*i) + 5) mod 16; 175 | } 176 | else if((i>=48) && (i<=63)) { 177 | F = C BXOR (B BOR (BNOT D)); 178 | g = (7*i) mod 16; 179 | } 180 | 181 | F = F + A + K[i+1] + M[g+1]; 182 | A = D 183 | D = C 184 | C = B 185 | B = B + ((F << s[i+1]) | (F >> (32 - s[i+1]))) 186 | } 187 | 188 | a0 = a0 + A 189 | b0 = b0 + B 190 | c0 = c0 + C 191 | d0 = d0 + D 192 | } 193 | 194 | digest = "ltba(ltle(a0)),ltba(ltle(b0)),ltba(ltle(c0)),ltba(ltle(d0))" 195 | 196 | return digest; 197 | } 198 | 199 | 200 | #end_if 201 | -------------------------------------------------------------------------------- /proto.axi: -------------------------------------------------------------------------------- 1 | program_name='proto' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: proto 5 | // 6 | // Description: 7 | // 8 | // - This include file includes all protocol libraries. 9 | // 10 | // Implementation: 11 | // 12 | // - Any NetLinx program utilising the proto include file must use either the INCLUDE or #INCLUDE keywords to 13 | // include the proto include file within the program. While the INCLUDE and #INCLUDE keywords are both 14 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 15 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 16 | // for backwards compatibility). 17 | // 18 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 19 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 20 | // 21 | // E.g: 22 | // 23 | // define_program 'Protocol Demo' 24 | // 25 | // #include 'proto' 26 | // 27 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 28 | 29 | #if_not_defined __PROTO__ 30 | #define __PROTO__ 31 | 32 | 33 | #include 'http' // HTTP 34 | #include 'json' // JSON 35 | #include 'uri' // URI / URL 36 | #include 'websockets' // WebSockets 37 | #include 'xml' // XML 38 | 39 | 40 | #end_if 41 | -------------------------------------------------------------------------------- /sha1.axi: -------------------------------------------------------------------------------- 1 | program_name='sha1' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: sha1 5 | // 6 | // Description: 7 | // 8 | // - This include file provides a NetLinx implementation of the SHA-1 (Secure Hash Algorithm 1) cryptographic hash 9 | // function as defined in RFC 3174 (see https://tools.ietf.org/html/rfc3174). 10 | // 11 | // Implementation: 12 | // 13 | // - Any NetLinx program utilising the sha1 include file must use either the INCLUDE or #INCLUDE keywords to include 14 | // the sha1 include file within the program. While the INCLUDE and #INCLUDE keywords are both functionally 15 | // equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE keyword is 16 | // from the earlier Axcess programming language and is included within the NetLinx programming language for 17 | // backwards compatibility). 18 | // 19 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 20 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 21 | // 22 | // E.g: 23 | // 24 | // define_program 'SHA-1 Demo' 25 | // 26 | // #include 'sha1' 27 | // 28 | // Usage: 29 | // 30 | // - The sha1 function provided within this include file takes a message of unbound length and returns a 160-bit 31 | // (20-byte) message digest in a CHAR array. 32 | // 33 | // E.g: 34 | // 35 | // hashedPassword = sha1('p@55w0rd'); 36 | // // returns "$CE,$0B,$2B,$77,$1F,$7D,$46,$8C,$01,$41,$91,$8D,$AE,$A7,$04,$E0,$E5,$AD,$45,$DB" 37 | // 38 | // - Some example SHA-1 results to test against are as follows (note the results are hex values, not ASCII strings): 39 | // 40 | // msg: '' 41 | // result: "$da,$39,$a3,$ee,$5e,$6b,$4b,$0d,$32,$55,$bf,$ef,$95,$60,$18,$90,$af,$d8,$07,$09" 42 | // 43 | // msg: 'The quick brown fox jumps over the lazy dog' 44 | // result: "$2f,$d4,$e1,$c6,$7a,$2d,$28,$fc,$ed,$84,$9e,$e1,$bb,$76,$e7,$39,$1b,$93,$eb,$12" 45 | // 46 | // msg: 'abc' 47 | // result: "$a9,$99,$3e,$36,$47,$06,$81,$6a,$ba,$3e,$25,$71,$78,$50,$c2,$6c,$9c,$d0,$d8,$9d" 48 | // 49 | // msg: 'abcdefghijklmnopqrstuvwxyz' 50 | // result: "$32,$d1,$0c,$7b,$8c,$f9,$65,$70,$ca,$04,$ce,$37,$f2,$a1,$9d,$84,$24,$0d,$3a,$89" 51 | // 52 | // msg: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 53 | // result: "$80,$25,$6f,$39,$a9,$d3,$08,$65,$0a,$c9,$0d,$9b,$e9,$a7,$2a,$95,$62,$45,$45,$74" 54 | // 55 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 56 | 57 | #if_not_defined __SHA_1__ 58 | #define __SHA_1__ 59 | 60 | 61 | #include 'binary' 62 | #include 'convert' 63 | #include 'string' 64 | 65 | 66 | define_constant 67 | 68 | SHA_1_BYTE_SIZE_BITS = 8 69 | SHA_1_WORD_SIZE_BITS = 32 70 | SHA_1_WORD_SIZE_BYTES = 4 71 | SHA_1_BLOCK_SIZE_BITS = 512 72 | SHA_1_BLOCK_SIZE_BYTES = 64 73 | 74 | 75 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 76 | // 77 | // Function: sha1 78 | // 79 | // Parameters: 80 | // char msg[] - Data to hash. 81 | // 82 | // Returns: 83 | // char[20] - 160-bit SHA-1 message digest. 84 | // 85 | // Description: 86 | // This is a NetLinx implemntation of the SHA-1 message digest algorithm as described in RFC3174 (see 87 | // https://tools.ietf.org/html/rfc3174). 88 | // 89 | // Takes a message parameter and returns a 160-bit (20-byte) SHA-1 hash of the message. 90 | // 91 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 92 | define_function CHAR[20] sha1(char msg[]) { 93 | char digest[20]; 94 | char binaryResult[1024]; 95 | char msgPadded[1048576]; 96 | 97 | long h0, h1, h2, h3, h4; 98 | 99 | integer mIdx; 100 | integer wIdx; 101 | integer i; 102 | integer n; 103 | integer t; 104 | 105 | long A, B, C, D, E; 106 | 107 | char M[SHA_1_BLOCK_SIZE_BYTES]; 108 | long W[80]; 109 | 110 | long lenMsg; 111 | 112 | long TEMP; 113 | 114 | lenMsg = length_array(msg); 115 | 116 | if((lenMsg % 64) > 55) { // not enough room to pad in the last 512-bit block will need to pad and then add another 512-bit block 117 | msgPadded = "msg,$80"; 118 | while((length_array(msgPadded) % 64) > 0) { 119 | msgPadded = "msgPadded,$00"; 120 | } 121 | while((length_array(msgPadded) % 64) < 56) { // pad with zeros but leave the last 64-bits alone (reserved for length) 122 | msgPadded = "msgPadded,$00"; 123 | } 124 | } else { 125 | msgPadded = "msg,$80"; 126 | while((length_array(msgPadded) % 64) < 56) { // pad with zeros but leave the last 64-bits alone (reserved for length) 127 | msgPadded = "msgPadded,$00"; 128 | } 129 | } 130 | msgPadded = "msgPadded,$00,$00,$00,$00,ltba(lenMsg*SHA_1_BYTE_SIZE_BITS)" 131 | 132 | h0 = $67452301; 133 | h1 = $EFCDAB89; 134 | h2 = $98BADCFE; 135 | h3 = $10325476; 136 | h4 = $C3D2E1F0; 137 | 138 | for(mIdx = 1; mIdx < length_array(msgPadded); mIdx=mIdx+SHA_1_BLOCK_SIZE_BYTES) { 139 | 140 | M = mid_string(msgPadded,mIdx,SHA_1_BLOCK_SIZE_BYTES); 141 | 142 | i = 1; 143 | for(wIdx = 1; wIdx < length_array(M) ; wIdx = wIdx + SHA_1_WORD_SIZE_BYTES) { 144 | stack_var char binary[32]; 145 | binary = stringToBinary(mid_string(M,wIdx,SHA_1_WORD_SIZE_BYTES)); 146 | W[i] = binaryToLong(binary); 147 | i++; 148 | } 149 | 150 | for(t = 17; t <= 80; t++) { 151 | W[t] = lcls((W[t-3] BXOR W[t-8] BXOR W[t-14] BXOR W[t-16]),1); 152 | } 153 | 154 | A = H0; 155 | B = H1; 156 | C = H2; 157 | D = H3; 158 | E = H4; 159 | 160 | for(t = 1; t <= 80; t++) { 161 | stack_var long f; 162 | stack_var long k; 163 | 164 | if((1 <= t) && (t <= 20)) { 165 | f = ((B BAND C) BOR ((BNOT B) BAND D)); 166 | k = $5A827999; 167 | } else if((21 <= t) && (t <= 40)) { 168 | f = (B BXOR C BXOR D); 169 | k = $6ED9EBA1; 170 | } else if((41 <= t) && (t <= 60)) { 171 | f = ((B BAND C) BOR (B BAND D) BOR (C BAND D)) 172 | k = $8F1BBCDC; 173 | } else if((61 <= t) && (t <= 80)) { 174 | f = (B BXOR C BXOR D) 175 | k = $CA62C1D6; 176 | } 177 | TEMP = lcls(A,5) + f + E + W[t] + K; 178 | E = D; 179 | D = C; 180 | C = lcls(B,30); 181 | B = A; 182 | A = TEMP; 183 | } 184 | 185 | H0 = (H0 + A); 186 | H1 = (H1 + B); 187 | H2 = (H2 + C); 188 | H3 = (H3 + D); 189 | H4 = (H4 + E); 190 | } 191 | 192 | digest = "ltba(H0),ltba(H1),ltba(H2),ltba(H3),ltba(H4)" 193 | 194 | return digest; 195 | } 196 | 197 | 198 | #end_if 199 | -------------------------------------------------------------------------------- /sha256.axi: -------------------------------------------------------------------------------- 1 | program_name='sha256' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: sha256 5 | // 6 | // Description: 7 | // 8 | // - This include file provides a NetLinx implementation of the SHA-2 (Secure Hash Algorithm 2) cryptographic hash 9 | // function, specifically SHA-256. 10 | // 11 | // Implementation: 12 | // 13 | // - Any NetLinx program utilising the sha256 include file must use either the INCLUDE or #INCLUDE keywords to 14 | // include the sha256 include file within the program. While the INCLUDE and #INCLUDE keywords are both 15 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the 16 | // INCLUDE keyword is from the earlier Axcess programming language and is included within the NetLinx programming 17 | // language for backwards compatibility). 18 | // 19 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 20 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 21 | // 22 | // E.g: 23 | // 24 | // define_program 'SHA-256 Demo' 25 | // 26 | // #include 'sha256' 27 | // 28 | // Usage: 29 | // 30 | // - The sha256 function provided within this include file takes a message of unbound length and returns a 256=-bit 31 | // (32-byte) message digest in a CHAR array. 32 | // 33 | // E.g: 34 | // 35 | // hashedPassword = sha256'p@55w0rd'); 36 | // // returns "$59,$F4,$6B,$B9,$0C,$FF,$B0,$ED,$7C,$7E,$5D,$B5,$8B,$B3,$00,$F3,$BC,$D7,$14,$F5,$1A,$E7,$23,$ED,$91,$B0,$6A,$3E,$13,$D4,$D5,$B6" 37 | // 38 | // - Some example SHA-256 results to test against are as follows (note the results are hex values, not ASCII strings): 39 | // 40 | // msg: '' 41 | // result: "$e3,$b0,$c4,$42,$98,$fc,$1c,$14,$9a,$fb,$f4,$c8,$99,$6f,$b9,$24,$27,$ae,$41,$e4,$64,$9b,$93,$4c,$a4,$95,$99,$1b,$78,$52,$b8,$55" 42 | // 43 | // msg: 'The quick brown fox jumps over the lazy dog' 44 | // result: "$D7,$A8,$FB,$B3,$07,$D7,$80,$94,$69,$CA,$9A,$BC,$B0,$08,$2E,$4F,$8D,$56,$51,$E4,$6D,$3C,$DB,$76,$2D,$02,$D0,$BF,$37,$C9,$E5,$92" 45 | // 46 | // msg: 'abc' 47 | // result: "$BA,$78,$16,$BF,$8F,$01,$CF,$EA,$41,$41,$40,$DE,$5D,$AE,$22,$23,$B0,$03,$61,$A3,$96,$17,$7A,$9C,$B4,$10,$FF,$61,$F2,$00,$15,$AD" 48 | // 49 | // msg: 'abcdefghijklmnopqrstuvwxyz' 50 | // result: "$71,$C4,$80,$DF,$93,$D6,$AE,$2F,$1E,$FA,$D1,$44,$7C,$66,$C9,$52,$5E,$31,$62,$18,$CF,$51,$FC,$8D,$9E,$D8,$32,$F2,$DA,$F1,$8B,$73" 51 | // 52 | // msg: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 53 | // result: "$D6,$EC,$68,$98,$DE,$87,$DD,$AC,$6E,$5B,$36,$11,$70,$8A,$7A,$A1,$C2,$D2,$98,$29,$33,$49,$CC,$1A,$6C,$29,$9A,$1D,$B7,$14,$9D,$38" 54 | // 55 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 56 | 57 | #if_not_defined __SHA_256__ 58 | #define __SHA_256__ 59 | 60 | 61 | #include 'binary' 62 | #include 'convert' 63 | #include 'string' 64 | 65 | 66 | define_constant 67 | 68 | SHA_256_BYTE_SIZE_BITS = 8 69 | SHA_256_WORD_SIZE_BITS = 32 70 | SHA_256_WORD_SIZE_BYTES = 4 71 | SHA_256_BLOCK_SIZE_BITS = 512 72 | SHA_256_BLOCK_SIZE_BYTES = 64 73 | 74 | 75 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 76 | // 77 | // Function: sha256 78 | // 79 | // Parameters: 80 | // char msg[] - Data to hash. 81 | // 82 | // Returns: 83 | // char[32] - 256-bit SHA-256 message digest. 84 | // 85 | // Description: 86 | // This is a NetLinx implemntation of the SHA-256 message digest algorithm as described in RFC6234 (see 87 | // https://tools.ietf.org/html/rfc6234). 88 | // 89 | // Takes a message parameter and returns a 256-bit (32-byte) SHA-256 hash of the message. 90 | // 91 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 92 | define_function CHAR[32] sha256(char msg[]) { 93 | char digest[32]; 94 | char binaryResult[1024]; 95 | char msgPadded[1048576]; 96 | 97 | long h0, h1, h2, h3, h4, h5, h6, h7; 98 | 99 | long k[64]; 100 | 101 | long ml; // message length in bits 102 | long L; // length (in 32-bit integers) 103 | long kZeros; 104 | 105 | integer mIdx; 106 | integer wIdx; 107 | integer i; 108 | integer n; 109 | integer t; 110 | 111 | long s0, s1; 112 | 113 | long a, b, c, d, e, f, g, h; 114 | 115 | long ch; 116 | long temp1, temp2; 117 | long maj; 118 | 119 | char M[SHA_256_BLOCK_SIZE_BYTES]; 120 | long W[64]; 121 | 122 | long lenMsg; 123 | 124 | long lenMsgBytes; 125 | long lenMsgBits; 126 | 127 | long TEMP; 128 | 129 | char chunk[64]; // 512 bits 130 | 131 | //////////////////////////// 132 | // Initialize hash values // 133 | h0 = $6a09e667; 134 | h1 = $bb67ae85; 135 | h2 = $3c6ef372; 136 | h3 = $a54ff53a; 137 | h4 = $510e527f; 138 | h5 = $9b05688c; 139 | h6 = $1f83d9ab; 140 | h7 = $5be0cd19; 141 | 142 | ///////////////////////////////////////// 143 | // Initialize array of round constants // 144 | k[1] = $428a2f98; k[2] = $71374491; k[3] = $b5c0fbcf; k[4] = $e9b5dba5; 145 | k[5] = $3956c25b; k[6] = $59f111f1; k[7] = $923f82a4; k[8] = $ab1c5ed5; 146 | k[9] = $d807aa98; k[10] = $12835b01; k[11] = $243185be; k[12] = $550c7dc3; 147 | k[13] = $72be5d74; k[14] = $80deb1fe; k[15] = $9bdc06a7; k[16] = $c19bf174; 148 | k[17] = $e49b69c1; k[18] = $efbe4786; k[19] = $0fc19dc6; k[20] = $240ca1cc; 149 | k[21] = $2de92c6f; k[22] = $4a7484aa; k[23] = $5cb0a9dc; k[24] = $76f988da; 150 | k[25] = $983e5152; k[26] = $a831c66d; k[27] = $b00327c8; k[28] = $bf597fc7; 151 | k[29] = $c6e00bf3; k[30] = $d5a79147; k[31] = $06ca6351; k[32] = $14292967; 152 | k[33] = $27b70a85; k[34] = $2e1b2138; k[35] = $4d2c6dfc; k[36] = $53380d13; 153 | k[37] = $650a7354; k[38] = $766a0abb; k[39] = $81c2c92e; k[40] = $92722c85; 154 | k[41] = $a2bfe8a1; k[42] = $a81a664b; k[43] = $c24b8b70; k[44] = $c76c51a3; 155 | k[45] = $d192e819; k[46] = $d6990624; k[47] = $f40e3585; k[48] = $106aa070; 156 | k[49] = $19a4c116; k[50] = $1e376c08; k[51] = $2748774c; k[52] = $34b0bcb5; 157 | k[53] = $391c0cb3; k[54] = $4ed8aa4a; k[55] = $5b9cca4f; k[56] = $682e6ff3; 158 | k[57] = $748f82ee; k[58] = $78a5636f; k[59] = $84c87814; k[60] = $8cc70208; 159 | k[61] = $90befffa; k[62] = $a4506ceb; k[63] = $bef9a3f7; k[64] = $c67178f2; 160 | 161 | ////////////////////////////// 162 | // Pre-processing (Padding) // 163 | 164 | //// begin with the original message of length L bits 165 | L = length_array(msg) * SHA_256_BYTE_SIZE_BITS; 166 | //// append a single '1' bit 167 | msgPadded = "msg,$80" 168 | //// append K '0' bits, where K is the minimum number >= 0 such that L + 1 + K + 64 is a multiple of 512 169 | kZeros = 7; // hex 80 = bin 10000000 so we're starting off with 7 zero's appended 170 | while(((L + 1 + kZeros + 64) % 512) != 0) { 171 | msgPadded = "msgPadded,$00"; 172 | kZeros = kZeros+SHA_256_BYTE_SIZE_BITS; 173 | } 174 | //// append L as a 64-bit big-endian integer, making the total post-processed length a multiple of 512 bits 175 | msgPadded = "msgPadded,$00,$00,$00,$00,ltba(L)"; 176 | 177 | 178 | ////////////////////////////////////////////////////// 179 | // Process the message in successive 512-bit chunks // 180 | 181 | // break message into 512-bit chunks 182 | // for each chunk 183 | for(mIdx = 1; mIdx < length_array(msgPadded); mIdx=mIdx+SHA_256_BLOCK_SIZE_BYTES) { 184 | 185 | // copy chunk into first 16 words w[0..15] of the message schedule array 186 | M = mid_string(msgPadded,mIdx,SHA_256_BLOCK_SIZE_BYTES); 187 | i = 1; 188 | for(wIdx = 1; wIdx < length_array(M) ; wIdx = wIdx + SHA_256_WORD_SIZE_BYTES) { 189 | stack_var char binary[32]; 190 | binary = stringToBinary(mid_string(M,wIdx,SHA_256_WORD_SIZE_BYTES)); 191 | W[i] = binaryToLong(binary); 192 | i++; 193 | } 194 | 195 | // Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array: 196 | for(t = 17; t <= 64; t++) { 197 | s0 = (lcrs(W[t-15],7) BXOR lcrs(W[t-15],18) BXOR (W[t-15] >> 3)); 198 | s1 = (lcrs(W[t-2],17) BXOR lcrs(W[t-2],19) BXOR (W[t-2] >> 10)); 199 | W[t] = W[t-16] + s0 + W[t-7] + s1; 200 | } 201 | 202 | // Initialize working variables to current hash value: 203 | a = h0; 204 | b = h1; 205 | c = h2; 206 | d = h3; 207 | e = h4; 208 | f = h5; 209 | g = h6; 210 | h = h7; 211 | 212 | // Compression function main loop: 213 | for(i=1; i<=64; i++) { 214 | s1 = lcrs(e,6) bxor lcrs(e,11) bxor lcrs(e,25); 215 | ch = (e band f) bxor ((bnot e) band g); 216 | temp1 = h + s1 + ch + k[i] + w[i]; 217 | s0 = lcrs(a,2) bxor lcrs(a,13) bxor lcrs(a,22); 218 | maj = (a band b) bxor (a band c) bxor (b band c); 219 | temp2 = s0 + maj; 220 | 221 | h = g; 222 | g = f; 223 | f = e; 224 | e = d + temp1; 225 | d = c; 226 | c = b; 227 | b = a; 228 | a = temp1 + temp2; 229 | } 230 | 231 | // Add the compressed chunk to the current hash value: 232 | h0 = h0 + a; 233 | h1 = h1 + b; 234 | h2 = h2 + c; 235 | h3 = h3 + d; 236 | h4 = h4 + e; 237 | h5 = h5 + f; 238 | h6 = h6 + g; 239 | h7 = h7 + h; 240 | } 241 | 242 | // Produce the final hash value (big-endian): 243 | digest = "ltba(h0),ltba(h1),ltba(h2),ltba(h3),ltba(h4),ltba(h5),ltba(h6),ltba(h7)" 244 | 245 | return digest; 246 | } 247 | 248 | 249 | #end_if 250 | -------------------------------------------------------------------------------- /string.axi: -------------------------------------------------------------------------------- 1 | program_name='string' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: string 5 | // 6 | // Description: 7 | // 8 | // - This include file provides extra string operation functions not natively provided in NetLinx. 9 | // 10 | // Implementation: 11 | // 12 | // - Any NetLinx program utilising the string include file must use either the INCLUDE or #INCLUDE keywords to 13 | // include the string include file within the program. While the INCLUDE and #INCLUDE keywords are both 14 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 15 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 16 | // for backwards compatibility). 17 | // 18 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 19 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 20 | // 21 | // E.g: 22 | // 23 | // define_program 'String Demo' 24 | // 25 | // #include 'string' 26 | // 27 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 28 | 29 | #if_not_defined __STRING__ 30 | #define __STRING__ 31 | 32 | 33 | define_constant 34 | 35 | char CR = $0D; 36 | char LF = $0A; 37 | char CRLF[] = {CR,LF}; 38 | char SPACE = ' '; 39 | char TABH = $09; 40 | char TABV = $11; 41 | char ESC = $27; 42 | char NULL = $00; 43 | char SOH = $01; 44 | char STX = $02; 45 | char ETX = $03; 46 | char EOT = $04; 47 | char ACK = $06; 48 | char BS = $08; 49 | char NAK = $21; 50 | 51 | 52 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 53 | // 54 | // Function: removeLeadingChars 55 | // 56 | // Parameters: 57 | // char str[] - String to remove leading characters from 58 | // char mask[] - Character mask 59 | // 60 | // Returns: 61 | // nothing 62 | // 63 | // Description: 64 | // Removes any leading characters listed in the character mask from the string. Order of characters in character mask 65 | // is not important. 66 | // E.g: 67 | // sAlphabet = 'abcdefghijlkmnopqrstuvwxyz' 68 | // removeTrailingChars(sAlphabet,'abc') // alphabet is now 'defghijlkmnopqrstuvwxyz' 69 | // E.g: 70 | // sAlphabet = 'abcdefghijlkmnopqrstuvwxyz' 71 | // removeTrailingChars(sAlphabet,'bac') // alphabet is now 'defghijlkmnopqrstuvwxyz' 72 | // E.g: 73 | // sAlphabet = 'aaabbbcccdddeeefffggg' 74 | // removeTrailingChars(sAlphabet,'ba') // alphabet is now 'cccdddeeefffggg' 75 | // 76 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 77 | define_function removeLeadingChars(char str[], char mask[]) { 78 | while(find_string(mask,"str[1]",1)) { 79 | str = right_string(str,length_string(str)-1); 80 | } 81 | } 82 | 83 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 84 | // 85 | // Function: removeTrailingChars 86 | // 87 | // Parameters: 88 | // char str[] - String to remove trailing characters from 89 | // char mask[] - Character mask 90 | // 91 | // Returns: 92 | // nothing 93 | // 94 | // Description: 95 | // Removes any trailing characters listed in the character mask from the string. Order of characters in character mask 96 | // is not important. 97 | // E.g: 98 | // sAlphabet = 'abcdefghijlkmnopqrstuvwxyz' 99 | // removeTrailingChars(sAlphabet,'xyz') // alphabet is now 'abcdefghijlkmnopqrstuvw' 100 | // E.g: 101 | // sAlphabet = 'abcdefghijlkmnopqrstuvwxyz' 102 | // removeTrailingChars(sAlphabet,'zxy') // alphabet is now 'abcdefghijlkmnopqrstuvw' 103 | // E.g: 104 | // sAlphabet = 'aaabbbcccdddeeefffggg' 105 | // removeTrailingChars(sAlphabet,'fg') // alphabet is now 'aaabbbcccdddeee' 106 | // 107 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 108 | define_function removeTrailingChars(char str[], char mask[]) { 109 | while(find_string(mask,"str[length_string(str)]",1)) { 110 | str = left_string(str,length_string(str)-1); 111 | } 112 | } 113 | 114 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Function: trim_string 117 | // 118 | // Parameters: 119 | // char str[] - String to trim 120 | // integer ltrim - Number of characters to trim from the left of the string 121 | // integer rtrim - Number of characters to trim from the right of the string 122 | // 123 | // Returns: 124 | // nothing 125 | // 126 | // Description: 127 | // Trims the string from the left and right. 128 | // E.g: 129 | // sAlphabet = 'abcdefghijlkmnopqrstuvwxyz' 130 | // trim(sAlphabet,3,5) // alphabet is now 'defghijlkmnopqrstu' 131 | // 132 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 133 | define_function trim_string(char str[], integer ltrim, integer rtrim) { 134 | if((ltrim+rtrim) >= length_string(str)) 135 | str = ''; 136 | else 137 | str = mid_string(str,ltrim+1,length_string(str)-ltrim-rtrim); 138 | } 139 | 140 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 141 | // 142 | // Function: delete_string 143 | // 144 | // Parameters: 145 | // char strSearch[] - String to search 146 | // char strParse[] - String to search for 147 | // 148 | // Returns: 149 | // nothing 150 | // 151 | // Description: 152 | // Searches a string for the first occurance of a defined substring and if found removes the first occurance of that 153 | // substring from the search string and returns a true result. If the substring is not found a false result is 154 | // returned. 155 | // Not to be confused with the REMOVE_STRING function which deletes everything up to and including the substring 156 | // from the string being searched and then returns the deleted string. 157 | // 158 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 159 | define_function integer delete_string(char strSearch[], char strParse[]) { 160 | stack_var integer indexParse; 161 | 162 | if((strSearch == '') || (strParse == '')) 163 | return false; 164 | 165 | indexParse = find_string(strSearch,strParse,1); 166 | 167 | if(!indexParse) 168 | return false; 169 | 170 | strSearch = "mid_string(strSearch, 171 | 1, 172 | (indexParse-1)), 173 | mid_string(strSearch, 174 | (indexParse+length_string(strParse)), 175 | (length_string(strSearch)-(indexParse+length_string(strParse))+1))" 176 | 177 | return true; 178 | } 179 | 180 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 181 | // 182 | // Function: replace_string 183 | // 184 | // Parameters: 185 | // char strSearch[] - String to search 186 | // char strToReplace[] - String to be replaced 187 | // char strReplacement[] - String to use as the rpelacement 188 | // 189 | // Returns: 190 | // nothing 191 | // 192 | // Description: 193 | // Searches a string for a substring and if found removes the substring and inserts a new string in place. 194 | // 195 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 196 | define_function integer replace_string(char strSearch[], char strToReplace[], char strReplacement[]) { 197 | stack_var integer indexReplace; 198 | 199 | if((strSearch == '') || (strToReplace == '')) 200 | return false; 201 | 202 | indexReplace = find_string(strSearch,strToReplace,1); 203 | 204 | if(!indexReplace) 205 | return false; 206 | 207 | strSearch = "mid_string(strSearch, 208 | 1, 209 | (indexReplace-1)), 210 | strReplacement, 211 | mid_string(strSearch, 212 | (indexReplace+length_string(strToReplace)), 213 | (length_string(strSearch)-(indexReplace+length_string(strToReplace))+1))" 214 | 215 | return true; 216 | } 217 | 218 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 219 | // 220 | // Function: append_string 221 | // 222 | // Parameters: 223 | // char str[] - String to be appended to 224 | // char append[] - String to append 225 | // 226 | // Returns: 227 | // nothing 228 | // 229 | // Description: 230 | // Appends one string onto another. Equivalent to string concatentation in NetLinx. 231 | // 232 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 233 | define_function append_string(char str[], char append[]) { 234 | str = "str,append"; 235 | } 236 | 237 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 238 | // 239 | // Function: insert_string 240 | // 241 | // Parameters: 242 | // char str[] - String to insert another string into 243 | // integer index - index where string is to be inserted 244 | // char insert[] - String to be inserted 245 | // 246 | // Returns: 247 | // nothing 248 | // 249 | // Description: 250 | // Inserts one string into another at the designated index. 251 | // 252 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 253 | define_function insert_string(char str[], integer index, char insert[]) { 254 | if((index = 0) || (index == 1)) 255 | str = "insert,str"; 256 | else if (index > length_string(str)) 257 | str = "str,insert"; 258 | else if (index <= length_string(str)) 259 | str = "left_string(str,index-1),insert,mid_string(str,index,length_string(str))" 260 | } 261 | 262 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 263 | // 264 | // Function: hex 265 | // 266 | // Parameters: 267 | // char msg[] - A character array of undertermined length 268 | // 269 | // Returns: 270 | // char[5000] - A character array (string) containing the value in uppercase hexadecimal ASCII formatting 271 | // 272 | // Description: 273 | // Returns an all uppercase hexidecimal formatted ASCII string representing the value of the data passed through. 274 | // 275 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 276 | define_function char[5000] hex(char data[]) { 277 | char result[5000]; 278 | integer i,count; 279 | long len; 280 | 281 | len = length_array(data); 282 | 283 | for(i=1,count=1; i<=length_string(data); i++,count++) { 284 | result = "result,format('%02X',data[i])" 285 | } 286 | return result; 287 | } 288 | 289 | 290 | #end_if 291 | -------------------------------------------------------------------------------- /uri.axi: -------------------------------------------------------------------------------- 1 | program_name='uri' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: uri 5 | // 6 | // Description: 7 | // 8 | // - This include file provides functions for working with URI's as defined RFC 3986 (see 9 | // https://tools.ietf.org/html/rfc3986). 10 | // 11 | // Implementation: 12 | // 13 | // - Any NetLinx program utilising the uri include file must use either the INCLUDE or #INCLUDE keywords to include 14 | // the uri include file within the program. While the INCLUDE and #INCLUDE keywords are both functionally 15 | // equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE keyword is 16 | // from the earlier Axcess programming language and is included within the NetLinx programming language for 17 | // backwards compatibility). 18 | // 19 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 20 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 21 | // 22 | // E.g: 23 | // 24 | // define_program 'URI Demo' 25 | // 26 | // #include 'uri' 27 | // 28 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 29 | 30 | #if_not_defined __URI__ 31 | #define __URI__ 32 | 33 | 34 | #include 'string' 35 | 36 | 37 | define_type 38 | 39 | struct Uri { 40 | char scheme[30]; 41 | char user[50]; 42 | char password[50]; 43 | char host[200]; 44 | integer port; 45 | char path[200]; 46 | char query[200]; 47 | char fragment[200]; 48 | } 49 | 50 | 51 | define_constant 52 | 53 | char URI_RESERVED_CHARACTERS [] = {':','/','?','#','[',']','@','!','$','&',$27,'(',')','*','+',',',';','='}; 54 | char URI_UNRESERVED_CHARACTERS[] = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~'; 55 | 56 | 57 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 58 | // 59 | // Function: uriToString 60 | // 61 | // Parameters: 62 | // Uri u - A URI object 63 | // 64 | // Returns: 65 | // char[1500] - A character array containing the URI in string forms 66 | // 67 | // Description: 68 | // Takes a URI object and returns a URI formatted string. 69 | // 70 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 71 | define_function char[1500] uriToString(Uri u) { 72 | char result[1500]; 73 | 74 | if(length_array(u.scheme)) { 75 | result = "result,u.scheme,':'"; 76 | } 77 | 78 | if(length_array(u.host)) { 79 | result = "result,'//'"; 80 | if(length_array(u.user) && length_array(u.password)) result = "result,u.user,':',u.password,'@'"; 81 | result = "result,u.host"; 82 | if(u.port) result = "result,':',itoa(u.port)"; 83 | if(find_string(u.path,"'/'",1) == 1) { 84 | result = "result,u.path"; 85 | } else { 86 | result = "result,'/',u.path"; 87 | } 88 | } else { 89 | result = "result,u.path"; 90 | } 91 | 92 | if(length_array(u.query)) { 93 | result = "result,'?',u.query" 94 | } 95 | 96 | if(length_array(u.fragment)) { 97 | result = "result,'#',u.fragment" 98 | } 99 | 100 | return result; 101 | } 102 | 103 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 104 | // 105 | // Function: uriFromString 106 | // 107 | // Parameters: 108 | // Uri u - A URI object 109 | // char str[] - A character array of undetermined length 110 | // 111 | // Returns: 112 | // nothing 113 | // 114 | // Description: 115 | // Takes a URI object and a character array assumed to be containing a URI string. Parses the URI string and updates 116 | // the values of the URI object to match the parsed results. 117 | // 118 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 119 | define_function uriFromString(Uri u, char str[]) { 120 | stack_var char temp[200] 121 | stack_var integer idx; 122 | stack_var char scheme[20], authority[300], path[200], query[200], fragment[200]; 123 | stack_var char user[50], password[50], host[200]; 124 | integer port; 125 | 126 | temp = str; 127 | 128 | scheme = remove_string(temp,"':'",1); 129 | trim_string(scheme,0,1); 130 | 131 | if(find_string(temp,"'//'",1)) { // contains authority 132 | remove_string(temp,"'//'",1); 133 | 134 | if(find_string(temp,"'/'",1)) { // contains path 135 | idx = (find_string(temp,"'/'",1)-1) 136 | } else if(find_string(temp,"'?'",1)) { // contains query 137 | idx = (find_string(temp,"'?'",1)-1) 138 | } else if(find_string(temp,"'#'",1)) { // contains fragment 139 | idx = (find_string(temp,"'#'",1)-1) 140 | } else { 141 | idx = length_string(temp); 142 | } 143 | 144 | authority = mid_string(temp,1,idx); 145 | remove_string(temp,authority,1); 146 | } 147 | 148 | if(find_string(temp,"'/'",1)) { // contains path 149 | 150 | if(find_string(temp,"'?'",1)) { // contains query 151 | idx = (find_string(temp,"'?'",1)-1) 152 | } else if(find_string(temp,"'#'",1)) { // contains fragment 153 | idx = (find_string(temp,"'#'",1)-1) 154 | } else { 155 | idx = length_string(temp); 156 | } 157 | 158 | path = mid_string(temp,1,idx); 159 | remove_string(temp,path,1); 160 | } else { 161 | if(find_string(temp,"'?'",1)) { 162 | path = mid_string(temp,1,find_string(temp,"'?'",1)-1) 163 | remove_string(temp,path,1); 164 | } else if(find_string(temp,"'#'",1)) { 165 | path = mid_string(temp,1,find_string(temp,"'#'",1)-1) 166 | remove_string(temp,path,1); 167 | } 168 | } 169 | 170 | if(find_string(temp,"'?'",1)) { // contains query 171 | remove_string(temp,"'?'",1); 172 | 173 | if(find_string(temp,"'#'",1)) { // contains fragment 174 | idx = (find_string(temp,"'#'",1)-1) 175 | } else { 176 | idx = length_string(temp); 177 | } 178 | 179 | query = mid_string(temp,1,idx); 180 | remove_string(temp,query,1); 181 | } 182 | 183 | if(find_string(temp,"'#'",1)) { // contains fragment 184 | remove_string(temp,"'#'",1); 185 | 186 | idx = length_string(temp); 187 | 188 | fragment = mid_string(temp,1,idx); 189 | remove_string(temp,fragment,1); 190 | } 191 | 192 | // break-apart the authority [user:password@]host[:port] 193 | temp = authority; 194 | if(find_string(temp,"'@'",1)) { 195 | user = remove_string(temp,"':'",1) 196 | trim_string(user,0,1); 197 | password = remove_string(temp,"'@'",1) 198 | trim_string(password,0,1); 199 | } 200 | 201 | if(find_string(temp,"':'",1)) { 202 | host = remove_string(temp,"':'",1) 203 | trim_string(host,0,1); 204 | port = atoi(temp); 205 | } else { 206 | host = temp; 207 | } 208 | 209 | u.scheme = scheme; 210 | u.user = user 211 | u.password = password 212 | u.host = host; 213 | u.path = path; 214 | u.port = port 215 | u.query = query; 216 | u.fragment = fragment; 217 | } 218 | 219 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 220 | // 221 | // Function: uriPercentEncodeString 222 | // 223 | // Parameters: 224 | // char u[] - A character array of undetermined length 225 | // 226 | // Returns: 227 | // char[2048] - A character array containing a percent-encoded string 228 | // 229 | // Description: 230 | // Takes a character array (string) and returns a string containing a percent-encoded version of that string. 231 | // 232 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 233 | define_function char[2048] uriPercentEncodeString(char u[]) { 234 | stack_var char i; 235 | stack_var char c; 236 | stack_var char encoded[2048]; 237 | 238 | for(i = 1; i <= length_string(u); i++) { 239 | c = u[i]; 240 | if(uriIsReservedChar(c) || uriIsUnreservedChar(c)) { 241 | append_string(encoded,"c"); 242 | } 243 | else { 244 | append_string(encoded,uriPercentEncodeChar(c)); 245 | } 246 | } 247 | 248 | return encoded; 249 | } 250 | 251 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 252 | // 253 | // Function: uriPercentEncodeChar 254 | // 255 | // Parameters: 256 | // char c - A char value 257 | // 258 | // Returns: 259 | // char[3] - A character array containing a percent-encoded string 260 | // 261 | // Description: 262 | // Takes a character and returns a string containing a percent-encoded version of that character. 263 | // 264 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 265 | define_function char[3] uriPercentEncodeChar(char c) { 266 | return "'%',format('%02X',"c")"; 267 | } 268 | 269 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 270 | // 271 | // Function: uriIsReservedChar 272 | // 273 | // Parameters: 274 | // char c - A char value 275 | // 276 | // Returns: 277 | // integer - An integer containing either a true (1) or false (0) value 278 | // 279 | // Description: 280 | // Takes a character and returns a true|false result indicating whether the character would be a reserved character 281 | // in a URI. 282 | // 283 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 284 | define_function integer uriIsReservedChar(char c) { 285 | return (find_string(URI_RESERVED_CHARACTERS,"c",1)) 286 | } 287 | 288 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 289 | // 290 | // Function: uriIsUnreservedChar 291 | // 292 | // Parameters: 293 | // char c - A char value 294 | // 295 | // Returns: 296 | // integer - An integer containing either a true (1) or false (0) value 297 | // 298 | // Description: 299 | // Takes a character and returns a true|false result indicating whether the character would not be a reserved 300 | // character in a URI (i.e., is it unreserved?) 301 | // 302 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 303 | define_function integer uriIsUnreservedChar(char c) { 304 | return (find_string(URI_UNRESERVED_CHARACTERS,"c",1)) 305 | } 306 | 307 | 308 | #end_if 309 | -------------------------------------------------------------------------------- /websockets.axi: -------------------------------------------------------------------------------- 1 | program_name='websockets' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: websockets 5 | // 6 | // Description: 7 | // 8 | // - This include file provides NetLinx management of client-side websockets as defined in RFC 6455 (see 9 | // https://tools.ietf.org/html/rfc6455). 10 | // 11 | // - It provides functions to open/close websockets as well as send/receive data on websockets. 12 | // 13 | // - Multiple websockets can be managed simultaneously with minimal programming outside of this include. 14 | // 15 | // - Callback functions are provided to be implemented outside the include file by the "main" program. These 16 | // callback functions will be triggered by this include file whenever an "event" occurs on a websocket. 17 | // 18 | // - The websockets include file performs session tracking through cooking storage which may be useful in situations 19 | // where multiple WebSockets may need to be opened to a web server (i.e., one to authenticate and another to 20 | // communicate). 21 | // 22 | // - The websockets include file manages the WebSockets ping/pong automatically to avoid connections going stale. 23 | // 24 | // Implementation: 25 | // 26 | // - Any NetLinx program utilising the websockets include file must use either the INCLUDE or #INCLUDE keywords to 27 | // include the websockets include file within the program. While the INCLUDE and #INCLUDE keywords are both 28 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 29 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 30 | // for backwards compatibility). 31 | // 32 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 33 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 34 | // 35 | // E.g: 36 | // 37 | // define_program 'Web Sockets Demo' 38 | // 39 | // #include 'websockets' 40 | // 41 | // Usage: 42 | // 43 | // - The following functions are provided to manage the websockets: 44 | // 45 | // webSocketOpen(dev socket, char url[], char subprotocols[], char sessionId[]) 46 | // 47 | // socket: NetLinx socket device (e.g., 0:2:0) 48 | // 49 | // url: URL for the WebSocket to connect to (e.g: 'ws://echo.websocket.org') 50 | // 51 | // subprotocols (optional): Sub-protocol or list of protocols to communicate at an application level (e.g: 52 | // 'chat', 'chat superchat'). Can be empty string ''. 53 | // The full list of registered WebSocket Subprotocols (as at time of writing) is 54 | // provided within the DEFINE_CONSTANT section of the websockets include file. 55 | // 56 | // sessionId (optional): Session ID for tracking a session with a webserver. Can be empty string ''. The 57 | // session ID is stored against the WebSocket and can get obtained at any time by 58 | // calling the webSocketGetSessionId function. The stored session ID will be updated 59 | // if the webserver includes a 'Set-Cookie' header field in the HTTP response during 60 | // the WebSocket Open Handshake. 61 | // 62 | // E.g: 63 | // 64 | // webSocketOpen(0:2:0,'ws://echo.websocket.org','',''); 65 | // 66 | // E.g: 67 | // 68 | // webSocketOpen(0:2:0,'wss://echo.websocket.org','soap',''); 69 | // 70 | // E.g: 71 | // 72 | // webSocketOpen(0:2:0,'ws://ws.websocketstest.com:8080/service','',''); 73 | // 74 | // E.g: 75 | // 76 | // webSocketOpen(0:2:0,'ws://192.168.7.118/login','','JSESSIONID=1iamqqta2o6hzbqjzfpdw1piy'); 77 | // 78 | // Note: If url is prefixed with 'ws://' an unsecured websocket will be opened on TCP port 80. If 79 | // wsResource is prefixed with 'wss://' a secure websocket will be opened on TCP port 443. Alternatively, if 80 | // the url contains a port this will be used instead (e.g., 'ws://ws.websocketstest.com:8080/service'). 81 | // 82 | // webSocketSend(dev socket, char data[]) 83 | // 84 | // socket: NetLinx socket device (e.g., 0:2:0) 85 | // 86 | // data: Data to send. 87 | // 88 | // E.g: 89 | // 90 | // webSocketSend(dvSocket,'Rock it with HTML WebSockets!'); 91 | // 92 | // webSocketClose(dev socket, integer code, char reason[]) 93 | // 94 | // socket: NetLinx socket device (e.g., 0:2:0) 95 | // 96 | // code: Status code indicating why we are closing the connection 97 | // 98 | // reason: Human-readable string indicating why we are closing the connection. 99 | // 100 | // E.g: 101 | // 102 | // webSocketClose(dvSocket,0,''); 103 | // 104 | // - There are 4 x callback functions defined within the websockets include file: webSocketOnOpen, webSocketOnClose, 105 | // webSocketOnError, and webSocketOnMessage. These callback functions will be called by the websockets include 106 | // file when corresponding "events" trigger on the websocket. 107 | // 108 | // The callback functions should not be edited within the websockets include file. Instead, the callback functions 109 | // should be copied into the main program and then the code statements within the functions can be customised for 110 | // the program. In this way, multiple NetLinx modules within the same program (or even different programs) can each 111 | // make use of the websocket include file without having to make multiple versions of the websockets include file 112 | // each with different code within the callback functions. 113 | // 114 | // Note that each of the callback functions are surrounded by #IF_NOT_DEFINED and #END_IF statements within the 115 | // websockets include file. These statements MUST NOT be copied into the main program, just the function itself. 116 | // The code within the open and closing braces of the function can then be completely customised. 117 | // 118 | // Finally, for each of the callback functions implemented within the main program a corresponding #DEFINE 119 | // statement MUST be placed in the main program ABOVE the #INCLUDE statement which includes the websocket include 120 | // file. The #DEFINE statement should use a symbol matching the symbol used by the #IF_NOT_DEFINED statement above 121 | // the definition of the callback function within the websockets include file. 122 | // 123 | // E.g: The webSocketsOnClose callback function is defined within the websockets include file like so: 124 | // 125 | // #if_not_defined WEBSOCKET_EVENT_CLOSE 126 | // define_function webSocketOnClose(dev webSocket) { 127 | // send_string 0, "'webSocketOnClose(',itoa(webSocket.number),':',itoa(webSocket.port),':',itoa(webSocket.system),')'" 128 | // } 129 | // #end_if 130 | // 131 | // This callback function SHOULD be implemented in the outside program as follows: 132 | // 133 | // At the top of the program, make sure to place the #DEFINE compiler directive above the #include compiler directive: 134 | // 135 | // define_program 'Web Sockets Demo' 136 | // 137 | // #define WEBSOCKET_EVENT_CLOSE 138 | // #include 'websockets' 139 | // 140 | // Then, lower in the code, implement the webSocketOnClose function: 141 | // 142 | // define_function webSocketOnClose(dev webSocket) { 143 | // // code goes here 144 | // } 145 | // 146 | // Implementing the callback function as shown above guarantees that the function defined in the outside program 147 | // file will be called instead of the unimplemented callback function defined within the websockets include file. 148 | // 149 | // - Some webservers may require authentication via one websocket before another websocket can be opened for 150 | // communication. In these scenarios the "comm" websocket will need to maintain the same session with the webserver 151 | // that was created for the "auth" websocket. When the "auth" websocket is initially establishing itself with the 152 | // webserver a HTTP handshake (known as the WebSocket Open Handshake) is performed and the webserver will send 153 | // a session ID to the "auth" websocket within the 'Set-Cookie' header field. When the "comm" websocket establishes 154 | // a connection to the webserver it will need to send the same session ID to the webserver during the intial HTTP 155 | // handshake within the 'Cookie' header field. 156 | // 157 | // The websockets include file maintains a record of session ID's for each websocket created. If the sessionId 158 | // parameter contained the empty string ('') when the WebSocketOpen function was called the HTTP GET request sent 159 | // to the webserver will not contain a 'Cookie' header field. 160 | // 161 | // E.g: 162 | // 163 | // webSocketOpen(dvSocketAuth,'ws://test.server.org','',''); 164 | // 165 | // If the sessionId parameter was not empty when the WebSocketOpen function was called this value will be cached 166 | // against the websocket in the websockets include file and sent to the webserver in the 'Cookie' header field of 167 | // the HTTP GET request. 168 | // 169 | // E.g: 170 | // 171 | // webSocketOpen(dvSocketAuth,'ws://test.server.org','','JSESSIONID=xnjgi1kj1jd7kfbjwvmqyg5m'); 172 | // 173 | // If the servers' HTTP response during the WebSocket Open Handshake contains a 'Set-Cookie' header field and the 174 | // value contains a session ID this session ID will replace the cached session ID for the WebSocket in the 175 | // websockets include file. At anytime, the cached session ID for a WebSocket can be read by calling the 176 | // WebSocketGetSessionId function. 177 | // 178 | // E.g: 179 | // 180 | // webSocketGetSessionId(dvSocketAuth); 181 | // 182 | // To implement session ID tracking across multiple WebSockets it is recommended to first open an "auth" WebSocket 183 | // and pass an empty string ('') to the sessionId parameter of the WebSocketOpen function - the server will send 184 | // a session ID when the WebSocket is being established and this will be cached against the "auth" WebSocket. 185 | // 186 | // E.g: 187 | // 188 | // webSocketOpen(dvSocketAuth,'ws://test.server.org/login','',''); 189 | // 190 | // Next, capture the open event for the "auth" webserver in the webSocketOpen function and send the login data 191 | // to the webserver to login. 192 | // 193 | // E.g: 194 | // 195 | // define_function webSocketOnOpen(dev socket) { 196 | // if(socket == dvSocketAuth) { 197 | // webSocketSend(dvSocketAuth,'{"login":{"username":"administrator","password":"p@55w0rd"}}'); 198 | // } 199 | // } 200 | // 201 | // Next, listen for login confirmation from the webserver in the webSocketOnMessage function and then open a new 202 | // WebSocket. Call the webSocketGetSessionId function to read the cached session ID from the "auth" webserver and 203 | // pass this to the sessionId parameter of the webSocketOpen function when creating the new WebSocket. 204 | // 205 | // E.g: 206 | // 207 | // define_function webSocketOnMessage(dev socket, char data[]) { 208 | // if(socket == dvSocketAuth) { 209 | // if(data == '{"login":"success"}') { 210 | // webSocketOpen(dvSocketComm,'ws://test.server.org/main','',webSocketGetCookie(dvSocketAuth)); 211 | // } 212 | // } 213 | // } 214 | // 215 | // Next, using the webSocketOnOpen function listen for the "auth" websocket being opened: 216 | // 217 | // define_function webSocketOnOpen(dev socket) { 218 | // if(socket == dvSocketAuth) { 219 | // webSocketSend(dvSocketAuth,'{"login":{"username":"administrator","password":"p@55w0rd"}}'); 220 | // } else if(socket == dvSocketComm) { 221 | // // insert code here 222 | // } 223 | // } 224 | // 225 | // At this point, the "auth" websocket will not only be open but also authenticated with the webserver. 226 | // 227 | // NOTE: If the HTTP response during the WebSocket Open Handshake contains multiple 'Set-Cookie' header fields 228 | // the session tracking in the websockets include file may not work (UNTESTED). 229 | // 230 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 231 | 232 | #if_not_defined __WEB_SOCKET__ 233 | #define __WEB_SOCKET__ 234 | 235 | 236 | //#include 'uri' 237 | #include 'convert' 238 | #include 'crypto' 239 | #include 'http' 240 | #include 'string' 241 | #include 'debug' 242 | 243 | 244 | define_type 245 | 246 | struct WebSocket { 247 | dev socket; 248 | Uri url; 249 | integer tcpPort; 250 | integer readyState; // CLOSED | OPEN | OPENING | CLOSING 251 | char subprotocols[50]; // Sub-protocols 252 | char clientHandshakeKey[24]; // Sec-WebSocket-Key value for HTTP Opening Handshake 253 | char sessionId[200]; 254 | char receiveBuffer[20000]; 255 | } 256 | 257 | struct WebSocketFrame { 258 | char fin; 259 | char rsv1; 260 | char rsv2; 261 | char rsv3; 262 | char opCode; 263 | char mask; 264 | char maskingKey[4]; 265 | char payloadData[20000]; // payload length can be determined from calling length_array or lenth_string for payloadData 266 | } 267 | 268 | 269 | define_constant 270 | 271 | MAX_WEB_SOCKETS = 20; // Edit this value if you want to manage more web sockets 272 | 273 | // WebSocket Opcodes (see http://www.iana.org/assignments/websocket/websocket.xhtml) 274 | WEBSOCKET_OPCODE_CONT = 0; 275 | WEBSOCKET_OPCODE_TEXT = 1; 276 | WEBSOcKET_OPCODE_BIN = 2; 277 | WEBSOCKET_OPCODE_CLOSE = 8; 278 | WEBSOCKET_OPCODE_PING = 9; 279 | WEBSOCKET_OPCODE_PONG = 10; 280 | 281 | // WebSocket Close Codes (see http://www.iana.org/assignments/websocket/websocket.xhtml) 282 | WEBSOCKET_CLOSE_STATUS_NORMAL_CLOSURE = 1000; 283 | WEBSOCKET_CLOSE_STATUS_GOING_AWAY = 1001; 284 | WEBSOCKET_CLOSE_STATUS_PROTOCOL_ERROR = 1002; 285 | WEBSOCKET_CLOSE_STATUS_UNSUPPORTED_DATA = 1003; 286 | WEBSOCKET_CLOSE_STATUS_NO_STATUS_RECEIVED = 1005; 287 | WEBSOCKET_CLOSE_STATUS_ABNORMAL_CLOSURE = 1006; 288 | WEBSOCKET_CLOSE_STATUS_INVALID_FRAME_PAYLOAD_DATA = 1007; 289 | WEBSOCKET_CLOSE_STATUS_POLICY_VIOLATION = 1008; 290 | WEBSOCKET_CLOSE_STATUS_MESSAGE_TOO_BIG = 1009; 291 | WEBSOCKET_CLOSE_STATUS_MANDATORY_EXT = 1010; 292 | WEBSOCKET_CLOSE_STATUS_INTERNAL_SERVER_ERROR = 1011; 293 | WEBSOCKET_CLOSE_STATUS_SERVICE_RESTART = 1012; 294 | WEBSOCKET_CLOSE_STATUS_TRY_AGAIN_LATER = 1013; 295 | WEBSOCKET_CLOSE_STATUS_INVALID_RESPONSE_FROM_UPSTREAM_SERVER = 1014; 296 | WEBSOCKET_CLOSE_STATUS_TLS_HANDSHAKE = 1015; 297 | 298 | // WebSocket Subprotocols (see http://www.iana.org/assignments/websocket/websocket.xhtml) 299 | WEBSOCKET_SUBPROTOCOL_MBWS[] = 'MBWS.huawai.com'; 300 | WEBSOCKET_SUBPROTOCOL_MBLWS[] = 'MBLWS.huawai.com'; 301 | WEBSOCKET_SUBPROTOCOL_SOAP[] = 'soap'; 302 | WEBSOCKET_SUBPROTOCOL_WAMP[] = 'wamp'; 303 | WEBSOCKET_SUBPROTOCOL_STOMP_10[] = 'v10.stomp'; 304 | WEBSOCKET_SUBPROTOCOL_STOMP_11[] = 'v11.stomp'; 305 | WEBSOCKET_SUBPROTOCOL_STOMP_12[] = 'v12.stomp'; 306 | WEBSOCKET_SUBPROTOCOL_OCPP_1_2[] = 'ocpp1.2'; 307 | WEBSOCKET_SUBPROTOCOL_OCPP_1_5[] = 'ocpp1.5'; 308 | WEBSOCKET_SUBPROTOCOL_OCPP_1_8[] = 'ocpp1.8'; 309 | WEBSOCKET_SUBPROTOCOL_OCPP_2_0[] = 'ocpp2.0'; 310 | WEBSOCKET_SUBPROTOCOL_RFB[] = 'rfb'; 311 | WEBSOCKET_SUBPROTOCOL_SIP[] = 'sip'; 312 | WEBSOCKET_SUBPROTOCOL_OMA_REST[] = 'notificationchannel-netapi-rest.openmobilealliance.org'; 313 | WEBSOCKET_SUBPROTOCOL_WPCP[] = 'wpcp'; 314 | WEBSOCKET_SUBPROTOCOL_AMQP[] = 'amqp'; 315 | WEBSOCKET_SUBPROTOCOL_MQTT[] = 'mqtt'; 316 | WEBSOCKET_SUBPROTOCOL_JSFLOW[] = 'jsflow'; 317 | WEBSOCKET_SUBPROTOCOL_RWPCP[] = 'rwpcp'; 318 | WEBSOCKET_SUBPROTOCOL_XMPP[] = 'xmpp'; 319 | WEBSOCKET_SUBPROTOCOL_SHIP[] = 'ship'; 320 | WEBSOCKET_SUBPROTOCOL_MIELE_CLOUD_CONNECT[] = 'mielecloudconnect'; 321 | WEBSOCKET_SUBPROTOCOL_PUSH_CHANNEL[] = 'v10.pcp.sap.com'; 322 | WEBSOCKET_SUBPROTOCOL_MSRP[] = 'msrp'; 323 | WEBSOCKET_SUBPROTOCOL_SALTY_RTC_1_0[] = 'v1.saltyrtc.org'; 324 | 325 | CLOSED = 0; 326 | CONNECTING = 1; 327 | OPEN = 2; 328 | CLOSING = 3; 329 | 330 | 331 | define_variable 332 | 333 | // WebSocket objects 334 | volatile WebSocket webSockets[MAX_WEB_SOCKETS]; 335 | 336 | // Socket device array (for data_event) 337 | volatile dev wsSockets[MAX_WEB_SOCKETS]; 338 | 339 | 340 | 341 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 342 | // 343 | // Callback functions to be implemented in the main program file 344 | // 345 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 346 | #if_not_defined WEBSOCKET_EVENT_CLOSE 347 | define_function webSocketOnClose(dev socket) { 348 | send_string 0, "'webSocketOnClose(',devToString(socket),')'" 349 | } 350 | #end_if 351 | 352 | #if_not_defined WEBSOCKET_EVENT_ERROR 353 | define_function webSocketOnError(dev socket, long errorCode, char errorReason[]) { 354 | send_string 0, "'webSocketOnError(',devToString(socket),',',itoa(errorCode),',',errorReason,')'" 355 | } 356 | #end_if 357 | 358 | #if_not_defined WEBSOCKET_EVENT_MESSAGE 359 | define_function webSocketOnMessage(dev socket, char data[]) { 360 | send_string 0, "'webSocketOnMessage(',devToString(socket),',',data,')'" 361 | } 362 | #end_if 363 | 364 | #if_not_defined WEBSOCKET_EVENT_OPEN 365 | define_function webSocketOnOpen(dev socket) { 366 | send_string 0, "'webSocketOnOpen(',devToString(socket),')'" 367 | } 368 | #end_if 369 | 370 | 371 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 372 | // 373 | // Function: webSocketOpen 374 | // 375 | // Parameters: 376 | // dev socket - A NetLinx socket device 377 | // char url[] - A character array (string) of undertermined length containing the WebSocket URL. 378 | // char subprotocols[] - A character array (string) of undetermined length. Contains one of more subprotocols. 379 | // 380 | // Returns: 381 | // nothing 382 | // 383 | // Description: 384 | // Initiates the opening and management of a websocket. The url parameter specifies the address for the websocket 385 | // to connect to. The subprotocols parameter specifies any subprotocols for the server to use once the websocket 386 | // connection has been established. 387 | // 388 | // The url must conform to the syntax for a URI as specified in RFC 3986 (see https://tools.ietf.org/html/rfc3986): 389 | // 390 | // scheme:[//[user:password@]host[:port]][/]path[?query][#fragment] 391 | // 392 | // The url must be prefixed with either 'ws://' or 'wss://' (for secure connections). If the url contains a port 393 | // then that port will be used to open the TCP/IP socket, otherwise port 80 will be used for unsecure websockets or 394 | // port 443 for secure websockets. 395 | // 396 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 397 | define_function webSocketOpen(dev socket, char url[], char subprotocols[], char sessionId[]) { 398 | stack_var integer idx; 399 | 400 | for(idx = 1; idx <= length_array(webSockets); idx++) { 401 | if(socket == webSockets[idx].socket) 402 | break; 403 | } 404 | 405 | if(idx > length_array(webSockets)) { // did not find matching sockets in managed websockets 406 | if(idx <= max_length_array(webSockets)) { // socket array not full so add socket 407 | set_length_array(WebSockets,idx); 408 | WebSockets[idx].socket = socket; 409 | set_length_array(wsSockets,idx); 410 | wsSockets[idx] = socket; 411 | rebuild_event(); 412 | } else { 413 | webSocketOnError(socket,1,'WebSockets Management Array Full') 414 | return; 415 | } 416 | } 417 | 418 | WebSockets[idx].subprotocols = subprotocols; 419 | 420 | WebSockets[idx].sessionId = sessionId; 421 | 422 | uriFromString(WebSockets[idx].url,url); 423 | 424 | if(WebSockets[idx].url.scheme == 'ws') { 425 | if(WebSockets[idx].url.port) { 426 | ip_client_open(socket.port,WebSockets[idx].url.host,WebSockets[idx].url.port,IP_TCP); 427 | } else { 428 | ip_client_open(socket.port,WebSockets[idx].url.host,80,IP_TCP); 429 | } 430 | } else if(WebSockets[idx].url.scheme == 'wss') { 431 | if(WebSockets[idx].url.port) { 432 | tls_client_open(socket.port,WebSockets[idx].url.host,WebSockets[idx].url.port,TLS_IGNORE_CERTIFICATE_ERRORS); 433 | } else { 434 | tls_client_open(socket.port,WebSockets[idx].url.host,443,TLS_IGNORE_CERTIFICATE_ERRORS); 435 | } 436 | } 437 | } 438 | 439 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 440 | // 441 | // Function: webSocketSend 442 | // 443 | // Parameters: 444 | // dev socket - A NetLinx socket device 445 | // char data[] - A character array (string) of undertermined length. Contains data to send. 446 | // 447 | // Returns: 448 | // nothing 449 | // 450 | // Description: 451 | // Sends a websocket message through a NetLinx socket device. The data is packed into a websocket frame prior to 452 | // sending. Data is treated as text. 453 | // 454 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 455 | define_function webSocketSend(dev socket, char data[]) { 456 | webSocketSendText(socket,data); 457 | } 458 | 459 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 460 | // 461 | // Function: webSocketClose 462 | // 463 | // Parameters: 464 | // dev socket - A NetLinx socket device 465 | // integer code - A character array (string) of undertermined length. Contains status code indicating why 466 | // the connection is being closed. 467 | // char reason[] - A character array (string) of undertermined length. Human-readable string explaining why the 468 | // connection is closing. 469 | // 470 | // Returns: 471 | // nothing 472 | // 473 | // Description: 474 | // Initiates the closing of a WebSocket connection or connection attempt. If the connection is already CLOSED, this 475 | // method does nothing. Note that, as per RFC6455, the socket connection for a websocket is ALWAYS closed by the 476 | // server and never the client. If the client wishes to close the connection it sends the websocket close frame to 477 | // the server and the server will close the socket. 478 | // 479 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 480 | define_function webSocketClose(dev socket, integer code, char reason[]) { 481 | stack_var integer idx; 482 | 483 | for(idx=1; idx<=length_array(wsSockets); idx++) { 484 | if(socket == wsSockets[idx]) { 485 | if ((webSockets[idx].readyState != CLOSED) && (webSockets[idx].readyState != CLOSING)) { 486 | webSockets[idx].readyState = CLOSING; 487 | webSocketSendClose(socket,''); 488 | } 489 | break; 490 | } 491 | } 492 | } 493 | 494 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 495 | // 496 | // Function: webSocketFrameToString 497 | // 498 | // Parameters: 499 | // WebSocketFrame wsf - A web socket frame structure 500 | // 501 | // Returns: 502 | // char[1024] - A character array (string) containing a websocket frame 503 | // 504 | // Description: 505 | // Builds a data string conforming to the websocket protocol data frame defined in RFC6455. 506 | // 507 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 508 | define_function char [1024] webSocketFrameToString(WebSocketFrame wsf) { 509 | char result[1024]; 510 | long payloadLen; 511 | 512 | result = ''; 513 | set_length_array(result,2); // minimum length for a valid websocket frame 514 | 515 | result[1] = type_cast((wsf.fin BAND $1) << 7); 516 | result[1] = type_cast(result[1] BOR ((wsf.rsv1 BAND $1) << 6)); 517 | result[1] = type_cast(result[1] BOR ((wsf.rsv2 BAND $1) << 5)); 518 | result[1] = type_cast(result[1] BOR ((wsf.rsv3 BAND $1) << 4)); 519 | 520 | result[1] = type_cast(result[1] BOR (wsf.opCode BAND $F)); 521 | 522 | result[2] = type_cast((wsf.mask BAND $1) << 7); 523 | 524 | payloadLen = length_array(wsf.payloadData); 525 | 526 | if(payloadLen == 0) { 527 | if(wsf.mask) { 528 | result = "result,wsf.maskingKey"; 529 | } 530 | return result; 531 | } else if(payloadLen <= 125) { 532 | result[2] = type_cast(result[2] BOR payloadLen); 533 | //result[2] = type_cast(result[2] BOR (payloadLen BAND $7F)); 534 | set_length_array(result,2); 535 | } else if(payloadLen <= 65535) { 536 | result[2] = type_cast(result[2] BOR 126); 537 | result[3] = type_cast((payloadLen BAND $FF00) >> 8); 538 | result[4] = type_cast(payloadLen BAND $FF); 539 | set_length_array(result,4); 540 | } else if(payloadLen > 65535) { 541 | result[2] = type_cast(result[2] BOR 127); 542 | set_length_array(result,6); 543 | result = "result,ltba(payloadLen)" 544 | set_length_array(result,10); 545 | } 546 | 547 | if(wsf.mask) { 548 | result = "result,wsf.maskingKey"; 549 | result = "result,webSocketMask(wsf.payloadData,wsf.maskingKey)"; 550 | } else { 551 | result = "result,wsf.payloadData"; 552 | } 553 | 554 | return result; 555 | } 556 | 557 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 558 | // 559 | // Function: webSocketFrameFromString 560 | // 561 | // Parameters: 562 | // WebSocketFrame wsf - A web socket frame structure 563 | // char data[] - A character array (string) of undetermined length. 564 | // 565 | // Returns: 566 | // nothing 567 | // 568 | // Description: 569 | // The data string is assumed to conform to the websocket protocol data frame defined in RFC6455. The data string 570 | // is parsed and the WebSocketFrame parameter (wsf) is updated to contain the values obtained during the parsing. 571 | // 572 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 573 | define_function sinteger webSocketFrameFromString(WebSocketFrame wsf, char data[]) { 574 | long payloadLen; 575 | integer nByte; 576 | 577 | if(length_array(data) < 2) { 578 | print("'webSocketFrameFromString exiting, data is less than 2 bytes'",false); 579 | return -1; 580 | } 581 | 582 | wsf.fin = 0; 583 | wsf.rsv1 = 0; 584 | wsf.rsv2 = 0; 585 | wsf.rsv3 = 0; 586 | wsf.opCode = 0; 587 | wsf.mask = 0; 588 | wsf.maskingKey = ''; 589 | wsf.payloadData = ''; 590 | 591 | wsf.fin = type_cast((data[1] BAND $80) >> 7) 592 | wsf.rsv1 = type_cast((data[1] BAND $40) >> 6) 593 | wsf.rsv2 = type_cast((data[1] BAND $20) >> 5) 594 | wsf.rsv3 = type_cast((data[1] BAND $10) >> 4) 595 | wsf.opCode = type_cast(data[1] BAND $F) 596 | 597 | wsf.mask = type_cast((data[2] BAND $80) >> 7) 598 | 599 | if((data[2] BAND $7F) == 0) { 600 | payloadLen = 0; 601 | nByte = 3; 602 | } else if((data[2] BAND $7F) <= 125) { 603 | payloadLen = (data[2] BAND $7F); 604 | nByte = 3; 605 | } else if((data[2] BAND $7F) == 126) { 606 | payloadLen = ((data[3] << 8) BOR (data[4])) 607 | nByte = 5; 608 | } else if((data[2] BAND $7F) == 127) { 609 | payloadLen = ((data[3] << 56) BOR (data[4] << 48) BOR (data[5] << 40) BOR (data[6] << 32) 610 | BOR (data[7] << 24) BOR (data[8] << 16) BOR (data[9] << 8) BOR (data[10])); 611 | nByte = 11; 612 | } 613 | if(wsf.mask) { 614 | wsf.maskingKey = "data[nByte],data[nByte+1],data[nByte+2],data[nByte+3]"; 615 | 616 | if((payloadLen != 0) && (length_array(data) < (nByte+4+payloadLen-1))) { 617 | print("'webSocketFrameFromString exiting, payload following masking key not complete'",false); 618 | return -1; 619 | } 620 | 621 | wsf.payloadData = webSocketUnmask(mid_string(data,nByte+4,payloadLen),wsf.maskingKey); 622 | } else { 623 | 624 | if((payloadLen != 0) && (length_array(data) < (nByte+payloadLen-1))) { 625 | print("'webSocketFrameFromString exiting, payload not complete'",false); 626 | return -1; 627 | } 628 | 629 | wsf.payloadData = mid_string(data,nByte,payloadLen); 630 | } 631 | 632 | if(wsf.payloadData == 0) { 633 | remove_string(data,"data[1],data[2]",1) 634 | } 635 | else { 636 | remove_string(data,wsf.payloadData,1); 637 | } 638 | 639 | print("'webSocketFrameFromString processed complete WebSocket Frame'",false); 640 | 641 | return 0; 642 | } 643 | 644 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 645 | // 646 | // Functions: 647 | // webSocketSendContinuation 648 | // webSocketSendText 649 | // webSocketSendBinary 650 | // webSocketSendPing 651 | // webSocketSendPong 652 | // webSocketSendClose 653 | // 654 | // Parameters: 655 | // dev socket - A NetLinx socket device 656 | // char data[] - A character array (string) of undertermined length. Contains data to send. 657 | // 658 | // Returns: 659 | // nothing 660 | // 661 | // Description: 662 | // Sends a websocket message through a NetLinx socket device. The data is packed into a websocket frame prior to 663 | // sending. 664 | // 665 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 666 | define_function webSocketSendContinuation(dev socket, char data[]) { 667 | print("'Sending websocket continuation frame (',itoa(length_array(webSocketPackFrame(WEBSOCKET_OPCODE_CONT,data))),' bytes) to Socket[',devToString(socket),'].'",false); 668 | send_string socket, webSocketPackFrame(WEBSOCKET_OPCODE_CONT,data); 669 | } 670 | 671 | define_function webSocketSendText(dev socket, char data[]) { 672 | print("'Sending websocket text data (',itoa(length_array(webSocketPackFrame(WEBSOCKET_OPCODE_TEXT,data))),' bytes) to Socket[',devToString(socket),'].'",false); 673 | send_string socket, webSocketPackFrame(WEBSOCKET_OPCODE_TEXT,data); 674 | } 675 | 676 | define_function webSocketSendBinary(dev socket, char data[]) { 677 | print("'Sending websocket binary data (',itoa(length_array(webSocketPackFrame(WEBSOCKET_OPCODE_BIN,data))),' bytes) to Socket[',devToString(socket),'].'",false); 678 | send_string socket, webSocketPackFrame(WEBSOCKET_OPCODE_BIN,data); 679 | } 680 | 681 | define_function webSocketSendPing(dev socket, char data[]) { 682 | print("'Sending websocket ping (',itoa(length_array(webSocketPackFrame(WEBSOCKET_OPCODE_PING,data))),' bytes) to Socket[',devToString(socket),'].'",false); 683 | send_string socket, webSocketPackFrame(WEBSOCKET_OPCODE_PING,data); 684 | } 685 | 686 | define_function webSocketSendPong(dev socket, char data[]) { 687 | print("'Sending websocket pong (',itoa(length_array(webSocketPackFrame(WEBSOCKET_OPCODE_PONG,data))),' bytes) to Socket[',devToString(socket),'].'",false); 688 | send_string socket, webSocketPackFrame(WEBSOCKET_OPCODE_PONG,data); 689 | } 690 | 691 | define_function webSocketSendClose(dev socket, char data[]) { 692 | print("'Sending websocket close (',itoa(length_array(webSocketPackFrame(WEBSOCKET_OPCODE_CLOSE,data))),' bytes) to Socket[',devToString(socket),'].'",false); 693 | send_string socket, webSocketPackFrame(WEBSOCKET_OPCODE_CLOSE,data); 694 | } 695 | 696 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 697 | // 698 | // Functions: 699 | // webSocketMask 700 | // webSocketUnmask 701 | // 702 | // Parameters: 703 | // char data[] - A character array (string) of undertermined length. Contains data to mask/unmask. 704 | // char maskingKey[] - A character array (string) of undertermined length. Contains masking key. 705 | // 706 | // Returns: 707 | // char[1024] - A character array (string) containing masked/unmasked data 708 | // 709 | // Description: 710 | // Masks/Unmasks data confirming to the masking/unmasking process defined in RFC6455 (same process for both). 711 | // 712 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 713 | define_function char[1024] webSocketMask(char data[], char maskingKey[4]) { 714 | char result[1024]; 715 | integer i, j; 716 | 717 | for(i = 1; i<=length_array(data); i++) { 718 | j = (((i-1) mod 4)+1); 719 | result[i] = (data[i] BXOR maskingKey[j]); 720 | } 721 | set_length_array(result,i-1); 722 | return result; 723 | } 724 | 725 | define_function char[1024] webSocketUnmask(char data[], char maskingKey[4]) { 726 | return webSocketMask(data,maskingKey); // the process for masking/unmasking is the same 727 | } 728 | 729 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 730 | // 731 | // Function: webSocketPackFrame 732 | // 733 | // Parameters: 734 | // char opCode - A single-byte char value. Contains Op Code. 735 | // char data[] - A character array (string) of undertermined length. Contains data. 736 | // 737 | // Returns: 738 | // char[1024] - A character array (string) conforming to the websocket protocol data frame defined in RFC6455 739 | // 740 | // Description: 741 | // Takes a websocket Op Code and some data and creates and returns a string containing a websocket protocol data 742 | // frame. The data string may be empty. 743 | // 744 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 745 | define_function char[1024] webSocketPackFrame(char opCode, char data[]) { 746 | char frame[1024]; 747 | long maskingKeyAsLong; 748 | char maskingKey[4]; 749 | char maskedData[1024]; 750 | char i,j; 751 | WebSocketFrame wsf; 752 | 753 | wsf.fin = 1; 754 | wsf.rsv1 = 0; 755 | wsf.rsv2 = 0; 756 | wsf.rsv3 = 0; 757 | wsf.opCode = opCode; 758 | wsf.mask = 1; 759 | wsf.maskingKey = ltba(random_number(4294967295)); 760 | wsf.payloadData = data; 761 | 762 | return webSocketFrameToString(wsf); 763 | } 764 | 765 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 766 | // 767 | // Functions: 768 | // webSocketPackTextFrame 769 | // webSocketPackBinaryFrame 770 | // webSocketPackPingFrame 771 | // webSocketPackPongFrame 772 | // webSocketPackCloseFrame 773 | // 774 | // Parameters: 775 | // char data[] - A character array (string) of undertermined length. Contains data. 776 | // 777 | // Returns: 778 | // char[1024] - A character array (string) conforming to the websocket protocol data frame defined in RFC6455 779 | // 780 | // Description: 781 | // Takes a some data and creates and returns a string containing a websocket protocol data frame. The data string 782 | // may be empty. 783 | // 784 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 785 | define_function char[1024] webSocketPackTextFrame(char data[]) { 786 | return webSocketPackFrame(WEBSOCKET_OPCODE_TEXT,data); 787 | } 788 | 789 | define_function char[1024] webSocketPackBinaryFrame(char data[]) { 790 | return webSocketPackFrame(WEBSOCKET_OPCODE_BIN,data); 791 | } 792 | 793 | define_function char[1024] webSocketPackPingFrame(char data[]) { 794 | return webSocketPackFrame(WEBSOCKET_OPCODE_PING,data); 795 | } 796 | 797 | define_function char[1024] webSocketPackPongFrame(char data[]) { 798 | return webSocketPackFrame(WEBSOCKET_OPCODE_PONG,data); 799 | } 800 | 801 | define_function char[1024] webSocketPackCloseFrame(char data[]) { 802 | return webSocketPackFrame(WEBSOCKET_OPCODE_CLOSE,data); 803 | } 804 | 805 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 806 | // 807 | // Function: webSocketCreateHandshakeKeyClient 808 | // 809 | // Parameters: 810 | // none 811 | // 812 | // Returns: 813 | // char[200] - A character array (string) containing the client-side key for the WebSocket HTTP Open Handshake 814 | // 815 | // Description: 816 | // Creates a client-side key to be used in the Sec-WebSocket-Key header of a WebSocket HTTP Open Hansdhake (see 817 | // RFC6455). 818 | // 819 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 820 | define_function char[200] webSocketCreateHandshakeKeyClient() { 821 | char secWebSocketKey[200], byteVal[16]; 822 | integer i; 823 | 824 | for(i=1;i<=max_length_array(byteVal);i++) { 825 | byteVal[i] = random_number(256) 826 | } 827 | set_length_array(byteVal,16); 828 | 829 | secWebSocketKey = encode('base64',byteVal); 830 | 831 | return secWebSocketKey; 832 | } 833 | 834 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 835 | // 836 | // Function: webSocketCreateHandshakeKeyServer 837 | // 838 | // Parameters: 839 | // char secWebSocketKey[] - A character array (string) of undertermined length. 840 | // 841 | // Returns: 842 | // char[200] - A character array (string) containing the server-side key for the WebSocket HTTP Open Handshake 843 | // 844 | // Description: 845 | // Creates a server-side key to be used in the Sec-WebSocket-Accept header of a WebSocket HTTP Open Hansdhake (see 846 | // RFC6455). The secWebSocketKey parameter should contain the client-side key which is used by the server to produce 847 | // the server-side key. 848 | // 849 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 850 | define_function char[200] webSocketCreateHandshakeKeyServer(char secWebSocketKey[]) { 851 | char secWebSocketAccept[200]; 852 | 853 | secWebSocketAccept = encode('base64',hash('sha1',"secWebSocketKey,'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'")); 854 | 855 | return secWebSocketAccept; 856 | } 857 | 858 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 859 | // 860 | // Function: webSocketSendOpenHandshake 861 | // 862 | // Parameters: 863 | // dev socket - A NetLinx socket device 864 | // 865 | // Returns: 866 | // nothing 867 | // 868 | // Description: 869 | // Creates and sends a HTTP string containing a WebSocket Open Handshake (See RFC6455). 870 | // 871 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 872 | define_function webSocketSendOpenHandshake(dev socket) { 873 | HttpRequest request; 874 | char secWebSocketKey[200], host[200]; 875 | integer idx; 876 | 877 | for(idx=1; idx<=length_array(webSockets); idx++) { 878 | if(socket == webSockets[idx].socket) { 879 | break; 880 | } 881 | } 882 | if(idx > length_array(wsSockets)) return; 883 | 884 | webSockets[idx].clientHandshakeKey = webSocketCreateHandshakeKeyClient(); 885 | 886 | httpInitRequest(request); 887 | request.method = HTTP_METHOD_GET; 888 | request.version = 1.1; 889 | request.requestUri = uriToString(webSockets[idx].url); //'ws://echo.websocket.org'); 890 | httpSetHeader(request.headers, 'Host', webSockets[idx].url.host); 891 | httpSetHeader(request.headers, 'Connection', 'Upgrade'); 892 | httpSetHeader(request.headers, 'Upgrade', 'websocket'); 893 | httpSetHeader(request.headers, 'Origin', "'http://',webSockets[idx].url.host"); 894 | if(length_string(WebSockets[idx].sessionId)) { 895 | httpSetHeader(request.headers, 'Cookie', WebSockets[idx].sessionId); //"'JSESSIONID=','6wi46ymy1v9ss6yv289loli2'"); // 5wi46ymy1v9ss6yv289loli2 896 | } 897 | httpSetHeader(request.headers, 'Sec-WebSocket-Version', '13'); // version 13 is the standard as defined by RFC6455 898 | httpSetHeader(request.headers, 'Sec-WebSocket-Key', webSockets[idx].clientHandshakeKey); 899 | if(length_string(webSockets[idx].subprotocols)) { 900 | httpSetHeader(request.headers, 'Sec-WebSocket-Protocol', webSockets[idx].subprotocols); 901 | } 902 | print("'Sending HTTP WebSocket Open Handshake to Socket[',devToString(socket),']:'",false); 903 | 904 | print(httpRequestToString(request),true); 905 | 906 | webSockets[idx].readyState = CONNECTING; 907 | 908 | send_string socket, httpRequestToString(request); 909 | } 910 | 911 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 912 | // 913 | // Function: webSocketGetSessionId 914 | // 915 | // Parameters: 916 | // dev socket - A NetLinx socket device 917 | // 918 | // Returns: 919 | // char[500] - A character array 920 | // 921 | // Description: 922 | // Returns the session ID stored for the websocket. May be an empty string (''). Returns empty string if socket not 923 | // being managed. 924 | // 925 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 926 | define_function char[500] webSocketGetSessionId(dev socket) { 927 | stack_var integer idx; 928 | 929 | for(idx = 1; idx <= length_array(webSockets); idx++) { 930 | if(socket == webSockets[idx].socket) 931 | return webSockets[idx].sessionId; 932 | } 933 | 934 | return ''; 935 | } 936 | 937 | 938 | define_event 939 | 940 | data_event[wsSockets] { 941 | 942 | online: { 943 | integer idx; 944 | dev socket; 945 | 946 | idx = get_last(wsSockets) 947 | socket = wsSockets[idx]; 948 | 949 | print("'Socket (',devToString(socket),') Open on port ',itoa(data.sourceport)",false); 950 | 951 | clear_buffer webSockets[idx].receiveBuffer 952 | 953 | webSocketSendOpenHandshake(socket); 954 | } 955 | 956 | offline: { 957 | integer idx; 958 | dev socket; 959 | 960 | idx = get_last(wsSockets) 961 | socket = wsSockets[idx]; 962 | 963 | print("'Socket (',devToString(socket),') Closed'",false); 964 | 965 | clear_buffer webSockets[idx].receiveBuffer 966 | 967 | webSockets[idx].readyState = CLOSED; 968 | webSocketOnClose(socket); 969 | } 970 | 971 | onerror: { 972 | integer idx; 973 | dev socket; 974 | 975 | idx = get_last(wsSockets) 976 | socket = wsSockets[idx]; 977 | 978 | print("'Socket (',devToString(socket),') Error: ',itoa(data.number)",false); 979 | 980 | if(data.number != 14) { // did notalready open 981 | webSockets[idx].readyState = CLOSED; 982 | webSocketOnError(socket,data.number,data.text); 983 | } 984 | } 985 | 986 | string: { 987 | integer idx; 988 | dev socket; 989 | stack_var HttpResponse response; //stack_var HttpMessage httpResponse; 990 | stack_var WebSocketFrame wsf; 991 | 992 | idx = get_last(wsSockets) 993 | socket = wsSockets[idx]; 994 | 995 | print("'Received data (',itoa(length_array(data.text)),' bytes) on Socket[',devToString(socket),'].'",false); 996 | 997 | webSockets[idx].receiveBuffer = "webSockets[idx].receiveBuffer,data.text" 998 | 999 | if((webSockets[idx].readyState == OPEN) || (webSockets[idx].readyState == CLOSING)) { // assume we received a websocket protocol string 1000 | 1001 | while(webSocketFrameFromString(wsf,webSockets[idx].receiveBuffer) == 0) { 1002 | 1003 | print("'Successfully built WebSocket Frame from string'",false); 1004 | 1005 | if(wsf.opCode == WEBSOCKET_OPCODE_TEXT) { 1006 | print("'Data received on Socket[',devToString(socket),'] is WebSocket text.'",false); 1007 | if(wsf.mask) { 1008 | webSocketOnMessage(socket,webSocketUnmask(wsf.payloadData,wsf.maskingKey)); 1009 | } else { 1010 | webSocketOnMessage(socket,wsf.payloadData); 1011 | } 1012 | } else if(wsf.opCode == WEBSOcKET_OPCODE_BIN) { 1013 | print("'Data received on Socket[',devToString(socket),'] is WebSocket binary.'",false); 1014 | if(wsf.mask) { 1015 | webSocketOnMessage(socket,webSocketUnmask(wsf.payloadData,wsf.maskingKey)); 1016 | } else { 1017 | webSocketOnMessage(socket,wsf.payloadData); 1018 | } 1019 | } else if(wsf.opCode == WEBSOCKET_OPCODE_CONT) { 1020 | print("'Data received on Socket[',devToString(socket),'] is WebSocket continuation.'",false); 1021 | if(wsf.mask) { 1022 | webSocketOnMessage(socket,webSocketUnmask(wsf.payloadData,wsf.maskingKey)); 1023 | } else { 1024 | webSocketOnMessage(socket,wsf.payloadData); 1025 | } 1026 | } else if(wsf.opCode == WEBSOCKET_OPCODE_PING) { 1027 | print("'Data received on Socket[',devToString(socket),'] is WebSocket ping. Sending pong in response.'",false); 1028 | webSocketSendPong(socket,wsf.payloadData); // pong must contain same "appication data" as received ping 1029 | } else if(wsf.opCode == WEBSOCKET_OPCODE_PONG) { 1030 | print("'Data received on Socket[',devToString(socket),'] is WebSocket pong.'",false); 1031 | } else if(wsf.opCode == WEBSOCKET_OPCODE_CLOSE) { 1032 | print("'Data received on Socket[',devToString(socket),'] is WebSocket close.'",false); 1033 | webSockets[idx].readyState = CLOSING; 1034 | } 1035 | else { 1036 | print("'Data received on Socket[',devToString(socket),'] unhandled.'",false); 1037 | } 1038 | 1039 | if(length_string(webSockets[idx].receiveBuffer) == 0) { 1040 | break; 1041 | } 1042 | } 1043 | 1044 | print("itoa(length_array(webSockets[idx].receiveBuffer)),' bytes remain in receive buffer unprocessed.'",false); 1045 | } else { // assume we received a HTTP protocol string 1046 | char expectedWebSocketAcceptValue[1024] 1047 | stack_var char sessionIdHeader[50]; 1048 | stack_var char cookie[100]; 1049 | 1050 | print("'Data received on Socket[',devToString(socket),'] is HTTP:'",false); 1051 | httpParseResponse(response, webSockets[idx].receiveBuffer); 1052 | clear_buffer webSockets[idx].receiveBuffer 1053 | print(httpResponseToString(response),true); 1054 | 1055 | expectedWebSocketAcceptValue = webSocketCreateHandshakeKeyServer(webSockets[idx].clientHandshakeKey); 1056 | 1057 | cookie = httpGetHeader(response.headers,'Set-Cookie') 1058 | if(cookie != '') { 1059 | if(find_string(cookie,"';'",1)) { 1060 | WebSockets[idx].sessionId = remove_string(cookie,"';'",1); 1061 | trim_string(WebSockets[idx].sessionId,0,1); 1062 | } else { 1063 | WebSockets[idx].sessionId = cookie; 1064 | } 1065 | } 1066 | 1067 | if(response.status.code != HTTP_STATUS_CODE_SWITCHING_PROTOCOLS) { 1068 | print("'WebSocket open handshake failed on Socket[',devToString(socket),']. Status Code: ',itoa(response.status.code)",false); 1069 | webSocketOnError(socket,0,''); 1070 | webSocketClose(socket,0,''); 1071 | } else if(LOWER_STRING(httpGetHeader(response.headers,'Upgrade')) != 'websocket') { 1072 | print("'WebSocket open handshake failed on Socket[',devToString(socket),']. Upgrade: ',httpGetHeader(response.headers,'Upgrade')",false); 1073 | webSocketOnError(socket,0,''); 1074 | webSocketClose(socket,0,''); 1075 | } else if(UPPER_STRING(httpGetHeader(response.headers,'Connection')) != UPPER_STRING('Upgrade')) { 1076 | print("'WebSocket open handshake failed on Socket[',devToString(socket),']. Connection: ',httpGetHeader(response.headers,'Connection')",false); 1077 | webSocketOnError(socket,0,''); 1078 | webSocketClose(socket,0,''); 1079 | } else if(httpGetHeader(response.headers,'Sec-WebSocket-Accept') != expectedWebSocketAcceptValue) { 1080 | print("'WebSocket open handshake failed on Socket[',devToString(socket),']. Sec-WebSocket-Accept: ',httpGetHeader(response.headers,'Sec-WebSocket-Accept')",false); 1081 | webSocketOnError(socket,0,''); 1082 | webSocketClose(socket,0,''); 1083 | } else { 1084 | // if we reached this point then the web socket opened successfully 1085 | print("'WebSocket open handshake succeeded on Socket[',devToString(socket),']. Switching protocol from HTTP to WebSocket .'",false); 1086 | webSockets[idx].readyState = OPEN; 1087 | webSocketOnOpen(socket); 1088 | } 1089 | } 1090 | } 1091 | } 1092 | 1093 | 1094 | #end_if 1095 | -------------------------------------------------------------------------------- /xml.axi: -------------------------------------------------------------------------------- 1 | program_name='xml' 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | // Include: xml 5 | // 6 | // Description: 7 | // 8 | // - This include file provides functions for working with XML (eXtensible Markup Language). 9 | // 10 | // Implementation: 11 | // 12 | // - Any NetLinx program utilising the xml include file must use either the INCLUDE or #INCLUDE keywords to 13 | // include the xml include file within the program. While the INCLUDE and #INCLUDE keywords are both 14 | // functionally equivalent the #INCLUDE keyword is recommended only because it is the NetLinx keyword (the INCLUDE 15 | // keyword is from the earlier Axcess programming language and is included within the NetLinx programming language 16 | // for backwards compatibility). 17 | // 18 | // Note: The NetLinx language is not case-sensitive when it comes to keywords. The convention used in this project 19 | // is for keywords to be written in lower case (e.g., include instead of INCLUDE). 20 | // 21 | // E.g: 22 | // 23 | // define_program 'XML Demo' 24 | // 25 | // #include 'xml' 26 | // 27 | // Usage: 28 | // 29 | // - See function header comments below for usage details. 30 | // 31 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 32 | 33 | #if_not_defined __XML__ 34 | #define __XML__ 35 | 36 | 37 | #include 'string' 38 | 39 | 40 | define_constant 41 | 42 | integer XML_MAX_CHARS = 20000; // Adjust if necessary. Note that 100000 chars crashes NSX if you try to add a char array this big to the watch window 43 | 44 | 45 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 46 | // 47 | // Function: xmlString 48 | // 49 | // Parameters: 50 | // char elementName[] - Name of element. 51 | // char content[] - Content for the element. 52 | // 53 | // Returns: 54 | // char[XML_MAX_CHARS] - XML formatted string (e.g., 'content'). 55 | // 56 | // Description: 57 | // Creates an XML formatted string using the provided elemenet name and contents. 58 | // Assumes that element name and content are correctly formatted (e.g., no spaces, left angle brackets, right angle 59 | // brackets, etc,, in element name). 60 | // The content may itself already be an XML formatted string so this function could be called repeatedly to build a 61 | // complete XML string. E.g, to build the following XML string: 62 | // 63 | // 64 | // Australia 65 | // 66 | // Queensland 67 | // QLD 68 | // 69 | // Brisbane 70 | // Gold Coast> 71 | // 72 | // 73 | // 74 | // New South Wales 75 | // NSW 76 | // 77 | // Sydney 78 | // 79 | // 80 | // 81 | // 82 | // You could write the following code: 83 | // 84 | // stack_var sXml[XML_MAX_CHARS] 85 | // stack_var sXmlTemp[XML_MAX_CHARS] 86 | // 87 | // sXmlTemp = xmlString('name','New South Wales'); 88 | // sXmlTemp = "sXmlTemp,xmlString('prefix','NSW')"; 89 | // sXmlTemp = "sXmlTemp,xmlString('city',xmlString('name','Sydney')); 90 | // sXmlTemp = xmlString('state',sXmlTemp); 91 | // 92 | // sXml = sXmlTemp; 93 | // 94 | // sXmlTemp = xmlString('name','Queensland'); 95 | // sXmlTemp = "sXmlTemp,xmlString('prefix','QLD')"; 96 | // sXmlTemp = "sXmlTemp,xmlString('city',"xmlString('name','Brisbane'),xmlString('name','Gold Coast')")"; 97 | // sXmlTemp = xmlString('state',sXmlTemp); 98 | // 99 | // sXml = "sXml,sXmlTemp"; 100 | // 101 | // sXml = "xmlString('name','Australia'),sXml"; 102 | // sXml = xmlString('country',sXml); 103 | // 104 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 105 | define_function char[XML_MAX_CHARS] xmlString (char elementName[], char content[]) { 106 | return "'<',elementName,'>',content,''" 107 | } 108 | 109 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 110 | // 111 | // Function: xmlGetElement 112 | // 113 | // Parameters: 114 | // char xml[] - XML formatted character string. 115 | // char elementName[] - Name of element to filter search. 116 | // char attribName[] - Name of attribute to filter search. 117 | // char attribValue[] - Value of attribute to filter search. 118 | // 119 | // Returns: 120 | // char[XML_MAX_CHARS] - Element including opening and closing XML tags. 121 | // 122 | // Description: 123 | // Returns an element from an XML string. 124 | // An element name can be provided to refine the search but is optional. If the element name is left out the 1st 125 | // element in the XMl string will be returned. If the element name is provided the 1st element in the XML string with a 126 | // matching name will be returned. 127 | // An attribute name/value pair can also be provided to further refine the search but is also optional. 128 | // 129 | // Usage: 130 | // If a variable (called xmlBooks) contained the following XML data: 131 | // 132 | // 133 | // 134 | // Gambardella, Matthew 135 | // XML Developer's Guide 136 | // Computer 137 | // 44.95 138 | // 2000-10-01 139 | // An in-depth look at creating applications 140 | // with XML. 141 | // 142 | // 143 | // Ralls, Kim 144 | // Midnight Rain 145 | // Fantasy 146 | // 5.95 147 | // 2000-12-16 148 | // A former architect battles corporate zombies, 149 | // an evil sorceress, and her own childhood to become queen 150 | // of the world. 151 | // 152 | // 153 | // 154 | // Calling xmlGetElement(xmlBooks,'','','') would return: 155 | // 156 | // 157 | // 158 | // Gambardella, Matthew 159 | // XML Developer's Guide 160 | // Computer 161 | // 44.95 162 | // 2000-10-01 163 | // An in-depth look at creating applications 164 | // with XML. 165 | // 166 | // 167 | // Ralls, Kim 168 | // Midnight Rain 169 | // Fantasy 170 | // 5.95 171 | // 2000-12-16 172 | // A former architect battles corporate zombies, 173 | // an evil sorceress, and her own childhood to become queen 174 | // of the world. 175 | // 176 | // 177 | // 178 | // Calling xmlGetElement(xmlBooks,'book','','') would return: 179 | // 180 | // 181 | // Gambardella, Matthew 182 | // XML Developer's Guide 183 | // Computer 184 | // 44.95 185 | // 2000-10-01 186 | // An in-depth look at creating applications 187 | // with XML. 188 | // 189 | // 190 | // Calling xmlGetElement(xmlBooks,'book','id','bk102') would return: 191 | // 192 | // 193 | // Ralls, Kim 194 | // Midnight Rain 195 | // Fantasy 196 | // 5.95 197 | // 2000-12-16 198 | // A former architect battles corporate zombies, 199 | // an evil sorceress, and her own childhood to become queen 200 | // of the world. 201 | // 202 | // 203 | // Calling xmlGetElement(xmlBooks,'book','id','bk103') would return nothing 204 | // 205 | // Calling xmlGetElement(xmlBooks,'chapter','','') would return nothing 206 | // 207 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 208 | define_function char[XML_MAX_CHARS] xmlGetElement (char xml[], char elementName[], char attribName[], char attribValue[]) { 209 | stack_var integer idxOpenTag; 210 | stack_var integer idxCloseTag; 211 | stack_var integer idxOpenTagChild; 212 | stack_var integer idxOpenTagRightBracket; 213 | stack_var integer idxCloseTagRightBracket; 214 | stack_var char leftBracket[1]; 215 | stack_var char rightBracket[1]; 216 | stack_var char xmlElement[XML_MAX_CHARS]; 217 | stack_var char elementNameTemp[100]; 218 | stack_var integer match; 219 | 220 | match = false; 221 | leftBracket = "'<'"; 222 | rightBracket = "'>'"; 223 | 224 | if(elementName == '') 225 | elementNameTemp = xmlGetName(xml); 226 | else 227 | elementNameTemp = elementName; 228 | 229 | idxOpenTag = find_string(xml,"'<',elementNameTemp",1); 230 | 231 | while(!match) { 232 | // search for open tag 233 | if(idxOpenTag == 0) 234 | return ''; 235 | 236 | while( (find_string(xml,"'<',elementNameTemp,'>'",idxOpenTag) != idxOpenTag) && (find_string(xml,"'<',elementNameTemp,' '",idxOpenTag) != idxOpenTag) ) { 237 | idxOpenTag = find_string(xml,"'<',elementNameTemp",idxOpenTag+1); 238 | if(idxOpenTag == 0) 239 | return ''; 240 | } 241 | 242 | // search for close tag 243 | idxCloseTag = find_string(xml,"''",idxOpenTag); 244 | if(idxCloseTag == 0) // poorly formed XML (XML rules state that all elements must have a closing tag) 245 | return ''; 246 | 247 | // check for other opening tags between the opening tag and closing tage we have found (child elements with matching names) 248 | idxOpenTagChild = find_string(xml,"'<',elementNameTemp",idxOpenTag+1); 249 | 250 | while(idxOpenTagChild) { 251 | while( (find_string(xml,"'<',elementNameTemp,'>'",idxOpenTagChild) != idxOpenTagChild) && (find_string(xml,"'<',elementNameTemp,' '",idxOpenTagChild) != idxOpenTagChild) ) { 252 | idxOpenTagChild = find_string(xml,"'<',elementNameTemp",idxOpenTagChild+1); 253 | if((idxOpenTagChild == 0) || (idxOpenTagChild > idxCloseTag)) // want to break out of both whiles but we can't so we'll break out here and then again after this while 254 | break; 255 | } 256 | 257 | if((idxOpenTagChild == 0) || (idxOpenTagChild > idxCloseTag)) // need to break again 258 | break; 259 | 260 | idxCloseTag = find_string(xml,"''",idxCloseTag+1); 261 | if(idxCloseTag == 0) // poorly formed XML (XML rules state that all elements must have a closing tag) 262 | return ''; 263 | 264 | idxOpenTagChild = find_string(xml,"'<',elementNameTemp",idxOpenTagChild+1); 265 | } 266 | 267 | // we made it to here so we know we have an element with a matching name (and child elements with matching names haven't caused us issues) 268 | idxCloseTagRightBracket = find_string(xml,"'>'",idxCloseTag); 269 | xmlElement = mid_string(xml,idxOpenTag,(idxCloseTagRightBracket-idxOpenTag+1)); 270 | if(attribName == '') { 271 | match = true; 272 | } 273 | else if((attribName != '') && (attribValue != '')) { 274 | stack_var char xmlOpenTag[XML_MAX_CHARS]; 275 | stack_var char attributeValue[200]; 276 | stack_var char rightAngleBracket[1]; 277 | 278 | idxOpenTagRightBracket = FIND_STRING(xmlElement,"'>'",1); 279 | if(!(idxOpenTag && idxOpenTagRightBracket)) 280 | return ''; 281 | 282 | xmlOpenTag = MID_STRING(xmlElement,1,idxOpenTagRightBracket); 283 | if(find_string(xmlOpenTag,"' ',attribName,'='",1)) { 284 | remove_string(xmlOpenTag,"' ',attribName,'='",1); 285 | if(xmlOpenTag[1] == '"') { // value is a string 286 | remove_string(xmlOpenTag,"'"'",1); 287 | attributeValue = remove_string(xmlOpenTag,"'"'",1); 288 | attributeValue = left_string(attributeValue,(length_string(attributeValue)-1)); 289 | } else { // value is a number, might be followed by a space (' ') or a right angle bracket ('>') 290 | if(find_string(xmlOpenTag,"' '",1)) { 291 | attributeValue = remove_string(xmlOpenTag,"' '",1); 292 | } else { 293 | attributeValue = remove_string(xmlOpenTag,"'>'",1); 294 | } 295 | attributeValue = left_string(attributeValue,(length_string(attributeValue)-1)); 296 | } 297 | 298 | if(attribValue == attributeValue) { 299 | match = true; 300 | } 301 | else { 302 | idxOpenTag = find_string(xml,"'<',elementNameTemp",idxOpenTag+1); 303 | } 304 | } 305 | else { 306 | idxOpenTag = find_string(xml,"'<',elementNameTemp",idxOpenTag+1); 307 | } 308 | } 309 | else { 310 | idxOpenTag = find_string(xml,"'<',elementNameTemp",idxOpenTag+1); 311 | //idxOpenTag = find_string(xml,"'<',elementNameTemp",idxCloseTagRightBracket+1); 312 | } 313 | } 314 | 315 | return xmlElement; 316 | } 317 | 318 | 319 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 320 | // 321 | // Function: xmlGetContent 322 | // 323 | // Parameters: 324 | // char xml[] - XML formatted character string. 325 | // char elementName[] - Name of element to filter search. 326 | // char attribName[] - Name of attribute to filter search. 327 | // char attribValue[] - Value of attribute to filter search. 328 | // 329 | // Returns: 330 | // char[XML_MAX_CHARS] - Content of element. 331 | // 332 | // Description: 333 | // Returns the content of an element in an XML string. An element name can be provided to refine the search but is 334 | // optional. If the element name is left out the content of the 1st element in the XML string will be returned. If the 335 | // element name is provided the content of the 1st element in the XML string with a matching name will be returned. An 336 | // attribute name/value pair can also be provided to further refine the search but is also optional. 337 | // 338 | // Usage: 339 | // If a variable (called xmlBooks) contained the following XML data: 340 | // 341 | // 342 | // 343 | // Gambardella, Matthew 344 | // XML Developer's Guide 345 | // Computer 346 | // 44.95 347 | // 2000-10-01 348 | // An in-depth look at creating applications 349 | // with XML. 350 | // 351 | // 352 | // Ralls, Kim 353 | // Midnight Rain 354 | // Fantasy 355 | // 5.95 356 | // 2000-12-16 357 | // A former architect battles corporate zombies, 358 | // an evil sorceress, and her own childhood to become queen 359 | // of the world. 360 | // 361 | // 362 | // 363 | // Calling xmlGetContent(xmlBooks,'','','') would return: 364 | // 365 | // 366 | // Gambardella, Matthew 367 | // XML Developer's Guide 368 | // Computer 369 | // 44.95 370 | // 2000-10-01 371 | // An in-depth look at creating applications 372 | // with XML. 373 | // 374 | // 375 | // Ralls, Kim 376 | // Midnight Rain 377 | // Fantasy 378 | // 5.95 379 | // 2000-12-16 380 | // A former architect battles corporate zombies, 381 | // an evil sorceress, and her own childhood to become queen 382 | // of the world. 383 | // 384 | // 385 | // Calling xmlGetContent(xmlBooks,'book','','') would return: 386 | // 387 | // Gambardella, Matthew 388 | // XML Developer's Guide 389 | // Computer 390 | // 44.95 391 | // 2000-10-01 392 | // An in-depth look at creating applications 393 | // with XML. 394 | // 395 | // Calling xmlGetContent(xmlBooks,'book','id','bk102') would return: 396 | // 397 | // Ralls, Kim 398 | // Midnight Rain 399 | // Fantasy 400 | // 5.95 401 | // 2000-12-16 402 | // A former architect battles corporate zombies, 403 | // an evil sorceress, and her own childhood to become queen 404 | // of the world. 405 | // 406 | // Calling xmlGetContent(xmlBooks,'book','id','bk103') would return nothing 407 | // 408 | // Calling xmlGetContent(xmlBooks,'chapter','','') would return nothing 409 | // 410 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 411 | define_function char[XML_MAX_CHARS] xmlGetContent (char xml[], char elementName[], char attribName[], char attribValue[]) { 412 | stack_var char xmlElement[XML_MAX_CHARS]; 413 | stack_var char content[XML_MAX_CHARS]; 414 | stack_var integer idxRightAngleBracket; 415 | stack_var integer idxCloseTag; 416 | stack_var char elementNameTemp[200]; 417 | stack_var integer idxOpenTagChild; 418 | 419 | if(elementName == '') 420 | elementNameTemp = xmlGetName(xml); 421 | else 422 | elementNameTemp = elementName 423 | 424 | xmlElement = xmlGetElement(xml,elementNameTemp,attribName,attribValue); 425 | if(xmlElement == '') 426 | return ''; 427 | 428 | idxRightAngleBracket = find_string(xmlElement,"'>'",1); 429 | if(!idxRightAngleBracket) 430 | return ''; 431 | 432 | // search for close tag 433 | idxCloseTag = find_string(xmlElement,"''",1); 434 | if(idxCloseTag == 0) // poorly formed XML (XML rules state that all elements must have a closing tag) 435 | return ''; 436 | 437 | // check for other opening tags between the opening tag and closing tage we have found (child elements with matching names) 438 | idxOpenTagChild = find_string(xmlElement,"'<',elementNameTemp",2); 439 | while(idxOpenTagChild) { 440 | while( (find_string(xmlElement,"'<',elementNameTemp,'>'",idxOpenTagChild) != idxOpenTagChild) && (find_string(xmlElement,"'<',elementNameTemp,' '",idxOpenTagChild) != idxOpenTagChild) ) { 441 | idxOpenTagChild = find_string(xmlElement,"'<',elementNameTemp",idxOpenTagChild+1); 442 | if((idxOpenTagChild == 0) || (idxOpenTagChild > idxCloseTag)) // want to break out of both whiles but we can't so we'll break out here and then again after this while 443 | break; 444 | } 445 | 446 | if((idxOpenTagChild == 0) || (idxOpenTagChild > idxCloseTag)) // need to break again 447 | break; 448 | 449 | idxCloseTag = find_string(xmlElement,"''",idxCloseTag+1); 450 | if(idxCloseTag == 0) // poorly formed XML (XML rules state that all elements must have a closing tag) 451 | return ''; 452 | 453 | idxOpenTagChild = find_string(xmlElement,"'<',elementNameTemp",idxOpenTagChild+1); 454 | } 455 | 456 | content = MID_STRING(xmlElement, 457 | (idxRightAngleBracket+1), 458 | (idxCloseTag-idxRightAngleBracket-1)); 459 | 460 | return content; 461 | } 462 | 463 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 464 | // 465 | // Function: xmlGetName 466 | // 467 | // Parameters: 468 | // char xml[] - XML formatted character string. 469 | // 470 | // Returns: 471 | // char[200] - Name of element. 472 | // 473 | // Description: 474 | // Returns the name of the 1st element in an XML string. 475 | // 476 | // Usage: 477 | // If a variable (called xmlBooks) contained the following XML data: 478 | // 479 | // 480 | // 481 | // Gambardella, Matthew 482 | // XML Developer's Guide 483 | // Computer 484 | // 44.95 485 | // 2000-10-01 486 | // An in-depth look at creating applications 487 | // with XML. 488 | // 489 | // 490 | // Ralls, Kim 491 | // Midnight Rain 492 | // Fantasy 493 | // 5.95 494 | // 2000-12-16 495 | // A former architect battles corporate zombies, 496 | // an evil sorceress, and her own childhood to become queen 497 | // of the world. 498 | // 499 | // 500 | // 501 | // Calling xmlGetName(xmlBooks) would return: 502 | // 503 | // catalog 504 | // 505 | // If a variable (called xmlBooks) contained the following XML data: 506 | // 507 | // 508 | // Gambardella, Matthew 509 | // XML Developer's Guide 510 | // Computer 511 | // 44.95 512 | // 2000-10-01 513 | // An in-depth look at creating applications 514 | // with XML. 515 | // 516 | // 517 | // Ralls, Kim 518 | // Midnight Rain 519 | // Fantasy 520 | // 5.95 521 | // 2000-12-16 522 | // A former architect battles corporate zombies, 523 | // an evil sorceress, and her own childhood to become queen 524 | // of the world. 525 | // 526 | // 527 | // Calling xmlGetName(xmlBooks) would return: 528 | // 529 | // book 530 | // 531 | // If a variable (called xmlBooks) contained the following XML data: 532 | // 533 | // Gambardella, Matthew 534 | // XML Developer's Guide 535 | // Computer 536 | // 44.95 537 | // 2000-10-01 538 | // An in-depth look at creating applications 539 | // with XML. 540 | // 541 | // Calling xmlGetName(xmlBooks) would return: 542 | // 543 | // author 544 | // 545 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 546 | define_function char[200] xmlGetName (char xml[]) { 547 | stack_var integer idxOpenTag; 548 | stack_var char elementName[50]; 549 | stack_var integer idx; 550 | 551 | idxOpenTag = FIND_STRING(xml,"'<'",1); 552 | 553 | if(!idxOpenTag) 554 | return ''; 555 | 556 | idx = idxOpenTag+1; 557 | while((xml[idx] != ' ') and (xml[idx] != '>')) { 558 | idx++; 559 | } 560 | 561 | elementName = MID_STRING(xml,idxOpenTag+1,(idx-idxOpenTag-1)); 562 | 563 | return elementName; 564 | } 565 | 566 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 567 | // 568 | // Function: xmlGetAttribute 569 | // 570 | // Parameters: 571 | // char xml[] - XML formatted character string. 572 | // char elementName[] - XML element tag identifier. 573 | // char attribName[] - Name of attribute to obtain value of. 574 | // char attribNameFilter[] - Name of attribute to filter search. 575 | // char attribValueFilter[] - Value of attribute to filter search. 576 | // 577 | // Returns: 578 | // char[50] - Value of attribute. 579 | // 580 | // Description: 581 | // Returns the value of a specified attribute from an element in an XML string. An element name can be provided to 582 | // refine the search but is optional. If the element name is left out the 1st element in the XML string will be used. If 583 | // the element name is provided the 1st element in the XML string with a matching name will be used. An attribute 584 | // name/value pair can also be provided to further refine the search but is also optional. 585 | // 586 | // Usage: 587 | // If a variable (called xmlBooks) contained the following XML data: 588 | // 589 | // 590 | // 591 | // Gambardella, Matthew 592 | // XML Developer's Guide 593 | // Computer 594 | // 44.95 595 | // 2000-10-01 596 | // An in-depth look at creating applications 597 | // with XML. 598 | // 599 | // 600 | // Ralls, Kim 601 | // Midnight Rain 602 | // Fantasy 603 | // 5.95 604 | // 2000-12-16 605 | // A former architect battles corporate zombies, 606 | // an evil sorceress, and her own childhood to become queen 607 | // of the world. 608 | // 609 | // 610 | // 611 | // Calling xmlGetAttribute(xmlBooks,'book','','','') would return: 612 | // 613 | // 614 | // 615 | // Gambardella, Matthew 616 | // XML Developer's Guide 617 | // Computer 618 | // 44.95 619 | // 2000-10-01 620 | // An in-depth look at creating applications 621 | // with XML. 622 | // 623 | // 624 | // Ralls, Kim 625 | // Midnight Rain 626 | // Fantasy 627 | // 5.95 628 | // 2000-12-16 629 | // A former architect battles corporate zombies, 630 | // an evil sorceress, and her own childhood to become queen 631 | // of the world. 632 | // 633 | // 634 | // 635 | // Calling xmlGetAttribute(xmlBooks,'book','','') would return: 636 | // 637 | // 638 | // Gambardella, Matthew 639 | // XML Developer's Guide 640 | // Computer 641 | // 44.95 642 | // 2000-10-01 643 | // An in-depth look at creating applications 644 | // with XML. 645 | // 646 | // 647 | // Calling xmlGetAttribute(xmlBooks,'book','id','bk102') would return: 648 | // 649 | // 650 | // Ralls, Kim 651 | // Midnight Rain 652 | // Fantasy 653 | // 5.95 654 | // 2000-12-16 655 | // A former architect battles corporate zombies, 656 | // an evil sorceress, and her own childhood to become queen 657 | // of the world. 658 | // 659 | // 660 | // Calling xmlGetAttribute(xmlBooks,'book','id','bk103') would return nothing 661 | // 662 | // Calling xmlGetAttribute(xmlBooks,'chapter','','') would return nothing 663 | // 664 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 665 | define_function char[50] xmlGetAttribute (char xml[], char elementName[], char attribName[], char attribNameFilter[], char attribValueFilter[]) { 666 | stack_var integer idxRightAngleBracket; 667 | stack_var char xmlOpenTag[500]; 668 | stack_var char attributeValue[200]; 669 | stack_var char xmlElement[XML_MAX_CHARS]; 670 | 671 | if((xml == '') || (attribName == '')) 672 | return ''; 673 | 674 | xmlElement = xmlGetElement(xml, elementName, attribNameFilter, attribValueFilter); 675 | 676 | if(xmlElement == '') 677 | return ''; 678 | 679 | if(attribName == '') 680 | return xmlElement; 681 | 682 | idxRightAngleBracket = FIND_STRING(xmlElement,"'>'",1); 683 | 684 | if(!idxRightAngleBracket) 685 | return ''; 686 | 687 | xmlOpenTag = MID_STRING(xmlElement,1,idxRightAngleBracket); // we can get away with this because the trailing delimiter is only one character 688 | 689 | if(!find_string(xmlOpenTag,"' ',attribName,'='",1)) 690 | return ''; 691 | 692 | remove_string(xmlOpenTag,"' ',attribName,'='",1); 693 | 694 | if(xmlOpenTag[1] == '"') { // value is a string 695 | remove_string(xmlOpenTag,"'"'",1); 696 | attributeValue = remove_string(xmlOpenTag,"'"'",1); 697 | attributeValue = left_string(attributeValue,(length_string(attributeValue)-1)); 698 | } else { // value is a number, might be followed by a space (' ') or a right angle bracket ('>') 699 | if(find_string(xmlOpenTag,"' '",1)) { 700 | attributeValue = remove_string(xmlOpenTag,"' '",1); 701 | } else { 702 | attributeValue = remove_string(xmlOpenTag,"'>'",1); 703 | } 704 | attributeValue = left_string(attributeValue,(length_string(attributeValue)-1)); 705 | } 706 | 707 | return attributeValue; 708 | } 709 | 710 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 711 | // 712 | // Function: xmlParseElement 713 | // 714 | // Parameters: 715 | // char xml[] - XML formatted character string. 716 | // char elementName[] - Name of element to filter search. 717 | // char attribName[] - Name of attribute to filter search. 718 | // char attribValue[] - Value of attribute to filter search. 719 | // 720 | // Returns: 721 | // char[XML_MAX_CHARS] - Element including opening and closing XML tags. 722 | // 723 | // Description: 724 | // Returns an element in an XML string. An element name can be provided to refine the search but is optional. If the 725 | // element name is left out the 1st element in the XMl string will be returned. If the element name is provided the 1st 726 | // element in the XML string with a matching name will be returned. An attribute name/value pair can also be provided to 727 | // further refine the search but is also optional. The returned element is also removed from the XML string. 728 | // 729 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 730 | define_function char[XML_MAX_CHARS] xmlParseElement (char xml[], char elementName[], char attribName[], char attribValue[]) { 731 | stack_var char xmlElement[XML_MAX_CHARS]; 732 | 733 | if(elementName == '') 734 | elementName = xmlGetName(xml); 735 | 736 | xmlElement = xmlGetElement(xml,elementName,attribName,attribValue); 737 | 738 | delete_string(xml,xmlElement); 739 | 740 | return xmlElement; 741 | } 742 | 743 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 744 | // 745 | // Function: xmlParseContent 746 | // 747 | // Parameters: 748 | // char xml[] - XML formatted character string. 749 | // char elementName[] - Name of element to filter search. 750 | // char attribName[] - Name of attribute to filter search. 751 | // char attribValue[] - Value of attribute to filter search. 752 | // 753 | // Returns: 754 | // char[XML_MAX_CHARS] - Content of element. 755 | // 756 | // Description: 757 | // Returns the content of an element in an XML string. An element name can be provided to refine the search but is 758 | // optional. If the element name is left out the content of the 1st element in the XML string will be returned. If the 759 | // element name is provided the content of the 1st element in the XML string with a matching name will be returned. An 760 | // attribute name/value pair can also be provided to further refine the search but is also optional. The returned 761 | // content is also removed from the XML string. 762 | // 763 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 764 | define_function char[XML_MAX_CHARS] xmlParseContent (char xml[], char elementName[], char attribName[], char attribValue[]) { 765 | stack_var char xmlElement[XML_MAX_CHARS]; 766 | stack_var char xmlElementContentRemoved[XML_MAX_CHARS]; 767 | stack_var char content[XML_MAX_CHARS]; 768 | 769 | if(elementName == '') 770 | elementName = xmlGetName(xml); 771 | 772 | xmlElement = xmlGetElement(xml,elementName,attribName,attribValue); 773 | 774 | content = xmlGetContent(xmlElement,elementName,attribName,attribValue); 775 | 776 | xmlElementContentRemoved = xmlElement; 777 | delete_string(xmlElementContentRemoved,content); 778 | replace_string(xml,xmlElement,xmlElementContentRemoved); 779 | 780 | return content; 781 | } 782 | 783 | 784 | #end_if 785 | --------------------------------------------------------------------------------