diff options
Diffstat (limited to 'appl/lib/palm.b')
| -rw-r--r-- | appl/lib/palm.b | 504 |
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); +} |
