|
1 # |
|
2 # Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 # All rights reserved. |
|
4 # This component and the accompanying materials are made available |
|
5 # under the terms of "Eclipse Public License v1.0" |
|
6 # which accompanies this distribution, and is available |
|
7 # at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 # |
|
9 # Initial Contributors: |
|
10 # Nokia Corporation - initial contribution. |
|
11 # |
|
12 # Contributors: |
|
13 # |
|
14 # Description: |
|
15 # |
|
16 import re |
|
17 from cone.public import exceptions |
|
18 import crml_reader |
|
19 from crml_model import * |
|
20 |
|
21 class CenRepEntry(object): |
|
22 """ |
|
23 Class representing an entry in a CenRep text file. |
|
24 """ |
|
25 def __init__(self, **kwargs): |
|
26 self.int = kwargs.get('int') |
|
27 self.crml_type = kwargs.get('crml_type') |
|
28 self.confml_type = kwargs.get('confml_type') |
|
29 self.value = kwargs.get('value') |
|
30 self.orig_value = kwargs.get('orig_value') |
|
31 self.access = kwargs.get('access') |
|
32 self.backup = kwargs.get('backup', False) |
|
33 |
|
34 @property |
|
35 def metadata(self): |
|
36 return _get_metadata(self.backup) |
|
37 |
|
38 def __lt__(self, other): |
|
39 return crml_reader.convert_num(self.int) < crml_reader.convert_num(other.int) |
|
40 |
|
41 class CenRepRfsRecord(object): |
|
42 """ |
|
43 Class representing an entry in the CenRep RFS text file. |
|
44 """ |
|
45 def __init__(self, repo_uid, key_uids=None): |
|
46 self.repo_uid = repo_uid |
|
47 self.key_uids = key_uids or [] |
|
48 |
|
49 def __eq__(self, other): |
|
50 if type(self) == type(other): return self.repo_uid == other.repo_uid |
|
51 else: return False |
|
52 |
|
53 def __ne__(self, other): |
|
54 return not (self == other) |
|
55 |
|
56 def __lt__(self, other): |
|
57 return self.repo_uid < other.repo_uid |
|
58 |
|
59 def __repr__(self): |
|
60 return "CenRepRfsRecord(repo_uid=%s, key_uids=%r)" % (self.repo_uid, self.key_uids) |
|
61 |
|
62 |
|
63 class CrmlTxtWriter(object): |
|
64 """ |
|
65 Writer class for generating CenRep .txt files based on a CRML model. |
|
66 """ |
|
67 |
|
68 def __init__(self, configuration, log): |
|
69 self.configuration = configuration |
|
70 self.log = log |
|
71 |
|
72 def get_cenrep_txt_data(self, repository): |
|
73 """ |
|
74 Return the text data for the CenRep txt generated based on the given |
|
75 CRML repository model. |
|
76 @return: Text data for the CenRep text file. |
|
77 """ |
|
78 data = [] |
|
79 |
|
80 # Generate header lines |
|
81 data.extend(self.get_header_lines(repository)) |
|
82 |
|
83 self._check_repository_attrs(repository) |
|
84 |
|
85 # Generate CenRep entries for all keys |
|
86 cenrep_entries = [] |
|
87 for key in repository.keys: |
|
88 cenrep_entries.extend(self.get_cenrep_entries(key)) |
|
89 |
|
90 # Generate entry lines based on the entries |
|
91 cenrep_entries.sort() |
|
92 for entry in cenrep_entries: |
|
93 data.append(self.get_cenrep_entry_line(entry)) |
|
94 |
|
95 data.append('') |
|
96 |
|
97 # Remove Nones from the line list |
|
98 data = filter(lambda val: val is not None, data) |
|
99 |
|
100 return '\r\n'.join(data) |
|
101 |
|
102 def get_cenrep_rfs_txt_data(self, rfs_records): |
|
103 """ |
|
104 Return the text data for the CenRep RFS txt generated based on the given |
|
105 CenRep RFS record list. |
|
106 """ |
|
107 data = [] |
|
108 |
|
109 # Make a distinct and sorted array of the records |
|
110 records = [] |
|
111 for r in rfs_records: |
|
112 if r not in records: records.append(r) |
|
113 records.sort() |
|
114 |
|
115 for record in records: |
|
116 repo_uid = record.repo_uid |
|
117 |
|
118 # Add padding zeros to the UID |
|
119 if len(repo_uid) < 8: |
|
120 repo_uid = (8 - (len(repo_uid) % 8)) * '0' + repo_uid |
|
121 temp = "CR %s" % repo_uid |
|
122 if record.key_uids: |
|
123 temp += " %s" % ' '.join(record.key_uids) |
|
124 data.append(temp) |
|
125 |
|
126 return '\r\n'.join(data) |
|
127 |
|
128 def get_cenrep_rfs_record(self, repository): |
|
129 """ |
|
130 Return the RFS record for the given CRML repository. |
|
131 |
|
132 @return: A CenRepRfsRecord object if the repository should be listed |
|
133 in cenrep_rfs.txt, None if not. |
|
134 """ |
|
135 # Get the UID as a hex value without the leading 0x |
|
136 repo_uid = _translate_key_uid(repository.uid_value)[2:] |
|
137 |
|
138 # Check if the whole repository has RFS=true |
|
139 if repository.rfs: |
|
140 return CenRepRfsRecord(repo_uid) |
|
141 |
|
142 # Collect the UIDs of the keys that should be listed |
|
143 rfs_key_uids = [] |
|
144 for key in repository.keys: |
|
145 if self._key_is_rfs(key) and key.int: |
|
146 # Get the UID as a hex value without the leading 0x |
|
147 uid = _translate_key_uid(key.int)[2:] |
|
148 rfs_key_uids.append(uid) |
|
149 |
|
150 if rfs_key_uids: |
|
151 return CenRepRfsRecord(repo_uid, rfs_key_uids) |
|
152 else: |
|
153 return None |
|
154 |
|
155 def _key_is_rfs(self, key): |
|
156 """ |
|
157 @return: True if the key UID should be listed in cenrep_rfs.txt |
|
158 """ |
|
159 if isinstance(key, CrmlSimpleKey): |
|
160 return bool(self._get_rfs_value(key.ref)) |
|
161 elif isinstance(key, CrmlBitmaskKey): |
|
162 for bit in key.bits: |
|
163 if self._get_rfs_value(bit.ref): |
|
164 return True |
|
165 else: |
|
166 return False |
|
167 |
|
168 def _get_rfs_value(self, ref): |
|
169 """ |
|
170 @return: The RFS value for the given setting, or None if not available. |
|
171 """ |
|
172 if ref is None: return |
|
173 |
|
174 try: |
|
175 feature = self._get_feature(ref) |
|
176 except exceptions.NotFound: |
|
177 # Feature not found in the configuration |
|
178 return None |
|
179 |
|
180 return feature.get_value(attr='rfs') |
|
181 |
|
182 def get_header_lines(self, repository): |
|
183 """ |
|
184 Return a list of lines to be written in the header section of the CenRep text file. |
|
185 """ |
|
186 data = ['cenrep', |
|
187 'version %s' % repository.version] |
|
188 |
|
189 if repository.owner: |
|
190 data.append('[owner]') |
|
191 data.append(repository.owner) |
|
192 |
|
193 data.append('[defaultmeta]') |
|
194 data.append(' %d' % _get_metadata(repository.backup)) |
|
195 for key in repository.keys: |
|
196 data.append(self.get_defaultmeta_line(key)) |
|
197 |
|
198 data.append('[platsec]') |
|
199 acc_text = self.get_access_line(repository.access) |
|
200 if acc_text: acc_text = ' ' + acc_text |
|
201 data.append(acc_text) |
|
202 for key in repository.keys: |
|
203 data.append(self.get_platsec_line(key, repository)) |
|
204 |
|
205 |
|
206 data.append('[Main]') |
|
207 return data |
|
208 |
|
209 def get_cenrep_entries(self, key): |
|
210 """ |
|
211 Generate CenRep entries based on the given CRML key object. |
|
212 @return: A list of CenRepEntry objects. |
|
213 """ |
|
214 if isinstance(key, CrmlSimpleKey): |
|
215 feature = self._get_feature(key.ref) |
|
216 entry = CenRepEntry(int = key.int, |
|
217 crml_type = key.type, |
|
218 confml_type = feature.get_type(), |
|
219 value = feature.get_value(), |
|
220 orig_value = feature.get_original_value(), |
|
221 backup = key.backup, |
|
222 access = key.access) |
|
223 return [entry] |
|
224 elif isinstance(key, CrmlBitmaskKey): |
|
225 return self.get_bitmask_key_cenrep_entries(key) |
|
226 elif isinstance(key, CrmlKeyRange): |
|
227 return self.get_key_range_cenrep_entries(key) |
|
228 else: |
|
229 raise TypeError("Unsupported CRML key object type %s" % type(key)) |
|
230 |
|
231 def get_key_range_cenrep_entries(self, key_range): |
|
232 """ |
|
233 Generate CenRep entries based on the given CrmlKeyRange object. |
|
234 @return: A list of CenRepEntry objects. |
|
235 """ |
|
236 entries = [] |
|
237 count = 0 |
|
238 |
|
239 # Generate the countInt entry if necessary |
|
240 if key_range.count_int is not None and key_range.ref is not None: |
|
241 try: |
|
242 feature = self._get_feature(key_range.ref) |
|
243 |
|
244 # For CT2 output compatibility |
|
245 if feature.get_type() != 'sequence': |
|
246 return [] |
|
247 |
|
248 values = feature.get_value() |
|
249 except exceptions.NotFound: |
|
250 values = [] |
|
251 |
|
252 count = len(values) |
|
253 |
|
254 entry = CenRepEntry(int = key_range.count_int, |
|
255 crml_type = 'int', |
|
256 confml_type = 'int', |
|
257 value = count, |
|
258 backup = key_range.backup, |
|
259 access = key_range.access) |
|
260 entries.append(entry) |
|
261 |
|
262 # Generate entries based on the sequence values |
|
263 for subkey in key_range.subkeys: |
|
264 full_ref = "%s.%s"% (key_range.ref, subkey.ref) |
|
265 |
|
266 try: |
|
267 feature = self._get_feature(full_ref) |
|
268 values = feature.get_value() |
|
269 confml_type = feature.get_type() |
|
270 backup = key_range.backup |
|
271 except exceptions.NotFound: |
|
272 # For CT2 output compatibility |
|
273 values = ['null' for i in xrange(count)] |
|
274 confml_type = None |
|
275 backup = False |
|
276 |
|
277 for i, value in enumerate(values): |
|
278 # Calculate the index of the entry |
|
279 index = self.get_index(crml_reader.convert_num(key_range.first_int), |
|
280 crml_reader.convert_num(key_range.first_index), |
|
281 crml_reader.convert_num(key_range.index_bits), |
|
282 i, |
|
283 crml_reader.convert_num(subkey.int)) |
|
284 |
|
285 entry = CenRepEntry(int = "0x%x" % index, |
|
286 crml_type = subkey.type, |
|
287 confml_type = confml_type, |
|
288 value = value, |
|
289 orig_value = value, |
|
290 backup = backup, |
|
291 access = key_range.access) |
|
292 entries.append(entry) |
|
293 |
|
294 return entries |
|
295 |
|
296 def get_bitmask_key_cenrep_entries(self, key): |
|
297 """ |
|
298 Generate CenRep entries based on the given CrmlBitmaskKey object. |
|
299 @return: A list of CenRepEntry objects. |
|
300 """ |
|
301 # Calculate the value based on the bit values |
|
302 # ------------------------------------------- |
|
303 value = 0 |
|
304 for bit in key.bits: |
|
305 feature = self._get_feature(bit.ref) |
|
306 bit_value = feature.get_value() |
|
307 if bit.invert: bit_value = not bit_value |
|
308 if bit_value: value |= 1 << (bit.index - 1) |
|
309 |
|
310 # Generate the textual representation of the bitmask value. |
|
311 # This is done at this point because in get_cenrep_entry_line() |
|
312 # we don't know anymore if the key was a bitmask key or a |
|
313 # simple key. |
|
314 # ------------------------------------------------------------- |
|
315 if key.type == 'binary': |
|
316 orig_value = "%X" % value |
|
317 # Add padding zeroes so that the number of digits |
|
318 # is divisible by 8 (done manually since the length |
|
319 # of a binary bitmask is unbounded). |
|
320 padding_zeroes = (8 - len(orig_value) % 8) * '0' |
|
321 # 4 is a special case for CT2 output compatibility |
|
322 if len(orig_value) != 4: |
|
323 orig_value = padding_zeroes + orig_value |
|
324 else: |
|
325 orig_value = str(value) |
|
326 |
|
327 entry = CenRepEntry(int = key.int, |
|
328 crml_type = key.type, |
|
329 confml_type = 'int', |
|
330 value = value, |
|
331 orig_value = orig_value, |
|
332 backup = key.backup, |
|
333 access = key.access) |
|
334 return [entry] |
|
335 |
|
336 def get_defaultmeta_line(self, key): |
|
337 """ |
|
338 Return the defaultmeta section line for the given CRML key object. |
|
339 """ |
|
340 if not isinstance(key, CrmlKeyRange): return None |
|
341 |
|
342 return "%s %s %d" % (key.first_int, |
|
343 key.last_int, |
|
344 _get_metadata(key.backup)) |
|
345 |
|
346 def get_platsec_line(self, key, repository): |
|
347 """ |
|
348 Return the platsec section line for the given CRML key object. |
|
349 """ |
|
350 if not isinstance(key, CrmlKeyRange): return None |
|
351 |
|
352 # In a key range platsec entry something must be present, so if |
|
353 # the access object is empty, use cap_rd and cap_wr from the repository's |
|
354 # global access definition |
|
355 access = key.access.copy() |
|
356 is_empty = True |
|
357 for attrname in ('sid_rd', 'cap_rd', 'sid_wr', 'cap_wr'): |
|
358 if getattr(access, attrname) not in ('', None): |
|
359 is_empty = False |
|
360 if is_empty: |
|
361 access.cap_rd = repository.access.cap_rd |
|
362 access.cap_wr = repository.access.cap_wr |
|
363 |
|
364 acc_text = self.get_access_line(access) |
|
365 if acc_text: acc_text = ' ' + acc_text |
|
366 |
|
367 return "%s %s%s" % (key.first_int, |
|
368 key.last_int, |
|
369 acc_text) |
|
370 |
|
371 |
|
372 def get_cenrep_entry_line(self, entry): |
|
373 """ |
|
374 Return the text line for a CenRepEntry object. |
|
375 """ |
|
376 value = None |
|
377 if entry.crml_type in ('string', 'string8'): |
|
378 if entry.confml_type is None: |
|
379 pass |
|
380 else: |
|
381 if entry.orig_value is None: |
|
382 value = '""' |
|
383 else: |
|
384 value = '"%s"' % entry.orig_value |
|
385 elif entry.crml_type == 'int': |
|
386 if entry.confml_type == 'boolean': |
|
387 if entry.value: value = '1' |
|
388 else: value = '0' |
|
389 else: |
|
390 value = entry.orig_value |
|
391 elif entry.crml_type == 'real': |
|
392 value = entry.orig_value or '' |
|
393 elif entry.crml_type == 'binary': |
|
394 # Empty binary values are denoted by a single dash |
|
395 value = entry.orig_value or '-' |
|
396 |
|
397 if value != '-': |
|
398 # Make sure that the number of digits is divisible by two |
|
399 if len(value) % 2 != 0: |
|
400 value = '0' + value |
|
401 |
|
402 if value is None: |
|
403 value = unicode(entry.value) |
|
404 |
|
405 self._check_value(entry, value) |
|
406 |
|
407 acc_text = self.get_access_line(entry.access) |
|
408 if acc_text: acc_text = ' ' + acc_text |
|
409 |
|
410 return '%s %s %s %d%s' % (_translate_key_uid(entry.int), |
|
411 entry.crml_type, |
|
412 value, |
|
413 entry.metadata, |
|
414 acc_text) |
|
415 def _check_value(self, entry, value): |
|
416 """ |
|
417 Check that the given value is valid for the given CenRep entry, |
|
418 and log a warning if it is not. |
|
419 """ |
|
420 if entry.crml_type == 'int': |
|
421 # Check if the value is a string, since it may already |
|
422 # be an integer |
|
423 if not isinstance(value, basestring): |
|
424 return |
|
425 |
|
426 try: |
|
427 value = value.strip() |
|
428 if value.lower().startswith('0x'): |
|
429 long(value, 16) |
|
430 else: |
|
431 long(value) |
|
432 except ValueError: |
|
433 self.log.warn("Key %s: Invalid integer value '%s'" % (entry.int, value)) |
|
434 elif entry.crml_type == 'real': |
|
435 try: |
|
436 float(value) |
|
437 except ValueError: |
|
438 self.log.warn("Key %s: Invalid real value '%s'" % (entry.int, value)) |
|
439 elif entry.crml_type == 'binary': |
|
440 if value != '-' and re.match(r'^(0[xX])?[0-9a-fA-F]+$', value) is None: |
|
441 self.log.warn("Key %s: Invalid binary value '%s'" % (entry.int, value)) |
|
442 |
|
443 def _check_repository_attrs(self, repository): |
|
444 """ |
|
445 Check that the attributes of the given repository are valid and |
|
446 log warnings if not. |
|
447 """ |
|
448 if repository.owner is not None: |
|
449 owner = repository.owner.strip() |
|
450 # An empty owner UID is OK, it doesn't generate anything |
|
451 # invalid into the output |
|
452 if owner != '': |
|
453 try: |
|
454 if owner.lower().startswith('0x'): |
|
455 long(owner, 16) |
|
456 else: |
|
457 long(owner) |
|
458 except ValueError: |
|
459 self.log.warn("Invalid owner UID '%s'" % owner) |
|
460 |
|
461 def get_access_line(self, access): |
|
462 """ |
|
463 Generate a line containing access information based on a CrmlAccess object. |
|
464 """ |
|
465 # Write the access information in a specific order, because it |
|
466 # won't work otherwise |
|
467 var_order = ['sid_rd', 'cap_rd', 'sid_wr', 'cap_wr'] |
|
468 data = [] |
|
469 for varname in var_order: |
|
470 val = getattr(access, varname) |
|
471 if val not in ('', None): |
|
472 # Using _translate_capability_string() on all, since a SID should |
|
473 # not contain anything that could be messed up by the function |
|
474 data.append('%s=%s' % (varname, _translate_capability_string(val))) |
|
475 |
|
476 return ' '.join(data) |
|
477 |
|
478 def _get_feature(self, ref): |
|
479 return self.configuration.get_default_view().get_feature(ref) |
|
480 |
|
481 @classmethod |
|
482 def get_index(cls,firstInt,firstIndex,indexBits,seqIndex, subIndex): |
|
483 """ |
|
484 @param firstIndex: The first value available in the keyrange |
|
485 @param lastInt: The last value available in the keyrange |
|
486 @param indexBits: The index bits or mask for sequence index |
|
487 @param seqIndex: The sequence index |
|
488 @param subIndex: The sequence sub element index |
|
489 @return: an numeric value for the encoded index |
|
490 """ |
|
491 rangeshift = cls.get_range_shift(indexBits) |
|
492 return (((seqIndex+firstIndex) << rangeshift) + firstInt) + subIndex |
|
493 |
|
494 @classmethod |
|
495 def get_seqid(cls,firstInt,firstIndex,indexBits,cenrepkey): |
|
496 """ |
|
497 @param firstIndex: The first value available in the keyrange |
|
498 @param lastInt: The last value available in the keyrange |
|
499 @param indexBits: The index bits or mask for sequence index |
|
500 @param cenrepkey: Crml key id |
|
501 @return: an numeric value for the encoded index |
|
502 """ |
|
503 rangeshift = cls.get_range_shift(indexBits) |
|
504 return (((cenrepkey & indexBits) -firstInt) >> rangeshift)-firstIndex |
|
505 |
|
506 @classmethod |
|
507 def get_subseqid(cls,firstInt,firstIndex,indexBits,cenrepkey): |
|
508 """ |
|
509 @param firstIndex: The first value available in the keyrange |
|
510 @param lastInt: The last value available in the keyrange |
|
511 @param indexBits: The index bits or mask for sequence index |
|
512 @param cenrepkey: Crml key id |
|
513 @return: an numeric value for the encoded index |
|
514 """ |
|
515 range = cls.get_range(indexBits) |
|
516 return (cenrepkey - firstInt) & range |
|
517 |
|
518 @classmethod |
|
519 def get_range_shift(cls,indexBits): |
|
520 """ Get the bit left to the """ |
|
521 seqrange = cls.get_range(indexBits) |
|
522 shiftamount = 0 |
|
523 for i in range(0,32): |
|
524 if (seqrange >> i) == 0: |
|
525 shiftamount = i |
|
526 break |
|
527 return shiftamount |
|
528 |
|
529 @classmethod |
|
530 def get_range(cls,indexBits): |
|
531 """ Get the bit left to the """ |
|
532 indexshift = 0 |
|
533 for i in range(0,32): |
|
534 if (indexBits >> i) == 0: |
|
535 indexshift = i |
|
536 break |
|
537 return ((0x1 << indexshift) - indexBits)-1 |
|
538 |
|
539 |
|
540 # ============================================================================= |
|
541 # Utility functions |
|
542 # ============================================================================= |
|
543 |
|
544 def _get_metadata(backup): |
|
545 metadata = 0 |
|
546 if backup: metadata |= 0x1000000 |
|
547 return metadata |
|
548 |
|
549 def _translate_key_uid(uid): |
|
550 """Translate a key UID given in CRML so that it matches the output of CT2.""" |
|
551 if uid.lower().startswith("0x"): |
|
552 prefix = uid[:2] |
|
553 temp = uid[2:] |
|
554 if int(temp, 16) == 0: return prefix + "0" |
|
555 else: return prefix + uid[2:].lstrip('0') |
|
556 else: |
|
557 if int(uid) == 0: return "0" |
|
558 else: return uid.lstrip('0') |
|
559 |
|
560 def _translate_capability_string(cap_str): |
|
561 """ |
|
562 Translate a capability string so that it is |
|
563 suitable for writing to a CenRep txt file. |
|
564 """ |
|
565 cap_str = cap_str.replace('AlwaysPass', 'alwayspass').replace('AlwaysFail', 'alwaysfail') |
|
566 |
|
567 # The capability string can be a list separated either by |
|
568 # whitespace or commas |
|
569 if ',' in cap_str: caps = cap_str.split(',') |
|
570 else: caps = cap_str.split() |
|
571 |
|
572 # The output must always be comma-separated |
|
573 return ','.join(caps) |
|
574 |