Private
Public Access
Add resilient handover, live LED layout refresh and TV calibration profiles
This commit is contained in:
1 parent
4ab4fa034b
commit
e55d37772a
26 files changed
+543
-134
No files matched your search
+55
-48
@@ -3,69 +3,76 @@
|
||||
/* An independent nonblocking connection. Tests never send colors or acquire
|
||||
* capture. Work per timer iteration is bounded to keep output responsive. */
|
||||
typedef enum {TEST_IDLE,TEST_CONNECT,TEST_SEND,TEST_LOGIN,TEST_LAYOUT,TEST_DONE,TEST_FAILED} TestStage;
|
||||
static struct {
|
||||
typedef struct {
|
||||
int fd;TestStage stage,after_send;TargetConfig target;
|
||||
uint64_t started,deadline,elapsed;unsigned leds;
|
||||
char request[384],error[128];size_t sent,used;char *response;
|
||||
} connection_test={.fd=-1};
|
||||
static const char *test_stage_name(void){
|
||||
LedBlend *mapping;
|
||||
} ReceiverProbe;
|
||||
static ReceiverProbe connection_test={.fd=-1};
|
||||
static const char *probe_stage_name(const ReceiverProbe *probe){
|
||||
static const char *names[]={"Not tested","Connecting","Sending request","Checking authentication","Reading LED layout","Passed","Failed"};
|
||||
return names[connection_test.stage];
|
||||
return names[probe->stage];
|
||||
}
|
||||
static bool test_busy(void){return connection_test.stage>=TEST_CONNECT && connection_test.stage<=TEST_LAYOUT;}
|
||||
static void test_close(void){if(connection_test.fd>=0)close(connection_test.fd);connection_test.fd=-1;free(connection_test.response);connection_test.response=NULL;memset(connection_test.request,0,sizeof(connection_test.request));memset(connection_test.target.token,0,sizeof(connection_test.target.token));}
|
||||
static void test_finish(const char *error,uint64_t now){
|
||||
test_close();connection_test.elapsed=now-connection_test.started;
|
||||
connection_test.stage=error?TEST_FAILED:TEST_DONE;
|
||||
snprintf(connection_test.error,sizeof(connection_test.error),"%s",error?error:"");
|
||||
static bool probe_busy(const ReceiverProbe *probe){return probe->stage>=TEST_CONNECT && probe->stage<=TEST_LAYOUT;}
|
||||
static void probe_close(ReceiverProbe *probe){if(probe->fd>=0)close(probe->fd);probe->fd=-1;free(probe->mapping);probe->mapping=NULL;free(probe->response);probe->response=NULL;memset(probe->request,0,sizeof(probe->request));memset(probe->target.token,0,sizeof(probe->target.token));}
|
||||
static void probe_finish(ReceiverProbe *probe,const char *error,uint64_t now){
|
||||
probe_close(probe);probe->elapsed=now-probe->started;
|
||||
probe->stage=error?TEST_FAILED:TEST_DONE;
|
||||
snprintf(probe->error,sizeof(probe->error),"%s",error?error:"");
|
||||
}
|
||||
static void test_request(bool login,uint64_t now){
|
||||
if(login)snprintf(connection_test.request,sizeof(connection_test.request),"{\"command\":\"authorize\",\"subcommand\":\"login\",\"token\":\"%s\"}\n",connection_test.target.token);
|
||||
else snprintf(connection_test.request,sizeof(connection_test.request),"{\"command\":\"serverinfo\"}\n");
|
||||
connection_test.sent=connection_test.used=0;connection_test.after_send=login?TEST_LOGIN:TEST_LAYOUT;
|
||||
connection_test.stage=TEST_SEND;connection_test.deadline=now+3000;
|
||||
static void probe_request(ReceiverProbe *probe,bool login,uint64_t now){
|
||||
if(login)snprintf(probe->request,sizeof(probe->request),"{\"command\":\"authorize\",\"subcommand\":\"login\",\"token\":\"%s\"}\n",probe->target.token);
|
||||
else snprintf(probe->request,sizeof(probe->request),"{\"command\":\"serverinfo\"}\n");
|
||||
probe->sent=probe->used=0;probe->after_send=login?TEST_LOGIN:TEST_LAYOUT;
|
||||
probe->stage=TEST_SEND;probe->deadline=now+3000;
|
||||
}
|
||||
static bool test_begin(const TargetConfig *target,uint64_t now){
|
||||
if(test_busy())return false;
|
||||
test_close();memset(&connection_test,0,sizeof(connection_test));connection_test.fd=-1;
|
||||
connection_test.target=*target;connection_test.started=now;
|
||||
connection_test.response=malloc(2*1024*1024+1);
|
||||
if(!connection_test.response){test_finish("Cannot allocate connection test buffer",now);return true;}
|
||||
connection_test.fd=target_open(target);
|
||||
if(connection_test.fd<0){test_finish("Cannot open receiver connection",now);return true;}
|
||||
connection_test.stage=TEST_CONNECT;connection_test.deadline=now+3000;return true;
|
||||
static bool probe_begin(ReceiverProbe *probe,const TargetConfig *target,uint64_t now){
|
||||
if(probe_busy(probe))return false;
|
||||
probe_close(probe);memset(probe,0,sizeof(*probe));probe->fd=-1;
|
||||
probe->target=*target;probe->started=now;
|
||||
probe->response=malloc(2*1024*1024+1);
|
||||
if(!probe->response){probe_finish(probe,"Cannot allocate connection test buffer",now);return true;}
|
||||
probe->fd=target_open(target);
|
||||
if(probe->fd<0){probe_finish(probe,"Cannot open receiver connection",now);return true;}
|
||||
probe->stage=TEST_CONNECT;probe->deadline=now+3000;return true;
|
||||
}
|
||||
static void test_tick(PlaybackJson *json,uint64_t now){
|
||||
if(!test_busy())return;
|
||||
if(now>=connection_test.deadline){test_finish("Receiver timed out",now);return;}
|
||||
int fd=connection_test.fd;
|
||||
if(connection_test.stage==TEST_CONNECT){
|
||||
static void probe_tick(ReceiverProbe *probe,PlaybackJson *json,uint64_t now){
|
||||
if(!probe_busy(probe))return;
|
||||
if(now>=probe->deadline){probe_finish(probe,"Receiver timed out",now);return;}
|
||||
int fd=probe->fd;
|
||||
if(probe->stage==TEST_CONNECT){
|
||||
struct pollfd p={fd,POLLOUT,0};int error=0;socklen_t size=sizeof(error);
|
||||
if(poll(&p,1,0)<=0)return;
|
||||
if(getsockopt(fd,SOL_SOCKET,SO_ERROR,&error,&size)!=0 || error){test_finish("Receiver connection refused or unreachable",now);return;}
|
||||
test_request(connection_test.target.token[0]!=0,now);return;
|
||||
if(getsockopt(fd,SOL_SOCKET,SO_ERROR,&error,&size)!=0 || error){probe_finish(probe,"Receiver connection refused or unreachable",now);return;}
|
||||
probe_request(probe,probe->target.token[0]!=0,now);return;
|
||||
}
|
||||
if(connection_test.stage==TEST_SEND){
|
||||
size_t length=strlen(connection_test.request);
|
||||
ssize_t n=send(fd,connection_test.request+connection_test.sent,length-connection_test.sent,MSG_NOSIGNAL);
|
||||
if(probe->stage==TEST_SEND){
|
||||
size_t length=strlen(probe->request);
|
||||
ssize_t n=send(fd,probe->request+probe->sent,length-probe->sent,MSG_NOSIGNAL);
|
||||
if(n<0 && (errno==EAGAIN || errno==EWOULDBLOCK || errno==EINTR))return;
|
||||
if(n<=0){test_finish("Receiver closed the test connection",now);return;}
|
||||
connection_test.sent+=(size_t)n;
|
||||
if(connection_test.sent==length){connection_test.stage=connection_test.after_send;connection_test.deadline=now+3000;memset(connection_test.request,0,sizeof(connection_test.request));}
|
||||
if(n<=0){probe_finish(probe,"Receiver closed the test connection",now);return;}
|
||||
probe->sent+=(size_t)n;
|
||||
if(probe->sent==length){probe->stage=probe->after_send;probe->deadline=now+3000;memset(probe->request,0,sizeof(probe->request));}
|
||||
return;
|
||||
}
|
||||
size_t available=2*1024*1024-connection_test.used;if(available>65536)available=65536;
|
||||
if(!available){test_finish("Receiver response exceeds 2 MiB",now);return;}
|
||||
ssize_t n=recv(fd,connection_test.response+connection_test.used,available,0);
|
||||
size_t available=2*1024*1024-probe->used;if(available>65536)available=65536;
|
||||
if(!available){probe_finish(probe,"Receiver response exceeds 2 MiB",now);return;}
|
||||
ssize_t n=recv(fd,probe->response+probe->used,available,0);
|
||||
if(n<0 && (errno==EAGAIN || errno==EWOULDBLOCK || errno==EINTR))return;
|
||||
if(n<=0){test_finish("Receiver closed the test connection",now);return;}
|
||||
connection_test.used+=(size_t)n;connection_test.response[connection_test.used]=0;
|
||||
char *end=strchr(connection_test.response,'\n');if(!end)return;*end=0;
|
||||
void *root=json->parse(connection_test.response);bool success=playback_true(json,root,"success");if(root)json->put(root);
|
||||
if(!success){test_finish(connection_test.stage==TEST_LOGIN?"Receiver authentication failed":"Receiver rejected serverinfo; check authorization",now);return;}
|
||||
if(connection_test.stage==TEST_LOGIN){test_request(false,now);return;}
|
||||
if(n<=0){probe_finish(probe,"Receiver closed the test connection",now);return;}
|
||||
probe->used+=(size_t)n;probe->response[probe->used]=0;
|
||||
char *end=strchr(probe->response,'\n');if(!end)return;*end=0;
|
||||
void *root=json->parse(probe->response);bool success=playback_true(json,root,"success");if(root)json->put(root);
|
||||
if(!success){probe_finish(probe,probe->stage==TEST_LOGIN?"Receiver authentication failed":"Receiver rejected serverinfo; check authorization",now);return;}
|
||||
if(probe->stage==TEST_LOGIN){probe_request(probe,false,now);return;}
|
||||
LedBlend *mapping=NULL;unsigned count=0;
|
||||
if(!layout_from_json(connection_test.response,&mapping,&count)){test_finish("Receiver returned an invalid or empty LED layout",now);return;}
|
||||
free(mapping);connection_test.leds=count;test_finish(NULL,now);
|
||||
if(!layout_from_json(probe->response,&mapping,&count)){probe_finish(probe,"Receiver returned an invalid or empty LED layout",now);return;}
|
||||
probe->leds=count;probe_finish(probe,NULL,now);probe->mapping=mapping;
|
||||
}
|
||||
static inline const char *test_stage_name(void){return probe_stage_name(&connection_test);}
|
||||
static inline bool test_busy(void){return probe_busy(&connection_test);}
|
||||
static inline void test_close(void){probe_close(&connection_test);}
|
||||
static inline bool test_begin(const TargetConfig *target,uint64_t now){return probe_begin(&connection_test,target,now);}
|
||||
static inline void test_tick(PlaybackJson *json,uint64_t now){probe_tick(&connection_test,json,now);}
|
||||
#endif
|
||||
Reference in new issue
Block a user