1 /**
  2  *  ---------
  3  * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
  4  * |#       #|  
  5  * |#       #|  Copyright (c) 1999-2009 CardContact Software & System Consulting
  6  * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
  7  *  --------- 
  8  *
  9  *  This file is part of OpenSCDP.
 10  *
 11  *  OpenSCDP is free software; you can redistribute it and/or modify
 12  *  it under the terms of the GNU General Public License version 2 as
 13  *  published by the Free Software Foundation.
 14  *
 15  *  OpenSCDP is distributed in the hope that it will be useful,
 16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 18  *  GNU General Public License for more details.
 19  *
 20  *  You should have received a copy of the GNU General Public License
 21  *  along with OpenSCDP; if not, write to the Free Software
 22  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 23  *
 24  * @fileoverview Implementation of a ISO 7816-4 file system simulation
 25  */
 26 
 27 
 28 load("apdu.js");
 29 load("securityenvironment.js");
 30 
 31 
 32 /**
 33  * Create a File Control Parameter containing information about a file system node
 34  *
 35  * @class Class storing File Control Parameter for a file system node
 36  * @constructor
 37  */
 38 function FCP() {
 39 }
 40 
 41 
 42 /** File type for DF */
 43 FCP.DEDICATEDFILE = 0x38;
 44 
 45 /** File type for transparent EF */
 46 FCP.TRANSPARENT   = 0x01;
 47 
 48 /** File type for record oriented EF with fixed record size */
 49 FCP.LINEARFIXED   = 0x02;
 50 
 51 /** File type for record oriented EF with variable record size */
 52 FCP.LINEARVARIABLE   = 0x04;
 53 
 54 
 55 /**
 56  * Convert an integer value into an two byte ByteString
 57  *
 58  * @param {Number} val the value
 59  * @type ByteString
 60  * @return the 2 byte encoded value MSB||LSB
 61  */
 62 FCP.short2bytestring = function(val) {
 63 	var bb = new ByteBuffer();
 64 	bb.append(val >> 8);
 65 	bb.append(val & 0xFF);
 66 	return(bb.toByteString());
 67 }
 68 
 69 
 70 
 71 /**
 72  * Construct a new FCP object from parameters.
 73  *
 74  * <p>This function should never be called directly. Use newTransparentDF(), newDF() or newLinearEF() instead.</p>
 75  *
 76  * @param {String|ByteString} fid the file identifier (2 Bytes)
 77  * @param {Number} sfi the short file identifier or -1 or 0 if not defined
 78  * @param {Number} type the file type, one of FCP.DEDICATEDFILE, FCP.TRANSPARENT or FCP.LINEAR*
 79  * @param {Boolean} shareable true, if file may be shared between logical channels
 80  * @param {Boolean} internal true, if file is internal only and not externally selectable
 81  * @param {ByteString} supl supplemental information
 82  * @type FCP
 83  * @return the newly constructed FCP object
 84  */
 85 FCP.newFCP = function(fid, sfi, type, shareable, internal, supl) {
 86 	var fcp = new FCP();
 87 	
 88 	if (fid != null) {
 89 		if (typeof(fid) == "string") {
 90 			if (fid.length != 4) {
 91 				throw new GPError("FCP", GPError.INVALID_DATA, 0, "File Identifier must be 2 bytes");
 92 			}
 93 			fcp.fid = new ByteString(fid, HEX);
 94 		} else if (fid instanceof ByteString) {
 95 			if (fid.length != 2) {
 96 				throw new GPError("FCP", GPError.INVALID_DATA, 0, "File Identifier must be 2 bytes");
 97 			}
 98 			fcp.fid = fid;
 99 		} else {
100 			throw new GPError("FCP", GPError.INVALID_TYPE, 0, "Argument must be of type String or ByteString");
101 		}
102 	}
103 	
104 	if (typeof(sfi) != "number") {
105 		throw new GPError("FCP", GPError.INVALID_TYPE, 1, "Argument must be of type Number");
106 	}
107 	if ((sfi >= -1) && (sfi <= 30)) {
108 		if (sfi > 0) {
109 			fcp.sfi = sfi;
110 		}
111 	} else {
112 		throw new GPError("FCP", GPError.INVALID_DATA, 1, "SFI must be in the range 1 to 30 or 0 if not defined");
113 	}
114 
115 	if (typeof(type) != "number") {
116 		throw new GPError("FCP", GPError.INVALID_TYPE, 2, "Argument must be of type Number");
117 	}
118 	fcp.type = type;
119 
120 	if (typeof(shareable) != "boolean") {
121 		throw new GPError("FCP", GPError.INVALID_TYPE, 3, "Argument must be of type Boolean");
122 	}
123 	fcp.shareable = shareable;
124 
125 	if (typeof(internal) != "boolean") {
126 		throw new GPError("FCP", GPError.INVALID_TYPE, 4, "Argument must be of type Boolean");
127 	}
128 	fcp.internal = internal;
129 
130 	fcp.supl = supl;
131 	return fcp;
132 }
133 
134 
135 
136 /**
137  * Construct a new FCP object for an EF of type transparent.
138  *
139  * @param {String|ByteString} fid the file identifier (2 Bytes)
140  * @param {Number} sfi the short file identifier or -1 or 0 if not defined
141  * @param {Number} size the file size
142  * @param {ByteString} supl supplemental information
143  * @type FCP
144  * @return the newly constructed FCP object
145  */
146 FCP.newTransparentEF = function(fid, sfi, size, supl) {
147 	if (typeof(size) != "number") {
148 		throw new GPError("FCP", GPError.INVALID_TYPE, 2, "Argument size must be of type Number");
149 	}
150 
151 	var fcp = FCP.newFCP(fid, sfi, FCP.TRANSPARENT, false, false, supl);
152 
153 	fcp.size = size;
154 	return fcp;
155 }
156 
157 
158 
159 /**
160  * Construct a new FCP object for a DF.
161  *
162  * @param {String|ByteString} fid the file identifier (2 Bytes)
163  * @param {ByteString} aid the DF's application identifier (DFName)
164  * @param {ByteString} supl supplemental information
165  * @type FCP
166  * @return the newly constructed FCP object
167  */
168 FCP.newDF = function(fid, aid, supl) {
169 	var fcp = FCP.newFCP(fid, -1, FCP.DEDICATEDFILE, false, false, supl);
170 
171 	if (aid != null) {
172 		if ((typeof(aid) != "object") && !(aid instanceof(ByteString))) {
173 			throw new GPError("FCP", GPError.INVALID_TYPE, 2, "Argument size must be of type Number");
174 		}
175 		fcp.aid = aid;
176 	}
177 
178 	return fcp;
179 }
180 
181 
182 
183 /**
184  * Construct a new FCP object for an EF of type linear.
185  *
186  * @param {String|ByteString} fid the file identifier (2 Bytes)
187  * @param {Number} sfi the short file identifier or -1 or 0 if not defined
188  * @param {Number} type the file type, one of FCP.LINEARFIXED or FCP.LINEARVARIABLE
189  * @param {Number} recno the maximum number of records
190  * @param {Number} recsize the maximum or fixed record size
191  * @param {ByteString} supl supplemental information
192  * @type FCP
193  * @return the newly constructed FCP object
194  */
195 FCP.newLinearEF = function(fid, sfi, type, recno, recsize, supl) {
196 	if (typeof(recsize) != "number") {
197 		throw new GPError("FCP", GPError.INVALID_TYPE, 3, "Argument recsize must be of type Number");
198 	}
199 	if (typeof(recno) != "number") {
200 		throw new GPError("FCP", GPError.INVALID_TYPE, 4, "Argument recno must be of type Number");
201 	}
202 
203 	var fcp = FCP.newFCP(fid, sfi, type, false, false, supl);
204 	return fcp;
205 }
206 
207 
208 
209 /**
210  * Returns the File Identifier (FID)
211  *
212  * @type ByteString
213  * @return the FID
214  */
215 FCP.prototype.getFID = function() {
216 	return this.fid;
217 }
218 
219 
220 
221 /**
222  * Returns the Application Identifier (AID)
223  *
224  * @type ByteString
225  * @return the AID
226  */
227 FCP.prototype.getAID = function() {
228 	return this.aid;
229 }
230 
231 
232 
233 /**
234  * Returns the Short File Identifier (SFI)
235  *
236  * @type Number
237  * @return the SFI
238  */
239 FCP.prototype.getSFI = function() {
240 	return this.sfi;
241 }
242 
243 
244 
245 /**
246  * Returns the encoded FCP
247  *
248  * @type ByteString
249  * @return the encoded FCP
250  */
251 FCP.prototype.getBytes = function() {
252 	var fcp = new ASN1("fcp", 0x62);
253 
254 	if (typeof(this.size) != "undefined") {
255 		fcp.add(new ASN1("fileSizeTransparent", 0x80, FCP.short2bytestring(this.size)));
256 	}
257 
258 	var bb = new ByteBuffer();
259 	bb.append(this.type);
260 	
261 	// ToDo: extra type bytes
262 	
263 	fcp.add(new ASN1("fileDescriptor", 0x82, bb.toByteString()));
264 	
265 	if (typeof(this.fid) != "undefined") {
266 		fcp.add(new ASN1("fileIdentifier", 0x83, this.fid));
267 	}
268 	
269 	if (typeof(this.aid) != "undefined") {
270 		fcp.add(new ASN1("dFName", 0x84, this.aid));
271 	}
272 	
273 	if (typeof(this.sfi) != "undefined") {
274 		var bb = new ByteBuffer();
275 		bb.append(this.sfi << 3);
276 		fcp.add(new ASN1("shortFileIdentifier", 0x88, bb.toByteString()));
277 	}
278 	
279 	return(fcp.getBytes());
280 }
281 
282 
283 
284 /**
285  * Returns the FCI
286  *
287  * @type ASN1
288  * @return the FCI
289  */
290 FCP.prototype.getFCI = function() {
291 	var fci = new ASN1("fci", 0x6F);
292 
293 	if (typeof(this.aid) != "undefined") {
294 		fci.add(new ASN1("dFName", 0x84, this.aid));
295 	}
296 
297 	if (this.supl) {
298 		fci.add(new ASN1(this.supl));
299 	}
300 
301 	return(fci);
302 }
303 
304 
305 
306 /**
307  * Return a human readible string for this object
308  *
309  * @type String
310  * @return the string
311  */
312 FCP.prototype.toString = function() {
313 	var str = "FCP(";
314 	
315 	for (var i in this) {
316 		if (typeof(this[i]) != "function") {
317 			str += i + "=" + this[i] + ",";
318 		}
319 	}
320 	str += ")";
321 	return str;
322 }
323 
324 
325 
326 /**
327  * Construct a file system node
328  *
329  * @class Abstract class for file system nodes
330  * @constructor
331  */
332 function FSNode(fcp) {
333 	this.parent = null;
334 	this.fcp = fcp;
335 }
336 
337 
338 
339 /**
340  * Sets the parent for this node
341  *
342  * @param {DF} the parent node
343  */
344 FSNode.prototype.setParent = function(parent) {
345 	if ((typeof(parent) != "object") && !(parent instanceof(DF))) {
346 		throw new GPError("FSNode", GPError.INVALID_TYPE, 0, "Argument parent must be of type DF");
347 	}
348 	this.parent = parent;
349 }
350 
351 
352 
353 /**
354  * Gets the parent node for this node
355  *
356  * @type DF
357  * @returns the parent node
358  */
359 FSNode.prototype.getParent = function() {
360 	return this.parent;
361 }
362 
363 
364 
365 /**
366  * Gets the file control parameter for this node
367  *
368  * @type FCP
369  * @returns the FCP
370  */
371 FSNode.prototype.getFCP = function() {
372 	return this.fcp;
373 }
374 
375 
376 
377 /**
378  * Returns true if this is a DF
379  *
380  * @type boolean
381  * @return true if this is a DF
382  */
383 FSNode.prototype.isDF = function() {
384 	return (this instanceof DF);
385 }
386 
387 
388 
389 /**
390  * Returns a human readible string
391  *
392  * @type String
393  * @return a string
394  */
395 FSNode.prototype.toString = function() {
396 	if (!this.fcp || !this.fcp.getFID()) {
397 		return "FSNode";
398 	}
399 	return this.fcp.getFID().toString(HEX);
400 }
401 
402 
403 
404 /**
405  * Create a file system node that represents a transparent EF
406  *
407  * @class Class implementing a transparent EF
408  * @constructor
409  * @param {FCP} fcp the FCP for this EF
410  * @param {ByteString} contents the contents for this EF
411  */
412 function TransparentEF(fcp, contents) {
413 	if (!(fcp instanceof FCP)) {
414 		throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 1 must be of type FCP");
415 	}
416 
417 	if ((typeof(contents) != "undefined") && (contents != null) && !(contents instanceof ByteString)) {
418 		throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 2 must be of type ByteString");
419 	}
420 
421 	FSNode.call(this, fcp);
422 	this.content = contents;
423 }
424 
425 TransparentEF.prototype = new FSNode();
426 TransparentEF.prototype.constructor = TransparentEF;
427 
428 
429 
430 /**
431  * Reads data from a transparent EF
432  *
433  * @param {APDU} apdu the APDU used for reading
434  * @param {Number} offset the offset to read from
435  * @param {Number} length the length in bytes or 0 for all in short APDU or 65536 for all in extended APDUs
436  * @type ByteString
437  * @return the data read
438  */
439 TransparentEF.prototype.readBinary = function(apdu, offset, length) {
440 	if (typeof(offset) != "number") {
441 		throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Offset must be type Number");
442 	}
443 	if (typeof(length) != "number") {
444 		throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Length must be type Number");
445 	}
446 
447 	if (offset >= this.content.length) {
448 		throw new GPError("TransparentEF", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Offset out of range");
449 	}
450 	
451 	var rlen = length;
452 	if ((length == 0) || (length == 65536)) {
453 		rlen = this.content.length - offset;
454 		if ((length == 0) && (rlen > 256)) {
455 			rlen = 256;
456 		}
457 	}
458 
459 	if (offset + rlen > this.content.length) {
460 		apdu.setSW(APDU.SW_EOF);
461 		rlen = this.content.length - offset;
462 	} else {
463 		apdu.setSW(APDU.SW_OK);
464 	}
465 
466 	return this.content.bytes(offset, rlen);
467 }
468 
469 
470 
471 /**
472  * Update data in transparent EF
473  *
474  * @param {APDU} apdu the APDU used for updating
475  * @param {Number} offset the offset to update
476  * @param {ByteString} data the data to write into the EF
477  */
478 TransparentEF.prototype.updateBinary = function(apdu, offset, data) {
479 	if (typeof(offset) != "number") {
480 		throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Offset must be type Number");
481 	}
482 	if ((typeof(data) != "object") || !(data instanceof ByteString)) {
483 		throw new GPError("TransparentEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Data must be a ByteString");
484 	}
485 
486 	if (offset + data.length > this.fcp.size) {
487 		throw new GPError("TransparentEF", GPError.INVALID_DATA, APDU.SW_WRONGLENGTH, "Writing beyond file limit");
488 	}
489 
490 	if (this.content) {
491 		if (offset > this.content.length) {
492 			throw new GPError("TransparentEF", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Offset out of range");
493 		}
494 		var newcontent = this.content.bytes(0, offset).concat(data);
495 		if (this.content.length > newcontent.length) {
496 			newcontent = newcontent.concat(this.content.bytes(newcontent.length));
497 		}
498 	} else {
499 		if (offset > 0) {
500 			throw new GPError("TransparentEF", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Offset out of range");
501 		}
502 		var newcontent = data;
503 	}
504 
505 	this.content = newcontent;
506 	apdu.setSW(APDU.SW_OK);
507 }
508 
509 
510 
511 /**
512  * Creates a LinearEF
513  *
514  * @class Class implementing linear EFs
515  * @constructor
516  * @param {FCP} the file control parameter
517  * @param {ByteString[]} records the array of records
518  */
519 function LinearEF(fcp, records) {
520 	if (!(fcp instanceof FCP)) {
521 		throw new GPError("LinearEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 1 must be of type FCP");
522 	}
523 	print(typeof(records));
524 	if ((typeof(records) != "undefined") && (records != null) && (typeof(records) != "object")) {
525 		throw new GPError("LinearEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 2 must be of type ByteString[]");
526 	}
527 
528 	FSNode.call(this, fcp);
529 	this.records = records;
530 }
531 
532 LinearEF.prototype = new FSNode();
533 LinearEF.prototype.constructor = LinearEF;
534 
535 
536 
537 /**
538  * Reads a record from a linear EF
539  *
540  * @param {APDU} apdu the APDU used for reading
541  * @param {Number} recno the record number
542  * @param {Number} qualifier the qualifier as encoded in bit b3 - b1 of P1
543  * @param {Number} length the length in bytes or 0 for all in short APDU or 65536 for all in extended APDUs
544  * @type ByteString
545  * @return the data read
546  */
547 LinearEF.prototype.readRecord = function(apdu, recno, qualifier, length) {
548 	if (typeof(recno) != "number") {
549 		throw new GPError("LinearEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Record number must be type Number");
550 	}
551 	if (typeof(qualifier) != "number") {
552 		throw new GPError("LinearEF", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Qualifier must be type Number");
553 	}
554 
555 	if (recno == 0) {
556 		throw new GPError("LinearEF", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Current record referencing with P1=00 not support");
557 	}
558 	recno--;
559 
560 	if (recno >= this.records.length) {
561 		throw new GPError("LinearEF", GPError.INVALID_DATA, APDU.SW_RECORDNOTFOUND, "Record number exeeds number of defined records");
562 	}
563 
564 	if (qualifier != 4) {
565 		throw new GPError("LinearEF", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Only absolute record references supported");
566 	}
567 
568 	var record = this.records[recno];
569 
570 	var rlen = length;
571 	if ((length == 0) || (length == 65536)) {
572 		rlen = record.length;
573 		if ((length == 0) && (rlen > 256)) {
574 			rlen = 256;
575 		}
576 	}
577 
578 	if (rlen > record.length) {
579 		apdu.setSW(APDU.SW_EOF);
580 		rlen = record.length;
581 	} else {
582 		apdu.setSW(APDU.SW_OK);
583 	}
584 
585 	return record.left(rlen);
586 }
587 
588 
589 
590 /**
591  * Creates a Dedicated File (DF)
592  *
593  * <p>The constructor supports as argument a list of child elements</p>
594  *
595  * @class Class implementing dedicated files
596  * @constructor
597  * @param {FCP} the file control parameter
598  */
599 function DF(fcp) {
600 	this.childs = new Array();
601 	this.fidmap = new Array();
602 	this.sfimap = new Array();
603 	this.aidmap = new Array();
604 	this.meta = new Array();
605 	
606 	FSNode.call(this, fcp);
607 	
608 	if (arguments.length > 1) {
609 		for (var i = 1; i < arguments.length; i++) {
610 			var arg = arguments[i];
611 			this.add(arg);
612 		}
613 	}
614 }
615 
616 DF.prototype = new FSNode();
617 DF.prototype.constructor = DF;
618 
619 
620 
621 /**
622  * Adds a new child node to the DF
623  *
624  * @param {FSNode} node the node to add
625  */
626 DF.prototype.add = function(node) {
627 	this.childs.push(node);
628 	node.setParent(this);
629 
630 	var f = node.getFCP();
631 	
632 	var fid = f.getFID();
633 	if (fid) {
634 		if (this.fidmap[fid]) {
635 			throw new GPError("DF", GPError.INVALID_DATA, APDU.SW_FILEEXISTS, "Duplicate file identifier " + fid);
636 		}
637 		this.fidmap[fid] = node;
638 	}
639 
640 	if (node.isDF()) {
641 		var aid = f.getAID();
642 		if (aid) {
643 			if (this.aidmap[aid]) {
644 				throw new GPError("DF", GPError.INVALID_DATA, APDU.SW_FILEEXISTS, "Duplicate application identifier " + aid);
645 			}
646 			this.aidmap[aid] = node;
647 		}
648 	} else {
649 		var sfi = f.getSFI();
650 //		print("Found SFI " + sfi);
651 		if (typeof(sfi) != "undefined") {
652 			if (this.sfimap[sfi]) {
653 				throw new GPError("DF", GPError.INVALID_DATA, APDU.SW_FILEEXISTS, "Duplicate short file identifier " + sfi);
654 			}
655 			this.sfimap[sfi] = node;
656 		}
657 	}
658 
659 }
660 
661 
662 
663 /**
664  * Add meta information to DF
665  *
666  * @param {String} name name of meta information
667  * @param {Object} value value of meta information
668  */
669 DF.prototype.addMeta = function(name, value) {
670 	this.meta[name] = value;
671 }
672 
673 
674 
675 /**
676  * Add object to DF
677  *
678  * @param {Object} o object to be added. Must have property type and id.
679  */
680 DF.prototype.addObject = function(o) {
681 	assert((typeof(o) == "object") && (o instanceof FileSystemIdObject), "Argument must be instance of FileSystemIdObject");
682 	if (typeof(this.meta[o.getType()]) == "undefined") {
683 		this.meta[o.getType()] = [];
684 	}
685 	this.meta[o.getType()][o.getId()] = o;
686 }
687 
688 
689 
690 /**
691  * Select a file contained in the DF using the file identifier
692  *
693  * @param {ByteString} the file identifier
694  * @type FSNode
695  * @return the found node or undefined
696  */
697 DF.prototype.selectByFID = function(fid) {
698 	return this.fidmap[fid];
699 }
700 
701 
702 
703 /**
704  * Select a file contained in the DF using the short file identifier
705  *
706  * @param {Number} the short file identifier
707  * @type FSNode
708  * @return the found node or undefined
709  */
710 DF.prototype.selectBySFI = function(sfi) {
711 	return this.sfimap[sfi];
712 }
713 
714 
715 
716 /**
717  * Select a DF contained in the DF using the application identifier
718  *
719  * @param {ByteString} the application identifier
720  * @type FSNode
721  * @return the found node or undefined
722  */
723 DF.prototype.selectByAID = function(aid) {
724 	return this.aidmap[aid];
725 }
726 
727 
728 
729 /**
730  * Dump the file system system recursively starting this this node
731  *
732  * @param {String} indent the string to prefix the output with
733  * @type String
734  * @return the dump 
735  */
736 DF.prototype.dump = function(indent) {
737 	if (typeof(indent) == "undefined") {
738 		indent = "";
739 	}
740 	var str = indent + this.toString() + "\n";
741 	
742 	if (this instanceof DF) {
743 		for (var i in this.meta) {
744 			str += indent + "  Meta:" + i + "\n";
745 			if (typeof(this.meta[i]) == "object") {
746 				for each (e in this.meta[i]) {
747 					if (e instanceof FileSystemIdObject) {
748 						str += indent + "    " + e.toString() + "\n";
749 					}
750 				}
751 			}
752 		}
753 	}
754 	
755 	for (var i = 0; i < this.childs.length; i++) {
756 		var c = this.childs[i];
757 		
758 		if (c instanceof DF) {
759 			str += c.dump("  " + indent);
760 		} else {
761 			str += "  " + indent + c.toString() + "\n";
762 		}
763 	}
764 	return str;
765 }
766 
767 
768 
769 /**
770  * Create a file system object identifiable by an id
771  *
772  * @class Abstract class for file system objects identified by an identifier
773  *
774  * @param {String} name the human readable name of the object
775  * @param {Number} id the id
776  */
777 function FileSystemIdObject(name, id) {
778 	this.name = name;
779 	this.id = id;
780 }
781 
782 
783 
784 /**
785  * Return id of object
786  */
787 FileSystemIdObject.prototype.getId = function() {
788 	return this.id;
789 }
790 
791 
792 
793 /**
794  * Return type of object
795  * @type string
796  * @return type of object
797  */
798 FileSystemIdObject.prototype.getType = function() {
799 	throw new GPError("FileSystemIdObject", GPError.NOT_IMPLEMENTED, 0, "Derived class must override getType()");
800 }
801 
802 
803 
804 /**
805  * Return human readable string
806  */
807 FileSystemIdObject.prototype.toString = function() {
808 	return this.name + "(" + this.id + ")";
809 }
810 
811 
812 
813 /**
814  * Create a file selector object
815  *
816  * @class Class implementing a file selector used to store information about the currently selected
817  *        file system object and to process the SELECT APDU
818  * @constructor
819  * @param {DF} mf the master file
820  */
821 function FileSelector(mf) {
822 	if ((typeof(mf) != "object") && !(mf instanceof DF)) {
823 		throw new GPError("FileSelector", GPError.INVALID_TYPE, APDU.SW_GENERALERROR, "Argument 1 must be of type DF");
824 	}
825 	
826 	this.mf = mf;
827 	this.selectMF();
828 	
829 	this.se = { VEXK: new SecurityEnvironment(), CDIK: new SecurityEnvironment(), SMRES: new SecurityEnvironment(), SMCOM: new SecurityEnvironment()};
830 	this.globalAuthenticationState = [];
831 }
832 
833 
834 
835 /**
836  * Returns the current EF, if any
837  *
838  * @type EF
839  * @return the current EF or null
840  */
841 FileSelector.prototype.getCurrentEF = function() {
842 	return this.currentEF;
843 }
844 
845 
846 
847 /**
848  * Return the current security environment
849  *
850  * @type Object
851  * @returns Object with properties VEXK, CDIK, SMRES and SMCOM containing SecurityEnvironment objects
852  */
853 FileSelector.prototype.getSecurityEnvironment = function() {
854 	return this.se;
855 }
856 
857 
858 
859 /**
860  * Return meta data associated with the current DF or MF
861  *
862  * @param {String} name the meta data name
863  * @type Object
864  * @returns The meta data
865  */
866 FileSelector.prototype.getMeta = function(name) {
867 	var meta;
868 
869 	if (this.currentDF) {
870 //		print("DF selected: " + this.currentDF);
871 		var meta = this.currentDF.meta[name];
872 //		print("Found: " + meta);
873 	}
874 
875 	if (!meta) {
876 		meta = this.mf.meta[name];
877 	}
878 	return meta;
879 }
880 
881 
882 
883 /**
884  * Return object of given type identified by id
885  *
886  * <p>If bit b8 in the id is 1, then the search will start in the current DF. If the object
887  *    is not found, the search is continued in the MF. If the bit is not set, then the search
888  *    will only look into the MF.</p>
889  *
890  * @param {String} type the type of the object
891  * @param {Number} id the id, bit b8 indicating local DF or global MF search
892  * @type {Object}
893  * @returns the object of the requested type or null if not found
894  */
895 FileSelector.prototype.getObject = function(type, id) {
896 	var olist;
897 
898 	if (id & 0x80) {
899 		olist = this.currentDF.meta[type];
900 		if (olist) {
901 			var o = olist[id & 0x7F];
902 			
903 			if (o) {
904 				return o;
905 			}
906 		}
907 	}
908 
909 	olist = this.mf.meta[type];
910 	if (olist) {
911 		var o = olist[id & 0x7F];
912 
913 		if (o) {
914 			return o;
915 		}
916 	}
917 	return null;
918 }
919 
920 
921 
922 /**
923  * Enumerate objects of a defined type
924  *
925  * @param {String} type the type of the object
926  * @type {Number[]}
927  * @returns the list of objects found
928  */
929 FileSelector.prototype.enumerateObjects = function(type) {
930 	var idlist = [];
931 
932 	if (this.mf != this.currentDF) {
933 		for each (var o in this.currentDF.meta[type]) {
934 			idlist.push(o.getId());
935 		}
936 	}
937 
938 	for each (var o in this.mf.meta[type]) {
939 		idlist.push(o.getId());
940 	}
941 
942 	return idlist;
943 }
944 
945 
946 
947 /**
948  * Add authenticated object to the list of authentication states for the local DF or global MF
949  *
950  * @param{boolean} global true if global state else local DF state
951  * @param{AuthenticationObject} ao the authentication object for which authentication was successfull
952  */
953 FileSelector.prototype.addAuthenticationState = function(global, ao) {
954 	if (global) {
955 		this.globalAuthenticationState.push(ao);
956 	} else {
957 		this.localAuthenticationState.push(ao);
958 	}
959 }
960 
961 
962 
963 /**
964  * Add authenticated object to the list of authentication states for the local DF or global MF
965  *
966  * @param{boolean} global true if global state else local DF state
967  * @param{AuthenticationObject} ao the authentication object for which authentication was successfull
968  */
969 FileSelector.prototype.isAuthenticated = function(global, ao) {
970 	if (global) {
971 		var list = this.globalAuthenticationState;
972 	} else {
973 		var list = this.localAuthenticationState;
974 	}
975 	for each (var aao in list) {
976 		if (aao === ao) {
977 			return true;
978 		}
979 	}
980 	return false;
981 }
982 
983 
984 
985 /**
986  * Select the MF
987  */
988 FileSelector.prototype.selectMF = function() {
989 	this.currentDF = this.mf;
990 	this.currentEF = null;
991 	this.localAuthenticationState = [];
992 
993 	return this.mf;
994 }
995 
996 
997 
998 /**
999  * Select a DF entry by FID
1000  *
1001  * @param {ByteString} fid the file identifier
1002  * @param {boolean} check if file matches expected type EF or DF
1003  * @param {boolean} df true if the check must check for a DF type, else a EF type
1004  * @type FSNode
1005  * @return the selected file system node
1006  */
1007 FileSelector.prototype.selectFID = function(fid, check, df) {
1008 	var node = this.currentDF.selectByFID(fid);
1009 	
1010 	if (!node) {
1011 		throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "File " + fid + " not found");
1012 	}
1013 
1014 	if (check) {
1015 		if ((df && !node.isDF()) || (!df && node.isDF())) {
1016 			throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "File " + fid + " not found or not of matching type");
1017 		}
1018 	}
1019 	
1020 	if (node.isDF()) {
1021 		this.currentDF = node;
1022 		this.localAuthenticationState = [];
1023 		this.currentEF = null;
1024 	} else {
1025 		this.currentEF = node;
1026 	}
1027 	return node;
1028 }
1029 
1030 
1031 
1032 /**
1033  * Select a DF entry by SFI
1034  *
1035  * @param {Number} sfi the short file identifier
1036  * @type FSNode
1037  * @return the selected file system node
1038  */
1039 FileSelector.prototype.selectSFI = function(sfi) {
1040 	var node = this.currentDF.selectBySFI(sfi);
1041 	
1042 	if (!node) {
1043 		throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "File with SFI " + sfi + " not found");
1044 	}
1045 
1046 	this.currentEF = node;
1047 	return node;
1048 }
1049 
1050 
1051 
1052 /**
1053  * Processes the SELECT APDU
1054  *
1055  * <p>Supports in P1</p>
1056  * <ul>
1057  *  <li>'00' with empty data to select the MF</li>
1058  *  <li>'00' with "3F00" to select the MF</li>
1059  *  <li>'00' with fid to select an entry in the current DF</li>
1060  *  <li>'01' with fid to select a DF in the current DF</li>
1061  *  <li>'02' with fid to select an EF in the current DF</li>
1062  *  <li>'03' with empty data to select the parent</li>
1063  * </ul>
1064  * <p>Supports in P2</p>
1065  * <ul>
1066  *  <li>'00' with P1=='00' return no data</li>
1067  *  <li>'04' return FCP</li>
1068  *  <li>'0C' return no data</li>
1069  * </ul>
1070  * @param {APDU} apdu the select APDU
1071  */
1072 FileSelector.prototype.processSelectAPDU = function(apdu) {
1073 	var node;
1074 
1075 	var p2 = apdu.getP2();
1076 	if ((p2 != 0x00) && (p2 != 0x04) && (p2 != 0x0C)) {
1077 		throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Incorrect parameter P2 (" + p2.toString(16) + ")");
1078 	}
1079 
1080 	var data = apdu.getCData();
1081 	var p1 = apdu.getP1();
1082 	switch(p1) {
1083 	case 0x00:
1084 		if ((typeof(data) == "undefined") || (data.toString(HEX) == "3F00")) {
1085 			node = this.selectMF();
1086 		} else {
1087 			node = this.selectFID(data, false, false);
1088 		}
1089 		break;
1090 	case 0x01:
1091 		node = this.selectFID(data, true, true);
1092 		break;
1093 	case 0x02:
1094 		node = this.selectFID(data, true, false);
1095 		break;
1096 	case 0x03:
1097 		// ToDo data must be missing APDU.SW_INVLC
1098 		if (this.currentEF) {
1099 			this.currentEF = null;
1100 			node = this.currentDF;
1101 		} else {
1102 			node = this.currentDF.getParent();
1103 			if (node) {
1104 				this.currentDF = node;
1105 				this.localAuthenticationState = [];
1106 			} else {
1107 				node = this.currentDF;
1108 			}
1109 		}
1110 		break;
1111 	case 0x04:
1112 		node = this.mf.selectByAID(data);
1113 		if (typeof(node) == "undefined") {
1114 			throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_FILENOTFOUND, "Application " + data + " not found");
1115 		}
1116 		this.currentDF = node;
1117 		this.currentEF = null;
1118 		this.localAuthenticationState = [];
1119 		break;
1120 	default:
1121 		throw new GPError("FileSelector", GPError.INVALID_DATA, APDU.SW_INCP1P2, "Incorrect parameter P1 (" + p1.toString(16) + ")");
1122 	}
1123 	
1124 	switch(p2) {
1125 	case 0x00:
1126 		apdu.setRData(node.getFCP().getFCI().getBytes());
1127 		break;
1128 	case 0x04:
1129 		apdu.setRData(node.getFCP().getBytes());
1130 		break;
1131 	}
1132 
1133 	apdu.setSW(APDU.SW_OK);
1134 }
1135 
1136 
1137 
1138 /**
1139  * Return a human readable string for this object
1140  */
1141 FileSelector.prototype.toString = function() {
1142 	var str = "FileSelector: Current DF=" + this.currentDF + " / Current EF=" + this.currentEF;
1143 	if (this.globalAuthenticationState.length > 0) {
1144 		str += "\nGlobally authenticated objects:";
1145 		for each (var aao in this.globalAuthenticationState) {
1146 			str += "\n" + aao.toString();
1147 		}
1148 	}
1149 	if (this.localAuthenticationState.length > 0) {
1150 		str += "\nLocally authenticated objects:";
1151 		for each (var aao in this.localAuthenticationState) {
1152 			str += "\n" + aao.toString();
1153 		}
1154 	}
1155 	return str;
1156 }
1157 
1158 
1159 
1160 FileSelector.test = function() {
1161 
1162 	var aid = new ByteString("A0000000010101", HEX);
1163 
1164 	var mf = new DF(FCP.newDF("3F00", null),
1165 						new TransparentEF(FCP.newTransparentEF("2F00", -1, 100)),
1166 						new TransparentEF(FCP.newTransparentEF("2F01", 0x17, 100)),
1167 						new DF(FCP.newDF("DF01", aid),
1168 							new TransparentEF(FCP.newTransparentEF("2F01", -1, 100))
1169 						)
1170 					);
1171 
1172 	print(mf.dump(""));
1173 
1174 	assert(mf.isDF());
1175 	
1176 	var ef = mf.selectByFID(new ByteString("2F00", HEX));
1177 	assert(!ef.isDF());
1178 	assert(ef.getFCP().getFID().toString(HEX) == "2F00");
1179 
1180 	var ef = mf.selectBySFI(0x17);
1181 	assert(ef.getFCP().getFID().toString(HEX) == "2F01");
1182 	
1183 	var df = mf.selectByAID(aid);
1184 	assert(df.getFCP().getFID().toString(HEX) == "DF01");
1185 
1186 	var fs = new FileSelector(mf);
1187 	
1188 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("3F00", HEX));
1189 	fs.processSelectAPDU(a);
1190 	print(fs);
1191 	print(a);
1192 
1193 	var a = new APDU(0x00, 0xA4, 0x00, 0x0C, new ByteString("2F00", HEX));
1194 	fs.processSelectAPDU(a);
1195 	print(fs);
1196 	print(a);
1197 	
1198 	var a = new APDU(0x00, 0xA4, 0x01, 0x0C, new ByteString("DF01", HEX));
1199 	fs.processSelectAPDU(a);
1200 	print(fs);
1201 	print(a);
1202 
1203 	var a = new APDU(0x00, 0xA4, 0x02, 0x0C, new ByteString("2F01", HEX));
1204 	fs.processSelectAPDU(a);
1205 	print(fs);
1206 	print(a);
1207 }
1208 
1209 
1210 // test();
1211 
1212