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