summaryrefslogtreecommitdiff
path: root/appl/lib/palm.b
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/lib/palm.b
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/lib/palm.b')
-rw-r--r--appl/lib/palm.b504
1 files changed, 504 insertions, 0 deletions
diff --git a/appl/lib/palm.b b/appl/lib/palm.b
new file mode 100644
index 00000000..d5a8ce34
--- /dev/null
+++ b/appl/lib/palm.b
@@ -0,0 +1,504 @@
+implement Palm;
+
+#
+# Copyright © 2001-2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+# Based on ``Palm® File Format Specification'', Document Number 3008-004, 1 May 2001, by Palm Inc.
+# Doc compression based on description by Paul Lucas, 18 August 1998
+#
+
+include "sys.m";
+ sys: Sys;
+
+include "daytime.m";
+ daytime: Daytime;
+
+include "palm.m";
+
+# Exact value of "Jan 1, 1970 0:00:00 GMT" - "Jan 1, 1904 0:00:00 GMT"
+Epochdelta: con 2082844800;
+tzoff := 0;
+
+init(): string
+{
+ sys = load Sys Sys->PATH;
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ return "can't load required module";
+ tzoff = daytime->local(0).tzoff;
+ return nil;
+}
+
+Record.new(id: int, attr: int, cat: int, size: int): ref Record
+{
+ return ref Record(id, attr, cat, array[size] of byte);
+}
+
+Resource.new(name: int, id: int, size: int): ref Resource
+{
+ return ref Resource(name, id, array[size] of byte);
+}
+
+Doc.open(m: Palmdb, file: ref Palmdb->PDB): (ref Doc, string)
+{
+ info := m->file.db.stat();
+ if(info.dtype != "TEXt" || info.creator != "REAd")
+ return (nil, "not a Doc file: wrong type or creator");
+ r := m->file.read(0);
+ if(r == nil)
+ return (nil, sys->sprint("not a valid Doc file: %r"));
+ a := r.data;
+ if(len a < 16)
+ return (nil, sys->sprint("not a valid Doc file: bad length: %d", len a));
+ maxrec := m->file.db.nentries()-1;
+ d := ref Doc;
+ d.m = m;
+ d.file = file;
+ d.version = get2(a);
+ err := "unknown";
+ if(d.version != 1 && d.version != 2)
+ err = "unknown Docfile version";
+ # a[2:] is spare
+ d.length = get4(a[4:]);
+ d.nrec = get2(a[8:]);
+ if(maxrec >= 0 && d.nrec > maxrec){
+ d.nrec = maxrec;
+ err = "invalid record count";
+ }
+ d.recsize = get2(a[10:]);
+ d.position = get4(a[12:]);
+ return (d, sys->sprint("unexpected Doc file format: %s", err));
+}
+
+Doc.iscompressed(d: self ref Doc): int
+{
+ return (d.version&7) == 2; # high-order bits are sometimes used, ignore them
+}
+
+Doc.read(doc: self ref Doc, index: int): (string, string)
+{
+ m := doc.m;
+ DB, PDB: import m;
+ r := doc.file.read(index+1);
+ if(r == nil)
+ return (nil, sys->sprint("%r"));
+ (s, serr) := doc.unpacktext(r.data);
+ if(s == nil)
+ return (nil, serr);
+ return (s, nil);
+}
+
+Doc.unpacktext(doc: self ref Doc, a: array of byte): (string, string)
+{
+ nb := len a;
+ s: string;
+ if(!doc.iscompressed()){
+ for(i := 0; i < nb; i++)
+ s[len s] = int a[i]; # assumes Latin-1
+ return (s, nil);
+ }
+ o := 0;
+ for(i := 0; i < nb;){
+ c := int a[i++];
+ if(c >= 9 && c <= 16r7F || c == 0)
+ s[o++] = c;
+ else if(c >= 1 && c <= 8){
+ if(i+c > nb)
+ return (nil, "missing data in record");
+ while(--c >= 0)
+ s[o++] = int a[i++];
+ }else if(c >= 16rC0 && c <= 16rFF){
+ s[o] = ' ';
+ s[o+1] = c & 16r7F;
+ o += 2;
+ }else{ # c >= 0x80 && c <= 16rBF
+ v := int a[i++];
+ m := ((c & 16r3F)<<5)|(v>>3);
+ n := (v&7) + 3;
+ if(m == 0 || m > o)
+ return (nil, sys->sprint("data is corrupt: m=%d n=%d o=%d", m, n, o));
+ for(; --n >= 0; o++)
+ s[o] = s[o-m];
+ }
+ }
+ return (s, nil);
+}
+
+Doc.textlength(doc: self ref Doc, a: array of byte): int
+{
+ nb := len a;
+ if(!doc.iscompressed())
+ return nb;
+ o := 0;
+ for(i := 0; i < nb;){
+ c := int a[i++];
+ if(c >= 9 && c <= 16r7F || c == 0)
+ o++;
+ else if(c >= 1 && c <= 8){
+ if(i+c > nb)
+ return -1;
+ o += c;
+ i += c;
+ }else if(c >= 16rC0 && c <= 16rFF){
+ o += 2;
+ }else{ # c >= 0x80 && c <= 16rBF
+ v := int a[i++];
+ m := ((c & 16r3F)<<5)|(v>>3);
+ n := (v&7) + 3;
+ if(m == 0 || m > o)
+ return -1;
+ o += n;
+ }
+ }
+ return o;
+}
+
+id2s(i: int): string
+{
+ if(i == 0)
+ return "";
+ return sys->sprint("%c%c%c%c", (i>>24)&16rFF, (i>>16)&16rFF, (i>>8)&16rFF, i&16rFF);
+}
+
+s2id(s: string): int
+{
+ n := 0;
+ for(i := 0; i < 4; i++){
+ c := 0;
+ if(i < len s)
+ c = s[i] & 16rFF;
+ n = (n<<8) | c;
+ }
+ return n;
+}
+
+DBInfo.new(name: string, attr: int, dtype: string, version: int, creator: string): ref DBInfo
+{
+ info := ref DBInfo;
+ info.name = name;
+ info.attr = attr;
+ info.version = version;
+ info.ctime = daytime->now();
+ info.mtime = daytime->now();
+ info.btime = 0;
+ info.modno = 0;
+ info.dtype = dtype;
+ info.creator = creator;
+ info.uidseed = 0;
+ info.index = 0;
+ return info;
+}
+
+Categories.new(labels: array of string): ref Categories
+{
+ c := ref Categories;
+ c.renamed = 0;
+ c.lastuid = 0;
+ c.labels = array[16] of string;
+ c.uids = array[] of {0 to 15 => 0};
+ for(i := 0; i < len labels && i < 16; i++){
+ c.labels[i] = labels[i];
+ c.lastuid = 16r80 + i;
+ c.uids[i] = c.lastuid;
+ }
+ return c;
+}
+
+Categories.unpack(a: array of byte): ref Categories
+{
+ if(len a < 16r114)
+ return nil; # doesn't match the structure
+ c := ref Categories;
+ c.renamed = get2(a);
+ c.labels = array[16] of string;
+ c.uids = array[16] of int;
+ j := 2;
+ for(i := 0; i < 16; i++){
+ c.labels[i] = latin1(a[j:j+16], 0);
+ j += 16;
+ c.uids[i] = int a[16r102+i];
+ }
+ c.lastuid = int a[16r112];
+ # one byte of padding is shown on p. 26, but
+ # two more are invariably used in practice
+ # before application specific data.
+ if(len a > 16r116)
+ c.appdata = a[16r116:];
+ return c;
+}
+
+Categories.pack(c: self ref Categories): array of byte
+{
+ a := array[16r116 + len c.appdata] of byte;
+ put2(a, c.renamed);
+ j := 2;
+ for(i := 0; i < 16; i++){
+ puts(a[j:j+16], c.labels[i]);
+ j += 16;
+ a[16r102+i] = byte c.uids[i];
+ }
+ a[16r112] = byte c.lastuid;
+ a[16r113] = byte 0; # pad shown on p. 26
+ a[16r114] = byte 0; # extra two bytes of padding used in practice
+ a[16r115] = byte 0;
+ if(c.appdata != nil)
+ a[16r116:] = c.appdata;
+ return a;
+}
+
+Categories.mkidmap(c: self ref Categories): array of int
+{
+ a := array[256] of {* => 0};
+ for(i := 0; i < len c.uids; i++)
+ a[c.uids[i]] = i;
+ return a;
+}
+
+#
+# because PalmOS treats all times as local times, and doesn't associate
+# them with time zones, we'll convert using local time on Plan 9 and Inferno
+#
+
+pilot2epoch(t: int): int
+{
+ if(t == 0)
+ return 0; # we'll assume it's not set
+ return t - Epochdelta + tzoff;
+}
+
+epoch2pilot(t: int): int
+{
+ if(t == 0)
+ return t;
+ return t - tzoff + Epochdelta;
+}
+
+#
+# map Palm name to string, assuming iso-8859-1,
+# but remap space and /
+#
+latin1(a: array of byte, remap: int): string
+{
+ s := "";
+ for(i := 0; i < len a; i++){
+ c := int a[i];
+ if(c == 0)
+ break;
+ if(remap){
+ if(c == ' ')
+ c = 16r00A0; # unpaddable space
+ else if(c == '/')
+ c = 16r2215; # division /
+ }
+ s[len s] = c;
+ }
+ return s;
+}
+
+#
+# map from Unicode to Palm name
+#
+filename(name: string): string
+{
+ s := "";
+ for(i := 0; i < len name; i++){
+ c := name[i];
+ if(c == ' ')
+ c = 16r00A0; # unpaddable space
+ else if(c == '/')
+ c = 16r2215; # division solidus
+ s[len s] = c;
+ }
+ return s;
+}
+
+dbname(name: string): string
+{
+ s := "";
+ for(i := 0; i < len name; i++){
+ c := name[i];
+ case c {
+ 0 => c = ' '; # unlikely, but just in case
+ 16r2215 => c = '/';
+ 16r00A0 => c = ' ';
+ }
+ s[len s] = c;
+ }
+ return s;
+}
+
+#
+# string conversion: can't use (string a) because
+# the bytes are Latin1, not Unicode
+#
+gets(a: array of byte): string
+{
+ s := "";
+ for(i := 0; i < len a; i++)
+ s[len s] = int a[i];
+ return s;
+}
+
+puts(a: array of byte, s: string)
+{
+ for(i := 0; i < len a-1 && i < len s; i++)
+ a[i] = byte s[i];
+ for(; i < len a; i++)
+ a[i] = byte 0;
+}
+
+#
+# big-endian packing
+#
+
+get4(p: array of byte): int
+{
+ return (((((int p[0] << 8) | int p[1]) << 8) | int p[2]) << 8) | int p[3];
+}
+
+get3(p: array of byte): int
+{
+ return (((int p[0] << 8) | int p[1]) << 8) | int p[2];
+}
+
+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);
+}
+
+#
+# DL protocol argument wrapping, based on conventions
+# extracted from include/Core/System/DLCommon.h in SDK 5
+#
+# tiny arguments
+# id: byte
+# size: byte # excluding this header
+# data: byte[]
+#
+# small arguments
+# id: byte # with 16r80 flag
+# pad: byte
+# size: byte[2]
+# data: byte[]
+#
+# long arguments
+# id: byte # with 16r40 flag
+# pad: byte
+# size: byte[4]
+# data: byte[]
+
+# wrapper format flag in request/response argument ID
+ShortWrap: con 16r80; # 2-byte count
+LongWrap: con 16r40; # 4-byte count
+
+Eshort: con "response shorter than expected";
+
+#
+# set the system error string
+#
+e(s: string): string
+{
+ if(s != nil)
+ sys->werrstr(s);
+ return s;
+}
+
+argsize(args: array of (int, array of byte)): int
+{
+ totnb := 0;
+ for(i := 0; i < len args; i++){
+ (nil, a) := args[i];
+ n := len a;
+ if(n > 65535)
+ totnb += 6; # long wrap
+ else if(n > 255)
+ totnb += 4; # short
+ else
+ totnb += 2; # tiny
+ totnb += n;
+ }
+ return totnb;
+}
+
+packargs(out: array of byte, args: array of (int, array of byte)): array of byte
+{
+ for(i := 0; i < len args; i++){
+ (id, a) := args[i];
+ n := len a;
+ if(n > 65535){
+ out[0] = byte (LongWrap|ShortWrap|id);
+ out[1] = byte 0;
+ put4(out[2:], n);
+ out = out[6:];
+ }else if(n > 255){
+ out[0] = byte (ShortWrap|id);
+ out[1] = byte 0;
+ put2(out[2:], n);
+ out = out[4:];
+ }else{
+ out[0] = byte id;
+ out[1] = byte n;
+ out = out[2:];
+ }
+ out[0:] = a;
+ out = out[n:];
+ }
+ return out;
+}
+
+unpackargs(argc: int, reply: array of byte): (array of (int, array of byte), string)
+{
+ replies := array[argc] of (int, array of byte);
+ o := 0;
+ for(i := 0; i < len replies; i++){
+ o = (o+1)&~1; # each argument starts at even offset
+ a := reply[o:];
+ if(len a < 2)
+ return (nil, e(Eshort));
+ rid := int a[0];
+ l: int;
+ if(rid & LongWrap){
+ if(len a < 6)
+ return (nil, e(Eshort));
+ l = get4(a[2:]);
+ a = a[6:];
+ o += 6;
+ }else if(rid & ShortWrap){
+ if(len a < 4)
+ return (nil, e(Eshort));
+ l = get2(a[2:]);
+ a = a[4:];
+ o += 4;
+ }else{
+ l = int a[1];
+ a = a[2:];
+ o += 2;
+ }
+ if(len a < l)
+ return (nil, e(Eshort));
+ replies[i] = (rid &~ 16rC0, a[0:l]);
+ o += l;
+ }
+ return (replies, nil);
+}