Use the real API v1 from a trusted C++ process with explicit signature and policy checks before enabling features. This REST sample is not an additional official SDK.
What you will learn
- Activate, validate and deactivate one stable Sandbox installation.
- Preserve signed bytes and verify the trusted Ed25519 key before policy.
- Handle cache, offline grace, explicit rejection and safe user messages.
Prerequisites
- Complete the Sandbox quickstart.
- Read authentication and signed responses.
- Have the product App ID, a Sandbox license and its matching customer email when supplied.
- Obtain the actual public-key document over independently trusted HTTPS and distribute/pin it through controlled application configuration, not from an untrusted envelope.
Runtime and application type
C++17 entry point plus C11/POSIX core, libcurl, yyjson0.13.0, libsodium1.0.20. A native C++17 executable links the full C ABI session core. The core is compiled as C11, not compiled accidentally as C++; no Python/Node bridge or hidden licensing service is involved. C and C++ intentionally share state format/namespace. POSIX storage/clock APIs are not a claim of native Windows support.
Complete project and integrity
<!-- P7-PROJECT-PACKAGE:START -->
The complete project archive contains every client/verifier source and the exact build/dependency metadata needed by this language. Extract it with its original directory layout. Dependencies/locks are included where this runtime uses them; no hidden helper or test-only clock is required.
kdpika-cpp-rest-example-1.0.0.zip · 11215 bytes · version 1.0.0
SHA-256: 942f52d4601709843bdba099a9993569ba2851b193801f3c72165d3949aa108e
<!-- P7-PROJECT-PACKAGE:END -->
Extract the entire project, preserving its samples/sdks and samples/security layout. The archive includes all first-party client/verifier sources and pinned manifests/locks; dependency downloads are development-time restoration, never extra services needed by the PHP host. Do not flatten the tree or replace the verification helper with a generic JSON reserializer.
Install and build
- Verify the archive SHA-256 against the current table. Expected: exact digest match; otherwise stop.
- Extract into a clean private development directory. Expected: complete samples tree with the language project.
- Enter samples/sdks/cpp. Expected: the main client and build/dependency files are present.
- Install the stated runtime/native libraries, then run the full build command below. Expected: successful syntax check or native executable, not a registry SDK download.
SODIUM_PREFIX=/opt/homebrew/opt/libsodium sh build.sh
The source scripts compile the exact files and validate third-party download checksums where applicable. Compiler toolchains are development dependencies, not tools to install on KDPika shared hosting. macOS is the actual tested runtime; other targets require their own build/permission/TLS-root checks.
Application configuration
| Variable | Required | Meaning |
|---|---|---|
| KDP_APP_ID | Yes | Public App ID, not internal product ID or developer key |
| KDP_BASE_URL | No | HTTPS API base ending /api/v1; default https://licensing.kdpika.com/api/v1 |
| KDP_STATE_DIR | Yes | Absolute private application state root; keep it stable |
| KDP_TRUSTED_KEY_FILE | Yes | Absolute path to an independently trusted public-key document |
| KDP_LICENSE_KEY | Activation | Sandbox key beginning TEST; never embedded in distributed source |
| KDP_CUSTOMER_EMAIL | Optional | Actual matching customer email for email-bound activation |
| KDP_ALLOW_LOOPBACK | Local testing only | 1 permits explicit localhost/127.0.0.1 HTTP, not arbitrary HTTP hosts |
Environment is fixed to SANDBOX in this example. Unsafe URL credentials/query/fragment and nonlocal HTTP are rejected. Official SDK constructors expect the site origin; these REST programs expect /api/v1. Store state and trusted keys outside public web roots. Do not ship lmp_ developer management keys.
Complete runnable client
// C++17 entry point. Link the complete C ABI native session core, yyjson,
// libcurl and libsodium. No extra Python/Node process or SDK dependency.
// Run ./session status|activate|check|validate|deactivate; safe JSON decisions.
extern "C" int kdpika_session_main(int argc,char **argv);
int main(int argc,char **argv){return kdpika_session_main(argc,argv);}
/* Complete Sandbox REST session core; C11/POSIX, libcurl, yyjson0.13.0,
* libsodium1.0.20. Linked by both C and C++ entry points. Never logs secrets.
* Run status|activate|check|validate|deactivate with documented KDP_* config. */
#define _DARWIN_C_SOURCE
#define _GNU_SOURCE
#include "../../security/c/canonical.h"
#include <curl/curl.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <limits.h>
typedef struct { char *app,*base,*directory,*installation,*trusted;size_t trusted_size;int lock;const char *error;char request_id[128]; } Session;
typedef struct { char *data;size_t size;int oversized; } Buffer;
static int safe_text(const char *value,size_t limit) {if(!value||!*value||strlen(value)>limit)return 0;for(const char *p=value;*p;p++)if(*p=='\r'||*p=='\n')return 0;return 1;}
static int identifier(const char *value,size_t limit) {if(!safe_text(value,limit))return 0;for(const char *p=value;*p;p++)if(!((*p>='a'&&*p<='z')||(*p>='A'&&*p<='Z')||(*p>='0'&&*p<='9')||*p=='_'||*p=='-'))return 0;return 1;}
static const char *string(yyjson_val *value,const char *name){return yyjson_get_str(yyjson_obj_get(value,name));}
static char *join(const char *a,const char *b){size_t size=strlen(a)+strlen(b)+1;char *v=(char*)malloc(size);if(v)snprintf(v,size,"%s%s",a,b);return v;}
static void hash(const char *value,char out[65]) {unsigned char digest[32];crypto_hash_sha256(digest,(const unsigned char*)value,strlen(value));sodium_bin2hex(out,65,digest,32);}
static char *read_private(const char *file,size_t *size) {
struct stat stat;int fd=open(file,O_RDONLY|O_NOFOLLOW);if(fd<0)return NULL;
if(fstat(fd,&stat)||!S_ISREG(stat.st_mode)||stat.st_size>1048576||stat.st_size<0){close(fd);return NULL;}
char *raw=(char*)malloc((size_t)stat.st_size+1);if(!raw){close(fd);return NULL;}size_t n=0;
while(n<(size_t)stat.st_size){ssize_t got=read(fd,raw+n,(size_t)stat.st_size-n);if(got<=0){free(raw);close(fd);return NULL;}n+=(size_t)got;}close(fd);raw[n]=0;*size=n;return raw;
}
static int write_atomic(Session *s,const char *name,const char *raw) {
char *template=join(s->directory,"/write-XXXXXX");if(!template)return 0;int fd=mkstemp(template);if(fd<0){free(template);return 0;}int ok=fchmod(fd,0600)==0;size_t n=0,len=strlen(raw);
while(ok&&n<len){ssize_t count=write(fd,raw+n,len-n);if(count<=0)ok=0;else n+=(size_t)count;}if(ok)ok=fsync(fd)==0;if(close(fd))ok=0;
char *target=join(s->directory,name);if(ok)ok=target&&rename(template,target)==0;free(target);free(template);return ok;
}
static char *dump(yyjson_val *value){Output result={NULL,0,0,1};canonical(&result,value,0);if(!result.ok){free(result.data);return NULL;}return result.data;}
static int directory(const char *path) {struct stat st;if(mkdir(path,0700)&&errno!=EEXIST)return 0;if(lstat(path,&st)||!S_ISDIR(st.st_mode)||S_ISLNK(st.st_mode))return 0;return chmod(path,0700)==0;}
static char *now_string(void){time_t now=time(NULL);struct tm utc;if(!gmtime_r(&now,&utc))return NULL;char text[40];if(!strftime(text,sizeof text,"%Y-%m-%dT%H:%M:%SZ",&utc))return NULL;return strdup(text);}
static int timestamp(const char *text,double *result) {
if(!text)return 0;struct tm tm={0};char *tail=strptime(text,"%Y-%m-%dT%H:%M:%S",&tm);if(!tail)return 0;double fraction=0;
if(*tail=='.'){char *end;fraction=strtod(tail,&end);if(end==tail)return 0;tail=end;}
int offset=0;if(*tail=='Z')tail++;else if(*tail=='+'||*tail=='-'){int sign=*tail++=='+'?1:-1;int hours,minutes,used=0;if(sscanf(tail,"%2d:%2d%n",&hours,&minutes,&used)!=2||used!=5||hours>23||minutes>59)return 0;offset=sign*(hours*3600+minutes*60);tail+=used;}else return 0;
if(*tail)return 0;*result=(double)timegm(&tm)+fraction-offset;return 1;
}
static size_t receive(char *data,size_t size,size_t count,void *context) {
Buffer *b=(Buffer*)context;if(size&&count>SIZE_MAX/size)return 0;size_t n=size*count;if(n>1048576-b->size){b->oversized=1;return 0;}char *next=(char*)realloc(b->data,b->size+n+1);if(!next)return 0;b->data=next;memcpy(next+b->size,data,n);b->size+=n;next[b->size]=0;return n;
}
static yyjson_doc *request(Session *s,const char *endpoint,const char *body,const char *token) {
s->error=NULL;s->request_id[0]=0;CURL *curl=curl_easy_init();if(!curl){s->error="NETWORK_ERROR";return NULL;}char *url=join(s->base,endpoint);Buffer buffer={NULL,0,0};struct curl_slist *headers=NULL;
headers=curl_slist_append(headers,"Accept: application/json");headers=curl_slist_append(headers,"Content-Type: application/json");char *auth=join(token?"Authorization: Bearer ":"X-App-ID: ",token?token:s->app);headers=curl_slist_append(headers,auth);free(auth);
if(token){char *installation=join("X-Installation-ID: ",s->installation);headers=curl_slist_append(headers,installation);free(installation);}else headers=curl_slist_append(headers,"X-Environment: SANDBOX");
curl_easy_setopt(curl,CURLOPT_URL,url);curl_easy_setopt(curl,CURLOPT_HTTPHEADER,headers);curl_easy_setopt(curl,CURLOPT_POSTFIELDS,body);curl_easy_setopt(curl,CURLOPT_POSTFIELDSIZE,(long)strlen(body));curl_easy_setopt(curl,CURLOPT_TIMEOUT,15L);curl_easy_setopt(curl,CURLOPT_CONNECTTIMEOUT,5L);
curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,0L);curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,1L);curl_easy_setopt(curl,CURLOPT_SSL_VERIFYHOST,2L);curl_easy_setopt(curl,CURLOPT_PROTOCOLS_STR,"http,https");curl_easy_setopt(curl,CURLOPT_USERAGENT,"KDPika-REST-native-example/1.0");curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,receive);curl_easy_setopt(curl,CURLOPT_WRITEDATA,&buffer);
CURLcode code=curl_easy_perform(curl);long status=0;curl_easy_getinfo(curl,CURLINFO_RESPONSE_CODE,&status);curl_easy_cleanup(curl);curl_slist_free_all(headers);free(url);
if(code!=CURLE_OK){s->error=buffer.oversized?"INVALID_RESPONSE":"NETWORK_ERROR";free(buffer.data);return NULL;}
yyjson_doc *doc=buffer.data?parse(buffer.data,buffer.size):NULL;free(buffer.data);if(!doc){s->error="INVALID_RESPONSE";return NULL;}yyjson_val *root=yyjson_doc_get_root(doc);const char *id=string(root,"request_id");if(id&&strncmp(id,"req_",4)==0&&identifier(id,124))snprintf(s->request_id,sizeof s->request_id,"%s",id);
if(status<200||status>=300||!yyjson_is_true(yyjson_obj_get(root,"success"))){const char *error=string(yyjson_obj_get(root,"error"),"code");s->error="HTTP_ERROR";if(error&&identifier(error,64)){static _Thread_local char copied[65];snprintf(copied,sizeof copied,"%s",error);s->error=copied;}yyjson_doc_free(doc);return NULL;}return doc;
}
static int save(Session *s,const char *token,const char *envelope,const char *checkpoint) {
yyjson_mut_doc *doc=yyjson_mut_doc_new(NULL);yyjson_mut_val *root=yyjson_mut_obj(doc);yyjson_mut_doc_set_root(doc,root);
yyjson_mut_obj_add_str(doc,root,"token",token);if(envelope)yyjson_mut_obj_add_str(doc,root,"envelope",envelope);else yyjson_mut_obj_add_null(doc,root,"envelope");yyjson_mut_obj_add_str(doc,root,"checkpoint",checkpoint);
char *raw=yyjson_mut_write(doc,0,NULL);int ok=raw&&write_atomic(s,"/session.json",raw);free(raw);yyjson_mut_doc_free(doc);return ok;
}
static void clear(Session *s) {char *file=join(s->directory,"/session.json");if(file){unlink(file);free(file);}}
static int verify(Session *s,const char *envelope,const char *source,const char *checkpoint,int *enabled,long long *seats) {
s->error=NULL;*enabled=0;*seats=-1;if(!verify_envelope(s->trusted,s->trusted_size,envelope,strlen(envelope))){s->error="SIGNATURE_INVALID";return 0;}
yyjson_doc *doc=parse(envelope,strlen(envelope));if(!doc){s->error="INVALID_RESPONSE";return 0;}yyjson_val *p=yyjson_obj_get(yyjson_doc_get_root(doc),"payload");char identity[65];hash(s->installation,identity);
if(!equals(p,"product_id",s->app)||!equals(p,"environment","sandbox")||!equals(yyjson_obj_get(p,"device"),"installation_hash",identity))s->error="BINDING_MISMATCH";
double server=0,next=0,grace=0,latest=0,expiry=0,start=0,now=(double)time(NULL);
if(!s->error&&(!timestamp(string(p,"server_time"),&server)||!timestamp(string(p,"next_validation_at"),&next)||!timestamp(string(p,"offline_grace_until"),&grace)||next<server||grace<server))s->error="INVALID_TIME";
latest=server;if(!s->error&&checkpoint){double previous;if(!timestamp(checkpoint,&previous))s->error="INVALID_TIME";else if(previous>latest)latest=previous;}
if(!s->error&&now+300<latest)s->error="CLOCK_ROLLBACK_DETECTED";if(!s->error&&!equals(p,"status","active"))s->error="LICENSE_NOT_ACTIVE";
yyjson_val *begin=yyjson_obj_get(p,"starts_at"),*end=yyjson_obj_get(p,"expires_at");
if(!s->error&&begin&&!yyjson_is_null(begin)){if(!timestamp(yyjson_get_str(begin),&start))s->error="INVALID_TIME";else if(now<start)s->error="LICENSE_NOT_STARTED";}
if(!s->error&&end&&!yyjson_is_null(end)){if(!timestamp(yyjson_get_str(end),&expiry))s->error="INVALID_TIME";else if(now>=expiry)s->error="LICENSE_EXPIRED";}
if(!s->error&&strcmp(source,"online")&&now>grace)s->error="OFFLINE_GRACE_EXPIRED";
if(!s->error){yyjson_val *features=yyjson_obj_get(p,"features");if(!yyjson_is_obj(features))s->error="INVALID_RESPONSE";else{*enabled=yyjson_is_true(yyjson_obj_get(features,"pdf_export"));yyjson_val *limit=yyjson_obj_get(features,"seat_count");if(yyjson_is_raw(limit)){const char *raw=yyjson_get_raw(limit);char *tail;errno=0;long long value=strtoll(raw,&tail,10);if(!errno&&tail==raw+yyjson_get_len(limit)&&value>=0&&value<=9007199254740991LL)*seats=value;}}}
yyjson_doc_free(doc);return s->error==NULL;
}
static void output(Session *s,int valid,const char *source,int signed_valid,int enabled,long long seats,const char *message) {
yyjson_mut_doc *doc=yyjson_mut_doc_new(NULL);yyjson_mut_val *root=yyjson_mut_obj(doc);yyjson_mut_doc_set_root(doc,root);yyjson_mut_obj_add_bool(doc,root,"valid",valid!=0);yyjson_mut_obj_add_str(doc,root,"source",source);yyjson_mut_obj_add_bool(doc,root,"signature_valid",signed_valid!=0);yyjson_mut_obj_add_bool(doc,root,"pdf_export_allowed",enabled!=0);if(seats>=0)yyjson_mut_obj_add_sint(doc,root,"seat_count",seats);else yyjson_mut_obj_add_null(doc,root,"seat_count");
if(s->error)yyjson_mut_obj_add_str(doc,root,"error_code",s->error);else yyjson_mut_obj_add_null(doc,root,"error_code");if(*s->request_id)yyjson_mut_obj_add_str(doc,root,"request_id",s->request_id);else yyjson_mut_obj_add_null(doc,root,"request_id");yyjson_mut_obj_add_str(doc,root,"message",message);char *raw=yyjson_mut_write(doc,0,NULL);if(raw)puts(raw);free(raw);yyjson_mut_doc_free(doc);
}
static int setup(Session *s) {
const char *app=getenv("KDP_APP_ID"),*base=getenv("KDP_BASE_URL"),*root=getenv("KDP_STATE_DIR"),*key=getenv("KDP_TRUSTED_KEY_FILE");if(!identifier(app,160)||!root||*root!='/'||!key||*key!='/')return 0;s->app=strdup(app);s->base=strdup(base?base:"https://licensing.kdpika.com/api/v1");if(!s->app||!s->base)return 0;
CURLU *url=curl_url();if(!url)return 0;CURLUcode code=curl_url_set(url,CURLUPART_URL,s->base,0);char *scheme=NULL,*host=NULL,*path=NULL,*value=NULL;int ok=code==CURLUE_OK&&curl_url_get(url,CURLUPART_SCHEME,&scheme,0)==CURLUE_OK&&curl_url_get(url,CURLUPART_HOST,&host,0)==CURLUE_OK&&curl_url_get(url,CURLUPART_PATH,&path,0)==CURLUE_OK;
int local=ok&&!strcmp(scheme,"http")&&(!strcmp(host,"127.0.0.1")||!strcmp(host,"localhost"))&&getenv("KDP_ALLOW_LOOPBACK")&&!strcmp(getenv("KDP_ALLOW_LOOPBACK"),"1");if(!ok||(strcmp(scheme,"https")&&!local)||strcmp(path,"/api/v1"))ok=0;
CURLUPart forbidden[]={CURLUPART_USER,CURLUPART_PASSWORD,CURLUPART_QUERY,CURLUPART_FRAGMENT};for(size_t i=0;i<4;i++)if(curl_url_get(url,forbidden[i],&value,0)==CURLUE_OK){ok=0;curl_free(value);value=NULL;}curl_free(scheme);curl_free(host);curl_free(path);curl_url_cleanup(url);if(!ok)return 0;
s->trusted=read_private(key,&s->trusted_size);yyjson_doc *trusted=s->trusted?parse(s->trusted,s->trusted_size):NULL;if(!trusted)return 0;ok=equals(yyjson_doc_get_root(trusted),"algorithm","Ed25519")&&equals(yyjson_doc_get_root(trusted),"encoding","base64url");yyjson_doc_free(trusted);if(!ok||!directory(root))return 0;
char *scope=join(s->base,s->app);if(!scope)return 0;char digest[65];hash(scope,digest);free(scope);char segment[24];snprintf(segment,sizeof segment,"/%.20s",digest);s->directory=join(root,segment);if(!s->directory||!directory(s->directory))return 0;
char *lock=join(s->directory,"/.lock");if(!lock)return 0;s->lock=open(lock,O_CREAT|O_RDWR|O_NOFOLLOW,0600);free(lock);if(s->lock<0||flock(s->lock,LOCK_EX|LOCK_NB))return 0;
char *identity=join(s->directory,"/installation.json");if(!identity)return 0;size_t size=0;char *raw=read_private(identity,&size);
if(!raw&&errno==ENOENT){unsigned char random[16];randombytes_buf(random,sizeof random);char hex[33];sodium_bin2hex(hex,sizeof hex,random,sizeof random);char body[100];snprintf(body,sizeof body,"{\"installation_id\":\"lmp-install-%.8s-%.4s-%.4s-%.4s-%.12s\"}",hex,hex+8,hex+12,hex+16,hex+20);if(!write_atomic(s,"/installation.json",body)){free(identity);return 0;}raw=read_private(identity,&size);}free(identity);yyjson_doc *doc=raw?parse(raw,size):NULL;free(raw);if(!doc)return 0;const char *id=string(yyjson_doc_get_root(doc),"installation_id");if(identifier(id,48)&&strlen(id)==48&&!strncmp(id,"lmp-install-",12))s->installation=strdup(id);yyjson_doc_free(doc);return s->installation!=NULL;
}
static int execute(Session *s,const char *action) {
char *file=join(s->directory,"/session.json");size_t size=0;struct stat stat;int exists=file&&lstat(file,&stat)==0;char *raw=exists?read_private(file,&size):NULL;free(file);if(exists&&!raw)return 2;yyjson_doc *doc=raw?parse(raw,size):NULL;free(raw);if(exists&&!doc)return 2;
yyjson_val *state=doc?yyjson_doc_get_root(doc):NULL;const char *token=state?string(state,"token"):NULL,*checkpoint=state?string(state,"checkpoint"):NULL,*cached=state?string(state,"envelope"):NULL;char *saved_token=token?strdup(token):NULL,*saved_time=checkpoint?strdup(checkpoint):NULL,*envelope=cached?strdup(cached):NULL;yyjson_doc_free(doc);
int result=2,enabled=0;long long seats=-1;char *clock=NULL;
if(exists&&(!saved_token||strncmp(saved_token,"act_",4)||!identifier(saved_token,256)||!saved_time))goto finish;
if(!strcmp(action,"status")){char identity[65];hash(s->installation,identity);printf("{\"activated\":%s,\"installation_hash\":\"%s\",\"storage_security\":\"permission-protected-file\"}\n",exists?"true":"false",identity);result=0;goto finish;}
if(!strcmp(action,"activate")){
const char *license=getenv("KDP_LICENSE_KEY"),*email=getenv("KDP_CUSTOMER_EMAIL");if(!safe_text(license,200)||strncmp(license,"TEST",4)||(email&&*email&&!safe_text(email,320)))goto finish;if(exists){s->error="ALREADY_ACTIVATED";goto refused;}
yyjson_mut_doc *body=yyjson_mut_doc_new(NULL);yyjson_mut_val *value=yyjson_mut_obj(body);yyjson_mut_doc_set_root(body,value);yyjson_mut_obj_add_str(body,value,"license_key",license);yyjson_mut_obj_add_str(body,value,"device_id",s->installation);yyjson_mut_obj_add_str(body,value,"device_name","Native REST example");yyjson_mut_obj_add_str(body,value,"platform","posix");yyjson_mut_obj_add_str(body,value,"app_version","1.0.0");if(email&&*email)yyjson_mut_obj_add_str(body,value,"email",email);char *text=yyjson_mut_write(body,0,NULL);yyjson_doc *reply=text?request(s,"/licenses/activate",text,NULL):NULL;free(text);yyjson_mut_doc_free(body);if(!reply)goto refused;
yyjson_val *response=yyjson_doc_get_root(reply);const char *new_token=string(response,"activation_token");if(!identifier(new_token,256)||strncmp(new_token,"act_",4)){s->error="INVALID_RESPONSE";yyjson_doc_free(reply);goto refused;}saved_token=strdup(new_token);envelope=dump(yyjson_obj_get(response,"license"));yyjson_doc_free(reply);if(!envelope){s->error="INVALID_RESPONSE";goto refused;}
if(!verify(s,envelope,"online",NULL,&enabled,&seats)){const char *failure=s->error;yyjson_doc *released=request(s,"/licenses/deactivate","{}",saved_token);yyjson_doc_free(released);s->error=failure;goto refused;}
clock=now_string();if(!clock||!save(s,saved_token,envelope,clock))goto finish;output(s,1,"online",1,enabled,seats,"Your license is ready.");result=0;goto finish;
}
if(!exists){s->error="NOT_ACTIVATED";goto refused;}
if(!strcmp(action,"deactivate")){yyjson_doc *reply=request(s,"/licenses/deactivate","{}",saved_token);if(!reply)goto refused;int ok=yyjson_is_true(yyjson_obj_get(yyjson_doc_get_root(reply),"deactivated"));yyjson_doc_free(reply);if(!ok){s->error="INVALID_RESPONSE";goto refused;}clear(s);output(s,1,"online",0,0,-1,"This installation has been deactivated.");result=0;goto finish;}
if(!strcmp(action,"check")&&envelope){if(verify(s,envelope,"cache",saved_time,&enabled,&seats)){yyjson_doc *env=parse(envelope,strlen(envelope));double next;int use=env&×tamp(string(yyjson_obj_get(yyjson_doc_get_root(env),"payload"),"next_validation_at"),&next)&&(double)time(NULL)<next;yyjson_doc_free(env);if(use){clock=now_string();if(!clock||!save(s,saved_token,envelope,clock))goto finish;output(s,1,"cache",1,enabled,seats,"Your license is ready.");result=0;goto finish;}}
else{free(envelope);envelope=NULL;if(!save(s,saved_token,NULL,saved_time))goto finish;}}
{yyjson_doc *reply=request(s,"/licenses/validate","{}",saved_token);const char *source="online";
if(!reply){if(s->error&&!strcmp(s->error,"NETWORK_ERROR")){if(!envelope){s->error="OFFLINE_CACHE_MISSING";goto refused;}source="offline";}
else{if(s->error&&(!strcmp(s->error,"INVALID_TOKEN")||!strcmp(s->error,"DEVICE_MISMATCH")||!strcmp(s->error,"LICENSE_REVOKED")||!strcmp(s->error,"LICENSE_EXPIRED")||!strcmp(s->error,"LICENSE_CANCELLED")))clear(s);else if(!save(s,saved_token,NULL,saved_time))goto finish;goto refused;}}
else{free(envelope);envelope=dump(yyjson_obj_get(yyjson_doc_get_root(reply),"license"));yyjson_doc_free(reply);if(!envelope){s->error="INVALID_RESPONSE";goto refused;}}
if(!verify(s,envelope,source,saved_time,&enabled,&seats)){if(!strcmp(source,"online"))save(s,saved_token,NULL,saved_time);goto refused;}clock=now_string();if(!clock||!save(s,saved_token,envelope,clock))goto finish;output(s,1,source,1,enabled,seats,"Your license is ready.");result=0;goto finish;}
refused:output(s,0,"local",0,0,-1,"Your license could not be confirmed. Connect and try again, or contact your software provider.");result=1;
finish:free(saved_token);free(saved_time);free(envelope);free(clock);return result;
}
int kdpika_session_main(int argc,char **argv){Session s={0};s.lock=-1;int result=2;if(argc==2&&(!strcmp(argv[1],"status")||!strcmp(argv[1],"activate")||!strcmp(argv[1],"check")||!strcmp(argv[1],"validate")||!strcmp(argv[1],"deactivate"))&&sodium_init()>=0&&curl_global_init(CURL_GLOBAL_DEFAULT)==CURLE_OK&&setup(&s))result=execute(&s,argv[1]);
if(result==2)fputs("Check application settings and private storage. Contact your software provider if the problem continues.\n",stderr);if(s.lock>=0)close(s.lock);free(s.app);free(s.base);free(s.directory);free(s.installation);free(s.trusted);curl_global_cleanup();return result;}
The complete project also contains the exact verification helper. Its source and all positive/negative vectors are available in the security verifier archive. Derived reusable files remove only the original standalone CLI entry point/imports that became unused; they do not change the signature algorithm or canonicalization.
Run the lifecycle
Use ./session followed by exactly one action. Preserve the same App ID, state root and origin across processes.
- Run status. Expected: activated=false for a new namespace and only an installation hash/storage category. Token presence is not a license validity decision.
- Set a Sandbox key and matching email, then run activate. Expected: valid=true and signature_valid=true, or a safe refusal. An existing token is not silently replaced.
- Run check. Expected: eligible authenticated cache before next_validation_at, otherwise an online validation attempt.
- Run validate. Expected: an online attempt; only transport failure can use an eligible verified offline cache. Explicit HTTP rejection never grants offline permission.
- Run deactivate. Expected: server-confirmed release, token/cache removal and no feature grant. valid=true here means the release operation succeeded, not permission to run a protected feature.
- Run status again. Expected: activated=false and the same installation hash. Cumulative activation_count is not reset.
Exit0 means accepted operation/status, exit1 means refusal or connectivity denial, exit2 means unsafe configuration/state or unexpected failure. Output is safe decision metadata: never raw keys, tokens, Authorization headers or signed customer payloads.
API contract and signature verification
Activation sends X-App-ID and X-Environment: SANDBOX plus license_key/device_id and optional matching email. Validation uses Bearer act_ and X-Installation-ID. Deactivation releases that token. See the endpoint reference for full field/response/error tables.
The response parser preserves numeric lexemes, rejects duplicate/invalid JSON through the shared tested verifier, and authenticates the exact envelope. Only a verified payload enters policy. Key ID is derived from the independently trusted raw public key; wrong algorithm/key/signature is rejected. Never repair a mismatch by stripping signed fields or using JSON.stringify-style numeric normalization.
Binding, features and fail-closed policy
The client requires its actual product App ID, environment=sandbox, SHA-256 installation binding and active status. It checks starts_at when present, expiry when nonnull, signed validation/grace dates and the last accepted checkpoint. Invalid data, status or policy is a refusal even if the payload carries a true feature.
pdf_export is allowed only for the boolean true on a fully accepted result. seat_count is exposed only as a nonnegative integer bounded by the shared safe-integer policy; otherwise it stays null. These example feature names must match your own product configuration. Constructing a result or reading token presence never authenticates a license.
Offline grace and time checks
Before next_validation_at, check can return a verified cache. After that instant it validates online. Only a transport failure may use the existing signed state within offline_grace_until and expires_at. An explicit HTTP refusal, bad signature, malformed success or binding mismatch cannot extend access. Selected token/revocation/device/expiry/cancellation refusals clear the token; other refusals clear cached authorization.
The last accepted checkpoint and signed server_time bound rollback with300 seconds tolerance. Grace is based on its signed deadline, not added to next_validation_at. Validating online does not promise permission if time/status/binding rules fail. No constructor flag creates an extra offline extension.
These strict sample policies are not changes to existing SDK behavior. A person controlling the host can patch the program or restore full snapshots. Use server-side authorization for high-value online actions and do not promise absolute local anti-tamper protection.
Private storage, concurrency and recovery
The installation ID is random, persistent and independent of MAC/serial/hardware fingerprints. A namespaced private session stores the activation token, lossless signed envelope and checkpoint. Writes are atomic and permissions are owner-only where supported; this is not automatic encryption or an OS-vault certification.
The program serializes one namespace. Java/Kotlin, Dart, Rust and the POSIX native core use an OS lock on a stable file; Go/Ruby/Swift use exclusive lock-file ownership and require manual reconciliation after a crash. Never delete an active lock or regenerate identity to avoid a refusal. A new identity can consume another cumulative activation. Keep separate namespaces for unrelated buyers/tenants.
Review secure token storage for native vault adapters. POSIX modes do not replace Windows ACL review. Do not put state under public_html, bundle a populated cache, or expose raw SDK/client methods to a renderer/browser.
Errors and uncertain mutations
The client accepts only bounded safe error/request identifiers and shows plain user messages. Unknown errors fail closed. No automatic mutation retry is supplied. A lost activation response may already have consumed a server activation; reconcile before retrying. If a returned new token fails signature/policy acceptance, the client attempts best-effort release, which can itself remain uncertain.
Read the complete server error table. Local example identifiers such as BINDING_MISMATCH, ALREADY_ACTIVATED, SIGNATURE_INVALID and INVALID_TIME are client policy results, not invented server emissions. Never show raw stack traces, private paths or customer payloads to end users.
Packaging and framework integration
A native C++17 executable links the full C ABI session core. The core is compiled as C11, not compiled accidentally as C++; no Python/Node bridge or hidden licensing service is involved. C and C++ intentionally share state format/namespace. POSIX storage/clock APIs are not a claim of native Windows support.
Keep stable state outside executable extraction/install directories. Include the exact verification modules and required native/runtime libraries; never embed a developer key or populated token/cache. A web route still needs application authentication, tenant authorization and CSRF protection. A desktop renderer gets narrow safe decisions only, not arbitrary file paths or token access.
Re-test real activation, cache reload, offline loss, explicit refusal and release after bundling or updating dependencies. macOS native source execution is not proof that every operating-system package, signing/notarization setup or framework UI has passed.
Recorded verification and limits
The exact native runtime executes the real isolated Sandbox lifecycle and21 negative/positive checks: wrong email, stable cross-process identity, cache without HTTP, online validation, injected network loss, modified offline cache, rollback, explicit HTTP503, modified signed response, no-follow307, recovery, release and unsafe setup guards. Refusal/outage/redirect injections are an explicit loopback test forwarder, not a claimed production outage.
Build evidence records actual toolchain/dependency hashes. All ten fresh project archives build and the complete native clients execute real Sandbox lifecycles. All23 published signature vectors pass in every REST language. Signature authenticity is separate from current license authorization. Native Windows/Linux/mobile GUI execution is not inferred from this macOS run.
Expected result
Your application authenticates signed state, applies binding/time/status policy and enables only verified features. A refusal or successful seat release cannot accidentally unlock a feature.
Common mistakes
- Using the internal product ID instead of the Public App ID.
- Losing numeric tokens before verifying the signature.
- Treating a true feature or HTTP success as a validity proof.
- Sharing one installation cache between unrelated tenants or deleting identity on failure.
Security notes
Keep HTTPS verification, independent key trust and fail-closed policy. This example does not change the platform contract, process payments or grant universal anti-tamper protection. Preserve real API/SDK boundaries and keep credentials off browser/renderer surfaces.
Troubleshooting
| Symptom | Cause | Action |
|---|---|---|
| Exit2 at startup | Unsafe URL, missing private path, bad identity or busy lock | Correct trusted configuration; do not expose raw diagnostics to users |
| INVALID_LICENSE | Key/email/App ID/environment mismatch | Use the actual matching Sandbox values |
| SIGNATURE_INVALID | Wrong key, modified state or changed canonical bytes | Deny access and run the published vectors |
| OFFLINE_CACHE_MISSING | No accepted signed state remains | Reconnect and validate; never manufacture a cache |
| CLOCK_ROLLBACK_DETECTED | System time behind trusted time | Correct the device clock and reconnect |
| Release uncertain | Lost response or network/server refusal | Preserve identity and reconcile the seat with your software provider |
Next steps
Review offline mode, signed responses and API errors.