xref: /inferno-os/appl/lib/palm.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1implement Palm;
2
3#
4# Copyright © 2001-2003 Vita Nuova Holdings Limited.  All rights reserved.
5#
6# Based on ``Palm® File Format Specification'', Document Number 3008-004, 1 May 2001, by Palm Inc.
7# Doc compression based on description by Paul Lucas, 18 August 1998
8#
9
10include "sys.m";
11	sys: Sys;
12
13include "daytime.m";
14	daytime: Daytime;
15
16include "palm.m";
17
18# Exact value of "Jan 1, 1970 0:00:00 GMT" - "Jan 1, 1904 0:00:00 GMT"
19Epochdelta: con 2082844800;
20tzoff := 0;
21
22init(): string
23{
24	sys = load Sys Sys->PATH;
25	daytime = load Daytime Daytime->PATH;
26	if(daytime == nil)
27		return "can't load required module";
28	tzoff = daytime->local(0).tzoff;
29	return nil;
30}
31
32Record.new(id: int, attr: int, cat: int, size: int): ref Record
33{
34	return ref Record(id, attr, cat, array[size] of byte);
35}
36
37Resource.new(name: int, id: int, size: int): ref Resource
38{
39	return ref Resource(name, id, array[size] of byte);
40}
41
42Doc.open(m: Palmdb, file: ref Palmdb->PDB): (ref Doc, string)
43{
44	info := m->file.db.stat();
45	if(info.dtype != "TEXt" || info.creator != "REAd")
46		return (nil, "not a Doc file: wrong type or creator");
47	r := m->file.read(0);
48	if(r == nil)
49		return (nil, sys->sprint("not a valid Doc file: %r"));
50	a := r.data;
51	if(len a < 16)
52		return (nil, sys->sprint("not a valid Doc file: bad length: %d", len a));
53	maxrec := m->file.db.nentries()-1;
54	d := ref Doc;
55	d.m = m;
56	d.file = file;
57	d.version = get2(a);
58	err := "unknown";
59	if(d.version != 1 && d.version != 2)
60		err = "unknown Docfile version";
61	# a[2:] is spare
62	d.length = get4(a[4:]);
63	d.nrec = get2(a[8:]);
64	if(maxrec >= 0 && d.nrec > maxrec){
65		d.nrec = maxrec;
66		err = "invalid record count";
67	}
68	d.recsize = get2(a[10:]);
69	d.position = get4(a[12:]);
70	return (d, sys->sprint("unexpected Doc file format: %s", err));
71}
72
73Doc.iscompressed(d: self ref Doc): int
74{
75	return (d.version&7) == 2;		# high-order bits are sometimes used, ignore them
76}
77
78Doc.read(doc: self ref Doc, index: int): (string, string)
79{
80	m := doc.m;
81	DB, PDB: import m;
82	r := doc.file.read(index+1);
83	if(r == nil)
84		return (nil, sys->sprint("%r"));
85	(s, serr) := doc.unpacktext(r.data);
86	if(s == nil)
87		return (nil, serr);
88	return (s, nil);
89}
90
91Doc.unpacktext(doc: self ref Doc, a: array of byte): (string, string)
92{
93	nb := len a;
94	s: string;
95	if(!doc.iscompressed()){
96		for(i := 0; i < nb; i++)
97			s[len s] = int a[i];	# assumes Latin-1
98		return (s, nil);
99	}
100	o := 0;
101	for(i := 0; i < nb;){
102		c := int a[i++];
103		if(c >= 9 && c <= 16r7F || c == 0)
104			s[o++] = c;
105		else if(c >= 1 && c <= 8){
106			if(i+c > nb)
107				return (nil, "missing data in record");
108			while(--c >= 0)
109				s[o++] = int a[i++];
110		}else if(c >= 16rC0 && c <= 16rFF){
111			s[o] = ' ';
112			s[o+1] = c & 16r7F;
113			o += 2;
114		}else{	# c >= 0x80 && c <= 16rBF
115			v := int a[i++];
116			m := ((c & 16r3F)<<5)|(v>>3);
117			n := (v&7) + 3;
118			if(m == 0 || m > o)
119				return (nil, sys->sprint("data is corrupt: m=%d n=%d o=%d", m, n, o));
120			for(; --n >= 0; o++)
121				s[o] = s[o-m];
122		}
123	}
124	return (s, nil);
125}
126
127Doc.textlength(doc: self ref Doc, a: array of byte): int
128{
129	nb := len a;
130	if(!doc.iscompressed())
131		return nb;
132	o := 0;
133	for(i := 0; i < nb;){
134		c := int a[i++];
135		if(c >= 9 && c <= 16r7F || c == 0)
136			o++;
137		else if(c >= 1 && c <= 8){
138			if(i+c > nb)
139				return -1;
140			o += c;
141			i += c;
142		}else if(c >= 16rC0 && c <= 16rFF){
143			o += 2;
144		}else{	# c >= 0x80 && c <= 16rBF
145			v := int a[i++];
146			m := ((c & 16r3F)<<5)|(v>>3);
147			n := (v&7) + 3;
148			if(m == 0 || m > o)
149				return -1;
150			o += n;
151		}
152	}
153	return o;
154}
155
156id2s(i: int): string
157{
158	if(i == 0)
159		return "";
160	return sys->sprint("%c%c%c%c", (i>>24)&16rFF, (i>>16)&16rFF, (i>>8)&16rFF, i&16rFF);
161}
162
163s2id(s: string): int
164{
165	n := 0;
166	for(i := 0; i < 4; i++){
167		c := 0;
168		if(i < len s)
169			c = s[i] & 16rFF;
170		n = (n<<8) | c;
171	}
172	return n;
173}
174
175DBInfo.new(name: string, attr: int, dtype: string, version: int, creator: string): ref DBInfo
176{
177	info := ref DBInfo;
178	info.name = name;
179	info.attr = attr;
180	info.version = version;
181	info.ctime = daytime->now();
182	info.mtime = daytime->now();
183	info.btime = 0;
184	info.modno = 0;
185	info.dtype = dtype;
186	info.creator = creator;
187	info.uidseed = 0;
188	info.index = 0;
189	return info;
190}
191
192Categories.new(labels: array of string): ref Categories
193{
194	c := ref Categories;
195	c.renamed = 0;
196	c.lastuid = 0;
197	c.labels = array[16] of string;
198	c.uids = array[] of {0 to 15 => 0};
199	for(i := 0; i < len labels && i < 16; i++){
200		c.labels[i] = labels[i];
201		c.lastuid = 16r80 + i;
202		c.uids[i] = c.lastuid;
203	}
204	return c;
205}
206
207Categories.unpack(a: array of byte): ref Categories
208{
209	if(len a < 16r114)
210		return nil;		# doesn't match the structure
211	c := ref Categories;
212	c.renamed = get2(a);
213	c.labels = array[16] of string;
214	c.uids = array[16] of int;
215	j := 2;
216	for(i := 0; i < 16; i++){
217		c.labels[i] = latin1(a[j:j+16], 0);
218		j += 16;
219		c.uids[i] = int a[16r102+i];
220	}
221	c.lastuid = int a[16r112];
222	# one byte of padding is shown on p. 26, but
223	# two more are invariably used in practice
224	# before application specific data.
225	if(len a > 16r116)
226		c.appdata = a[16r116:];
227	return c;
228}
229
230Categories.pack(c: self ref Categories): array of byte
231{
232	a := array[16r116 + len c.appdata] of byte;
233	put2(a, c.renamed);
234	j := 2;
235	for(i := 0; i < 16; i++){
236		puts(a[j:j+16], c.labels[i]);
237		j += 16;
238		a[16r102+i] = byte c.uids[i];
239	}
240	a[16r112] = byte c.lastuid;
241	a[16r113] = byte 0;	# pad shown on p. 26
242	a[16r114] = byte 0;	# extra two bytes of padding used in practice
243	a[16r115] = byte 0;
244	if(c.appdata != nil)
245		a[16r116:] = c.appdata;
246	return a;
247}
248
249Categories.mkidmap(c: self ref Categories): array of int
250{
251	a := array[256] of {* => 0};
252	for(i := 0; i < len c.uids; i++)
253		a[c.uids[i]] = i;
254	return a;
255}
256
257#
258# because PalmOS treats all times as local times, and doesn't associate
259# them with time zones, we'll convert using local time on Plan 9 and Inferno
260#
261
262pilot2epoch(t: int): int
263{
264	if(t == 0)
265		return 0;	# we'll assume it's not set
266	return t - Epochdelta + tzoff;
267}
268
269epoch2pilot(t: int): int
270{
271	if(t == 0)
272		return t;
273	return t - tzoff + Epochdelta;
274}
275
276#
277# map Palm name to string, assuming iso-8859-1,
278# but remap space and /
279#
280latin1(a: array of byte, remap: int): string
281{
282	s := "";
283	for(i := 0; i < len a; i++){
284		c := int a[i];
285		if(c == 0)
286			break;
287		if(remap){
288			if(c == ' ')
289				c = 16r00A0;	# unpaddable space
290			else if(c == '/')
291				c = 16r2215;	# division /
292		}
293		s[len s] = c;
294	}
295	return s;
296}
297
298#
299# map from Unicode to Palm name
300#
301filename(name: string): string
302{
303	s := "";
304	for(i := 0; i < len name; i++){
305		c := name[i];
306		if(c == ' ')
307			c = 16r00A0;	# unpaddable space
308		else if(c == '/')
309			c = 16r2215;	# division solidus
310		s[len s] = c;
311	}
312	return s;
313}
314
315dbname(name: string): string
316{
317	s := "";
318	for(i := 0; i < len name; i++){
319		c := name[i];
320		case c {
321		0 =>			c = ' ';	# unlikely, but just in case
322		16r2215 =>	c = '/';
323		16r00A0 =>	c = ' ';
324		}
325		s[len s] = c;
326	}
327	return s;
328}
329
330#
331# string conversion: can't use (string a) because
332# the bytes are Latin1, not Unicode
333#
334gets(a: array of byte): string
335{
336	s := "";
337	for(i := 0; i < len a; i++)
338		s[len s] = int a[i];
339	return s;
340}
341
342puts(a: array of byte, s: string)
343{
344	for(i := 0; i < len a-1 && i < len s; i++)
345		a[i] = byte s[i];
346	for(; i < len a; i++)
347		a[i] = byte 0;
348}
349
350#
351#  big-endian packing
352#
353
354get4(p: array of byte): int
355{
356	return (((((int p[0] << 8) | int p[1]) << 8) | int p[2]) << 8) | int p[3];
357}
358
359get3(p: array of byte): int
360{
361	return (((int p[0] << 8) | int p[1]) << 8) | int p[2];
362}
363
364get2(p: array of byte): int
365{
366	return (int p[0]<<8) | int p[1];
367}
368
369put4(p: array of byte, v: int)
370{
371	p[0] = byte (v>>24);
372	p[1] = byte (v>>16);
373	p[2] = byte (v>>8);
374	p[3] = byte (v & 16rFF);
375}
376
377put3(p: array of byte, v: int)
378{
379	p[0] = byte (v>>16);
380	p[1] = byte (v>>8);
381	p[2] = byte (v & 16rFF);
382}
383
384put2(p: array of byte, v: int)
385{
386	p[0] = byte (v>>8);
387	p[1] = byte (v & 16rFF);
388}
389
390#
391# DL protocol argument wrapping, based on conventions
392# extracted from include/Core/System/DLCommon.h in SDK 5
393#
394# tiny arguments
395#	id: byte
396#	size: byte	# excluding this header
397#	data: byte[]
398#
399# small arguments
400#	id: byte	# with 16r80 flag
401#	pad: byte
402#	size: byte[2]
403#	data: byte[]
404#
405# long arguments
406#	id: byte	# with 16r40 flag
407#	pad: byte
408#	size: byte[4]
409#	data: byte[]
410
411# wrapper format flag in request/response argument ID
412ShortWrap: con 16r80;	# 2-byte count
413LongWrap: con 16r40;	# 4-byte count
414
415Eshort: con "response shorter than expected";
416
417#
418# set the system error string
419#
420e(s: string): string
421{
422	if(s != nil)
423		sys->werrstr(s);
424	return s;
425}
426
427argsize(args: array of (int, array of byte)): int
428{
429	totnb := 0;
430	for(i := 0; i < len args; i++){
431		(nil, a) := args[i];
432		n := len a;
433		if(n > 65535)
434			totnb += 6;	# long wrap
435		else if(n > 255)
436			totnb += 4;	# short
437		else
438			totnb += 2;	# tiny
439		totnb += n;
440	}
441	return totnb;
442}
443
444packargs(out: array of byte, args: array of (int, array of byte)): array of byte
445{
446	for(i := 0; i < len args; i++){
447		(id, a) := args[i];
448		n := len a;
449		if(n > 65535){
450			out[0] = byte (LongWrap|ShortWrap|id);
451			out[1] = byte 0;
452			put4(out[2:], n);
453			out = out[6:];
454		}else if(n > 255){
455			out[0] = byte (ShortWrap|id);
456			out[1] = byte 0;
457			put2(out[2:], n);
458			out = out[4:];
459		}else{
460			out[0] = byte id;
461			out[1] = byte n;
462			out = out[2:];
463		}
464		out[0:] = a;
465		out = out[n:];
466	}
467	return out;
468}
469
470unpackargs(argc: int, reply: array of byte): (array of (int, array of byte), string)
471{
472	replies := array[argc] of (int, array of byte);
473	o := 0;
474	for(i := 0; i < len replies; i++){
475		o = (o+1)&~1;	# each argument starts at even offset
476		a := reply[o:];
477		if(len a < 2)
478			return (nil, e(Eshort));
479		rid := int a[0];
480		l: int;
481		if(rid & LongWrap){
482			if(len a < 6)
483				return (nil, e(Eshort));
484			l = get4(a[2:]);
485			a = a[6:];
486			o += 6;
487		}else if(rid & ShortWrap){
488			if(len a < 4)
489				return (nil, e(Eshort));
490			l = get2(a[2:]);
491			a = a[4:];
492			o += 4;
493		}else{
494			l = int a[1];
495			a = a[2:];
496			o += 2;
497		}
498		if(len a < l)
499			return (nil, e(Eshort));
500		replies[i] = (rid &~ 16rC0, a[0:l]);
501		o += l;
502	}
503	return (replies, nil);
504}
505