summaryrefslogtreecommitdiff
path: root/appl/cmd/palm
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/cmd/palm
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/cmd/palm')
-rw-r--r--appl/cmd/palm/connex.b124
-rw-r--r--appl/cmd/palm/desklink.b843
-rw-r--r--appl/cmd/palm/desklink.m90
-rw-r--r--appl/cmd/palm/mkfile16
-rw-r--r--appl/cmd/palm/palmsrv.b901
5 files changed, 1974 insertions, 0 deletions
diff --git a/appl/cmd/palm/connex.b b/appl/cmd/palm/connex.b
new file mode 100644
index 00000000..2cd66fd8
--- /dev/null
+++ b/appl/cmd/palm/connex.b
@@ -0,0 +1,124 @@
+implement Connex;
+
+#
+# temporary test program for palmsrv development
+#
+# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "palm.m";
+ palm: Palm;
+ Record: import palm;
+ palmdb: Palmdb;
+ DB, PDB, PRC: import palmdb;
+
+include "desklink.m";
+ desklink: Desklink;
+ SysInfo: import desklink;
+
+Connex: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+init(nil: ref Draw->Context, nil: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->FORKFD|Sys->NEWPGRP, nil);
+ palm = load Palm Palm->PATH;
+ if(palm == nil)
+ error(sys->sprint("can't load %s: %r", palm->PATH));
+ desklink = load Desklink Desklink->PATH1;
+ if(desklink == nil)
+ error(sys->sprint("can't load Desklink: %r"));
+
+ palm->init();
+
+ err: string;
+ (palmdb, err) = desklink->connect("/chan/palmsrv");
+ if(palmdb == nil)
+ error(sys->sprint("can't init Desklink: %s", err));
+ desklink->init(palm);
+ sysinfo := desklink->ReadSysInfo();
+ if(sysinfo == nil)
+ error(sys->sprint("can't read sys Info: %r"));
+ sys->print("ROM: %8.8ux locale: %8.8ux product: '%s'\n", sysinfo.romversion, sysinfo.locale, sysinfo.product);
+ user := desklink->ReadUserInfo();
+ if(user == nil)
+ error(sys->sprint("can't read user info"));
+ sys->print("userid: %d viewerid: %d lastsyncpc: %d succsync: %8.8ux lastsync: %8.8ux uname: '%s' password: %s\n",
+ user.userid, user.viewerid, user.lastsyncpc, user.succsynctime, user.lastsynctime, user.username, ba(user.password));
+ sys->print("Storage:\n");
+ for(cno:=0;;){
+ (cards, more, err) := desklink->ReadStorageInfo(cno);
+ for(i:=0; i<len cards; i++){
+ sys->print("%2d v=%d c=%d romsize=%d ramsize=%d ramfree=%d name='%s' maker='%s'\n",
+ cards[i].cardno, cards[i].version, cards[i].creation, cards[i].romsize, cards[i].ramsize,
+ cards[i].ramfree, cards[i].name, cards[i].maker);
+ cno = cards[i].cardno+1;
+ }
+ if(!more)
+ break;
+ }
+ sys->print("ROM DBs:\n");
+ listdbs(Desklink->DBListROM);
+ sys->print("RAM DBs:\n");
+ listdbs(Desklink->DBListRAM);
+
+ (db, ee) := DB.open("AddressDB", Palmdb->OREAD);
+ if(db == nil){
+ sys->print("error: AddressDB: %s\n", ee);
+ exit;
+ }
+ pdb := db.records();
+ if(pdb == nil){
+ sys->print("error: AddressDB: %r\n");
+ exit;
+ }
+ dumpfd := sys->create("dump", Sys->OWRITE, 8r600);
+ for(i:=0; (r := pdb.read(i)) != nil; i++)
+ sys->write(dumpfd, r.data, len r.data);
+# desklink->EndOfSync(Desklink->SyncNormal);
+ desklink->hangup();
+}
+
+listdbs(sort: int)
+{
+ index := 0;
+ for(;;){
+ (dbs, more, e) := desklink->ReadDBList(0, sort, index);
+ if(dbs == nil){
+ if(e != nil)
+ sys->print("ReadDBList: %s\n", e);
+ break;
+ }
+ for(i := 0; i < len dbs; i++){
+ sys->print("#%4.4ux '%s'\n", dbs[i].index, dbs[i].name);
+ index = dbs[i].index+1;
+ }
+ if(!more)
+ break;
+ }
+}
+
+ba(a: array of byte): string
+{
+ s := "";
+ for(i := 0; i < len a; i++)
+ s += sys->sprint("%2.2ux", int a[i]);
+ return s;
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "tconn: %s\n", s);
+ fd := sys->open("/prog/"+string sys->pctl(0,nil)+"/ctl", Sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "killgrp");
+ raise "fail:error";
+}
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;
+}
diff --git a/appl/cmd/palm/desklink.m b/appl/cmd/palm/desklink.m
new file mode 100644
index 00000000..cc8d69c4
--- /dev/null
+++ b/appl/cmd/palm/desklink.m
@@ -0,0 +1,90 @@
+
+#
+# desktop/Pilot link protocol
+#
+
+Desklink: module {
+
+ PATH1: con "/dis/palm/desklink.dis";
+
+ User: adt {
+ userid: int;
+ viewerid: int;
+ lastsyncpc: int;
+ succsynctime: int;
+ lastsynctime: int;
+ username: string;
+ password: array of byte;
+ };
+
+ SysInfo: adt {
+ romversion: int;
+ locale: int;
+ product: string;
+ };
+
+ CardInfo: adt {
+ cardno: int;
+ version: int;
+ creation: int;
+ romsize: int;
+ ramsize: int;
+ ramfree: int;
+ name: string;
+ maker: string;
+ };
+
+ connect: fn(srvfile: string): (Palmdb, string);
+ hangup: fn(): int;
+
+ #
+ # Desk Link Protocol functions (usually with the same names as in PalmOS)
+ #
+
+ ReadUserInfo: fn(): ref User;
+ WriteUserInfo: fn(u: ref User, flags: int): int;
+
+ # WriteUserInfo update flags
+ UserInfoModUserID: con 16r80;
+ UserInfoModSyncPC: con 16r40;
+ UserInfoModSyncDate: con 16r20;
+ UserInfoModName: con 16r10;
+ UserInfoModViewerID: con 16r08;
+
+ ReadSysInfo: fn(): ref SysInfo;
+ ReadSysInfoVer: fn(): (int, int, int); # DLP 1.2
+
+ GetSysDateTime: fn(): int;
+ SetSysDateTime: fn(nil: int): int;
+
+ ReadStorageInfo: fn(cardno: int): (array of ref CardInfo, int, string);
+ ReadDBCount: fn(cardno: int): (int, int);
+
+ ReadDBList: fn(cardno: int, flags: int, start: int): (array of ref Palm->DBInfo, int, string); # flags must contain DBListRAM and/or DBListROM
+ FindDBInfo: fn(cardno: int, start: int, name: string, dtype, creator: string): ref Palm->DBInfo;
+
+ # list location and options
+ DBListRAM: con 16r80;
+ DBListROM: con 16r40;
+ DBListMultiple: con 16r20; # ok to return multiple entries
+
+ # OpenDB, CreateDB, ReadAppBlock, ... ResetSyncFlags, ReadOpenDBInfo, MoveCategory are functions in DB
+ CloseDB_All: fn(): int;
+ DeleteDB: fn(name: string): int;
+
+ ResetSystem: fn(): int;
+
+ OpenConduit: fn(): int;
+ EndOfSync: fn(status: int): int;
+
+ # EndOfSync status parameter
+ SyncNormal, SyncOutOfMemory, SyncCancelled, SyncError, SyncIncompatible: con iota;
+
+ AddSyncLogEntry: fn(entry: string): int;
+
+ #
+ # Palmdb implementation
+ #
+
+ init: fn(m: Palm): string;
+};
diff --git a/appl/cmd/palm/mkfile b/appl/cmd/palm/mkfile
new file mode 100644
index 00000000..2b0cb209
--- /dev/null
+++ b/appl/cmd/palm/mkfile
@@ -0,0 +1,16 @@
+<../../../mkconfig
+
+TARG=\
+ palmsrv.dis\
+ desklink.dis\
+ connex.dis\
+
+MODULES=\
+ desklink.m\
+
+SYSMODULES=\
+ palm.m\
+
+DISBIN=$ROOT/dis/palm
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/cmd/palm/palmsrv.b b/appl/cmd/palm/palmsrv.b
new file mode 100644
index 00000000..f878be03
--- /dev/null
+++ b/appl/cmd/palm/palmsrv.b
@@ -0,0 +1,901 @@
+implement Palmsrv;
+
+#
+# serve up a Palm using SLP and PADP
+#
+# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+# forsyth@vitanuova.com
+#
+# TO DO
+# USB and possibly other transports
+# tickle
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "timers.m";
+ timers: Timers;
+ Timer, Sec: import timers;
+
+include "palm.m";
+
+include "arg.m";
+
+Palmsrv: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+debug := 0;
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: palm/palmsrv [-d /dev/eia0] [-s 57600]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+
+ device, speed: string;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ error(sys->sprint("can't load %s: %r", Arg->PATH));
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'D' =>
+ debug++;
+ 'd' =>
+ device = arg->arg();
+ 's' =>
+ speed = arg->arg();
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(device == nil)
+ device = "/dev/eia0";
+ if(speed == nil)
+ speed = "57600";
+
+ dfd := sys->open(device, Sys->ORDWR);
+ if(dfd == nil)
+ error(sys->sprint("can't open %s: %r", device));
+ cfd := sys->open(device+"ctl", Sys->OWRITE);
+
+ timers = load Timers Timers->PATH;
+ if(timers == nil)
+ error(sys->sprint("can't load %s: %r", Timers->PATH));
+ srvio := sys->file2chan("/chan", "palmsrv");
+ if(srvio == nil)
+ error(sys->sprint("can't create channel /chan/palmsrv: %r"));
+ timers->init(Sec/100);
+ p := Pchan.init(dfd, cfd);
+ spawn server(srvio, p);
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "palmsrv: %s\n", s);
+ raise "fail:error";
+}
+
+Xact: adt
+{
+ fid: int;
+ reply: array of byte;
+ error: string;
+};
+
+server(srv: ref Sys->FileIO, p: ref Pchan)
+{
+ actions: list of ref Xact;
+ nuser := 0;
+ for(;;)alt{
+ (nil, nbytes, fid, rc) := <-srv.read =>
+ if(rc == nil){
+ actions = delact(actions, fid);
+ break;
+ }
+ act := findact(actions, fid);
+ if(act == nil){
+ rc <-= (nil, "no transaction in progress");
+ break;
+ }
+ actions = delact(actions, fid);
+ if(p.shutdown)
+ rc <-= (nil, "link shut down");
+ else if(act.error != nil)
+ rc <-= (nil, act.error);
+ else if(act.reply != nil)
+ rc <-= (act.reply, nil);
+ else
+ rc <-= (nil, "no reply"); # probably shouldn't happen
+
+ (nil, data, fid, wc) := <-srv.write =>
+ actions = delact(actions, fid); # discard result of any previous transaction
+ if(wc == nil){
+ if(--nuser <= 0){
+ nuser = 0;
+ p.stop();
+ }
+ break;
+ }
+ if(len data == 4 && string data == "exit"){
+ p.close();
+ wc <-= (len data, nil);
+ exit;
+ }
+ if(p.shutdown){
+ wc <-= (0, "link shut down"); # must close then reopen
+ break;
+ }
+ if(!p.started){
+ err := p.start();
+ if(err != nil){
+ wc <-= (0, sys->sprint("can't start protocol: %s", err));
+ break;
+ }
+ nuser++;
+ }
+ (result, err) := p.padp_xchg(data, 20*1000);
+ if(err != nil){
+ wc <-= (0, err);
+ break;
+ }
+ actions = ref Xact(fid, result, err) :: actions;
+ wc <-= (len data, nil);
+ }
+}
+
+findact(l: list of ref Xact, fid: int): ref Xact
+{
+ for(; l != nil; l = tl l)
+ if((a := hd l).fid == fid)
+ return a;
+ return nil;
+}
+
+delact(l: list of ref Xact, fid: int): list of ref Xact
+{
+ ol := l;
+ l = nil;
+ for(; ol != nil; ol = tl ol)
+ if((a := hd ol).fid != fid)
+ l = a :: l;
+ return l;
+}
+
+killpid(pid: int)
+{
+ if(pid != 0){
+ fd := sys->open("/prog/"+string pid+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+ }
+}
+
+#
+# protocol implementation
+# Serial Link Protocol (framing)
+# Connection Management Protocol (wakeup, negotiation)
+# Packet Assembly/Disassembly Protocol (reliable delivery fragmented datagram)
+#
+
+DATALIM: con 1024;
+
+# SLP packet types
+SLP_System, SLP_Unused, SLP_PAD, SLP_Loop: con iota;
+
+# SLP block content, without framing
+Sblock: adt {
+ src: int; # socket ID
+ dst: int; # socket ID
+ proto: int; # packet type
+ xid: int; # transaction ID
+ data: array of byte;
+
+ new: fn(): ref Sblock;
+ print: fn(sb: self ref Sblock, dir: string);
+};
+
+#
+# Palm channel
+#
+Pchan: adt {
+ started: int;
+ shutdown: int;
+
+ protocol: int;
+ lport: byte;
+ rport: byte;
+
+ fd: ref Sys->FD;
+ cfd: ref Sys->FD;
+ baud: int;
+
+ rpid: int;
+ lastid: int;
+ rd: chan of ref Sblock;
+ reply: ref Sblock; # data replacing lost ack
+
+ init: fn(dfd: ref Sys->FD, cfd: ref Sys->FD): ref Pchan;
+ start: fn(p: self ref Pchan): string;
+ stop: fn(p: self ref Pchan);
+ close: fn(p: self ref Pchan): int;
+ slp_read: fn(p: self ref Pchan, nil: int): (ref Sblock, string);
+ slp_write: fn(p: self ref Pchan, xid: int, nil: array of byte): string;
+
+ setbaud: fn(p: self ref Pchan, nil: int);
+
+ padp_read: fn(p: self ref Pchan, xid: int, timeout: int): (array of byte, string);
+ padp_write: fn(p: self ref Pchan, msg: array of byte, xid: int): string;
+ padp_xchg: fn(p: self ref Pchan, msg: array of byte, timeout: int): (array of byte, string);
+ tickle: fn(p: self ref Pchan);
+
+ connect: fn(p: self ref Pchan): string;
+ accept: fn(p: self ref Pchan, baud: int): string;
+
+ nextseq: fn(p: self ref Pchan): int;
+};
+
+Pchan.init(dfd: ref Sys->FD, cfd: ref Sys->FD): ref Pchan
+{
+ p := ref Pchan;
+ p.fd = dfd;
+ p.cfd = cfd;
+ p.baud = InitBaud;
+ p.protocol = SLP_PAD;
+ p.rport = byte 3;
+ p.lport = byte 3;
+ p.rd = chan of ref Sblock;
+ p.lastid = 0;
+ p.rpid = 0;
+ p.started = 0;
+ p.shutdown = 0;
+ return p;
+}
+
+Pchan.start(p: self ref Pchan): string
+{
+ if(p.started)
+ return nil;
+ p.shutdown = 0;
+ p.baud = InitBaud;
+ p.reply = nil;
+ ctl(p, "f");
+ ctl(p, "d1");
+ ctl(p, "r1");
+ ctl(p, "i8");
+ ctl(p, "q8192");
+ ctl(p, sys->sprint("b%d", InitBaud));
+ pidc := chan of int;
+ spawn slp_recv(p, pidc);
+ p.started = 1;
+ p.rpid = <-pidc;
+ err := p.accept(57600);
+ if(err != nil)
+ p.stop();
+ return err;
+}
+
+ctl(p: ref Pchan, s: string)
+{
+ if(p.cfd != nil)
+ sys->fprint(p.cfd, "%s", s);
+}
+
+Pchan.setbaud(p: self ref Pchan, baud: int)
+{
+ if(p.baud != baud){
+ p.baud = baud;
+ ctl(p, sys->sprint("b%d", baud));
+ sys->sleep(200);
+ }
+}
+
+Pchan.stop(p: self ref Pchan)
+{
+ p.shutdown = 0;
+ if(!p.started)
+ return;
+ killpid(p.rpid);
+ p.rpid = 0;
+ p.reply = nil;
+# ctl(p, "f");
+# ctl(p, "d0");
+# ctl(p, "r0");
+# ctl(p, sys->sprint("b%d", InitBaud));
+ p.started = 0;
+}
+
+Pchan.close(p: self ref Pchan): int
+{
+ if(p.started)
+ p.stop();
+ p.reply = nil;
+ p.cfd = nil;
+ p.fd = nil;
+ timers->shutdown();
+ return 0;
+}
+
+# CMP protocol for connection management
+# See include/Core/System/CMCommon.h, Palm SDK
+# There are two major versions: the original V1, still always used in wakeup messsages;
+# and V2, which is completely different (similar structure to Desklink) and used by newer devices, but the headers
+# are the same length. Start off in V1 announcing version 2.x, then switch to that.
+# My device supports only V1, so I use that.
+
+CMPHDRLEN: con 10; # V1: type[1] flags[1] vermajor[1] verminor[1] mbz[2] baud[4]
+ # V2: type[1] cmd[1] error[2] argc[1] mbz[1] mbz[4]
+
+# CMP V1
+Cmajor: con 1;
+Cminor: con 2;
+
+InitBaud: con 9600;
+
+# type
+Cwake, Cinit, Cabort, Cextended: con 1+iota;
+
+# Cinit flags
+ChangeBaud: con 16r80;
+RcvTimeout1: con 16r40; # tell Palm to set receive timeout to 1 minute (CMP v1.1)
+RcvTimeout2: con 16r20; # tell Palm to set receive timeout to 2 minutes (v1.1)
+
+# Cinit and Cwake flag
+LongPacketEnable: con 16r10; # enable long packet support (v1.2)
+
+# Cabort flags
+WrongVersion: con 16r80; # incompatible com versions
+
+# CMP V2
+Carg1: con Palm->ArgIDbase;
+Cresponse: con 16r80;
+Cxchgprefs, Chandshake: con 16r10+iota;
+
+Pchan.connect(p: self ref Pchan): string
+{
+ (nil, e1) := cmp_write(p, Cwake, 0, Cmajor, Cminor, 57600);
+ if(e1 != nil)
+ return e1;
+ (op, flag, nil, nil, baud, e2) := cmp_read(p, 0);
+ if(e2 != nil)
+ return e2;
+ case op {
+ Cinit=>
+ if(flag & ChangeBaud)
+ p.setbaud(baud);
+ return nil;
+
+ Cabort=>
+ return "Palm rejected connect";
+
+ * =>
+ return sys->sprint("Palm connect: reply %d", op);
+ }
+ return nil;
+}
+
+Pchan.accept(p: self ref Pchan, maxbaud: int): string
+{
+ (op, nil, major, minor, baud, err) := cmp_read(p, 0);
+ if(err != nil)
+ return err;
+ if(major != 1){
+ sys->fprint(sys->fildes(2), "palmsrv: comm version mismatch: %d.%d\n", major, minor);
+ cmp_write(p, Cabort, WrongVersion, Cmajor, 0, 0);
+ return sys->sprint("comm version mismatch: %d.%d", major, minor);
+ }
+ if(baud > maxbaud)
+ baud = maxbaud;
+ flag := 0;
+ if(baud != InitBaud)
+ flag = ChangeBaud;
+ (nil, err) = cmp_write(p, Cinit, flag, Cmajor, Cminor, baud);
+ if(err != nil)
+ return err;
+ p.setbaud(baud);
+ return nil;
+}
+
+cmp_write(p: ref Pchan, op: int, flag: int, major: int, minor: int, baud: int): (int, string)
+{
+ cmpbuf := array[CMPHDRLEN] of byte;
+ cmpbuf[0] = byte op;
+ cmpbuf[1] = byte flag;
+ cmpbuf[2] = byte major;
+ cmpbuf[3] = byte minor;
+ cmpbuf[4] = byte 0;
+ cmpbuf[5] = byte 0;
+ put4(cmpbuf[6:], baud);
+
+ if(op == Cwake)
+ return (16rFF, p.padp_write(cmpbuf, 16rFF));
+ xid := p.nextseq();
+ return (xid, p.padp_write(cmpbuf, xid));
+}
+
+cmp_read(p: ref Pchan, xid: int): (int, int, int, int, int, string)
+{
+ (c, err) := p.padp_read(xid, 20*Sec);
+ if(err != nil)
+ return (0, 0, 0, 0, 0, err);
+ if(len c != CMPHDRLEN)
+ return (0, 0, 0, 0, 0, "CMP: bad response");
+ return (int c[0], int c[1], int c[2], int c[3], get4(c[6:]), nil);
+}
+
+#
+# Palm PADP protocol
+# ``The Packet Assembly/Disassembly Protocol'' in
+# Developing Palm OS Communications, US Robotics, 1996, pp. 53-68.
+#
+# forsyth@caldo.demon.co.uk, 1997
+#
+
+FIRST: con 16r80;
+LAST: con 16r40;
+MEMERROR: con 16r20;
+
+# packet types
+Pdata: con 1;
+Pack: con 2;
+Ptickle: con 4;
+Pabort: con 8;
+
+PADPHDRLEN: con 4; # type[1] flags[1] size[2]
+
+RetryInterval: con 4*Sec;
+MaxRetries: con 14; # they say 14 `seconds', but later state they might need 20 for heap mgmt, so i'll assume 14 attempts (at 4sec ea)
+
+Pchan.padp_xchg(p: self ref Pchan, msg: array of byte, timeout: int): (array of byte, string)
+{
+ xid := p.nextseq();
+ err := p.padp_write(msg, xid);
+ if(err != nil)
+ return (nil, err);
+ return p.padp_read(xid, timeout);
+}
+
+#
+# PADP header
+# type[1] flags[2] size[2], high byte first for size
+#
+# max block size is 2^16-1
+# must ack within 2 seconds
+# wait at most 10 seconds for next chunk
+# 10 retries
+#
+
+Pchan.padp_write(p: self ref Pchan, buf: array of byte, xid: int): string
+{
+ count := len buf;
+ if(count >= 1<<16)
+ return "padp: write too big";
+ p.reply = nil;
+ flags := FIRST;
+ mem := buf[0:];
+ offset := 0;
+ while(count > 0){
+ n := count;
+ if(n > DATALIM)
+ n = DATALIM;
+ else
+ flags |= LAST;
+ ob := array[PADPHDRLEN+n] of byte;
+ ob[0] = byte Pdata;
+ ob[1] = byte flags;
+ l: int;
+ if(flags & FIRST)
+ l = count; # total size in first segment
+ else
+ l = offset; # offset in rest
+ put2(ob[2:], l);
+ ob[PADPHDRLEN:] = mem[0:n];
+ if(debug)
+ padp_dump(ob, "Tx");
+ p.slp_write(xid, ob);
+ retries := 0;
+ for(;;){
+ (ib, nil) := p.slp_read(RetryInterval);
+ if(ib == nil){
+ sys->print("padp write: ack timeout\n");
+ retries++;
+ if(retries > MaxRetries){
+ # USR says not to give up if (flags&LAST)!=0; giving up seems safer
+ sys->print("padp write: give up\n");
+ return "PADP: no response";
+ }
+ p.slp_write(xid, ob);
+ continue;
+ }
+ if(ib.proto != SLP_PAD || len ib.data < PADPHDRLEN || ib.xid != xid && ib.xid != 16rFF){
+ sys->print("padp write: ack wrong type(%d) or xid(%d,%d), or len %d\n", ib.proto, ib.xid, xid, len ib.data);
+ continue;
+ }
+ if(ib.xid == 16rFF){ # connection management
+ if(int ib.data[0] == Ptickle)
+ continue;
+ if(int ib.data[0] == Pabort){
+ sys->print("padp write: device abort\n");
+ p.shutdown = 1;
+ return "device cancelled operation";
+ }
+ }
+ if(int ib.data[0] != Pack){
+ if(int ib.data[0] == Ptickle)
+ continue;
+ # right transaction ... if it's acceptable data, USR says to save it & treat as ack
+ sys->print("padp write: type %d, not ack\n", int ib.data[0]);
+ if(int ib.data[0] == Pdata && flags & LAST && int ib.data[1] & FIRST){
+ p.reply = ib;
+ break;
+ }
+ continue;
+ }
+ if(int ib.data[1] & MEMERROR)
+ return "padp: pilot out of memory";
+ if((flags&(FIRST|LAST)) != (int ib.data[1]&(FIRST|LAST)) ||
+ get2(ib.data[2:]) != get2(ob[2:])){
+ sys->print("padp write: ack, wrong flags (#%x,#%x) or offset (%d,%d)\n", int ib.data[1], flags, get2(ib.data[2:]), get2(ob[2:]));
+ continue;
+ }
+ if(debug)
+ sys->print("padp write: ack %d %d\n", xid, get2(ob[2:]));
+ break;
+ }
+ mem = mem[n:];
+ count -= n;
+ offset += n;
+ flags &= ~FIRST;
+ }
+ return nil;
+}
+
+Pchan.padp_read(p: self ref Pchan, xid, timeout: int): (array of byte, string)
+{
+ buf, mem: array of byte;
+
+ offset := 0;
+ ready := 0;
+ retries := 0;
+ ack := array[PADPHDRLEN] of byte;
+ for(;;){
+ b := p.reply;
+ if(b == nil){
+ err: string;
+ (b, err) = p.slp_read(timeout);
+ if(b == nil){
+ sys->print("padp read: timeout %d\n", retries);
+ if(++retries <= 5)
+ continue;
+ sys->print("padp read: gave up\n");
+ return (nil, err);
+ }
+ retries = 0;
+ } else
+ p.reply = nil;
+ if(debug)
+ padp_dump(b.data, "Rx");
+ if(len b.data < PADPHDRLEN){
+ sys->print("padp read: length\n");
+ continue;
+ }
+ if(b.proto != SLP_PAD){
+ sys->print("padp read: bad proto (%d)\n", b.proto);
+ continue;
+ }
+ if(int b.data[0] == Pabort && b.xid == 16rFF){
+ p.shutdown = 1;
+ return (nil, "device cancelled transaction");
+ }
+ if(int b.data[0] != Pdata || xid != 0 && b.xid != xid){
+ sys->print("padp read mismatch: type (%d) or xid(%d::%d)\n", int b.data[0], b.xid, xid);
+ continue;
+ }
+ f := int b.data[1];
+ o := get2(b.data[2:]);
+ if(f & FIRST){
+ buf = array[o] of byte;
+ ready = 1;
+ offset = 0;
+ o = 0;
+ mem = buf;
+ timeout = 4*Sec;
+ }
+ if(!ready || o != offset){
+ sys->print("padp read: offset %d, expected %d\n", o, offset);
+ continue;
+ }
+ n := len b.data - PADPHDRLEN;
+ if(n > len mem){
+ sys->print("padp read: record too long (%d/%d)\n", n, len mem);
+ # it's probably fatal, but retrying does no harm
+ continue;
+ }
+ mem[0:] = b.data[PADPHDRLEN:PADPHDRLEN+n];
+ mem = mem[n:];
+ offset += n;
+ ack[0:] = b.data[0:PADPHDRLEN];
+ ack[0] = byte Pack;
+ p.slp_write(xid, ack);
+ if(f & LAST)
+ break;
+ }
+ if(offset != len buf)
+ return (buf[0:offset], nil);
+ return (buf, nil);
+}
+
+Pchan.nextseq(p: self ref Pchan): int
+{
+ n := p.lastid + 1;
+ if(n >= 16rFF)
+ n = 1;
+ p.lastid = n;
+ return n;
+}
+
+Pchan.tickle(p: self ref Pchan)
+{
+ xid := p.nextseq();
+ data := array[PADPHDRLEN] of byte;
+ data[0] = byte Ptickle;
+ data[1] = byte (FIRST|LAST);
+ put2(data[2:], 0);
+ if(debug)
+ sys->print("PADP: tickle\n");
+ p.slp_write(xid, data);
+}
+
+padp_dump(data: array of byte, dir: string)
+{
+ stype: string;
+
+ case int data[0] {
+ Pdata => stype = "Data";
+ Pack => stype = "Ack";
+ Ptickle => stype = "Tickle";
+ Pabort => stype = "Abort";
+ * => stype = sys->sprint("#%x", int data[0]);
+ }
+
+ sys->print("PADP %s %s flags=#%x len=%d\n", stype, dir, int data[1], get2(data[2:]));
+
+ if(debug > 1 && (data[0] != byte Pack || len data > 4)){
+ data = data[4:];
+ for(i := 0; i < len data;){
+ sys->print(" %.2x", int data[i]);
+ if(++i%16 == 0)
+ sys->print("\n");
+ }
+ sys->print("\n");
+ }
+}
+
+#
+# Palm's Serial Link Protocol
+# See include/Core/System/SerialLinkMgr.h in Palm SDK
+# and the description in the USR document mentioned above.
+#
+
+SLPHDRLEN: con 10; # BE[1] EF[1] ED[1] dest[1] src[1] type[1] size[2] xid[1] check[1] body[size] crc[2]
+SLP_MTU: con SLPHDRLEN+PADPHDRLEN+DATALIM;
+
+Sblock.new(): ref Sblock
+{
+ return ref Sblock(0, 0, 0, 16rFF, nil);
+}
+
+#
+# format and write an SLP frame
+#
+Pchan.slp_write(p: self ref Pchan, xid: int, b: array of byte): string
+{
+ d := array[SLPHDRLEN] of byte;
+ cb := array[2] of byte;
+
+ nb := len b;
+ d[0] = byte 16rBE;
+ d[1] = byte 16rEF;
+ d[2] = byte 16rED;
+ d[3] = byte p.rport;
+ d[4] = byte p.lport;
+ d[5] = byte p.protocol;
+ d[6] = byte (nb >> 8);
+ d[7] = byte (nb & 16rFF);
+ d[8] = byte xid;
+ d[9] = byte 0;
+ n := 0;
+ for(i:=0; i<len d; i++)
+ n += int d[i];
+ d[9] = byte (n & 16rFF);
+ if(debug)
+ printbytes(d, "SLP Tx hdr");
+ crc := crc16(d, 0);
+ put2(cb, crc16(b, crc));
+
+ if(sys->write(p.fd, d, SLPHDRLEN) != SLPHDRLEN ||
+ sys->write(p.fd, b, nb) != len b ||
+ sys->write(p.fd, cb, 2) != 2)
+ return sys->sprint("%r");
+ return nil;
+}
+
+Pchan.slp_read(p: self ref Pchan, timeout: int): (ref Sblock, string)
+{
+ clock := Timer.start(timeout);
+ alt {
+ <-clock.timeout =>
+ if(debug)
+ sys->print("SLP: timeout\n");
+ return (nil, "SLP: timeout");
+ b := <-p.rd =>
+ clock.stop();
+ return (b, nil);
+ }
+}
+
+slp_recv(p: ref Pchan, pidc: chan of int)
+{
+ n: int;
+
+ pidc <-= sys->pctl(0, nil);
+ buf := array[2*SLP_MTU] of byte;
+ sb := Sblock.new();
+ rd := wr := 0;
+Work:
+ for(;;){
+
+ if(wr != rd){
+ # data already in buffer might start a new frame
+ if(rd != 0){
+ buf[0:] = buf[rd:wr];
+ wr -= rd;
+ rd = 0;
+ }
+ }else
+ rd = wr = 0;
+
+ # header
+ while(wr < SLPHDRLEN){
+ n = sys->read(p.fd, buf[wr:], SLPHDRLEN-wr);
+ if(n <= 0)
+ break Work;
+ wr += n;
+ }
+# {for(i:=0; i<wr;i++)sys->print("%.2x", int buf[i]);sys->print("\n");}
+ if(buf[0] != byte 16rBE || buf[1] != byte 16rEF || buf[2] != byte 16rED){
+ rd++;
+ continue;
+ }
+ if(debug)
+ printbytes(buf[0:wr], "SLP Rx hdr");
+ n = 0;
+ for(i:=0; i<SLPHDRLEN-1; i++)
+ n += int buf[i];
+ if((n & 16rFF) != int buf[9]){
+ rd += 3;
+ continue;
+ }
+ hdr := buf[0:SLPHDRLEN];
+ sb.dst = int hdr[3];
+ sb.src = int hdr[4];
+ sb.proto = int hdr[5];
+ size := (int hdr[6]<<8) | int hdr[7];
+ sb.xid = int hdr[8];
+ sb.data = array[size] of byte;
+ crc := crc16(hdr, 0);
+ rd += SLPHDRLEN;
+ if(rd == wr)
+ rd = wr = 0;
+
+ # data and CRC
+ while(wr-rd < size+2){
+ n = sys->read(p.fd, buf[wr:], size+2-(wr-rd));
+ if(n <= 0)
+ break Work;
+ wr += n;
+ }
+ crc = crc16(buf[rd:rd+size], crc);
+ if(crc != get2(buf[rd+size:])){
+ if(debug)
+ sys->print("CRC error: local=#%.4ux pilot=#%.4ux\n", crc, get2(buf[rd+size:]));
+ for(; rd < wr && buf[rd] != byte 16rBE; rd++)
+ ; # hunt for next header
+ continue;
+ }
+ if(sb.proto != SLP_Loop){
+ sb.data[0:] = buf[rd:rd+size];
+ if(debug)
+ sb.print("Rx");
+ rd += size+2;
+ p.rd <-= sb;
+ sb = Sblock.new();
+ } else {
+ # should we reflect these?
+ if(debug)
+ sb.print("Loop");
+ rd += size+2;
+ }
+ }
+ p.rd <-= nil;
+}
+
+Sblock.print(b: self ref Sblock, dir: string)
+{
+ sys->print("SLP %s %d->%d len=%d proto=%d xid=#%.2x\n",
+ dir, int b.src, int b.dst, len b.data, int b.proto, int b.xid);
+}
+
+printbytes(d: array of byte, what: string)
+{
+ buf := sys->sprint("%s[", what);
+ for(i:=0; i<len d; i++)
+ buf += sys->sprint(" #%.2x", int d[i]);
+ buf += "]";
+ sys->print("%s\n", buf);
+}
+
+get4(p: array of byte): int
+{
+ return (int p[0]<<24) | (int p[1]<<16) | (int p[2]<<8) | int p[3];
+}
+
+get3(p: array of byte): int
+{
+ return (int p[1]<<16) | (int p[2]<<8) | int p[3];
+}
+
+get2(p: array of byte): int
+{
+ return (int p[0]<<8) | int p[1];
+}
+
+put4(p: array of byte, v: int)
+{
+ p[0] = byte (v>>24);
+ p[1] = byte (v>>16);
+ p[2] = byte (v>>8);
+ p[3] = byte (v & 16rFF);
+}
+
+put3(p: array of byte, v: int)
+{
+ p[0] = byte (v>>16);
+ p[1] = byte (v>>8);
+ p[2] = byte (v & 16rFF);
+}
+
+put2(p: array of byte, v: int)
+{
+ p[0] = byte (v>>8);
+ p[1] = byte (v & 16rFF);
+}
+
+# this will be done by table look up;
+# polynomial is xⁱ⁶+xⁱ⁲+x⁵+1
+
+crc16(buf: array of byte, crc: int): int
+{
+ for(j := 0; j < len buf; j++){
+ crc = crc ^ (int buf[j]) << 8;
+ for(i := 0; i < 8; i++)
+ if(crc & 16r8000)
+ crc = (crc << 1) ^ 16r1021;
+ else
+ crc = crc << 1;
+ }
+ return crc & 16rffff;
+}