1-- 2-- This file is part of Cardpeek, the smart card reader utility. 3-- 4-- Copyright 2009-2013 by Alain Pannetrat <L1L1@gmx.com> 5-- 6-- Cardpeek is free software: you can redistribute it and/or modify 7-- it under the terms of the GNU General Public License as published by 8-- the Free Software Foundation, either version 3 of the License, or 9-- (at your option) any later version. 10-- 11-- Cardpeek is distributed in the hope that it will be useful, 12-- but WITHOUT ANY WARRANTY; without even the implied warranty of 13-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14-- GNU General Public License for more details. 15-- 16-- You should have received a copy of the GNU General Public License 17-- along with Cardpeek. If not, see <http://www.gnu.org/licenses/>. 18-- 19--********************************************************************-- 20-- 21-- This file, c376n2.lua, was authored and contributed to cardpeek by 22-- Anthony Berkow (C) 2013 23-- 24-- Some minor alterations of the original file were performed for 25-- integration with calypso.lua 26-- 27--********************************************************************-- 28 29require('lib.apdu') 30require('lib.tlv') 31require('lib.en1545') 32require('lib.country_codes') 33require('etc.ravkav-strings') 34 35--Classes 36ISO_CLS_STD = 0x00 --Structure and coding of command and response according to ISO/IEC 7816 5.4.1 (plus optional secure messaging and logical channel) 37ISO_CLS_PRO9 = 0x90 --As for ISO_CLS_STD but the coding and meaning of command and response are proprietary 38--Class modifiers (SM) 39ISO_CLS_SM_PRO = 0x04 --Proprietary secure messaging format 40 41--Application ID 42AID_RavKav = "#315449432e494341" --"1TIC.ICA" 43 44--LIDs 45CALYPSO_LID_ENVIRONMENT = "2001" --SFI=0x07, linear, 1 record 46CALYPSO_LID_EVENTS_LOG = "2010" --SFI=0x08, cyclic, 3 records 47CALYPSO_LID_CONTRACTS = "2020" --SFI=0x09, linear, 4 records 48CALYPSO_LID_COUNTERS = "2069" --SFI=0x19, counters, 9 counters 49 50LID_LIST = { 51 {"General Information", CALYPSO_LID_ENVIRONMENT, "environment"}, 52 {"Counters", CALYPSO_LID_COUNTERS, "counters"}, 53 {"Latest Travel", CALYPSO_LID_EVENTS_LOG, "event"}, 54 {"Contracts", CALYPSO_LID_CONTRACTS, "contract"} 55} 56 57function ravkav_parse_serial_number(node,data) 58 nodes.set_attribute(node,"val", data) 59 nodes.set_attribute(node,"alt", string.format("%010d",bytes.tonumber(data))) 60end 61 62function ravkav_parse_ats(node,data) 63 if data and 5 <= #data then 64 local byteOffset = 1 65 byteOffset = RavKav_parseBits(data, byteOffset, 1, node, "File type", en1545_NUMBER) 66 byteOffset = RavKav_parseBits(data, byteOffset, 1, node, "EF type", en1545_NUMBER) 67 byteOffset, recordLen = RavKav_parseBits(data,byteOffset, 1, node, "Record size", en1545_NUMBER) 68 byteOffset, numRecords = RavKav_parseBits(data,byteOffset, 1, node, "Record count", en1545_NUMBER) 69 byteOffset = RavKav_parseBits(data, byteOffset, 4, node, "Access", en1545_UNDEFINED) 70 end 71end 72 73--Tags 74RAVKAV_IDO = { 75 ['A5/BF0C'] = {"Secure messaging data"}, 76 ['BF0C/C7'] = {"Serial number", ravkav_parse_serial_number}, 77 ['85'] = {"Record Info", ravkav_parse_ats}, 78} 79 80ValidUntilOption = { 81 None = 0, 82 Date = 1, 83 EndOfService = 2, 84 Cancelled = 3 85} 86 87function bytes.is_all(bs, byte) 88 if 0 == #bs then return false end 89 local i 90 for i = 0, #bs - 1 do 91 if bs[i] ~= byte then return false end 92 end 93 return true 94end 95 96function RavKav_addTextNode(ctx, a_label, text) 97 local REF = nodes.append(ctx, { classname="item", label=a_label }) 98 nodes.set_attribute(REF,"alt", text) 99end 100 101function RavKav_selectApplication(root) 102 card.CLA = ISO_CLS_STD 103 local sw, resp = card.select(AID_RavKav, card.SELECT_RETURN_FIRST + card.SELECT_RETURN_FCI, 0) 104 if 0x9000 ~= sw then 105 log.print(log.ERROR, "Failed to select Calypso application.") 106 return nil 107 end 108 109 local APP_REF = nodes.append(root, { classname="application", label="RavKav Card", size=#resp }) 110 nodes.set_attribute(APP_REF,"val", resp) 111 return APP_REF 112end 113 114function RavKav_readCalypsoEf(lid, ctx) 115 card.CLA = ISO_CLS_STD 116 local sw, resp = card.select("."..lid) 117 if 0x9000 ~= sw then 118 log.print(log.ERROR, "Failed to select EF "..lid) 119 else 120 tlv_parse(ctx, resp) 121 end 122end 123 124function RavKav_parseEf(ctx) 125 local numRecords, recordLen 126 local RECINFO_REF, ri_data = RavKav_getField(ctx, "Record Info") 127 if ri_data and 5 <= #ri_data then 128 --recordLen = ri_data[3] 129 --numRecords = ri_data[4] 130 local byteOffset = 1 131 byteOffset = RavKav_parseBits(ri_data, byteOffset, 1, RECINFO_REF, "File type", en1545_NUMBER) 132 byteOffset = RavKav_parseBits(ri_data, byteOffset, 1, RECINFO_REF, "EF type", en1545_NUMBER) 133 byteOffset, recordLen = RavKav_parseBits(ri_data,byteOffset, 1, RECINFO_REF, "Record size", en1545_NUMBER) 134 byteOffset, numRecords = RavKav_parseBits(ri_data,byteOffset, 1, RECINFO_REF, "Record count", en1545_NUMBER) 135 byteOffset = RavKav_parseBits(ri_data, byteOffset, 4, RECINFO_REF, "Access", en1545_UNDEFINED) 136 log.print(log.DBG, string.format("numRecords: %d, recordLen: %d", numRecords, recordLen)) 137 end 138 return numRecords, recordLen 139end 140 141function RavKav_getRecordInfo(ctx) 142 local numRecords = 0 143 local recordLen = 0 144 local RECINFO_REF = nodes.find_first(ctx, {label="Record Info"}) 145 if RECINFO_REF then 146 numRecords = RavKav_getFieldAsNumber(RECINFO_REF, "Record count") 147 recordLen = RavKav_getFieldAsNumber(RECINFO_REF, "Record size") 148 end 149 return numRecords, recordLen 150end 151 152function RavKav_readRecord(nRec, recordLen) 153 card.CLA = ISO_CLS_PRO9 + ISO_CLS_SM_PRO 154 local sw, resp = card.read_record(0, nRec, recordLen) 155 if 0x9000 ~= sw then 156 log.print(log.ERROR, string.format("Failed to read record %d", nRec)) 157 return nil 158 end 159 return resp 160end 161 162-- Note: this function can work with either bits or bytes depending on the data format 163function RavKav_parseBits(data, bitOffset, nBits, REC, a_label, func, a_id) 164 local valData = bytes.sub(data, bitOffset, bitOffset + nBits - 1) 165 local value = func(valData) 166 local intVal = bytes.tonumber(valData) 167 local NEW_REF = nil 168 if REC then 169 NEW_REF = nodes.append(REC, {classname="item", label=a_label, id=a_id, size=nBits }) 170 nodes.set_attribute(NEW_REF,"val", bytes.convert(valData,8)) 171 nodes.set_attribute(NEW_REF,"alt", value) 172 end 173 return bitOffset + nBits, value, NEW_REF, intVal 174end 175 176function RavKav_rkDaysToSeconds(dateDaysPart) 177 bytes.pad_left(dateDaysPart, 32, 0) 178 return EPOCH + bytes.tonumber(dateDaysPart) * 24 * 3600 179end 180 181-- The bits in dateDaysPart have been inverted. 182function RavKav_rkInvertedDaysToSeconds(dateDaysPart) 183 bytes.pad_left(dateDaysPart, 32, 0) 184 return EPOCH + bit.XOR(bytes.tonumber(dateDaysPart), 0x3fff) * 24 * 3600 185end 186 187-- Return the date (in seconds) corresponding to the last day of the month for the given date (in seconds). 188-- If addMonths > 0 then the month is incremented by addMonths months. 189function RavKav_calculateMonthEnd(dateSeconds, addMonths) 190 local dt = os.date("*t", dateSeconds) 191 192 if 0 < addMonths then 193 dt.month = dt.month + addMonths 194 local addYears = math.floor(dt.month / 12) 195 dt.year = dt.year + addYears 196 dt.month = dt.month - addYears * 12 197 end 198 199 dt.day = 28 200 local tmp_dt = dt 201 repeat 202 local date_s = os.time(tmp_dt) + 24 * 3600 --add 1 day 203 tmp_dt = os.date("*t",date_s) 204 if 1 ~= tmp_dt.day then --did not wrap round to a new month 205 dt.day = dt.day + 1 206 end 207 until 1 == tmp_dt.day 208 209 return os.time(dt) 210end 211 212function RavKav_INVERTED_DATE(source) 213 return os.date("%x", RavKav_rkInvertedDaysToSeconds(source)) 214end 215 216function RavKav_PERCENT(source) 217 return string.format("%u %%", bytes.tonumber(source)) 218end 219 220-- source is in BCD! 221function RavKav_COUNTRY(source) 222 local countryId = bytes.tonumber(source) 223 countryId = tonumber(string.format("%X", countryId)) 224 return iso_country_code_name(countryId) 225end 226 227function RavKav_ISSUER(source) 228 local issuerId = bytes.tonumber(source) 229 local issuer = RAVKAV_ISSUERS[issuerId] 230 if issuer then return issuer end 231 return tostring(issuerId) 232end 233 234function RavKav_PROFILE(source) 235 local profileId = bytes.tonumber(source) 236 local profile = RAVKAV_PROFILES[profileId] 237 if profile then return profile end 238 return tostring(profileId) 239end 240 241function RavKav_ROUTE(source) 242 local routeSystemId = bytes.tonumber(source) 243 local route = RAVKAV_ROUTES[routeSystemId] 244 if route then return route end 245 return tostring(routeSystemId) 246end 247 248function RavKav_LOCATION(source) 249 local locationId = bytes.tonumber(source) 250 local location = RAVKAV_LOCATIONS[locationId] 251 if location then return location end 252 return tostring(locationId) 253end 254 255function RavKav_VALIDITY_TYPE(source) 256 local validityType = bytes.tonumber(source) 257 local validity = RAVKAV_VALIDITY_TYPES[validityType] 258 if validity then return validity end 259 return tostring(validityType) 260end 261 262function RavKav_CONTRACT_TYPE(source) 263 local contractType = bytes.tonumber(source) 264 local contract = RAVKAV_CONTRACT_TYPES[contractType] 265 if contract then return contract end 266 return tostring(contractType) 267end 268 269-- Returns CSV list of which bits are set 270function RavKav_ZONES(source) 271 local zonesPart = bytes.sub(source, 0, 11) --12 bits 272 local zones = bytes.tonumber(zonesPart) 273 local haveZones = false 274 local zoneList = "" 275 local i 276 for i = 1, 12 do 277 if 1 == bit.AND(zones, 1) then 278 if haveZones then zoneList = zoneList.."," end 279 zoneList = zoneList..tostring(i) 280 haveZones = true 281 end 282 zones = bit.SHR(zones, 1) 283 end 284 return zoneList 285end 286 287function RavKav_readRecords(EF_REF, numRecords, recordLen, rec_desc) 288 local nRec 289 for nRec = 1, numRecords do 290 local record = RavKav_readRecord(nRec, recordLen) 291 if record then 292 local REC_REF = nodes.append(EF_REF, {classname="record", label=rec_desc, id=nRec, size=#record}) 293 nodes.set_attribute(REC_REF,"val", record) 294 end 295 end 296end 297 298function RavKav_readFile(APP_REF, desc, lid, rec_desc) 299 log.print(log.DBG, "Reading "..desc.."...") 300 local EF_REF = nodes.append(APP_REF, {classname="file", label=desc}) 301 RavKav_readCalypsoEf(lid, EF_REF) 302 local numRecords, recordLen = RavKav_parseEf(EF_REF) 303 if numRecords then 304 RavKav_readRecords(EF_REF, numRecords, recordLen, rec_desc) 305 end 306end 307 308function RavKav_getField(SRC_REF, a_label, a_id) 309 local data, text 310 local REF = nodes.find_first(SRC_REF, {label=a_label, id=a_id}) 311 if REF then 312 data = nodes.get_attribute(REF,"val") 313 text = nodes.get_attribute(REF,"alt") 314 end 315 return REF, data, text 316end 317 318function RavKav_getFieldAsNumber(SRC_REF, label, id) 319 local REF, data, text = RavKav_getField(SRC_REF, label, id) 320 if data then return bytes.tonumber(data) end 321 return 0 322end 323 324function RavKav_getFieldsAsNumberAry(SRC_REF, a_label, a_id) 325 local node 326 local ary = {} 327 328 for node in nodes.find(SRC_REF, { label=a_label, id=a_id }) do 329 local data = nodes.get_attribute(node,"val") 330 if data then table.insert(ary, bytes.tonumber(data)) end 331 end 332 return ary 333end 334 335-- if ary is supplied then only create new node if the field is a pointer to a valid entry in ary 336function RavKav_copyField(SRC_REF, DEST_REF, a_label, a_id, ary) 337 local SRC_REF2, data, text = RavKav_getField(SRC_REF, a_label, a_id) 338 if nil == data then return nil, nil end 339 340 if ary then 341 local dataId = en1545_NUMBER(data) 342 if nil == ary[dataId] then return nil, nil end 343 end 344 345 NEW_REF = nodes.append(DEST_REF, {classname="item", label=a_label, id=a_id, size=#data }) 346 nodes.set_attribute(NEW_REF,"val", data) 347 nodes.set_attribute(NEW_REF,"alt", text) 348 return SRC_REF2, NEW_REF 349end 350 351function RavKav_parseEnvironment(ENV_REF, nRec) 352 local ENV_REC_REF = nodes.find_first(ENV_REF, {label="record", id=nRec}) 353 if nil == ENV_REC_REF then return false end 354 355 local record = nodes.get_attribute(ENV_REC_REF,"val") 356 if nil == record then return false end 357 358 local data = bytes.convert(record,1) 359 360 local bitOffset = 0 361 bitOffset = RavKav_parseBits(data, bitOffset, 3, ENV_REC_REF, "Version number", en1545_NUMBER) 362 bitOffset = RavKav_parseBits(data, bitOffset, 12, ENV_REC_REF, "Country", RavKav_COUNTRY) 363 bitOffset = RavKav_parseBits(data, bitOffset, 8, ENV_REC_REF, "Issuer", RavKav_ISSUER) 364 bitOffset = RavKav_parseBits(data, bitOffset, 26, ENV_REC_REF, "Application number", en1545_NUMBER) 365 bitOffset = RavKav_parseBits(data, bitOffset, 14, ENV_REC_REF, "Date of issue", en1545_DATE) 366 bitOffset = RavKav_parseBits(data, bitOffset, 14, ENV_REC_REF, "End date", en1545_DATE) 367 bitOffset = RavKav_parseBits(data, bitOffset, 3, ENV_REC_REF, "Pay method", en1545_NUMBER) 368 bitOffset = RavKav_parseBits(data, bitOffset, 32, ENV_REC_REF, "Date of birth", en1545_BCD_DATE) 369 bitOffset = RavKav_parseBits(data, bitOffset, 14, ENV_REC_REF, "Company (not set)", en1545_NUMBER) 370 bitOffset = RavKav_parseBits(data, bitOffset, 30, ENV_REC_REF, "Company ID (not set)", en1545_NUMBER) 371 bitOffset = RavKav_parseBits(data, bitOffset, 30, ENV_REC_REF, "ID number", en1545_NUMBER) 372 373 local PROFILES_REF = nodes.append(ENV_REC_REF, {classname="item", label="Profiles"}) 374 local profile, ref 375 bitOffset, profile, ref = RavKav_parseBits(data, bitOffset, 6, PROFILES_REF, "Profile", RavKav_PROFILE, 1) 376 bitOffset = RavKav_parseBits(data, bitOffset, 14, ref, "Valid until", en1545_DATE) 377 bitOffset, profile, ref = RavKav_parseBits(data, bitOffset, 6, PROFILES_REF, "Profile", RavKav_PROFILE, 2) 378 RavKav_parseBits(data, bitOffset, 14, ref, "Valid until", en1545_DATE) 379 return true 380end 381 382function RavKav_parseGeneralInfo(APP_REF) 383 log.print(log.DBG, "Parsing general info (environment)...") 384 local ENV_REF = nodes.find_first(APP_REF, {label="Environment"}) 385 if ENV_REF then 386 local numRecords = RavKav_getRecordInfo(ENV_REF) 387 local nRec 388 for nRec = 1, numRecords do 389 -- only the first record contains valid data 390 if RavKav_parseEnvironment(ENV_REF, nRec) then break end 391 end 392 end 393end 394 395function RavKav_parseCounter(CNTRS_REF, nRec) 396 local CNTR_REC_REF = nodes.find_first(CNTRS_REF, {label="record", id=nRec}) 397 if CNTR_REC_REF then 398 local record = nodes.get_attribute(CNTR_REC_REF,"val") 399 if record then 400 local data = bytes.convert(record,1) 401 local bitOffset = 0 402 local maxCounters = math.floor(#data / 24) 403 local nRec, nCnt 404 local cntrAry = {} 405 for nRec = 1, maxCounters do 406 bitOffset, nCnt = RavKav_parseBits(data, bitOffset, 24, CNTR_REC_REF, "Counter", en1545_NUMBER, nRec) 407 cntrAry[nRec] = nCnt 408 end 409 return cntrAry 410 end 411 end 412 return nil 413end 414 415function RavKav_parseCounters(APP_REF) 416 log.print(log.DBG, "Parsing counters...") 417 local CNTRS_REF = nodes.find_first(APP_REF, {id=CALYPSO_LID_COUNTERS}) 418 if CNTRS_REF then 419 local numRecords = RavKav_getRecordInfo(CNTRS_REF) 420 if 0 < numRecords then 421 -- there should only be one counter record 422 return RavKav_parseCounter(CNTRS_REF, 1) 423 end 424 end 425 return nil 426end 427 428function RavKav_parseEvent(EVENTS_REF, nRec) 429 local EVENT_REC_REF = nodes.find_first(EVENTS_REF, {label="record", id=nRec}) 430 if nil == EVENT_REC_REF then return end 431 432 local record = nodes.get_attribute(EVENT_REC_REF,"val") 433 if nil == record then return end 434 435 local data = bytes.convert(record,1) 436 437 if bytes.is_all(data, 0) then return end 438 439 local bitOffset = 0 440 441 local text, ref, issuerId 442 443 bitOffset = RavKav_parseBits(data, bitOffset, 3, EVENT_REC_REF, "Version number", en1545_NUMBER) 444 bitOffset, text, ref, issuerId = RavKav_parseBits(data, bitOffset, 8, EVENT_REC_REF, "Issuer", RavKav_ISSUER) 445 446 if issuerId == 0 then return end 447 448 local contractId, eventType 449 450 bitOffset, contractId = RavKav_parseBits(data, bitOffset, 4, EVENT_REC_REF, "Contract ID", en1545_NUMBER) 451 bitOffset = RavKav_parseBits(data, bitOffset, 4, EVENT_REC_REF, "Area ID", en1545_NUMBER) --1 == urban, 2 == intercity 452 bitOffset, eventType = RavKav_parseBits(data, bitOffset, 4, EVENT_REC_REF, "Event type", en1545_NUMBER) 453 bitOffset = RavKav_parseBits(data, bitOffset, 30, EVENT_REC_REF, "Event time", en1545_DATE_TIME) 454 bitOffset = RavKav_parseBits(data, bitOffset, 1, EVENT_REC_REF, "Journey interchanges flag", en1545_NUMBER) --includes switching/continuing beyond 455 bitOffset = RavKav_parseBits(data, bitOffset, 30, EVENT_REC_REF, "First event time", en1545_DATE_TIME) --identical to 'Event time' 456 457 local PRIORITIES_REF = nodes.append(EVENT_REC_REF, {classname="item", label="Best contract priorities"}) 458 local nContract 459 for nContract = 1, 8 do 460 bitOffset = RavKav_parseBits(data, bitOffset, 4, PRIORITIES_REF, "Contract", RavKav_PERCENT, nContract) 461 end 462 463 local locationBitmap 464 local ln_ref = nil 465 466 bitOffset, text, ref, locationBitmap = RavKav_parseBits(data, bitOffset, 7, EVENT_REC_REF, "Location bitmap", en1545_UNDEFINED) --defined per issuer 467 468 if 0 < bit.AND(locationBitmap, 1) then 469 if 2 == issuerId then --Israel Rail 470 bitOffset = RavKav_parseBits(data, bitOffset, 16, EVENT_REC_REF, "Location", RavKav_LOCATION) --station 471 else 472 bitOffset = RavKav_parseBits(data, bitOffset, 16, EVENT_REC_REF, "Location ID", en1545_NUMBER) --place 473 end 474 end 475 476 if 0 < bit.AND(locationBitmap, 2) then 477 local lineNumber 478 bitOffset, lineNumber, ln_ref = RavKav_parseBits(data, bitOffset, 16, EVENT_REC_REF, "Line number", en1545_NUMBER) --number of line on route 479 end 480 481 if 0 < bit.AND(locationBitmap, 4) then 482 bitOffset = RavKav_parseBits(data, bitOffset, 8, EVENT_REC_REF, "Stop en route", en1545_NUMBER) 483 end 484 485 if 0 < bit.AND(locationBitmap, 8) then --12 unknown bits 486 bitOffset = RavKav_parseBits(data, bitOffset, 12, EVENT_REC_REF, "Location[3]", en1545_UNDEFINED) 487 end 488 489 if 0 < bit.AND(locationBitmap, 0x10) then 490 bitOffset = RavKav_parseBits(data, bitOffset, 14, EVENT_REC_REF, "Vehicle", en1545_UNDEFINED) 491 end 492 493 if 0 < bit.AND(locationBitmap, 0x20) then --how many bits?? 494 log.print(log.DBG, string.format("Event[%d]: Location[5]: unknown value", nRec)) 495 end 496 497 if 0 < bit.AND(locationBitmap, 0x40) then --8 unknown bits 498 if 0 < bit.AND(locationBitmap, 0x20) then -- we are lost due to previous unknown field 499 log.print(log.DBG, string.format("Event[%d]: Location[6]: unknown value", nRec)) 500 else 501 bitOffset = RavKav_parseBits(data, bitOffset, 8, EVENT_REC_REF, "Location[6]", en1545_UNDEFINED) 502 end 503 end 504 505 if 0 == bit.AND(locationBitmap, 0x20) then 506 local eventExtension 507 bitOffset, text, ref, eventExtension = RavKav_parseBits(data, bitOffset, 3, EVENT_REC_REF, "Event extension bitmap", en1545_UNDEFINED) 508 509 if 0 < bit.AND(eventExtension, 1) then 510 bitOffset = RavKav_parseBits(data, bitOffset, 10, EVENT_REC_REF, "Route system", RavKav_ROUTE) 511 bitOffset = RavKav_parseBits(data, bitOffset, 8, EVENT_REC_REF, "Fare code", en1545_NUMBER) 512 local debitAmount, dr_ref 513 bitOffset, debitAmount, dr_ref = RavKav_parseBits(data, bitOffset, 16, EVENT_REC_REF, "Debit amount", en1545_NUMBER) 514 if 0 < debitAmount and 6 ~= eventType and 0 < contractId and 9 > contractId then --not a transit trip 515 if 21 > debitAmount then 516 dr_ref:set_attribute("alt",string.format("%u trip(s)", debitAmount)) 517 else 518 dr_ref:set_attribute("alt",string.format("NIS %0.2f", debitAmount / 100.0)) 519 end 520 end 521 end 522 523 if 0 < bit.AND(eventExtension, 2) then 524 bitOffset = RavKav_parseBits(data, bitOffset, 32, EVENT_REC_REF, "Event extension[2]", en1545_UNDEFINED) 525 end 526 527 if 0 < bit.AND(eventExtension, 4) then 528 bitOffset = RavKav_parseBits(data, bitOffset, 32, EVENT_REC_REF, "Event extension[3]", en1545_UNDEFINED) 529 end 530 end 531 532 -- fix 'Line number' field 533 local lnBitsize = 0 534 if 3 == issuerId then --Egged 535 bitOffset = 155 536 lnBitsize = 10 537 if 0 < bit.AND(locationBitmap, 8) then bitOffset = bitOffset + 12 end 538 elseif 15 == issuerId then --Metropolis 539 bitOffset = 123 540 lnBitsize = 32 541 elseif 2 ~= issuerId and 14 ~= issuerId and 16 ~= issuerId then --not Israel Rail, Nativ Express nor Superbus 542 bitOffset = 145 543 lnBitsize = 10 544 end 545 if 0 < lnBitsize then 546 if ln_ref then ln_ref:remove() end 547 RavKav_parseBits(data, bitOffset, lnBitsize, EVENT_REC_REF, "Line number", en1545_NUMBER) 548 end 549end 550 551function RavKav_parseEventsLog(APP_REF) 552 log.print(log.DBG, "Parsing events...") 553 local EVENTS_REF = nodes.find_first(APP_REF, {label="Event logs"} ) 554 if EVENTS_REF then 555 local numRecords = RavKav_getRecordInfo(EVENTS_REF) 556 local nRec 557 for nRec = 1, numRecords do 558 RavKav_parseEvent(EVENTS_REF, nRec) 559 end 560 end 561end 562 563function RavKav_parseContract(CONTRACTS_REF, nRec, counter) 564 local CONTRACT_REC_REF = nodes.find_first(CONTRACTS_REF, {label="record", id=nRec}) 565 if nil == CONTRACT_REC_REF then return end 566 567 local record = nodes.get_attribute(CONTRACT_REC_REF,"val") 568 if nil == record then return end 569 570 local data = bytes.convert(record,1) 571 if bytes.is_all(data, 0) then 572 nodes.set_attribute(CONTRACT_REC_REF,"alt","empty") 573 return 574 end 575 576 local bitOffset = 0 577 578 local text, ref, vfd_ref, issuerId 579 580 bitOffset = RavKav_parseBits(data, bitOffset, 3, CONTRACT_REC_REF, "Version number", en1545_NUMBER) 581 bitOffset, text, vfd_ref = RavKav_parseBits(data, bitOffset, 14, CONTRACT_REC_REF, "Valid from", RavKav_INVERTED_DATE) --validity start date 582 583 local validFromSeconds = RavKav_rkInvertedDaysToSeconds(nodes.get_attribute(vfd_ref,"val")) 584 585 bitOffset, text, ref, issuerId = RavKav_parseBits(data, bitOffset, 8, CONTRACT_REC_REF, "Issuer", RavKav_ISSUER) 586 if issuerId == 0 then return end 587 588 local ticketType, validityBitmap 589 local contractValid = true 590 591 bitOffset = RavKav_parseBits(data, bitOffset, 2, CONTRACT_REC_REF, "Tariff transport access", en1545_NUMBER) 592 bitOffset = RavKav_parseBits(data, bitOffset, 3, CONTRACT_REC_REF, "Tariff counter use", en1545_NUMBER) --0 == not used, 2 == number of tokens, 3 == monetary amount 593 bitOffset, ticketType = RavKav_parseBits(data, bitOffset, 6, CONTRACT_REC_REF, "Ticket type", en1545_NUMBER) 594 595 if 1 == ticketType or 6 == ticketType or 7 == ticketType then --Single or multiple, Aggregate value or Single or multiple 596 contractValid = 0 < counter 597 local balance 598 if 6 == ticketType then --Aggregate value 599 balance = string.format("NIS %0.2f", counter / 100.0) --balanceRemaining 600 else 601 balance = string.format("%d trip(s)", counter) --tripsRemaining 602 end 603 local NEW_REF = nodes.append(CONTRACT_REC_REF, {classname="item", label="Balance", size=24}) 604 nodes.set_attribute(NEW_REF,"val", bytes.new(8, string.format("%06X", counter))) 605 nodes.set_attribute(NEW_REF,"alt", balance) 606 end 607 608 bitOffset = RavKav_parseBits(data, bitOffset, 14, CONTRACT_REC_REF, "Date of purchase", en1545_DATE) --sale date 609 bitOffset = RavKav_parseBits(data, bitOffset, 12, CONTRACT_REC_REF, "Sale device", en1545_NUMBER) --serial number of device that made the sale 610 bitOffset = RavKav_parseBits(data, bitOffset, 10, CONTRACT_REC_REF, "Sale number", en1545_NUMBER) --runnning sale number on the day of the sale 611 bitOffset = RavKav_parseBits(data, bitOffset, 1, CONTRACT_REC_REF, "Journey interchanges flag", en1545_NUMBER) --includes switching/continuing beyond 612 bitOffset, text, ref, validityBitmap = RavKav_parseBits(data, bitOffset, 9, CONTRACT_REC_REF, "Validity bitmap", en1545_UNDEFINED) 613 614 if 0 < bit.AND(validityBitmap, 1) then 615 bitOffset = RavKav_parseBits(data, bitOffset, 5, CONTRACT_REC_REF, "Validity[1]", en1545_UNDEFINED) 616 end 617 618 local restrictionCode = 0 619 if 0 < bit.AND(validityBitmap, 2) then 620 bitOffset, restrictionCode = RavKav_parseBits(data, bitOffset, 5, CONTRACT_REC_REF, "Restriction code", en1545_NUMBER) 621 end 622 623 if 0 < bit.AND(validityBitmap, 4) then 624 local iDuration, rd_ref 625 bitOffset, iDuration, rd_ref = RavKav_parseBits(data, bitOffset, 6, CONTRACT_REC_REF, "Restriction duration", en1545_NUMBER) 626 if 16 == restrictionCode then 627 iDuration = iDuration * 5 628 else 629 iDuration = iDuration * 30 630 end 631 rd_ref:set_attribute("alt",string.format("%d minute(s)", iDuration)) 632 end 633 634 if 0 < bit.AND(validityBitmap,8) then --validity end date 635 local vtd_ref 636 bitOffset, text, vtd_ref = RavKav_parseBits(data, bitOffset, 14, CONTRACT_REC_REF, "Valid until", en1545_DATE) 637 if contractValid then 638 contractValid = RavKav_rkDaysToSeconds(vtd_ref:val()) > os.time() 639 end 640 end 641 642 local validUntilOption = ValidUntilOption.None 643 if 0 < bit.AND(validityBitmap, 0x10) then 644 local validMonths 645 bitOffset, validMonths = RavKav_parseBits(data, bitOffset, 8, CONTRACT_REC_REF, "Valid months", en1545_NUMBER) 646 if 0 < validMonths then 647 if 1 == validMonths then 648 validUntilOption = ValidUntilOption.Date 649 local validUntilSeconds = RavKav_calculateMonthEnd(validFromSeconds, validMonths - 1) --month end date 650 651 local NEW_REF = nodes.append(CONTRACT_REC_REF, {classname="item", label="Valid to", size=14}) --month end date 652 nodes.set_attribute(NEW_REF,"val", bytes.new(8, string.format("%04X", validUntilSeconds))) 653 nodes.set_attribute(NEW_REF,"alt", os.date("%x", validUntilSeconds)) 654 655 if contractValid then 656 contractValid = validUntilSeconds > os.time() 657 end 658 elseif 129 == validMonths then 659 validUntilOption = ValidUntilOption.EndOfService 660 log.print(log.DBG, string.format("Contract[%d]: Valid until the end of the service", nRec)) 661 662 local NEW_REF = nodes.append(CONTRACT_REC_REF, {classname="item", label="Valid to", size=2}) 663 nodes.set_attribute(NEW_REF,"val", bytes.new(8, string.format("%01X", validUntilOption))) 664 nodes.set_attribute(NEW_REF,"alt", "The end of the service") 665 666 if contractValid then 667 contractValid = validFromSeconds > os.time() 668 end 669 else 670 log.print(log.DBG, string.format("Contract[%d]: Valid months unknown value: %d", nRec, validMonths)) 671 end 672 else 673 validUntilOption = ValidUntilOption.Cancelled 674 log.print(log.DBG, string.format("Contract[%d]: Validity cancelled", nRec)) 675 676 local NEW_REF = nodes.append(CONTRACT_REC_REF, {classname="item", label="Valid to", size=2}) 677 nodes.set_attribute(NEW_REF,"val", bytes.new(8, string.format("%01X", validUntilOption))) 678 nodes.set_attribute(NEW_REF,"alt", "Validity cancelled") 679 end 680 end 681 682 if 0 < bit.AND(validityBitmap, 0x20) then 683 bitOffset = RavKav_parseBits(data, bitOffset, 32, CONTRACT_REC_REF, "Validity[5]", en1545_UNDEFINED) 684 end 685 686 if 0 < bit.AND(validityBitmap, 0x40) then 687 bitOffset = RavKav_parseBits(data, bitOffset, 6, CONTRACT_REC_REF, "Validity[6]", en1545_UNDEFINED) 688 end 689 690 if 0 < bit.AND(validityBitmap, 0x80) then 691 bitOffset = RavKav_parseBits(data, bitOffset, 32, CONTRACT_REC_REF, "Validity[7]", en1545_UNDEFINED) 692 end 693 694 if 0 < bit.AND(validityBitmap, 0x100) then 695 bitOffset = RavKav_parseBits(data, bitOffset, 32, CONTRACT_REC_REF, "Validity[8]", en1545_UNDEFINED) 696 end 697 698 -- read validity locations 699 local VALID_LOCS_REF = nodes.append(CONTRACT_REC_REF, {classname="item", label="Validity locations"}) 700 local nLoc 701 for nLoc = 1, 7 do --limit to 7 just in case the end marker is missing? 702 local LOC_REF = nodes.append(VALID_LOCS_REF, {classname="item", label="Validity", id=nLoc}) 703 704 local validity, ref, validityType 705 bitOffset, validity, ref, validityType = RavKav_parseBits(data, bitOffset, 4, LOC_REF, "Validity type", RavKav_VALIDITY_TYPE) 706 707 if 15 == validityType then --end of validity locations 708 log.print(log.DBG, string.format("Contract %d: Validity location[%d]: validityType: 15 (end of validity locations)", nRec, nLoc)) 709 nodes.remove(LOC_REF) 710 break 711 end 712 713 if 0 == validityType then 714 bitOffset = RavKav_parseBits(data, bitOffset, 10, LOC_REF, "Route ID", RavKav_ROUTE, validityType) 715 bitOffset = RavKav_parseBits(data, bitOffset, 12, LOC_REF, "Spatial zones", RavKav_ZONES, validityType) 716 elseif 1 == validityType then 717 bitOffset = RavKav_parseBits(data, bitOffset, 10, LOC_REF, "Route ID", RavKav_ROUTE, validityType) 718 bitOffset = RavKav_parseBits(data, bitOffset, 8, LOC_REF, "Tariff code", en1545_NUMBER, validityType) 719 elseif 3 == validityType then 720 bitOffset = RavKav_parseBits(data, bitOffset, 32, LOC_REF, "Validity", en1545_UNDEFINED, validityType) 721 elseif 7 == validityType then 722 bitOffset = RavKav_parseBits(data, bitOffset, 10, LOC_REF, "Route ID", RavKav_ROUTE, validityType) 723 bitOffset = RavKav_parseBits(data, bitOffset, 8, LOC_REF, "Tariff code", en1545_NUMBER, validityType) 724 elseif 8 == validityType then --unknown validity type except for Israel Rail? 725 bitOffset = RavKav_parseBits(data, bitOffset, 10, LOC_REF, "Route ID", RavKav_ROUTE, validityType) 726 bitOffset = RavKav_parseBits(data, bitOffset, 8, LOC_REF, "Tariff code", en1545_NUMBER, validityType) --? 727 bitOffset = RavKav_parseBits(data, bitOffset, 14, LOC_REF, "Unknown", en1545_UNDEFINED, validityType) 728 elseif 9 == validityType then 729 bitOffset = RavKav_parseBits(data, bitOffset, 3, LOC_REF, "Extended ticket type (ETT)", en1545_NUMBER, validityType) 730 bitOffset = RavKav_parseBits(data, bitOffset, 11, LOC_REF, "Contract type", RavKav_CONTRACT_TYPE, validityType) 731 elseif 11 == validityType then 732 bitOffset = RavKav_parseBits(data, bitOffset, 21, LOC_REF, "Validity", en1545_UNDEFINED, validityType) 733 elseif 14 == validityType then 734 bitOffset = RavKav_parseBits(data, bitOffset, 10, LOC_REF, "Route ID", en1545_NUMBER, validityType) 735 bitOffset = RavKav_parseBits(data, bitOffset, 12, LOC_REF, "Spatial zones", RavKav_ZONES, validityType) 736 else 737 log.print(log.DBG, string.format("Contract %d: Validity location[%d]: unrecognised validityType: %d", nRec, nLoc, validityType)) 738 break --since we don't know the next bit position 739 end 740 end 741 742 RavKav_parseBits(data, 224, 8,CONTRACT_REC_REF, "Contract authenticator", en1545_UNDEFINED) --checksum? 743 744 local cv_ref = nodes.append(CONTRACT_REC_REF, {classname="item", label="Contract valid", size=1}) 745 if contractValid then 746 nodes.set_attribute(cv_ref,"val", bytes.new(1, 1)) 747 nodes.set_attribute(cv_ref,"alt", "True") 748 else 749 nodes.set_attribute(cv_ref,"val", bytes.new(1, 0)) 750 nodes.set_attribute(cv_ref,"alt", "False") 751 end 752end 753 754function RavKav_parseContracts(APP_REF, cntrAry) 755 log.print(log.DBG, "Parsing contracts...") 756 local CONTRACTS_REF = nodes.find_first(APP_REF, {label="Contracts"}) 757 if CONTRACTS_REF then 758 local numRecords = RavKav_getRecordInfo(CONTRACTS_REF) 759 local nRec 760 for nRec = 1, numRecords do 761 RavKav_parseContract(CONTRACTS_REF, nRec, cntrAry[nRec]) 762 end 763 end 764end 765 766function RavKav_SummariseProfile(PROFS_REF, id, GI_REF) 767 local SRC_REF, NEW_REF = RavKav_copyField(PROFS_REF, GI_REF, "Profile", id, RAVKAV_PROFILES) 768 if NEW_REF then 769 if 0 < RavKav_getFieldAsNumber(SRC_REF, "Valid until") then 770 RavKav_copyField(SRC_REF, NEW_REF, "Valid until") 771 end 772 end 773end 774 775function RavKav_summariseGeneralInfo(APP_REF, SUM_REF) 776 log.print(log.DBG, "Summarising general info...") 777 local ENV_REF = APP_REF:find_first({label="Environment"}):find_first({label="record"}) 778 if ENV_REF then 779 local GI_REF = nodes.append(SUM_REF, {classname="file", label="General Information"}) 780 781 RavKav_copyField(APP_REF, GI_REF, "Serial number") 782 RavKav_copyField(ENV_REF, GI_REF, "Issuer", nil, RAVKAV_ISSUERS) 783 784 local idNumber = RavKav_getFieldAsNumber(ENV_REF, "ID number") 785 if 0 == idNumber then 786 local REF = nodes.append(GI_REF, {classname="item", label="Identity number", size=0}) 787 nodes.set_attribute(REF,"alt", "Anonymous Rav-Kav") 788 return 789 end 790 791 RavKav_copyField(ENV_REF,GI_REF, "ID number") 792 RavKav_copyField(ENV_REF,GI_REF, "Date of birth") 793 794 local PROFS_REF = nodes.find_first(ENV_REF, {label="Profiles"}) 795 if PROFS_REF then 796 RavKav_SummariseProfile(PROFS_REF, 1, GI_REF) 797 RavKav_SummariseProfile(PROFS_REF, 2, GI_REF) 798 end 799 end 800end 801 802function RavKav_summariseEvent(EVENTS_REF, nRec, LT_REF) 803 local EVENT_REC_REF = nodes.find_first(EVENTS_REF, {label="record", id=nRec}) 804 if nil == EVENT_REC_REF then return end 805 806 local issuerId = RavKav_getFieldAsNumber(EVENT_REC_REF, "Issuer") 807 if 0 == issuerId then return end 808 809 local EVSUM_REF = nodes.append(LT_REF, {classname="record", label="Event", id=nRec}) 810 811 812 -- Description --------------------------- 813 local description = "" 814 815 if RAVKAV_ISSUERS[issuerId] then description = RAVKAV_ISSUERS[issuerId] end 816 817 local lineNumber = RavKav_getFieldAsNumber(EVENT_REC_REF, "Line number") 818 if 0 < lineNumber then 819 description = description.." line " 820 local routeSystemId = RavKav_getFieldAsNumber(EVENT_REC_REF, "Route system") 821 local sLineNum = tostring(lineNumber) 822 if RAVKAV_ROUTES[routeSystemId] then 823 if 15 == issuerId then --Metropolis 824 local sRouteSystemId = tostring(routeSystemId) 825 local posS, posE = string.find(sLineNum, sRouteSystemId) 826 if 1 == posS then --Does "1234567..." start with "12"? 827 sLineNum = string.sub(sLineNum, posE + 1, posE + 4) --"1234567..." -> "345" 828 end 829 end 830 description = description..sLineNum..", cluster "..RAVKAV_ROUTES[routeSystemId] 831 else 832 description = description..sLineNum 833 end 834 end 835 if 0 < #description then RavKav_addTextNode(EVSUM_REF, "Description", description) end 836 ------------------------------------------ 837 838 839 if 0 < RavKav_getFieldAsNumber(EVENT_REC_REF, "Event time") then 840 RavKav_copyField(EVENT_REC_REF, EVSUM_REF, "Event time") 841 end 842 843 if 2 == issuerId then --Israel Rail 844 local locationId = RavKav_getFieldAsNumber(EVENT_REC_REF, "Location") 845 if RAVKAV_LOCATIONS[locationId] then 846 RavKav_addTextNode(EVSUM_REF, "Station", RAVKAV_LOCATIONS[locationId]) 847 end 848 end 849 850 851 -- Details ------------------------------- 852 details = "" 853 local eventType = RavKav_getFieldAsNumber(EVENT_REC_REF, "Event type") 854 local contractId = RavKav_getFieldAsNumber(EVENT_REC_REF, "Contract ID") 855 if 6 ~= eventType and 0 < contractId and 9 > contractId then --not a transit trip 856 local REF, data, debitedText = RavKav_getField(EVENT_REC_REF, "Debit amount") 857 local debitAmount = 0 858 if data then debitAmount = bytes.tonumber(data) end 859 if 0 < debitAmount then 860 if 1 ~= eventType then --not an exit event 861 details = RAVKAV_EVENT_TYPES[eventType] 862 end 863 details = details.." contract "..tostring(contractId).." charged "..debitedText 864 else 865 details = RAVKAV_EVENT_TYPES[eventType].." contract "..tostring(contractId) 866 end 867 868 local fareCode = RavKav_getFieldAsNumber(EVENT_REC_REF, "Fare code") 869 if 0 < fareCode then 870 details = details.." (code "..tostring(fareCode)..")" 871 end 872 else 873 details = RAVKAV_EVENT_TYPES[eventType] 874 end 875 if 0 < #details then RavKav_addTextNode(EVSUM_REF, "Details", details) end 876 ------------------------------------------ 877end 878 879function RavKav_summariseLatestTravel(APP_REF, SUM_REF) 880 log.print(log.DBG, "Summarising latest travel...") 881 local EVENTS_REF = nodes.find_first(APP_REF, {label="Event logs"}) 882 if EVENTS_REF then 883 local LT_REF = nodes.append(SUM_REF, {classname="file", label="Latest Travel"}) 884 local numRecords = RavKav_getRecordInfo(EVENTS_REF) 885 local nRec 886 for nRec = 1, numRecords do 887 RavKav_summariseEvent(EVENTS_REF, nRec, LT_REF) 888 end 889 end 890end 891 892function RavKav_summariseTextArray(REC_REF, itmLabel, lookupAry, SM_REF, sumLabel) 893 local summary = "" 894 local ary = RavKav_getFieldsAsNumberAry(REC_REF, itmLabel) 895 local i, id 896 for i, id in ipairs(ary) do 897 if lookupAry[id] then 898 if 0 < #summary then summary = summary.."\n " end 899 summary = summary..lookupAry[id] 900 end 901 end 902 if 0 < #summary then RavKav_addTextNode(SM_REF, sumLabel, summary) end 903end 904 905function RavKav_summariseIntegerArray(REC_REF, itmLabel, SM_REF, sumLabel) 906 local summary = "" 907 local ary = RavKav_getFieldsAsNumberAry(REC_REF, itmLabel) 908 local i, val 909 for i, val in ipairs(ary) do 910 if 0 < val then 911 if 0 < #summary then summary = summary..", " end 912 summary = summary..tostring(val) 913 end 914 end 915 if 0 < #summary then RavKav_addTextNode(SM_REF, sumLabel, summary) end 916end 917 918function RavKav_summariseContract(CONTRACTS_REF, nRec, VC_REF, IC_REF) 919 local CONTRACT_REC_REF = nodes.find_first(CONTRACTS_REF, {label="record", id=nRec}) 920 if nil == CONTRACT_REC_REF then return end 921 922 local issuerId = RavKav_getFieldAsNumber(CONTRACT_REC_REF, "Issuer") 923 if 0 == issuerId then return end 924 925 local contractValid = 0 ~= RavKav_getFieldAsNumber(CONTRACT_REC_REF, "Contract valid") 926 local CT_REF = IC_REF 927 if contractValid then CT_REF = VC_REF end 928 929 local CTSUM_REF = nodes.append(CT_REF, {classname="record", label="Contract", id=nRec}) 930 931 -- Description --------------------------- 932 local description = "" 933 934 if RAVKAV_ISSUERS[issuerId] then description = RAVKAV_ISSUERS[issuerId] end 935 936 local ticketType = RavKav_getFieldAsNumber(CONTRACT_REC_REF, "Ticket type") 937 if RAVKAV_TICKET_TYPES[ticketType] then description = description.." "..RAVKAV_TICKET_TYPES[ticketType] end 938 939 if 0 < #description then RavKav_addTextNode(CTSUM_REF, "Description", description) end 940 ------------------------------------------ 941 942 RavKav_summariseTextArray(CONTRACT_REC_REF, "Route ID", RAVKAV_ROUTES, CTSUM_REF, "Clusters") 943 944 -- Type of contract ---------------------- 945 if 0 < ticketType then 946 local REF, data = RavKav_getField(CONTRACT_REC_REF, "Extended ticket type (ETT)") 947 if data then 948 local ett = bytes.tonumber(data) 949 if RAVKAV_CONTRACT_DESCRIPTIONS[ticketType][ett] then 950 RavKav_addTextNode(CTSUM_REF, "Type of contract", RAVKAV_CONTRACT_DESCRIPTIONS[ticketType][ett]) 951 end 952 end 953 end 954 ------------------------------------------ 955 956 RavKav_summariseIntegerArray(CONTRACT_REC_REF, "Tariff code", CTSUM_REF, "Tariff codes") 957 RavKav_summariseTextArray(CONTRACT_REC_REF, "Contract type", RAVKAV_CONTRACT_TYPES, CTSUM_REF, "Contract types") 958 959 local journeyInterchangesFlag = 0 ~= RavKav_getFieldAsNumber(CONTRACT_REC_REF, "Journey interchanges flag") 960 if journeyInterchangesFlag then RavKav_addTextNode(CTSUM_REF, "Journey interchanges", "Includes switching/resume") end 961 962 if 0 < RavKav_getFieldAsNumber(CONTRACT_REC_REF, "Restriction duration") then 963 RavKav_copyField(CONTRACT_REC_REF, CTSUM_REF, "Restriction duration") 964 end 965 966 RavKav_copyField(CONTRACT_REC_REF, CTSUM_REF, "Balance") 967 968 if 0x3fff ~= RavKav_getFieldAsNumber(CONTRACT_REC_REF, "Valid from") then --14 bits of valid from date are inverted 969 RavKav_copyField(CONTRACT_REC_REF, CTSUM_REF, "Valid from") 970 elseif 0 < RavKav_getFieldAsNumber(CONTRACT_REC_REF, "Date of purchase") then 971 RavKav_copyField(CONTRACT_REC_REF, CTSUM_REF, "Date of purchase") 972 end 973 974 RavKav_copyField(CONTRACT_REC_REF, CTSUM_REF, "Valid to") 975 976 if 0 < RavKav_getFieldAsNumber(CONTRACT_REC_REF, "Valid until") then 977 RavKav_copyField(CONTRACT_REC_REF, CTSUM_REF, "Valid until") 978 end 979end 980 981function RavKav_summariseContracts(APP_REF, SUM_REF) 982 log.print(log.DBG, "Summarising contracts...") 983 local CONTRACTS_REF = nodes.find_first(APP_REF, {label="Contracts"}) 984 if CONTRACTS_REF then 985 local VC_REF = nodes.append(SUM_REF, {classname="file", label="Valid Contracts"}) 986 local IC_REF = nodes.append(SUM_REF, {classname="file", label="Invalid Contracts"}) 987 local numRecords = RavKav_getRecordInfo(CONTRACTS_REF) 988 local nRec 989 for nRec = 1, numRecords do 990 RavKav_summariseContract(CONTRACTS_REF, nRec, VC_REF, IC_REF) 991 end 992 end 993end 994 995function RavKav_readFiles(APP_REF) 996 local lid_index 997 local lid_desc 998 for lid_index, lid_desc in ipairs(LID_LIST) do 999 RavKav_readFile(APP_REF, lid_desc[1], lid_desc[2], lid_desc[3]) 1000 end 1001end 1002 1003function RavKav_parseRecords(APP_REF) 1004 RavKav_parseGeneralInfo(APP_REF) 1005 local cntrAry = RavKav_parseCounters(APP_REF) 1006 RavKav_parseEventsLog(APP_REF) 1007 RavKav_parseContracts(APP_REF, cntrAry) 1008end 1009 1010function RavKav_summariseRecords(APP_REF, root) 1011 local SUM_REF = nodes.append(root, {classname="block", label="Summary"}) 1012 RavKav_summariseGeneralInfo(APP_REF, SUM_REF) 1013 RavKav_summariseLatestTravel(APP_REF, SUM_REF) 1014 RavKav_summariseContracts(APP_REF, SUM_REF) 1015end 1016 1017function RavKav_parseAnswerToSelect(ctx) 1018 local ats 1019 local data 1020 1021 for ats in ctx:find({label="answer to select"}) do 1022 data = nodes.get_attribute(ats,"val") 1023 tlv_parse(ats,data,RAVKAV_IDO) 1024 end 1025end 1026 1027 1028RavKav_parseAnswerToSelect(CARD) 1029RavKav_parseRecords(CARD) 1030RavKav_summariseRecords(CARD, CARD) 1031 1032