summaryrefslogtreecommitdiff
path: root/appl/cmd/palm/desklink.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/palm/desklink.b')
-rw-r--r--appl/cmd/palm/desklink.b843
1 files changed, 843 insertions, 0 deletions
diff --git a/appl/cmd/palm/desklink.b b/appl/cmd/palm/desklink.b
new file mode 100644
index 00000000..23dafaa7
--- /dev/null
+++ b/appl/cmd/palm/desklink.b
@@ -0,0 +1,843 @@
+implement Palmdb, Desklink;
+
+#
+# Palm Desk Link Protocol (DLP)
+#
+# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+# Request and response formats were extracted from
+# include/Core/System/DLCommon.h in the PalmOS SDK-5
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "daytime.m";
+ daytime: Daytime;
+ Tm: import daytime;
+
+include "palm.m";
+ palm: Palm;
+ DBInfo, Record, Resource, id2s, s2id, get2, put2, get4, put4, gets, argsize, packargs, unpackargs: import palm;
+
+include "timers.m";
+
+include "desklink.m";
+
+Maxrecbytes: con 16rFFFF;
+
+# operations defined by Palm
+
+T_ReadUserInfo, T_WriteUserInfo, T_ReadSysInfo, T_GetSysDateTime,
+T_SetSysDateTime, T_ReadStorageInfo, T_ReadDBList, T_OpenDB, T_CreateDB,
+T_CloseDB, T_DeleteDB, T_ReadAppBlock, T_WriteAppBlock, T_ReadSortBlock,
+T_WriteSortBlock, T_ReadNextModifiedRec, T_ReadRecord, T_WriteRecord,
+T_DeleteRecord, T_ReadResource, T_WriteResource, T_DeleteResource,
+T_CleanUpDatabase, T_ResetSyncFlags, T_CallApplication, T_ResetSystem,
+T_AddSyncLogEntry, T_ReadOpenDBInfo, T_MoveCategory, T_ProcessRPC,
+T_OpenConduit, T_EndOfSync, T_ResetDBIndex, T_ReadRecordIDList,
+# DLP 1.1 functions
+T_ReadNextRecInCategory, T_ReadNextModifiedRecInCategory,
+T_ReadAppPreference, T_WriteAppPreference, T_ReadNetSyncInfo,
+T_WriteNetSyncInfo, T_ReadFeature,
+# DLP 1.2 functions
+T_FindDB, T_SetDBInfo,
+# DLP 1.3 functions
+T_LoopBackTest, T_ExpSlotEnumerate, T_ExpCardPresent, T_ExpCardInfo: con 16r10+iota;
+# then there's a group of VFS requests that we don't currently use
+
+Response: con 16r80;
+
+Maxname: con 32;
+
+A1, A2: con Palm->ArgIDbase+iota; # argument IDs have request-specific interpretation (most have only one ID)
+
+Timeout: con 30; # seconds time out used by Palm's headers
+srvfd: ref Sys->FD;
+selfdb: Palmdb;
+
+errorlist := array [] of {
+ "no error",
+ "general Pilot system error",
+ "unknown request",
+ "out of dynamic memory on device",
+ "invalid parameter",
+ "not found",
+ "no open databases",
+ "database already open",
+ "too many open databases",
+ "database already exists",
+ "cannot open database",
+ "record previously deleted",
+ "record busy",
+ "operation not supported",
+ "unexpected error (ErrUnused1)",
+ "read only object",
+ "not enough space",
+ "size limit exceeded",
+ "sync cancelled",
+ "bad arg wrapper",
+ "argument missing",
+ "bad argument size",
+};
+
+Eshort: con "desklink protocol: response too short";
+
+debug := 0;
+
+connect(srvfile: string): (Palmdb, string)
+{
+ sys = load Sys Sys->PATH;
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ return (nil, sys->sprint("can't load %s: %r", Daytime->PATH));
+ srvfd = sys->open(srvfile, Sys->ORDWR);
+ if(srvfd == nil)
+ return (nil, sys->sprint("can't open %s: %r", srvfile));
+ selfdb = load Palmdb "$self";
+ if(selfdb == nil)
+ return (nil, sys->sprint("can't load self as Palmdb: %r"));
+ return (selfdb, nil);
+}
+
+hangup(): int
+{
+ srvfd = nil;
+ return 0;
+}
+
+#
+# set the system error string
+#
+e(s: string): string
+{
+ if(s != nil){
+ s = "palm: "+s;
+ sys->werrstr(s);
+ }
+ return s;
+}
+
+#
+# sent before each conduit is opened by the desktop,
+# apparently to detect a pending cancel request (on the device)
+#
+OpenConduit(): int
+{
+ return nexec(T_OpenConduit, A1, nil);
+}
+
+#
+# end of sync on desktop
+#
+EndOfSync(status: int): int
+{
+ req := array[2] of byte;
+ put2(req, status);
+ return nexec(T_EndOfSync, A1, req);
+}
+
+ReadSysInfo(): ref SysInfo
+{
+ if((reply := dexec(T_ReadSysInfo, A1, nil, 14)) == nil)
+ return nil;
+ s := ref SysInfo;
+ s.romversion = get4(reply);
+ s.locale = get4(reply[4:]);
+ l := int reply[9]; # should be at most 4 apparently?
+ s.product = gets(reply[10:10+l]);
+ return s;
+}
+
+ReadSysInfoVer(): (int, int, int)
+{
+ req := array[4] of byte;
+ put2(req, 1); # major version
+ put2(req, 2); # minor version
+ if((reply := dexec(T_ReadSysInfo, A2, req, 12)) == nil)
+ return (0, 0, 0);
+ return (get4(reply), get4(reply[4:]), get4(reply[8:]));
+}
+
+ReadUserInfo(): ref User
+{
+ if((reply := dexec(T_ReadUserInfo, 0, nil, 30)) == nil)
+ return nil;
+ u := ref User;
+ u.userid = get4(reply);
+ u.viewerid = get4(reply[4:]);
+ u.lastsyncpc = get4(reply[8:]);
+ u.succsynctime = getdate(reply[12:]);
+ u.lastsynctime = getdate(reply[20:]);
+ userlen := int reply[28];
+ pwlen := int reply[29];
+ u.username = gets(reply[30:30+userlen]);
+ u.password = array[pwlen] of byte;
+ u.password[0:] = reply[30+userlen:30+userlen+pwlen];
+ return u;
+}
+
+WriteUserInfo(u: ref User, flags: int): int
+{
+ req := array[22+Maxname] of byte;
+ put4(req, u.userid);
+ put4(req[4:], u.viewerid);
+ put4(req[8:], u.lastsyncpc);
+ putdate(req[12:], u.lastsynctime);
+ req[20] = byte flags;
+ l := puts(req[22:], u.username);
+ req[21] = byte l;
+ return nexec(T_WriteUserInfo, A1, req[0:22+l]);
+}
+
+GetSysDateTime(): int
+{
+ if((reply := dexec(T_GetSysDateTime, A1, nil, 8)) == nil)
+ return -1;
+ return getdate(reply);
+}
+
+SetSysDateTime(time: int): int
+{
+ return nexec(T_SetSysDateTime, A1, putdate(array[8] of byte, time));
+}
+
+ReadStorageInfo(cardno: int): (array of ref CardInfo, int, string)
+{
+ req := array[2] of byte;
+ req[0] = byte cardno;
+ req[1] = byte 0;
+ (reply, err) := rexec(T_ReadStorageInfo, A1, req, 30);
+ if(reply == nil)
+ return (nil, 0, err);
+ nc := int reply[3];
+ if(nc <= 0)
+ return (nil, 0, nil);
+ more := int reply[1] != 0;
+ a := array[nc] of ref CardInfo;
+ p := 4;
+ for(i:=0; i<nc; i++){
+ nb: int;
+ (a[i], nb) = unpackcard(reply[p:]);
+ p += nb;
+ }
+ return (a, more, nil);
+}
+
+unpackcard(a: array of byte): (ref CardInfo, int)
+{
+ nb := int a[0]; # total size of this card's info
+ c := ref CardInfo;
+ c.cardno = int a[1];
+ c.version = get2(a[2:]);
+ c.creation = getdate(a[4:]);
+ c.romsize = get4(a[12:]);
+ c.ramsize = get4(a[16:]);
+ c.ramfree = get4(a[20:]);
+ l1 := int a[24] + 26;
+ l2 := int a[25];
+ c.name = gets(a[26:l1]);
+ c.maker = gets(a[l1:l1+l2]);
+ return (c, nb);
+}
+
+ReadDBCount(cardno: int): (int, int)
+{
+ req := array[2] of byte;
+ req[0] = byte cardno;
+ req[1] = byte 0;
+ if((reply := dexec(T_ReadStorageInfo, A2, req, 20)) == nil)
+ return (-1, -1);
+ return (get2(req[0:]), get2(req[2:]));
+}
+
+unpackdbinfo(a: array of byte): (ref DBInfo, int)
+{
+ size := int a[0];
+ misc := int a[1];
+ info := ref DBInfo;
+ info.attr = get2(a[2:]);
+ info.dtype = id2s(get4(a[4:]));
+ info.creator = id2s(get4(a[8:]));
+ info.version = get2(a[12:]);
+ info.modno = get4(a[14:]);
+ info.ctime = getdate(a[18:]);
+ info.mtime = getdate(a[26:]);
+ info.btime = getdate(a[34:]);
+ info.index = get2(a[42:]);
+ if(size > len a)
+ size = len a;
+ info.name = gets(a[44:size]);
+ return (info, size);
+}
+
+ReadDBList(cardno: int, flags: int, start: int): (array of ref DBInfo, int, string)
+{
+ req := array[4] of byte;
+ req[0] = byte (flags | DBListMultiple);
+ req[1] = byte cardno;
+ put2(req[2:], start);
+ (reply, err) := rexec(T_ReadDBList, A1, req, 48);
+ if(reply == nil || int reply[3] == 0)
+ return (nil, 0, err);
+ # lastindex[2] flags[1] actcount[1]
+ # flags is 16r80 => more to list
+ more := (reply[2] & byte 16r80) != byte 0;
+ dbs := array[int reply[3]] of ref DBInfo;
+#sys->print("ndb=%d more=%d lastindex=#%4.4ux\n", len dbs, more, get2(reply));
+ a := reply[4:];
+ for(i := 0; i < len dbs; i++){
+ (db, n) := unpackdbinfo(a);
+ dbs[i] = db;
+ a = a[n:];
+ }
+ return (dbs, more, nil);
+}
+
+matchdb(cardno: int, flag: int, start: int, dbname: string, dtype: string, creator: string): (ref DBInfo, int)
+{
+ for(;;){
+ (dbs, more, err) := ReadDBList(cardno, flag, start);
+ if(dbs == nil)
+ break;
+ for(i := 0; i < len dbs; i++){
+ info := dbs[i];
+ if((dbname == nil || info.name == dbname) &&
+ (dtype == nil || info.dtype == dtype) &&
+ (creator == nil || info.creator == creator))
+ return (info, info.index);
+ start = info.index+1;
+ }
+ }
+ return (nil, 0);
+}
+
+
+FindDBInfo(cardno: int, start: int, dbname: string, dtype: string, creator: string): ref DBInfo
+{
+ if(start < 16r1000) {
+ (info, i) := matchdb(cardno, 16r80, start, dbname, dtype, creator);
+ if(info != nil)
+ return info;
+ }
+ (info, i) := matchdb(cardno, 16r40, start&~16r1000, dbname, dtype, creator);
+ if(info != nil)
+ info.index |= 16r1000;
+ return info;
+}
+
+DeleteDB(name: string): int
+{
+ (cardno, dbname) := parsedb(name);
+ req := array[2+Maxname] of byte;
+ req[0] = byte cardno;
+ req[1] = byte 0;
+ n := puts(req[2:], dbname);
+ return nexec(T_DeleteDB, A1, req[0:2+n]);
+}
+
+ResetSystem(): int
+{
+ return nexec(T_ResetSystem, 0, nil);
+}
+
+CloseDB_All(): int
+{
+ return nexec(T_CloseDB, A2, nil);
+}
+
+AddSyncLogEntry(entry: string): int
+{
+ req := array[256] of byte;
+ n := puts(req, entry);
+ return nexec(T_AddSyncLogEntry, A1, req[0:n]);
+}
+
+#
+# this implements a Palmdb->DB directly accessed using the desklink protocol
+#
+
+init(m: Palm): string
+{
+ palm = m;
+ return nil;
+}
+
+#
+# syntax is [cardno/]dbname
+# where cardno defaults to 0
+#
+parsedb(name: string): (int, string)
+{
+ (nf, flds) := sys->tokenize(name, "/");
+ if(nf > 1)
+ return (int hd flds, hd tl flds);
+ return (0, name);
+}
+
+DB.open(name: string, mode: int): (ref DB, string)
+{
+ (cardno, dbname) := parsedb(name);
+ req := array[2+Maxname] of byte;
+ req[0] = byte cardno;
+ req[1] = byte mode;
+ n := puts(req[2:], dbname);
+ (reply, err) := rexec(T_OpenDB, A1, req[0:2+n], 1);
+ if(reply == nil)
+ return (nil, err);
+ db := ref DB;
+ db.x = int reply[0];
+ inf := db.stat();
+ if(inf == nil)
+ return (nil, sys->sprint("can't get DBInfo: %r"));
+ db.attr = inf.attr; # mainly need to know whether it's Fresource or not
+ return (db, nil);
+}
+
+DB.create(name: string, nil: int, nil: int, inf: ref DBInfo): (ref DB, string)
+{
+ (cardno, dbname) := parsedb(name);
+ req := array[14+Maxname] of byte;
+ put4(req, s2id(inf.creator));
+ put4(req[4:], s2id(inf.dtype));
+ req[8] = byte cardno;
+ req[9] = byte 0;
+ put2(req[10:], inf.attr);
+ put2(req[12:], inf.version);
+ n := puts(req[14:], dbname);
+ (reply, err) := rexec(T_CreateDB, A1, req[0:14+n], 1);
+ if(reply == nil)
+ return (nil, err);
+ db := ref DB;
+ db.x = int reply[0];
+ db.attr = inf.attr;
+ return (db, nil);
+}
+
+DB.stat(db: self ref DB): ref DBInfo
+{
+ (reply, err) := rexec(T_FindDB, A2, array[] of {byte 16r80, byte db.x}, 54);
+ if(err != nil)
+ return nil;
+ return unpackdbinfo(reply[10:]).t0;
+}
+
+DB.wstat(db: self ref DB, inf: ref DBInfo, flags: int)
+{
+ # TO DO
+}
+
+DB.close(db: self ref DB): string
+{
+ return rexec(T_CloseDB, A1, array[] of {byte db.x}, 0).t1;
+}
+
+DB.records(db: self ref DB): ref PDB
+{
+ if(db.attr & Palm->Fresource){
+ sys->werrstr("not a database file");
+ return nil;
+ }
+ return ref PDB(db);
+}
+
+DB.resources(db: self ref DB): ref PRC
+{
+ if((db.attr & Palm->Fresource) == 0){
+ sys->werrstr("not a resource file");
+ return nil;
+ }
+ return ref PRC(db);
+}
+
+DB.readidlist(db: self ref DB, sort: int): array of int
+{
+ req := array[6] of byte;
+ req[0] = byte db.x;
+ if(sort)
+ req[1] = byte 16r80;
+ else
+ req[1] = byte 0;
+ put2(req[2:], 0);
+ put2(req[4:], -1);
+ p := dexec(T_ReadRecordIDList, A1, req, 2);
+ if(p == nil)
+ return nil;
+ ret := get2(p);
+ ids := array[ret] of int;
+ p = p[8:];
+ for (i := 0; i < ret; p = p[4:])
+ ids[i++] = get4(p);
+ return ids;
+}
+
+DB.nentries(db: self ref DB): int
+{
+ if((reply := dexec(T_ReadOpenDBInfo, A1, array[] of {byte db.x}, 2)) == nil)
+ return -1;
+ return get2(reply);
+}
+
+DB.rdappinfo(db: self ref DB): (array of byte, string)
+{
+ req := array[6] of byte;
+ req[0] = byte db.x;
+ req[1] = byte 0;
+ put2(req[2:], 0); # offset
+ put2(req[4:], -1); # to end
+ (reply, err) := rexec(T_ReadAppBlock, A1, req, 2);
+ if(reply == nil)
+ return (nil, err);
+ if(get2(reply) < len reply-2)
+ return (nil, "short reply");
+ return (reply[2:], nil);
+}
+
+DB.wrappinfo(db: self ref DB, data: array of byte): string
+{
+ req := array[4 + len data] of byte;
+ req[0] = byte db.x;
+ req[1] = byte 0;
+ put2(req[2:], len data);
+ req[4:] = data;
+ return rexec(T_WriteAppBlock, A1, req, 0).t1;
+}
+
+DB.rdsortinfo(db: self ref DB): (array of int, string)
+{
+ req := array[6] of byte;
+ req[0] = byte db.x;
+ req[1] = byte 0;
+ put2(req[2:], 0);
+ put2(req[4:], -1);
+ (reply, err) := rexec(T_ReadSortBlock, A1, req, 2);
+ if(reply == nil)
+ return (nil, err);
+ n := len reply;
+ a := reply[2:n];
+ n = (n-2)/2;
+ s := array[n] of int;
+ for(i := 0; i < n; i++)
+ s[i] = get2(a[i*2:]);
+ return (s, nil);
+}
+
+DB.wrsortinfo(db: self ref DB, s: array of int): string
+{
+ n := len s;
+ req := array[4+2*n] of byte;
+ req[0] = byte db.x;
+ req[1] = byte 0;
+ put2(req[2:], 2*n);
+ for(i := 0; i < n; i++)
+ put2(req[2+i*2:], s[i]);
+ return rexec(T_WriteSortBlock, A1, req, 0).t1;
+}
+
+PDB.purge(db: self ref PDB): string
+{
+ return rexec(T_CleanUpDatabase, A1, array[] of {byte db.db.x}, 0).t1;
+}
+
+DB.resetsyncflags(db: self ref DB): string
+{
+ return rexec(T_ResetSyncFlags, A1, array[] of {byte db.x}, 0).t1;
+}
+
+#
+# .pdb and other data base files
+#
+
+PDB.read(db: self ref PDB, index: int): ref Record
+{
+ req := array[8] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put2(req[2:], index);
+ put2(req[4:], 0); # offset
+ put2(req[6:], Maxrecbytes);
+ return unpackrec(dexec(T_ReadRecord, A2, req, 10)).t0;
+}
+
+PDB.readid(db: self ref PDB, id: int): (ref Record, int)
+{
+ req := array[10] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], id);
+ put2(req[6:], 0); # offset
+ put2(req[8:], Maxrecbytes);
+ return unpackrec(dexec(T_ReadRecord, A1, req, 10));
+}
+
+PDB.write(db: self ref PDB, r: ref Record): string
+{
+ req := array[8+len r.data] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], r.id);
+ req[6] = byte (r.attr & Palm->Rsecret);
+ req[7] = byte r.cat;
+ req[8:] = r.data;
+ (reply, err) := rexec(T_WriteRecord, A1, req, 4);
+ if(reply == nil)
+ return err;
+ if(r.id == 0)
+ r.id = get4(reply);
+ return nil;
+}
+
+PDB.movecat(db: self ref PDB, from: int, tox: int): string
+{
+ req := array[4] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte from;
+ req[2] = byte tox;
+ req[3] = byte 0;
+ return rexec(T_MoveCategory, A1, req, 0).t1;
+}
+
+PDB.resetnext(db: self ref PDB): int
+{
+ return nexec(T_ResetDBIndex, A1, array[] of {byte db.db.x});
+}
+
+PDB.readnextmod(db: self ref PDB): (ref Record, int)
+{
+ return unpackrec(dexec(T_ReadNextModifiedRec, A1, array[] of {byte db.db.x}, 10));
+}
+
+PDB.delete(db: self ref PDB, id: int): string
+{
+ req := array[6] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], id);
+ return rexec(T_DeleteRecord, A1, req, 0).t1;
+}
+
+PDB.deletecat(db: self ref PDB, cat: int): string
+{
+ return rexec(T_DeleteRecord, A1, array[] of {byte db.db.x, byte 16r40, 2 to 6 => byte 0, 7=>byte cat}, 0).t1;
+}
+
+PDB.truncate(db: self ref PDB): string
+{
+ return rexec(T_DeleteRecord, A1, array[] of {byte db.db.x, byte 16r80, 2 to 7 => byte 0}, 0).t1;
+}
+
+#
+# .prc resource files
+#
+
+PRC.write(db: self ref PRC, r: ref Resource): string
+{
+ req := array[8+len r.data] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], r.name);
+ put2(req[6:], r.id);
+ put2(req[8:], len r.data);
+ return rexec(T_WriteResource, A1, req, 0).t1;
+}
+
+PRC.delete(db: self ref PRC, name: int, id: int): string
+{
+ req := array[8] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], name);
+ put4(req[6:], id);
+ return rexec(T_DeleteResource, A1, req, 0).t1;
+}
+
+PRC.readtype(db: self ref PRC, name: int, id: int): (ref Resource, int)
+{
+ req := array[12] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put4(req[2:], name);
+ put2(req[6:], id);
+ put2(req[8:], 0); # Offset into record
+ put2(req[10:], Maxrecbytes);
+ return unpackresource(dexec(T_ReadResource, A2, req, 10));
+}
+
+PRC.truncate(db: self ref PRC): string
+{
+ return rexec(T_DeleteResource, A1, array[] of {byte db.db.x, byte 16r80, 2 to 7 => byte 0}, 0).t1;
+}
+
+PRC.read(db: self ref PRC, index: int): ref Resource
+{
+ req := array[8] of byte;
+ req[0] = byte db.db.x;
+ req[1] = byte 0;
+ put2(req[2:], index);
+ put2(req[4:], 0); # offset
+ put2(req[6:], Maxrecbytes);
+ return unpackresource(dexec(T_ReadResource, A1, req, 12)).t0;
+}
+
+#
+# DL protocol
+#
+# request
+# id: byte # operation
+# argc: byte # arg count
+# args: byte[]
+#
+# response
+# id: byte # cmd|16r80
+# argc: byte # argc response arguments follow header
+# error: byte[2] # error code
+# args: byte[]
+#
+# args wrapped by Palm->packargs etc.
+#
+
+#
+# RPC exchange with device
+#
+rpc(req: array of byte): (array of (int, array of byte), string)
+{
+ if(sys->write(srvfd, req, len req) != len req)
+ return (nil, sys->sprint("link: %r"));
+ reply := array[65536] of byte;
+ nb := sys->read(srvfd, reply, len reply);
+ if(nb == 0)
+ return (nil, "link: hangup");
+ if(nb < 0)
+ return (nil, sys->sprint("link: %r"));
+ r := int reply[0];
+ if((r & Response) == 0)
+ return (nil, e(sys->sprint("received request #%2.2x not response", r)));
+ if(r != (Response|int req[0]))
+ return (nil, e(sys->sprint("wrong response #%x", r)));
+ if(nb < 4)
+ return (nil, e(Eshort));
+ rc := get2(reply[2:]);
+ if(rc != 0){
+ if(rc < 0 || rc >= len errorlist)
+ return (nil, e(sys->sprint("unknown error %d", rc)));
+ return (nil, e(errorlist[rc]));
+ }
+ argc := int reply[1]; # count of following arguments
+ if(argc == 0)
+ return (nil, nil);
+ return unpackargs(argc, reply[4:nb]);
+}
+
+rexec(cmd: int, argid: int, arg: array of byte, minlen: int): (array of byte, string)
+{
+ args: array of (int, array of byte);
+ if(arg != nil)
+ args = array[] of {(argid, arg)};
+ req := array[2+argsize(args)] of byte;
+ req[0] = byte cmd;
+ req[1] = byte len args;
+ packargs(req[2:], args);
+ (replies, err) := rpc(req);
+ if(replies == nil){
+ if(err != nil)
+ return (nil, err);
+ if(minlen > 0)
+ return (nil, e(Eshort));
+ return (nil, nil);
+ }
+ (nil, reply) := replies[0];
+ if(len reply < minlen)
+ return (nil, e(Eshort));
+ return (reply, nil);
+}
+
+dexec(cmd: int, argid: int, msg: array of byte, minlen: int): array of byte
+{
+ (reply, nil) := rexec(cmd, argid, msg, minlen);
+ return reply;
+}
+
+nexec(cmd: int, argid: int, msg: array of byte): int
+{
+ (nil, err) := rexec(cmd, argid, msg, 0);
+ if(err != nil)
+ return -1;
+ return 0;
+}
+
+unpackresource(a: array of byte): (ref Resource, int)
+{
+ nb := len a;
+ if(nb < 10)
+ return (nil, -1);
+ size := get2(a[8:]);
+ if(nb-10 < size)
+ return (nil, -1);
+ r := Resource.new(get4(a), get2(a[4:]), size);
+ r.data[0:] = a[10:10+size];
+ return (r, get2(a[6:]));
+}
+
+unpackrec(a: array of byte): (ref Record, int)
+{
+ nb := len a;
+ if(nb < 10)
+ return (nil, -1);
+ size := get2(a[6:]);
+ if(nb-10 < size)
+ return (nil, -1);
+ r := Record.new(get4(a), int a[8], int a[9], size);
+ r.data[0:] = a[10:10+size];
+ return (r, get2(a[4:]));
+}
+
+#
+# pack string (must be Latin1) as zero-terminated array of byte
+#
+puts(a: array of byte, s: string): int
+{
+ for(i := 0; i < len s && i < len a-1; i++)
+ a[i] = byte s[i];
+ a[i++] = byte 0;
+ return i;
+}
+
+#
+# the conversion via local time might be wrong,
+# since the computers might be in different time zones,
+# but is hard to avoid
+#
+
+getdate(data: array of byte): int
+{
+ yr := (int data[0] << 8) | int data[1];
+ if(yr == 0)
+ return 0; # unspecified
+ t := ref Tm;
+ t.sec = int data[6];
+ t.min = int data[5];
+ t.hour = int data[4];
+ t.mday = int data[3];
+ t.mon = int data[2] - 1;
+ t.year = yr - 1900;
+ t.wday = 0;
+ t.yday = 0;
+ return daytime->tm2epoch(t);
+}
+
+putdate(data: array of byte, time: int): array of byte
+{
+ t := daytime->local(time);
+ y := t.year + 1900;
+ if(time == 0)
+ y = 0; # `unchanged'
+ data[7] = byte 0; # pad
+ data[6] = byte t.sec;
+ data[5] = byte t.min;
+ data[4] = byte t.hour;
+ data[3] = byte t.mday;
+ data[2] = byte (t.mon + 1);
+ data[0] = byte ((y >> 8) & 16rff);
+ data[1] = byte (y & 16rff);
+ return data;
+}