LightNest / linux /responses_http.h
LIghtJUNction's picture
Cursor
Add the October 17 experiment source without the result archives.
3f69d68 verified
Raw History Blame Contribute Delete
15.1 kB
/* Deliberately small, stateless loopback HTTP adapter. No files or subprocesses.
* Included after lp_predict so the executable uses its embedded model directly. */
#include <arpa/inet.h>
#include <inttypes.h>
#include <poll.h>
#include <sys/socket.h>
#include <time.h>
#include <strings.h>
#define RH_BODY 8192
#define RH_HEADER 8192
#define RH_TOKENS 64
#define RH_STRING 2048
/* A bounded JSON tree: children are linked siblings; decoded strings are UTF-8. */
typedef struct { int type, child, next; char value[RH_STRING]; } RhToken;
typedef struct { const unsigned char *p; RhToken t[RH_TOKENS]; int count; } RhJson;
static int rh_utf8(const unsigned char *p,size_t n) {
for(size_t i=0;i<n;) {
unsigned c=p[i++],v; int k;
if(!c) return 0;
if(c<128) continue;
if(c>=0xc2 && c<=0xdf) {k=1;v=c&31;}
else if(c>=0xe0 && c<=0xef) {k=2;v=c&15;}
else if(c>=0xf0 && c<=0xf4) {k=3;v=c&7;} else return 0;
if(i+(size_t)k>n) return 0;
for(int j=0;j<k;j++) {unsigned b=p[i++];if((b&0xc0)!=0x80)return 0;v=(v<<6)|(b&63);}
if((k==1&&v<128)||(k==2&&v<2048)||(k==3&&v<65536)||v>0x10ffff||(v>=0xd800&&v<=0xdfff))return 0;
} return 1;
}
static int rh_hex(unsigned c) {if(c>='0'&&c<='9')return (int)c-'0';if(c>='a'&&c<='f')return (int)c-'a'+10;if(c>='A'&&c<='F')return (int)c-'A'+10;return -1;}
static void rh_ws(RhJson *j) {while(*j->p==' '||*j->p=='\t'||*j->p=='\r'||*j->p=='\n')j->p++;}
static int rh_string(RhJson *j,char *out) {
size_t n=0;if(*j->p++!='"')return 0;
while(*j->p && *j->p!='"') {
unsigned c=*j->p++;if(c<32)return 0;
if(c=='\\') {
c=*j->p++;if(!c)return 0;
if(c=='u') {
unsigned v=0;for(int i=0;i<4;i++){int h=rh_hex(*j->p);if(h<0)return 0;j->p++;v=(v<<4)|(unsigned)h;}
if(!v||(v>=0xd800&&v<=0xdfff))return 0;
if(v<128)c=v;
else {if(n+3>=RH_STRING)return 0;if(v<2048)out[n++]=(char)(0xc0|(v>>6));else {out[n++]=(char)(0xe0|(v>>12));out[n++]=(char)(0x80|((v>>6)&63));}c=0x80|(v&63);}
} else if(c=='b')c='\b';else if(c=='f')c='\f';else if(c=='n')c='\n';else if(c=='r')c='\r';else if(c=='t')c='\t';else if(c!='"'&&c!='\\'&&c!='/')return 0;
}
if(n+1>=RH_STRING)return 0;out[n++]=(char)c;
}
if(*j->p!='"')return 0;j->p++;out[n]=0;return 1;
}
static int rh_parse_value(RhJson *j,int depth) {
rh_ws(j);if(depth>8||j->count>=RH_TOKENS)return -1;
int id=j->count++;RhToken *t=&j->t[id];t->child=t->next=-1;
unsigned c=*j->p;
if(c=='"'){t->type='s';return rh_string(j,t->value)?id:-1;}
if(c=='{'||c=='[') {
t->type=(int)c;j->p++;rh_ws(j);unsigned end=c=='{'?'}':']';int last=-1;
if(*j->p==end){j->p++;return id;}
while(1) {
int item;
if(c=='{') {
if(*j->p!='"'||j->count>=RH_TOKENS)return -1;
item=j->count++;RhToken *key=&j->t[item];key->type='k';key->child=key->next=-1;
if(!rh_string(j,key->value))return -1;
for(int k=t->child;k>=0;k=j->t[k].next)if(!strcmp(j->t[k].value,key->value))return -1;
rh_ws(j);if(*j->p++!=':')return -1;
key->child=rh_parse_value(j,depth+1);if(key->child<0)return -1;
} else {item=rh_parse_value(j,depth+1);if(item<0)return -1;}
if(last<0)t->child=item;else j->t[last].next=item;last=item;
rh_ws(j);if(*j->p==end){j->p++;return id;}if(*j->p++!=',')return -1;rh_ws(j);
}
}
if(c>='0'&&c<='9') {
t->type='n';size_t n=0;
do {if(n>=20)return -1;t->value[n++]=(char)*j->p++;}while(*j->p>='0'&&*j->p<='9');
t->value[n]=0;if(n>1&&c=='0')return -1;return id;
}
const char *literals[]={"false","true","null"};
for(int i=0;i<3;i++)if(!strncmp((const char*)j->p,literals[i],strlen(literals[i]))) {t->type='l';strcpy(t->value,literals[i]);j->p+=strlen(literals[i]);return id;}
return -1;
}
static int rh_parse(RhJson *j,const char *s) {
memset(j,0,sizeof(*j));j->p=(const unsigned char*)s;
if(!rh_utf8(j->p,strlen(s))||rh_parse_value(j,0)!=0)return 0;rh_ws(j);return !*j->p;
}
static int rh_get(RhJson *j,int obj,const char *name) {
if(obj<0||j->t[obj].type!='{')return -1;
for(int k=j->t[obj].child;k>=0;k=j->t[k].next)if(!strcmp(j->t[k].value,name))return j->t[k].child;return -1;
}
static int rh_fields(RhJson *j,int obj,const char *const *names,int count) {
if(obj<0||j->t[obj].type!='{')return 0;
for(int k=j->t[obj].child;k>=0;k=j->t[k].next){int found=0;for(int n=0;n<count;n++)if(!strcmp(j->t[k].value,names[n]))found=1;if(!found)return 0;}return 1;
}
static int rh_eq(RhJson *j,int id,int type,const char *s){return id>=0&&j->t[id].type==type&&!strcmp(j->t[id].value,s);}
static int rh_uint(const char *s,uint64_t max,uint64_t *out) {
uint64_t n=0;if(!*s)return 0;for(;*s;s++){if(*s<'0'||*s>'9'||n>(max-(unsigned)(*s-'0'))/10)return 0;n=n*10+(unsigned)(*s-'0');}*out=n;return 1;
}
static int64_t rh_now(void){struct timespec t;if(clock_gettime(CLOCK_MONOTONIC,&t))return 0;return (int64_t)t.tv_sec*1000+t.tv_nsec/1000000;}
static ssize_t rh_read(int fd,void *buf,size_t n,int64_t deadline) {
for(;;){int64_t left=deadline-rh_now();if(left<=0)return -1;struct pollfd p={fd,POLLIN,0};int r=poll(&p,1,(int)left);if(r<0&&errno==EINTR)continue;if(r<=0)return -1;return recv(fd,buf,n,0);}
}
static void rh_reply(int fd,int code,const char *body) {
char h[256];const char *reason=code==200?"OK":code==400?"Bad Request":code==404?"Not Found":code==405?"Method Not Allowed":code==413?"Content Too Large":code==415?"Unsupported Media Type":"Request Timeout";
int n=snprintf(h,sizeof(h),"HTTP/1.1 %d %s\r\nContent-Type: application/json\r\nContent-Length: %zu\r\nConnection: close\r\nX-Content-Type-Options: nosniff\r\n\r\n",code,reason,strlen(body));
const char *parts[]={h,body};size_t sizes[]={(size_t)n,strlen(body)};
int64_t deadline=rh_now()+5000;
for(int p=0;p<2;p++){size_t off=0;while(off<sizes[p]){int64_t left=deadline-rh_now();if(left<=0)return;struct pollfd f={fd,POLLOUT,0};if(poll(&f,1,(int)left)<=0)return;ssize_t wrote=send(fd,parts[p]+off,sizes[p]-off,MSG_NOSIGNAL|MSG_DONTWAIT);if(wrote<=0)return;off+=(size_t)wrote;}}
}
static void rh_error(int fd,int code,const char *message) {char b[512];snprintf(b,sizeof(b),"{\"error\":{\"message\":\"%s\",\"type\":\"invalid_request_error\",\"param\":null,\"code\":\"unsupported_or_invalid_request\"}}",message);rh_reply(fd,code,b);}
static void rh_response(int fd,const PatchModel *model,const char *body,uint64_t serial) {
RhJson j,e;
if(!rh_parse(&j,body)){rh_error(fd,400,"Malformed JSON, duplicate keys, invalid UTF-8, or JSON exceeds bounded grammar");return;}
const char *root[]={"model","input","stream","store"};
if(!rh_fields(&j,0,root,4)||!rh_eq(&j,rh_get(&j,0,"model"),'s',"lightnest-patch-v2")){rh_error(fd,400,"Only model lightnest-patch-v2 and input, stream:false, store:false are supported");return;}
int stream=rh_get(&j,0,"stream"),store=rh_get(&j,0,"store");
if((stream>=0&&!rh_eq(&j,stream,'l',"false"))||(store>=0&&!rh_eq(&j,store,'l',"false"))){rh_error(fd,400,"Streaming and storage are unsupported; use false or omit");return;}
int input=rh_get(&j,0,"input");
if(input>=0&&j.t[input].type=='[') {
int msg=j.t[input].child;const char *mf[]={"role","content"};const char *cf[]={"type","text"};
if(msg<0||j.t[msg].next>=0||!rh_fields(&j,msg,mf,2)||!rh_eq(&j,rh_get(&j,msg,"role"),'s',"user")){rh_error(fd,400,"Input must contain exactly one user message");return;}
int content=rh_get(&j,msg,"content"),part=content>=0?j.t[content].child:-1;
if(content<0||j.t[content].type!='['||part<0||j.t[part].next>=0||!rh_fields(&j,part,cf,2)||!rh_eq(&j,rh_get(&j,part,"type"),'s',"input_text")){rh_error(fd,400,"Content must contain exactly one input_text part");return;}
input=rh_get(&j,part,"text");
}
if(input<0||j.t[input].type!='s'||!rh_parse(&e,j.t[input].value)){rh_error(fd,400,"Input text must be a JSON task envelope");return;}
const char *ef[]={"command","binary_hex","base_offset"};int command=rh_get(&e,0,"command"),hex=rh_get(&e,0,"binary_hex"),base=rh_get(&e,0,"base_offset");uint64_t offset=0;
if(!rh_fields(&e,0,ef,3)||command<0||e.t[command].type!='s'||!strlen(e.t[command].value)||strlen(e.t[command].value)>96||hex<0||e.t[hex].type!='s'||(base>=0&&(e.t[base].type!='n'||!rh_uint(e.t[base].value,UINT64_C(9007199254740736),&offset)))){rh_error(fd,400,"Envelope requires command (1..96 UTF-8 bytes), binary_hex, optional nonnegative bounded integer base_offset");return;}
size_t size=strlen(e.t[hex].value);unsigned char bytes[256];
if(!size||size>512||size%2){rh_error(fd,400,"binary_hex must encode 1..256 bytes");return;}
for(size_t i=0;i<size/2;i++){int a=rh_hex(e.t[hex].value[2*i]),b=rh_hex(e.t[hex].value[2*i+1]);if(a<0||b<0){rh_error(fd,400,"binary_hex contains non-hex characters");return;}bytes[i]=(unsigned char)((a<<4)|b);}
PatchResult r=lp_predict(model,e.t[command].value,bytes,(int)(size/2));
char text[1024],escaped[2048],response[4096],absolute[32];
if(r.proposed)snprintf(absolute,sizeof(absolute),"%" PRIu64,offset+(uint64_t)r.position);else strcpy(absolute,"null");
snprintf(text,sizeof(text),"{\"result\":\"%s\",\"status\":\"%s\",\"validated\":false,\"proposed\":%s,\"position\":%d,\"replacement\":%d,\"confidence\":%.9g,\"base_offset\":%" PRIu64 ",\"offset\":%s}",r.proposed?"PATCH":"NONE",r.status,r.proposed?"true":"false",r.position,r.replacement,r.confidence,offset,absolute);
size_t n=0;for(size_t i=0;text[i];i++){if(text[i]=='"'||text[i]=='\\')escaped[n++]='\\';escaped[n++]=text[i];}escaped[n]=0;
time_t now=time(NULL);
snprintf(response,sizeof(response),"{\"id\":\"resp_ln_%ld_%ld_%" PRIu64 "\",\"object\":\"response\",\"created_at\":%ld,\"status\":\"completed\",\"error\":null,\"incomplete_details\":null,\"model\":\"lightnest-patch-v2\",\"output\":[{\"id\":\"msg_ln_%" PRIu64 "\",\"type\":\"message\",\"status\":\"completed\",\"role\":\"assistant\",\"content\":[{\"type\":\"output_text\",\"text\":\"%s\",\"annotations\":[]}]}],\"usage\":null,\"store\":false,\"instructions\":null,\"metadata\":{},\"parallel_tool_calls\":false,\"tool_choice\":\"none\",\"tools\":[],\"temperature\":null,\"top_p\":null,\"truncation\":\"disabled\"}",(long)now,(long)getpid(),serial,(long)now,serial,escaped);
rh_reply(fd,200,response);
}
static void rh_client(int fd,const PatchModel *model,uint64_t serial,unsigned port) {
char header[RH_HEADER+1],body[RH_BODY+1];size_t n=0;int64_t deadline=rh_now()+5000;
while(n<RH_HEADER){ssize_t got=rh_read(fd,header+n,1,deadline);if(got!=1){rh_error(fd,408,"Request read timeout or premature EOF");return;}if(!header[n]){rh_error(fd,400,"NUL in header");return;}n++;if(n>=4&&!memcmp(header+n-4,"\r\n\r\n",4))break;}
if(n<4||memcmp(header+n-4,"\r\n\r\n",4)){rh_error(fd,400,"Header too large");return;}header[n]=0;
for(size_t i=0;i<n;i++)if(((unsigned char)header[i]<32&&header[i]!='\r'&&header[i]!='\n'&&header[i]!='\t')||(unsigned char)header[i]>=127){rh_error(fd,400,"Invalid header characters");return;}
for(size_t i=0;i<n;i++)if((header[i]=='\r'&&(i+1==n||header[i+1]!='\n'))||(header[i]=='\n'&&(!i||header[i-1]!='\r'))){rh_error(fd,400,"Headers require CRLF");return;}
char *line=strstr(header,"\r\n");*line=0;char method[16],path[128],version[16],extra;
if(sscanf(header,"%15s %127s %15s %c",method,path,version,&extra)!=3||strcmp(version,"HTTP/1.1")){rh_error(fd,400,"Expected HTTP/1.1 request");return;}
int cl=0,ct=0,host=0;uint64_t length=0;char *p=line+2;
while(*p){char *end=strstr(p,"\r\n");if(!end){rh_error(fd,400,"Invalid header line");return;}*end=0;if(!*p)break;char *colon=strchr(p,':');if(!colon||colon==p){rh_error(fd,400,"Invalid header");return;}*colon=0;
for(char *k=p;*k;k++)if(!isalnum((unsigned char)*k)&&!strchr("!#$%&'*+-.^_`|~",*k)){rh_error(fd,400,"Invalid header name");return;}
char *v=colon+1;while(*v==' '||*v=='\t')v++;char *tail=v+strlen(v);while(tail>v&&(tail[-1]==' '||tail[-1]=='\t'))*--tail=0;
if(!strcasecmp(p,"Transfer-Encoding")||!strcasecmp(p,"Expect")||!strcasecmp(p,"Origin")){rh_error(fd,400,"Transfer-Encoding, Expect and browser Origin requests are unsupported");return;}
if(!strcasecmp(p,"Content-Length")){if(cl++||!rh_uint(v,UINT64_MAX,&length)){rh_error(fd,400,"Invalid or duplicate Content-Length");return;}}
if(!strcasecmp(p,"Content-Type")){if(ct++||(strcasecmp(v,"application/json")&&strcasecmp(v,"application/json; charset=utf-8"))){rh_error(fd,415,"Use application/json");return;}}
if(!strcasecmp(p,"Host")){char expected[64];snprintf(expected,sizeof(expected),"127.0.0.1:%u",port);if(host++||strcmp(v,expected)){rh_error(fd,400,"Host must match the loopback listener including port");return;}}
p=end+2;
}
if(!host){rh_error(fd,400,"Host is required");return;}
if(length>RH_BODY){rh_error(fd,413,"Body exceeds 8192 bytes");return;}
if(!strcmp(method,"GET")&&(!strcmp(path,"/health")||!strcmp(path,"/v1/models"))){if(length){rh_error(fd,400,"GET body unsupported");return;}rh_reply(fd,200,!strcmp(path,"/health")?"{\"status\":\"ok\",\"model\":\"lightnest-patch-v2\",\"stateless\":true,\"max_command_utf8_bytes\":96,\"max_binary_bytes\":256,\"streaming\":false,\"semantic_validation\":false}":"{\"object\":\"list\",\"data\":[{\"id\":\"lightnest-patch-v2\",\"object\":\"model\",\"created\":0,\"owned_by\":\"local\"}]}");return;}
if(strcmp(path,"/v1/responses")){rh_error(fd,404,"Unknown route");return;}if(strcmp(method,"POST")){rh_error(fd,405,"Use POST");return;}
if(!cl||!ct||!length){rh_error(fd,400,"JSON Content-Type and nonzero Content-Length are required");return;}
n=0;while(n<(size_t)length){ssize_t got=rh_read(fd,body+n,(size_t)length-n,deadline);if(got<=0){rh_error(fd,408,"Request read timeout or premature EOF");return;}n+=(size_t)got;}body[n]=0;
if(memchr(body,0,n)||!rh_utf8((unsigned char*)body,n)){rh_error(fd,400,"Body must be UTF-8 without NUL");return;}rh_response(fd,model,body,serial);
}
static int rh_serve(const PatchModel *model,const char *port_string) {
uint64_t port;if(!rh_uint(port_string,65535,&port)||!port)die("--serve requires port 1..65535");
int server=socket(AF_INET,SOCK_STREAM,0);if(server<0)die("cannot create loopback socket");
struct sockaddr_in addr={0};addr.sin_family=AF_INET;addr.sin_port=htons((uint16_t)port);addr.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
if(bind(server,(struct sockaddr*)&addr,sizeof(addr))||listen(server,8)){close(server);die("cannot bind/listen on 127.0.0.1");}
fprintf(stderr,"LightNest native stateless Responses subset listening on http://127.0.0.1:%u (no authentication; local processes only)\n",(unsigned)port);
uint64_t serial=0;for(;;){int fd=accept(server,NULL,NULL);if(fd<0){if(errno==EINTR)continue;close(server);die("accept failed");}rh_client(fd,model,++serial,(unsigned)port);close(fd);}
}