Axeda Ready Demo for Freescale FRDM-KL46Z as accident alert system
Dependencies: FRDM_MMA8451Q KL46Z-USBHost MAG3110 SocketModem TSI mbed FATFileSystem
Fork of AxedaGo-Freescal_FRDM-KL46Z revert by
axHTTP.c
00001 #include "axHTTP.h" 00002 #include "axStatusCodes.h" 00003 #include "axConstants.h" 00004 #include "ctype.h" 00005 #include <string.h> 00006 #include <stdio.h> 00007 #include <stdlib.h> 00008 #include "axSettings.h" 00009 #include "axTransport.h" 00010 00011 #define AX_HTTP_OK 200 00012 #define AX_HTTP_SYNTAX_INVALID 400 //The Request syntax is incorrect 00013 #define AX_HTTP_UNAUTHORIZED 401 //You need permission to access that resource. Agent Authentication failure 00014 #define AX_HTTP_FORBIDDEN 403 //You need permission to access that resource. 00015 #define AX_HTTP_NOT_FOUND 404 //The resource was not found 00016 #define AX_HTTP_REQ_INVALID 405 //Content type is invalid, should be application/json most of the time 00017 #define AX_HTTP_CONTENT_INVALID 422 //The Request is valid syntactically but the content is invalid e.g. wrong instruction 00018 #define AX_HTTP_SERVER_ERROR 500 //The server attempted to handle your request but a failure occurred; check the server logs. 00019 00020 #define HTTPKEY_GET 0 00021 #define HTTPKEY_POST 1 00022 #define HTTPKEY_PUT 2 00023 #define HTTPKEY_PROTOCOL 3 00024 #define HTTPKEY_CRLF 4 00025 00026 00027 #define HTTPH_HOST 0 00028 #define HTTPH_TYPE 1 00029 #define HTTPH_LENGTH 2 00030 #define HTTPH_CONNECTION 3 00031 #define HTTPH_XCOUNT 4 00032 #define HTTPH_CONTENTDIS 5 00033 #define HTTPH_MULTIPART 6 00034 #define HTTPH_FORMDATA 7 00035 #define HTTPH_ACCEPT 8 00036 00037 #define HTTPR_TYPE 0 00038 #define HTTPR_LENGTH 1 00039 #define HTTPR_BODY_SEP 2 00040 #define HTTPR_DELIMS 3 00041 00042 int http_debug=AX_FALSE; 00043 int x_count=0; 00044 00045 int http_getResponse(HTTP_Transmission *response, ax_socket *source); 00046 00047 char *HTTP_KEY[]= {"GET", "POST", "PUT", "HTTP/1.1", "\r\n"}; 00048 char *HTTP_HEADER[] = { "Host: ", "Content-Type: ", "Content-Length: ", "Connection: close", "x-count: ", "Content-Disposition: ","multipart/form-data; boundary=", "form-data;", "Accept: */*"}; 00049 char *HTTP_RESP[] = { "Content-Type:", "Content-Length:", "\r\n\r\n", " \r\n"}; 00050 00051 int http_send(HTTP_Transmission *request, char *host, int port, int secure, HTTP_Transmission *response){ 00052 int retVal=AX_UNKNOWN; 00053 00054 switch(request->operation) { 00055 case AX_HTTP_GET: 00056 retVal=http_send_get(request->resource, host, port, secure, response); 00057 break; 00058 case AX_HTTP_POST: 00059 retVal=http_send_post(request->resource, host, port, secure, &request->headers, request->body, request->body_length, response); 00060 break; 00061 case AX_HTTP_PUT: 00062 retVal=http_send_put(request->resource, host, port, secure, &request->headers, request->body, request->body_length, response); 00063 break; 00064 case AX_HTTP_MPOST: 00065 retVal=http_send_mpost(request->resource, host, port, secure, &request->headers, request->body, request->body_length, request->mpData, 1, response); 00066 break; 00067 } 00068 00069 return retVal; 00070 } 00071 00072 int http_send_get(char *resource, char *host, int port, int secure, HTTP_Transmission *response) { 00073 int retVal=AX_UNKNOWN; 00074 00075 if(!resource||!host||!response) { return AX_ARGNULL; } 00076 if((port < 0)||(port>65536)) { return AX_NET_PORT_INVALID; } 00077 int sz=strlen(HTTP_KEY[HTTPKEY_GET])+strlen(resource)+strlen(HTTP_KEY[HTTPKEY_PROTOCOL])+strlen(HTTP_HEADER[HTTPH_HOST])+strlen(host)+13+6+strlen(HTTP_HEADER[HTTPH_CONNECTION])+strlen(HTTP_HEADER[HTTPH_ACCEPT]); //+6 adds space for extra CRLFs, spaces, and null terminator 00078 00079 ax_socket sock; 00080 00081 retVal=net_socketInit(&sock); 00082 retVal=net_socketOpen(&sock, host, port, secure); 00083 if(retVal!=AX_OK){ 00084 return retVal; 00085 } 00086 00087 char *buff=(char *)malloc(sizeof(char)*sz); 00088 int wrtSz=snprintf(buff, sz, "%s %s %s%s%s%s:%d%s%s%s%s", HTTP_KEY[HTTPKEY_GET], resource, HTTP_KEY[HTTPKEY_PROTOCOL], HTTP_KEY[HTTPKEY_CRLF], HTTP_HEADER[HTTPH_HOST], host, port, HTTP_KEY[HTTPKEY_CRLF], HTTP_HEADER[HTTPH_CONNECTION], HTTP_KEY[HTTPKEY_CRLF], HTTP_KEY[HTTPKEY_CRLF]); 00089 00090 if(wrtSz>sz) { printDebug("http_send_get(): Buffer allocated for header was too small, truncated"); } 00091 retVal=net_socketWrite(&sock, buff, sz); 00092 retVal=net_socketFlush(&sock); 00093 00094 retVal=http_getResponse(response, &sock); 00095 if(retVal==AX_HTTP_OK) { retVal=AX_OK; } 00096 00097 net_socketClose(&sock); 00098 free(buff); 00099 return retVal; 00100 } 00101 00102 int http_send_put(char *resource, char *host, int port, int secure, HTTP_Headers *headers, char *data, int data_sz, HTTP_Transmission *response) { 00103 int retVal=AX_UNKNOWN; 00104 00105 if(!resource||!host||!response) { return AX_ARGNULL; } 00106 if((port < 0)||(port>65536)) { return AX_NET_PORT_INVALID; } 00107 if((!data)&&(data_sz!=0)) { return AX_CONFLICTING_ARG; } 00108 00109 ax_socket sock; 00110 00111 retVal=net_socketInit(&sock); 00112 retVal=net_socketOpen(&sock, host, port, secure); 00113 if(retVal!=AX_OK){ //if a failure occurred bail out. failures here would be that a port is not free 00114 return retVal; 00115 } 00116 printDebug("Attempting to create headers"); 00117 char *header=NULL; 00118 header=createHeaders(header, HTTPKEY_PUT, resource, HTTPKEY_PROTOCOL, host, headers->contentType, data_sz, x_count); //returns a malloc'd string with the headers 00119 00120 retVal=net_socketWrite(&sock, header, strlen(header)); 00121 if(retVal!=AX_OK){ //if a failure occurred bail out. failures here would be that a port is not free 00122 goto cleanup; 00123 } 00124 retVal=net_socketWrite(&sock, data, data_sz); 00125 net_socketFlush(&sock); 00126 if(retVal!=AX_OK) { 00127 retVal=AX_NET_ERR_DATA_WRITE; 00128 goto cleanup; 00129 } 00130 00131 retVal=http_getResponse(response, &sock); 00132 if(retVal==AX_HTTP_OK) { retVal=AX_OK; } 00133 00134 x_count++; 00135 goto cleanup; 00136 00137 00138 cleanup: 00139 net_socketClose(&sock); 00140 free(header); 00141 return retVal; 00142 } 00143 00144 00145 00146 int http_send_post(char *resource, char *host, int port, int secure, HTTP_Headers *headers, char *data, int data_sz, HTTP_Transmission *response){ 00147 int retVal=AX_UNKNOWN; 00148 00149 if(!resource||!host||!response) { return AX_ARGNULL; } 00150 if((port < 0)||(port>65536)) { return AX_NET_PORT_INVALID; } 00151 if((!data)&&(data_sz!=0)) { return AX_CONFLICTING_ARG; } 00152 00153 ax_socket sock; 00154 00155 retVal=net_socketInit(&sock); 00156 retVal=net_socketOpen(&sock, host, port, secure); 00157 if(retVal!=AX_OK){ //if a failure occurred bail out. failures here would be that a port is not free 00158 return retVal; 00159 } 00160 printDebug("Attempting to create headers"); 00161 char *header=NULL; 00162 header=createHeaders(header, HTTPKEY_POST, resource, HTTPKEY_PROTOCOL, host, headers->contentType, data_sz, x_count); //returns a malloc'd string with the headers 00163 00164 retVal=net_socketWrite(&sock, header, strlen(header)); 00165 if(retVal!=AX_OK){ //if a failure occurred bail out. failures here would be that a port is not free 00166 goto cleanup; 00167 } 00168 retVal=net_socketWrite(&sock, data, data_sz); 00169 net_socketFlush(&sock); 00170 x_count++; 00171 printDebug("Transmission sent, Waiting for response\n"); 00172 00173 00174 retVal=http_getResponse(response, &sock); 00175 if(retVal==AX_HTTP_OK) { retVal=AX_OK; } 00176 00177 goto cleanup; 00178 00179 cleanup: 00180 net_socketClose(&sock); 00181 free(header); 00182 return retVal; 00183 } 00184 00185 int http_send_mpost(char *resource, char *host, int port, int secure, HTTP_Headers *headers, char *data, int data_sz, HTTP_Part *files, int part_ct, HTTP_Transmission *response) { 00186 int retVal=AX_UNKNOWN; 00187 // char xcount_tmp[10]; 00188 // char clength_tmp[10]; 00189 int boundary_sz=10; 00190 int body_sz=0; 00191 int hint_sz=0; 00192 char *hint=NULL; 00193 char *header=NULL; 00194 ax_socket sock; 00195 00196 if(!resource||!host||!response) { return AX_ARGNULL; } 00197 if((port < 0)||(port>65536)) { return AX_NET_PORT_INVALID; } 00198 if((!data)&&(data_sz!=0)) { return AX_CONFLICTING_ARG; } 00199 //Assemble body Parts 00200 char *fileargs=NULL; 00201 int file_arg_len=strlen(files->file_name)+51; 00202 fileargs=(char *)malloc(sizeof(char)*file_arg_len); 00203 int wrtfarg=snprintf(fileargs, file_arg_len, "name=\"file\"; filename=\"%s\"\r\nContent-Type: text/plain", files->file_name); 00204 if(wrtfarg>file_arg_len) { printDebug("http_send_mpost(): Buffer allocated for file arguments was too small, truncated"); } 00205 if(files->hint!=NULL) 00206 { 00207 hint_sz=strlen(files->hint)+18;//+1 for terminating char 00208 hint=(char *)malloc(sizeof(char)*hint_sz); 00209 int wrthint=snprintf(hint, hint_sz, "name=\"hint\"\r\n\r\n%s\r\n", files->hint); 00210 if(wrthint>hint_sz) { printDebug("http_send_mpost(): Buffer allocated for hint was too small, truncated"); } 00211 } 00212 //body size calculation here 00213 //Boundary: --<boundary> 00214 body_sz=body_sz+2+boundary_sz; 00215 //adding body ---for mparts 00216 if(data_sz>0){ 00217 body_sz=body_sz+36+data_sz+boundary_sz; 00218 } 00219 //Add Hint Content-Disposition: form-data;name="<hint>"\r\n--<boundary>\r\n 00220 if(files->hint!=NULL){ 00221 body_sz=body_sz+38+strlen(hint)+boundary_sz; 00222 } 00223 if(files->data_sz>0) { 00224 body_sz=body_sz+38+strlen(fileargs)+files->data_sz; 00225 } 00226 body_sz=body_sz+8+boundary_sz; 00227 00228 //get a boundary for the multipart 00229 char *boundary=(char *)malloc(sizeof(char)*boundary_sz); 00230 http_getBoundary(boundary, boundary_sz); 00231 //convert the data size integer into a string 00232 // int wrtCLen=snprintf(clength_tmp, 10, "%d", body_sz); 00233 // if(wrtCLen>10) { printDebug("http_send_mpost(): Buffer allocated for clength_tmp was too small, truncated"); } 00234 //Add operation line 00235 00236 printDebug("Attempting to create headers"); 00237 00238 //Create Content Type 00239 int cTypeSz=0; 00240 cTypeSz=strlen(HTTP_HEADER[HTTPH_MULTIPART])+strlen(boundary)+1; 00241 char *contentType=(char *)calloc(cTypeSz, sizeof(char)); 00242 int wrt_ctype=snprintf(contentType, cTypeSz, "%s%s", HTTP_HEADER[HTTPH_MULTIPART], boundary); 00243 if(wrt_ctype>cTypeSz) { printDebug("http_send_mpost(): Buffer allocated for contentType was too small, truncated");} 00244 //Get the headers 00245 header=createHeaders(header, HTTPKEY_POST, resource, HTTPKEY_PROTOCOL, host, contentType, body_sz, x_count); //returns a malloc'd string with the headers 00246 free(contentType); //we're done with the string we used to cat the values 00247 //Start to send the data 00248 retVal=net_socketInit(&sock); 00249 retVal=net_socketOpen(&sock, host, port, secure); 00250 if(retVal!=AX_OK){ //if a failure occurred, bail out. A failure here would indicate that a port is closed, or the network is not available. 00251 return retVal; 00252 } 00253 //Write the headers 00254 retVal=net_socketWrite(&sock, header, strlen(header)); 00255 if(retVal!=AX_OK){ goto cleanup; } 00256 00257 //write the body... 00258 //start boundary 00259 retVal=net_socketWrite(&sock, "--", 2); 00260 retVal=net_socketWrite(&sock, boundary, boundary_sz); 00261 retVal=net_socketWrite(&sock, HTTP_KEY[HTTPKEY_CRLF], 2); 00262 //1st section -uses the data in body if specified 00263 if(data_sz>0){ 00264 retVal=net_socketWrite(&sock, HTTP_HEADER[HTTPH_CONTENTDIS], 21); 00265 retVal=net_socketWrite(&sock, HTTP_HEADER[HTTPH_FORMDATA], 11); 00266 retVal=net_socketWrite(&sock, data, data_sz); //Write body data, the fields and values 00267 retVal=net_socketWrite(&sock, HTTP_KEY[HTTPKEY_CRLF], 2); 00268 retVal=net_socketWrite(&sock, "--", 2); 00269 retVal=net_socketWrite(&sock, boundary, boundary_sz); 00270 } 00271 if(files->hint!=NULL){ 00272 retVal=net_socketWrite(&sock, HTTP_HEADER[HTTPH_CONTENTDIS], 21); 00273 retVal=net_socketWrite(&sock, HTTP_HEADER[HTTPH_FORMDATA], 11); 00274 retVal=net_socketWrite(&sock, hint, hint_sz); //Write body data, the fields and values 00275 retVal=net_socketWrite(&sock, HTTP_KEY[HTTPKEY_CRLF], 2); 00276 retVal=net_socketWrite(&sock, "--", 2); 00277 retVal=net_socketWrite(&sock, boundary, boundary_sz); 00278 retVal=net_socketWrite(&sock, HTTP_KEY[HTTPKEY_CRLF], 2); 00279 } 00280 if(files->data_sz>0) { 00281 retVal=net_socketWrite(&sock, HTTP_HEADER[HTTPH_CONTENTDIS], 21); 00282 retVal=net_socketWrite(&sock, HTTP_HEADER[HTTPH_FORMDATA], 11); 00283 retVal=net_socketWrite(&sock, fileargs, file_arg_len); 00284 retVal=net_socketWrite(&sock, HTTP_KEY[HTTPKEY_CRLF], 2); 00285 retVal=net_socketWrite(&sock, HTTP_KEY[HTTPKEY_CRLF], 2); 00286 retVal=net_socketWrite(&sock, (char *)files->data, files->data_sz); 00287 retVal=net_socketWrite(&sock, HTTP_KEY[HTTPKEY_CRLF], 2); 00288 } 00289 retVal=net_socketWrite(&sock, "--", 2); 00290 retVal=net_socketWrite(&sock, boundary, boundary_sz); 00291 retVal=net_socketWrite(&sock, "--", 2); 00292 retVal=net_socketWrite(&sock, HTTP_KEY[HTTPKEY_CRLF], 2); 00293 retVal=net_socketWrite(&sock, HTTP_KEY[HTTPKEY_CRLF], 2); 00294 00295 00296 net_socketFlush(&sock); 00297 x_count++; 00298 retVal=http_getResponse(response, &sock); 00299 if(retVal==AX_HTTP_OK) { retVal=AX_OK; } 00300 00301 cleanup: 00302 net_socketClose(&sock); 00303 free(header); 00304 free(hint); 00305 free(fileargs); 00306 free(boundary); 00307 return retVal; 00308 } 00309 00310 void http_add_mpart(HTTP_Transmission *request, char *file_name, unsigned char *data, int file_sz) { 00311 //For future expansion. 00312 } 00313 00314 //Return Handling 00315 HTTP_Transmission *http_parse(HTTP_Transmission *response, char *data){ 00316 printDebug("Starting parse of "); 00317 printDebug(data); 00318 00319 char clength[7]; 00320 int reqSize=0; 00321 char *temp=NULL; 00322 char *bodyStart=NULL; 00323 response->body_length=0; 00324 response->response_code=-1; //indicate that no response code has been recieved(yet) 00325 00326 reqSize=strlen(data); 00327 if(reqSize<=12) { printDebug("No Body included"); } 00328 else { printDebug("body present" ); 00329 bodyStart=strstr(data, HTTP_RESP[HTTPR_BODY_SEP]); 00330 if(bodyStart==NULL) { printDebug("D'oh! Couldn't parse for body\n"); } 00331 else { 00332 int body_length=strlen(bodyStart)-4; //don't count the extra \r\n\r\n 00333 response->body_length=body_length; 00334 response->body=(char *)calloc(body_length+1, sizeof(char)); 00335 snprintf(response->body, body_length+1, "%s", bodyStart+4); //the +4 gets rid of the preceeding \r\n\r\n 00336 } 00337 } 00338 00339 temp = strtok(data, HTTP_RESP[HTTPR_DELIMS]); 00340 while (temp!=NULL) { 00341 00342 if((strlen(temp)==3)&&(isdigit((int)temp[0]))&&(isdigit((int)temp[1]))&&(isdigit((int)temp[2]))) { 00343 response->response_code=atoi(temp); //convert the text response code to an int. 00344 } 00345 00346 if(strcmp(temp, HTTP_RESP[HTTPR_TYPE])==0) { 00347 response->headers.contentType=strtok(NULL, HTTP_RESP[HTTPR_DELIMS]); 00348 } 00349 if(strcmp(temp, HTTP_RESP[HTTPR_LENGTH])==0){ 00350 temp=strtok(NULL, HTTP_RESP[HTTPR_DELIMS]); 00351 strcpy(clength, temp); 00352 response->body_length=atoi(clength); 00353 if(response->body_length<=0) { 00354 response->body_length=strlen(bodyStart+4); 00355 } 00356 } 00357 00358 temp=strtok(NULL, HTTP_RESP[HTTPR_DELIMS]); 00359 } 00360 00361 return response; 00362 } 00363 00364 int http_getResponse(HTTP_Transmission *response, ax_socket *source){ 00365 int retVal=AX_UNKNOWN; 00366 char *responseTxt=NULL; 00367 responseTxt=(char *)calloc(AX_NET_BUFF_S, sizeof(char)); 00368 int length=0; 00369 response->response_code=0; 00370 retVal=net_socketRead(source, 300, (unsigned char *)responseTxt, AX_NET_BUFF_S, &length); 00371 if(retVal==AX_OK) { printDebug("response received"); } 00372 else { printDebug("Response Timeout"); return AX_NET_ERR_TIMEOUT;} 00373 http_parse(response, (char *)responseTxt); 00374 00375 free(responseTxt); 00376 return response->response_code; 00377 } 00378 00379 00380 //Supporting Methods 00381 /***************************************************************************************************/ 00382 /*http_getBoundary() */ 00383 /* */ 00384 /*This method creates a unique string token that is used when creating mulitpart form transmissions*/ 00385 /*in HTTP. It will return a random sequence of letters and numbers. */ 00386 /***************************************************************************************************/ 00387 char *http_getBoundary(char *buff, int length){ 00388 int ctr=0; 00389 if(buff==NULL) { return NULL; } //if no pointer was given to us give a nothing back. 00390 if(length<=0) {return NULL;} 00391 int randNum=0; 00392 for(ctr=0; ctr<length; ctr++) { 00393 randNum=abs(randInt()%62); 00394 if(randNum<=10) { 00395 if(randNum==0) { randNum++; } //avoid using slashes in boundary, char 47 00396 buff[ctr]=randNum+47; 00397 } 00398 else if((randNum>10)&&(randNum<=36)) { 00399 buff[ctr]=(randNum-10)+64; 00400 } 00401 else { 00402 buff[ctr]=(randNum-37)+97; 00403 } 00404 if(buff[ctr]==32) {buff[ctr]=97; } 00405 } 00406 00407 return buff; 00408 } 00409 00410 void zeroOutHTTPXmission(HTTP_Transmission *tgt){ 00411 00412 tgt->operation=-1; 00413 tgt->resource=NULL; 00414 tgt->headers.contentType=NULL; 00415 tgt->body=NULL; 00416 tgt->body_length=0; 00417 tgt->mpData=NULL; 00418 tgt->response_code=-1; 00419 tgt->secure=-1; 00420 } 00421 00422 char *createHeaders(char *tgtBuff, int http_operation, char *resource, int httpver, char *hostname, char *contentType, int contentLength, int xCount) { 00423 int headerSz=4; //we account for the closing CRLF CRLF here 00424 char clength_tmp[10]; 00425 char xCount_tmp[10]; 00426 int wrthdr=0; 00427 //convert the ints to strings so we can get an strlen on them 00428 snprintf(clength_tmp, 10, "%d", contentLength); 00429 snprintf(xCount_tmp, 10, "%d", xCount); 00430 00431 //String Size Calculations 00432 headerSz=headerSz+strlen(resource)+strlen(HTTP_KEY[http_operation])+strlen(HTTP_KEY[httpver])+5; //"POST HTTP/1.1" there are 2 spaces and a CRLF in there as well., 1 for the null 00433 headerSz=headerSz+strlen(HTTP_HEADER[HTTPH_HOST])+strlen(hostname)+2; //Host: <hostname> 00434 headerSz=headerSz+strlen(HTTP_HEADER[HTTPH_TYPE])+strlen(contentType)+2; //Content-Type: <contentType> 00435 headerSz=headerSz+strlen(HTTP_HEADER[HTTPH_LENGTH])+strlen(clength_tmp)+2; //Content-Length: <contentLength> 00436 headerSz=headerSz+strlen(HTTP_HEADER[HTTPH_CONNECTION])+2; //Connection: Close 00437 if(xCount>0) { 00438 headerSz=headerSz+strlen(HTTP_HEADER[HTTPH_XCOUNT])+strlen(xCount_tmp)+2; 00439 } 00440 00441 //String buffer creation 00442 tgtBuff=(char *)calloc(headerSz, sizeof(char)); 00443 //Stuffin the buffer 00444 if((xCount>0)&&(http_debug==AX_TRUE)) { 00445 wrthdr=snprintf(tgtBuff, headerSz, "%s %s %s\r\n%s%s\r\n%s%s\r\n%s%s\r\n%s\r\n%s%s\r\n\r\n", HTTP_KEY[http_operation], resource, HTTP_KEY[httpver], HTTP_HEADER[HTTPH_HOST], hostname, HTTP_HEADER[HTTPH_TYPE], contentType, HTTP_HEADER[HTTPH_LENGTH], clength_tmp, HTTP_HEADER[HTTPH_CONNECTION], HTTP_HEADER[HTTPH_XCOUNT], xCount_tmp); 00446 } 00447 else { 00448 wrthdr=snprintf(tgtBuff, headerSz, "%s %s %s\r\n%s%s\r\n%s%s\r\n%s%s\r\n%s\r\n\r\n", HTTP_KEY[http_operation], resource, HTTP_KEY[httpver], HTTP_HEADER[HTTPH_HOST], hostname, HTTP_HEADER[HTTPH_TYPE], contentType, HTTP_HEADER[HTTPH_LENGTH], clength_tmp, HTTP_HEADER[HTTPH_CONNECTION]); 00449 00450 } 00451 if(wrthdr>headerSz) { printDebug("http_send_post(): Buffer allocated for header was too small, truncated"); } 00452 00453 return tgtBuff; 00454 } 00455 00456 00457 00458 00459 00460
Generated on Wed Jul 13 2022 02:45:01 by 1.7.2