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