When CivTAK (and presumably other variants of the same code) receives a CoT event containing an image, the "stale" field is not reliably honored when the stale time is the same or before the "time" and "start" fields.
This issue was seen on ATAK v4.4.0.6 (2e7a914b) which is the latest available available on the Play Store at the time of writing.
Since this reproducing this issue is very particular, I’ve provided example multi-platform source code for a simple command line tool.
This issue happens whether the event comes to CivTAK/ATAK via a server or is provided directly to CivTAK/ATAK. For simplicity these instructions use the latter mechanism (send CoT event directly to the wide open 4242 TCP port on the CivTAK/ATAK client).
To follow these instructions, you need to know the IP address of the CivTAK/ATAK client under test and you need a PC that can access said IP address and can run this sample code.
Compile the provided demostale.c source code and run it. For this example, the TAK client is 192.168.1.2:
./demostale 192.168.1.2 4242 5
The code generates a CoT event with an image (TAK logo).
You should see the event appear, and the event should gray out after about 5 seconds (5 is the third command line parameter). The user can run this tool multiple times and the icon should gray out after roughly 5 seconds after the last event. You can change the "5" command line argument to some other positive, non-zero integer number to change the time behavior.
However, try this:
./demostale 192.168.1.2 4242 0
./demostale 192.168.1.2 4242 0
Upon running the first line, you will see CivTAK/ATAK create and immediately gray out the the event. This seems to be the expected behavior.
However, upon running the second line, the event becomes active and subsequently never becomes stale. Running the tool additional times does not change the behavior. The third parameter can also be a negative integer (causing the stale time to be before time and start) and the behavior is the same.
/*
THIS FILE IS PROVIDED AS IS WITH NO WARRANTY OF ANY KIND, INCLUDING THE
WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#if defined(_MSC_VER) || defined(__MINGW32__)
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
typedef struct sockaddr * LPSOCKADDR;
typedef int SOCKET;
typedef struct sockaddr_in SOCKADDR_IN;
#endif
int main(int argc, char *argv[])
{
int outcome, portno;
#if defined(_MSC_VER) || defined(__MINGW32__)
WSADATA wsaData;
#endif
SOCKADDR_IN Server;
SOCKET tcpSocket;
char sn[64];
double lat = 38.678315, lon = -77.138435, hae = 250.0;
int stale_secs = 10;
const char *ipaddr;
time_t t, ft;
char datetime[128], staledatetime[128];
int length;
static char buffer[2048];
if (argc < 3)
{
printf("%s <ip_addr> <tcp_port> [stale_secs]\n", argv[0]);
return -1;
}
if (argc > 3) stale_secs = atoi(argv[3]);
// srand(time(NULL));
snprintf(sn, sizeof(sn), "%x", rand());
ipaddr = argv[1];
portno = atoi(argv[2]);
#if defined(_MSC_VER) || defined(__MINGW32__)
outcome = WSAStartup(MAKEWORD(2,0), &wsaData);
/* check the version */
if (wsaData.wVersion != MAKEWORD(2,0)) return -1;
#endif
Server.sin_family = AF_INET;
Server.sin_addr.s_addr = inet_addr(ipaddr);
Server.sin_port = htons((unsigned short)portno);
#if defined(_MSC_VER) || defined(__MINGW32__)
tcpSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == tcpSocket) return -1;
#else
tcpSocket = socket(AF_INET, SOCK_STREAM, 0);
if (tcpSocket <= 0) return -1;
#endif
outcome = connect(tcpSocket, (LPSOCKADDR)&Server, sizeof(Server));
t = time(NULL);
ft = t + stale_secs;
strftime(datetime, sizeof(datetime), "%Y-%m-%dT%H:%M:%S.000Z", gmtime(&t));
strftime(staledatetime, sizeof(staledatetime), "%Y-%m-%dT%H:%M:%S.000Z", gmtime(&ft));
snprintf(buffer, sizeof(buffer),
"<?xml version=\"1.0\"?>\xA<event how=\"m-g\" stale=\"%s\" time=\"%s\" " \
"type=\"a-h-A-M-H-Q\" uid=\"%s\" start=\"%s\" version=\"2.0\"><detail>" \
"<_flow_tags pubsrv=\"%s\"/><image mime=\"image/png\" resolution=\"1\" " \
"type=\"EO\" size=\"983\">\xA" \
"iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAAAAADH8yjkAAADnklEQVR4nO1ZSZLkIAxMV/QX5Uei\xA" \
"R9IHNgmEgC47Yg6jQ1UYgxIlWjBcEe/K56z7ffEpQjwRAkBHI+IRAAHHCAcAoRh9hLAPEASv4XmA\xA" \
"kOkJpzRtAuTpUzhG2HNTvpNWAkABAHjXX7cA7qQ/Tx5BNj4AUCbbVjmo9i8BOM+UlBclW+57DfGz\xA" \
"1J9V6mYCAwBzoW0qvgV8GdPPLZtGeACFneQ9AwSVTi7ENU3XzGWgnD5LrOJHo4FrAFt9B4DaiUwj\xA" \
"pwBc5zaww/1zg4CJMQJwG2SQ3wNA9x4xNIBUbq/tCIB+jO4hAbQ72KxaABqiW3MZaIvJO0LUYdgA\xA" \
"f1MuMGCASIouCKe0uZhJ6313FMlI1io3EllVf89nM012xMwjWTcwcDhjP8k8FwWAeytY/Jamm2W0\xA" \
"D2C9BYL6cKfZCc/mjFyeuM5FijLQqwfhbnoIJKenHMZJdd4ioxVhAMw3V4b4Frx7qRSrikYkKjvX\xA" \
"f0m0oX6bItyEINRx91+iS9WLXodKdirSsHJAgSLmBEDlT7cmbyWNoZN+/sxfoZV2R/2qx2rbQuTx\xA" \
"NMyesbTg+BNpISsLnFw/W6F5RQOu+TDl/ZMCbzjRCOAEfslTTqnoq0FPEbkr0LYoXq/u5Wd4OcdI\xA" \
"xEzpsceuF1kAkE7Xs2koGS1YDfb68Pi6SxW0CISwSM47AD6Ca6M5cvxW/Vpo9hn7dJYAMG68HhEZ\xA" \
"vMKC5wyQml4HEBQ9xpDiqFnQGyA8gVRLdraQ3pDoYOmaAwxihwBZY8Xzj9VYnvVg6v7b03AsInV5\xA" \
"UZZOlIqEGGNoLYkiUl1U3y7Q1j5U04g0IdhDW9PhuWm/bSDAYEhJMWX6ruqKMcbEEwDBgpnCBop2\xA" \
"o2zc+NjeVfXtA8z1WG+aPochTVGMMQZqcZW8aMkRvFIwAEjWE0CwArm+bmvw50S35shhSFlQyKht\xA" \
"ee6+CxYLNgyYFeOVCWeBZmztpgBF4myVvpVc+lPBebDWNEkr8wFe2k5kra8DXBEvMZQ5Ok3XxzI/\xA" \
"+c0H4/63MjPUkcYongVkbIX141q/T1EguEdrG/oXa5Cua77Rv1rkAExrLjM2jjMWADRHSPqX3xQr\xA" \
"N80II02880G4AVCuzYZ936b+jUAzEZJJy8OeLQALIZ3P7uh3I7mKuGlkKljr9QWwmYuyrmxEpmf3\xA" \
"mzRuSWGj0rJ9o7x9Ya353r+x/mduxOP7d/p1IQ6mfwgQYyDQmf64FQffyOs1+T/AUn4BMI8WNv1H\xA" \
"NBIAAAAASUVORK5CYII=\xA" \
"</image></detail><point lat=\"%f\" lon=\"%f\" hae=\"%f\" ce=\"10.1\" le=\"9999999.0\"/></event>",
staledatetime, datetime, sn, datetime, datetime, lat, lon, hae);
length = strlen(buffer);
#if defined(_MSC_VER) || defined(__MINGW32__)
outcome = send(tcpSocket, buffer, length, 0);
#else
outcome = write(tcpSocket, buffer, length);
#endif
#if defined(_MSC_VER) || defined(__MINGW32__)
closesocket(tcpSocket);
#else
close(tcpSocket);
#endif
return 0;
}