├── 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,'',elementName,'>'"
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,"'',elementNameTemp,'>'",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,"'',elementNameTemp,'>'",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,"'',elementNameTemp,'>'",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,"'',elementNameTemp,'>'",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 |
--------------------------------------------------------------------------------