venerdì 1 settembre 2023

Published settembre 01, 2023 by Django Faiola with 0 comment

dfPassJSON (Carte e biglietti in Wallet)

Panoramica

Un pass è una raccolta di dati che rappresentano un biglietto, un coupon o una carta. Può essere destinato a un singolo utilizzo da parte di un individuo (e quindi contenere dettagli come un numero di volo e un'allocazione dei posti) o può essere un token di utilizzo multiplo che può essere condiviso da qualsiasi numero di utenti (ad esempio un coupon di sconto). Una descrizione dettagliata è disponibile nel documento About Pass Files di Apple.

https://learn.microsoft.com/it-it/xamarin/ios/platform/passkit

Wallet, una sorta di portafogli elettronico, è l'app di Apple che archivia e visualizza carte di credito o di debito, biglietti per i trasporti, biglietti per eventi, coupon e carte fedeltà nei propri dispositivi.

I pass vengono distribuiti all'utente come file compressi firmati digitalmente che contengono un file JSON con la descrizione e altre risorse come immagini e localizzazione delle stringhe (opzionale).

Struttura del pass sul disco

Sul disco vengono memorizzati nella cartella principale:

Backup: HomeDomain/Library/Passes/
File System: /private/var/mobile/Library/Passes/

Ogni pass ha una sua sottocartella denominata "<nome-del-pass>.pkpass".

I file presenti nella radice di questa cartella sono:

  • background.png: Immagine con il sottofondo della parte frontale del pass;
  • footer.png: Immagine visualizzata sulla parte frontale del pass vicino al codice a barre;
  • pass.json: Dizionario JSON con i dati e i metadati del pass;
  • icon.png: L'icona del pass visualizzata nelle notifiche, email e nella schermata di blocco;
  • logo.png: Immagine visualizzata sulla parte frontale del pass in alto a sinistra;
  • manifest.json: Dizionario JSON dove ogni chiave è il percorso relativo del file al top level del pacchetto e il valore della chiave è l'hash SHA-1 di questo file. Vengono esclusi dolo il file manifest.json stesso e signature;
  • pass.json: Dizionario JSON che definisce il pass (dati e metadati);
  • signature: Una firma PKCS #7 del file "manifest.json";
  • strip.png: L'immagine visualizzata dietro il campi primari sulla parte frontale del pass;
  • thumbnail.png: Immagine (miniatura) aggiuntiva visualizzata sulla parte frontale del pass utilizzata per esempio come foto del titolare in una tessera;

xx.ljproj sono le cartelle che contengono la localizzazione delle stringhe del pass memorizzate sul file "pass.strings". A volte sono presenti anche le immagini dei loghi personalizzati per la lingua o altre risorse.

Esempi di layout di un pass:

Carta d'imbarco

Coupon
Biglietto per eventi
Generico
Carte fedeltà

Per approfondimenti consulta la documentazione ufficiale Pass Design and Creation.

pass.json

Questo file JSON contiene dati e metadati che permettono sia l'identificazione del pass che la sua rappresentazione visiva. E' suddiviso a livelli di chiavi con il primo livello (Top-Level Keys) come segue:

  • Standard Keys: Informazioni di base per l'identificazione del pass;
    • description: (localizable string), required, descrizione del pass;
    • formatVersion: (integer), required, versione del formato del file, 1 (uno);
    • organizationName: (localizable string), required, nome dell'organizzazione che ha generato e firmato il pass;
    • passTypeIdentifier: (string), required, identificatore del tipo di pass;
    • serialNumber: (string), required, numero di serie univoco del pass;
    • teamIdentifier: (string), required, identificatore del team dell'organizzazione che ha generato e firmato il pass.
  • Associated App Keys: Informazioni su un'app associata a un pass;
    • appLaunchURL: (string), un URL da passare all'app associata durante l' avvio;
    • associatedStoreIdentifiers: (array of numbers), un elenco di identificatori di elementi di iTunes per le app associate.
  • Companion App Keys: Informazioni personalizzate su un pass fornito per un'app Companion da utilizzare.
    • userInfo : (any JSON data), informazioni personalizzate che non vengono visualizzate dall'utente.
  • Expiration Keys: Informazioni sulla scadenza e validità del pass;
    • expirationDate: (W3C date, as a string), data di scadenza completa di ore, minuti e facoltativamente secondi;
    • voided: (boolean), indica se il pass è stato esplicitamente contrassegnato come annullato (es. un coupon riscattato). Valore predefinito false.
  • Relevance Keys: Informazioni sul dove e quando un pass è rilevante (es. beacon, posizione o data e ora);
    • beacons: (array of beacon, dictionaries), i beacon che segnano i luoghi in cui il pass è rilevante; Il beacon è un dizionario di tipo "Beacon Dictionary Keys";
    • locations: (array of location, dictionaries), posizioni in cui il pass è rilevante; La location è un dizionario di tipo "Location Dictionary Keys".
    • maxDistance: (number), distanza massima in metri da una latitudine e longitudine rilevanti che per il pass è rilevante;
    • relevantDate: (W3C date, as a string), data e ora quando il pass diventa rilevante (es. biglietto del cinema, inizio del film).
  • Style Keys: Unica chiave che corrisponde al tipo del pass. Il valore di questa chiave è un dizionario di tipo "Pass Structure Dictionary Keys".
    • boardingPass: carta d'imbarco;
    • coupon: coupon;
    • eventTicket: biglietti per eventi;
    • generic: pass generici;
    • storeCard: carte fedeltà.
  • Visual Appearance Keys: Informazioni che definiscono l'aspetto estetico del pass (es. codici a barre, colori, sfondo, etc.);
    • barcode: (barcode dictionary), dizionario "Barcode Dictionary Keys" con le informazioni sul codice a barre;
    • barcodes: (array of barcode, dictionaries), lista di barcode;
    • backgroundColor: (color, as a string), colore di sfondo del pass, es. rgb(23, 187, 82);
    • foregroundColor: (color, as a string), colore del testo del pass, es. rgb(100, 10, 110);
    • groupingIdentifier: (string), identificatore di raggruppamento per pass correlati es. pass di gruppo. Valido solo per i biglietti per eventi e carte d'imbarco;
    • labelColor: (color, as a string), colore del testo dell'etichetta;
    • logoText: (string), testo visualizzato accanto al logo del pass;
    • suppressStripShine: (boolean), se true, l'immagine viene visualizzata senza l'effetto brillante.
  • Web Service Keys: Informazioni per l'aggiornamento del pass via web tramite URL e token di autenticazione;
    • authenticationToken: (string), il token di autorizzazione per il servizio web. Il token deve essere almeno 16 caratteri;
    • webServiceURL: (string), l'URL del servizio web conforme alle API descritte in PassKit Web Service Reference.
  • NFC-Enabled Pass Keys: Informazioni per il supporto NFC per inviare informazioni sulla carta premi come parte di una transazione Apple Pay;
    • message: (string), required, il payload da trasmettere al terminal Apple Pay. Deve essere 64 byte o meno;
    • encryptionPublicKey: (string), chiave crittografica pubblica in Base64.
  • Semantic Tags Keys: I metadati presenti nei tag semantici sono leggibili a livello macchina e aiutano il sistema a comprendere meglio i pass del portafogli e a suggerire le azioni pertinenti che l'utente può intraprendere; es. l'impostazione della modalità non disturbare per la durata di un film. Possono essere presenti a livello di root del pass o a livello di Top-Level-Keys all'interno delle chiavi dei dizionari "Pass Structure Dictionary Keys" ; auxiliaryFields, backFields, headerFields, primaryFields, secondaryField.
A un livello più profondo (diciamo secondo livello) vi sono le "Pass Structure Dictionary Keys" che definiscono la struttura del pass, dividendola in sottoparti:

  • auxiliaryFields: (array of field, dictionaries), campi aggiuntivi da visualizzare sulla parte frontale del pass;
  • backFields: (array of field, dictionaries), campi sulla parte posteriore del pass;
  • headerFields: (array of field, dictionaries), campi da visualizzare nell'intestazione della parte anteriore del pass;
  • primaryFields: (array of field, dictionaries), campi da visualizzare in modo prominente sulla parte frontale del pass;
  • secondaryFields: (array of field, dictionaries), campi da visualizzare in modo normale sulla parte frontale del pass;
  • transitType: (string), è richiesto per le carte d'imbarco e ha uno dei seguenti valori: PKTransitTypeAir, PKTransitTypeBoat, PKTransitTypeBus, PKTransitTypeGenericPKTransitTypeTrain.

Field Dictionary Keys: un campo è un dizionario che contiene le chiavi per definire diverse categorie di valori: standard, date e numeri.

  • Standard Field Dictionary Keys: Queste chiavi sono usate per tutti i dizionari che richiedono un campo;
    • attributedValue: (localizable string, ISO 8601 date as a string, or number), valore dell'attributo del campo (es. codice HTML per un link);
    • changeMessage: Specifica il formato della stringa di testo utilizzata per notificare che il pass è stato aggiornato (escape %@);
    • dataDetectorTypes: Rilevatori di dati che vengono applicati al valore del campo della parte posteriore del pass. Possibili valori:
      • PKDataDetectorTypePhoneNumber;
      • PKDataDetectorTypeLink;
      • PKDataDetectorTypeAddress;
      • PKDataDetectorTypeCalendarEvent.
    • key: (string), required, la chiave deve essere univoca all'interno dell'ambito dell'intero pass;
    • label: (localizable string), testo dell'etichetta associata;
    • textAlignment: (string), allineamento del contenuto del campo:
      • PKTextAlignmentLeft: a sinistra;
      • PKTextAlignmentCenter: al centro;
      • PKTextAlignmentRight: a destra;
      • PKTextAlignmentNatural: basato sulla direzione di scrittura.
    • value: (localizable string, ISO 8601 date as a string, or number), required, è il valore del campo.
  • Date Style Keys: Informazioni su come una data e/o ora devono essere rappresentati in visualizzazione;
    • dateStyle: (string), formato di visualizzazione delle date tra i possibili valori:
      • PKDateStyleNone: "";
      • PKDateStyleShort: "11/23/37" o "3:30 PM";
      • PKDateStyleMedium: "Nov 23, 1937" o "3:30:32 PM";
      • PKDateStyleLong: "November 23, 1937" o "3:30:32 PM PST";
      • PKDateStyleFull: "Tuesday, April 12, 1952 AD" o "3:30:42 PM Pacific Standard Time".
    • ignoresTimeZone: (boolean), visualizza sempre la data e l'ora nel fuso orario dato e il suo valore predefinito è false;
    • isRelative: (boolean), se true l'etichetta viene visualizzata come data relativa e il suo valore predefinito è false;
    • timeStyle: (string), formato di visualizzazione delle ore con i possibili valori già elencati in dateStyle;
  • Number Style Keys: Informazioni su come un numero deve essere rappresentato in visualizzazione.
    • currencyCode: (string), ISO 4217 currency code (EUR, USD);
    • numberStyle: (string), possibili valori con locale (en_US, fr_FR e  zh_CN):
      • PKNumberStyleDecimal: "1,234.568", "1 234,568", "1,234.568";
      • PKNumberStylePercent: "12%", "12 %", "12%";
      • PKNumberStyleScientific: "1.2345678E3", "1,2345678E3", "1.2345678E3";
      • PKNumberStyleSpellOut: "one hundred twenty-three", "cent vingt-trois", "一百二十三".

Location Dictionary Keys: contiene le informazioni sulla posizione.

  • altitude: (double), altitudine in metri;
  • latitude: (double), required, latitudine in gradi decimali;
  • longitude: (double), required, longitudine in gradi decimali;
  • relevantText: (string), testo visualizzato sulla schermata di blocco quando il pass è attualmente rilevante.

Beacon Dictionary Keys: contiene le informazioni dei beacon.

  • major: (16-bit unsigned integer), versione maggiore del beacon Bluetooth Low Energy;
  • minor: (16-bit unsigned integer), versione minore del beacon Bluetooth Low Energy;
  • proximityUUID: (string), required, identificatore univoco del beacon Bluetooth Low Energy;
  • relevantText: (string), testo visualizzato sulla schermata di blocco quando il pass è attualmente rilevante.
Barcode Dictionary Keys: contiene le informazioni sul codice a barre del pass.
  • altText: (string), testo visualizzato vicino al codice a barre;
  • format : (string), required, formato del codice a barre. Possibili valori;
    • PKBarcodeFormatQR;
    • PKBarcodeFormatPDF417;
    • PKBarcodeFormatAztec;
    • PKBarcodeFormatCode128
  • message: (string), required, messaggio da visualizzare come codi;
  • messageEncoding: (IANA character set name, as a string), required, il nome del set di caratteri IANA della codifica del testo da utilizzare per convertire il messaggio da una rappresentazione di stringa in una rappresentazione dei dati che il sistema rende come codice a barre, come "ISO-8859-1".
Semantic Tag Currency Amount: contiene le informazioni sulla quantità di denaro e tipo di valuta.
  • amount: (string), quantità di denaro;
  • currencyCode: (ISO 4217 currency code as a string), codice valuta es. EUR.
Semantic Tag Seat: contiene le informazioni sull'identificazione del posto per le carte d'imbarco o per i biglietti di evento.
  • seatDescription: (localizable string), descrizione del posto es. "A flat bed seat";
  • seatIdentifier: (localizable string), codice identificatore del posto;
  • seatNumber: (localizable string), numero del posto;
  • seatRow: (localizable string), fila che contiene il posto;
  • seatSection: (localizable string), sezione che contiene il posto;
  • seatType: (localizable string), tipo di posto, es. "Reserved seating".
Semantic Tag Location: contiene le informazioni sulle coordinate del posto.
  • latitude: (double), required, latitudine in gradi decimali;
  • longitude: (double), required, longitudine in gradi decimali;
Semantic Tag WiFi Network: contiene le informazioni richieste per la connessione alla rete WiFi.
  • password: (string), required, password della rete WiFi;
  • ssid: (string), required, nome della rete WiFi.
Semantic Tag Person Name Component: contiene le informazioni che compongono il nome di una persona.
  • familyName: (string), nome di famiglia o cognome;
  • givenName: (string), nome;
  • middleName: (string), secondo nome;
  • namePrefix: (string), prefisso per il nome es. "Dr";
  • nameSuffix: (string), suffisso per il nome es. "Junior";
  • nickname: (string), nickname;
  • phoneticRepresentation: (string), pronuncia del nome.

Per completare il quadro dei pass bisogna tenere conto anche della lingua se disponibile ovvero la traduzione locale di tutti i campi di tipo localizable string.

Questo è un esempio di "pass.strings" della lingua italiana presente nella sottocartella "it.lproj" del pass.

"departure_time_label" = "Ora";
"departure_date_label" = "Data";
"gate_close_time_label" = "Il gate chiude";
"queue_name_label" = "Fila";
"seat_number_label" = "Posto";
"seat_paid_number_label" = "Posto*";
"passenger_name_label" = "Passeggero";
"flight_number_label" = "N. volo";
"sequence_number_label" = "Sequenza";
"recordlocator_label" = "Riferimento";
"departure_station_label" = "Da";
"arrival_station_label" = "A";
"baggage_label" = "Bagagli";
"discount_label" = "Sconto";
"fast_track" = "Fast Track";
"business_plus" = "Flexi Plus";
"leisure_plus" = "Plus";
"infant" = "Neonato";
"queue_other" = "Non Prioritario";
"queue_priority" = "Priorità";
"boarding_pass" = "Carta D’Imbarco";
"ssr_label" = "Extra per il viaggio";
"bags_collected_at_label" = "Bagagli ritirati a";
"connection_flight_label" = "Volo in coincidenza - Finale";
"operated_by_label" = "Operato da";
"smallbag_label" = "1 borsa piccola a bordo (40 cm x 20 cm x 25 cm) che entra sotto il sedile";
"twocabinbags_label" = "2 x Bagagli a mano";
"10kg_label" = "Bagaglio da stiva da 10 kg";
"departure_station" = "Roma (Ciampino)";
"arrival_station" = "Francoforte (Hahn)";
"ssr_names" = "1xBagaglio da stiva da 10 kg"; 
Come si procede? semplice, per ogni valore del campo di tipo localizable string si cerca la corrispondente traduzione. Supponiamo di avere un campo del tipo  "label": "departure_time_label", a "departure_time_label" corrisponde nel dizionario "it.lproj" il nuovo valore localizzato "Ora", quindi "label": "Ora".

Per maggiori informazioni sui Wallet Passes fare riferimento alla documentazione ufficiale Wallet Passes.

Esempio di decodifica

Per fare un po' di chiarezza a tutto questo "pastrocchio" analizziamo un caso concreto di una carta d'imbarco (cartella principale del pass 48LkoTqneTAeJW5kDN3H7f9CvQk=.pkpass).

pass.json
{
   "serialNumber":"465829952",
   "formatVersion":1,
   "passTypeIdentifier":"pass.booking.swiss.com",
   "teamIdentifier":"6K643MP477",
   "authenticationToken": "daeb1f05-XXXX-YYYY-ZZZZ-883ffd57146b",
   "webServiceURL": "https://prod.bc.ncrwebhost.mobi/mobiqa/servlet/Passbook",
   "description":"SWISS Boarding pass",
   "locations": [
         {
           "latitude" : 49.6233,
           "longitude" : 6.2044,
           "relevantText" : "LX759 Boarding 18:35"
         }
        ],
   "expirationDate":"2016-09-26T05:46:30+00:00",
   "barcode":{
      "message":"M1FAIOLA/D      E8LO2H7 LUXZRHLX 0759 267Y011A0047 148>5180 M267 BLX              2A7242301648536N                           N",
      "format":"PKBarcodeFormatAztec",
      "messageEncoding":"iso-8859-1"
   },
   "organizationName":"SWISS",
   "logoText":"",
   "foregroundColor":"rgb(0, 0, 0)",
   "backgroundColor":"rgb(255, 255, 255)",
   "labelColor":"rgb(0, 0, 0)",
   "boardingPass":{
      "transitType":"PKTransitTypeAir",
      "headerFields":[
         {
            "key":"seat",
            "label":"Seat",
            "value":"11A",
            "changeMessage":"Seat number changed to %@"
         },
{
            "key":"gate",
            "label":"Gate",
            "value":"A09",
            "changeMessage":"Gate changed to %@."
         }
      ],
      "primaryFields":[
         {
            "key":"depart",
            "label":"Departure",
            "value":"LUX",
            "changeMessage":"Departure airport changed to %@."
         },
         {
            "key":"destination",
            "label":"Arrival",
            "value":"ZRH",
            "changeMessage":"Destination airport changed to %@."
         }
      ],
      "secondaryFields":[         
         {
            "key":"passenger",
            "label":"Passenger",
            "value":"FAIOLA/D",
            "changeMessage":"Passenger changed to %@."
         },
{
            "key":"ff_status",
            "label":"Status",
            "value":"",
            "changeMessage":"Status number changed to %@"
         }],
              
      "auxiliaryFields":[
         {
            "key":"flightNumber",
            "label":"Flight",
            "value":"LX759",
            "changeMessage":"Flight number changed to %@"
         },
         {
            "key":"date",
            "label":"Date",
            "value":"23September",
            "changeMessage":"Date number changed to %@"
         },
         {
            "key":"boarding",
            "label":"Boarding",
            "value":"18:35",
            "changeMessage":"Boarding number changed to %@"
         },
         {
            "key":"bookingClass",
            "label":"Class",
            "value":"Y",
            "changeMessage":"Class number changed to %@"
         }
      ],
"backFields" : [
{
"key":"eTicket",
"label":"Your ticket number:",
"value":"724 2301 648 536"
},
{
"key":"SWISSwishes",
"label":"SWISS wishes you a pleasant flight.",
"value":"Before departure please read the detailed information regarding  baggage regulations carefully.\r\rNo  dangerous goods are allowed in either checked or carry-on baggage. \r\rTo change your seat or if you plan not to travel, please go to <a href='https://checkin.swiss.com/ck.fly?locale=EN&amp;first_name1=D&amp;last_name1=FAIOLA&amp;departure_port=LUX&amp;departure_date=2016/09/23&amp;flight_number=759&amp;carrier=LX&amp;group=NO&amp;etkt=7242301648536#'>swiss.com</a>.\r\rSWISS.COM"
}
    ]
    
   }
}

I risultati dei campi sono formattati come (key) label=value

  • Standard Keys
    • description: SWISS Boarding pass
    • formatVersion: 1
    • organizationName: SWISS
    • passTypeIdentifier: pass.booking.swiss.com
    • serialNumber: 465829952
    • teamIdentifier: 6K643MP477
  • Expiration Keys
    • expirationDate: 2016-09-26T05:46:30+00:00
  • locations (posizione[0])
    • latitude: 49.6233
    • longitude: 6.2044
    • relevantText: LX759 Boarding 18:35
  • Style Keys: boardingPass "Pass Structure Dictionary Keys"
    • transitType: PKTransitTypeAir (aereo)
    • headerFields (campi di intestazione[0]): (seat) Seat=11A
      • key: seat
      • label: Seat
      • value: 11A
    • headerFields (campi di intestazione[1]): (gate) Gate=A09
      • key: gate
      • label: Gate
      • value: A09
    • primaryFields (campi primari[0]): (depart) Departure=LUX
      • key: depart
      • label: Departure
      • value: LUX
    • primaryFields (campi primari[1]): (destination) Arrival=ZRH
      • key: destination
      • label: Arrival
      • value: ZRH
    • secondaryFields (campi secondari[0]): (passenger) Passenger=FAIOLA/D
      • key: passenger
      • label: Passenger
      • value: FAIOLA/D
    • secondaryFields (campi secondari[1]): (ff_status) Status=
      • key: ff_status
      • label: Status
      • value
    • auxiliaryFields (campi ausiliari[0]): (flightNumber) Flight=LX759
      • key: flightNumber
      • label: Flight
      • value: LX759
    • auxiliaryFields (campi ausiliari[1]): (date) Date=23September
      • key: date
      • label: Date
      • value: 23September
    • auxiliaryFields (campi ausiliari[2]): (boarding) Boarding=18:35
      • key: boarding
      • label: Boarding
      • value: 18:35
    • auxiliaryFields (campi ausiliari[3]): (bookingClass) Class=Y
      • key: bookingClass
      • label: Class
      • value: Y
    • backFields (campi retro[0]): (eTicket) Your ticket number:=724 2301 648 536
      • key: eTicket
      • label: Your ticket number:
      • value: 724 2301 648 536
    • backFields (campi retro[1]): (SWISSwishes) SWISS wishes you a pleasant flight.=Before departure please read the detailed information regarding  baggage regulations carefully.\r\rNo  dangerous goods are allowed in either checked or carry-on baggage. \r\rTo change your seat or if you plan not to travel, please go to <a href='https://checkin.swiss.com/ck.fly?locale=EN&amp;first_name1=C&amp;last_name1=FAIOLA&amp;departure_port=LUX&amp;departure_date=2016/09/23&amp;flight_number=759&amp;carrier=LX&amp;group=NO&amp;etkt=7242301648536#'>swiss.com</a>.\r\rSWISS.COM
      • key: SWISSwishes
      • label: SWISS wishes you a pleasant flight.
      • value: Before departure please read the detailed information regarding  baggage regulations carefully.\r\rNo  dangerous goods are allowed in either checked or carry-on baggage. \r\rTo change your seat or if you plan not to travel, please go to <a href='https://checkin.swiss.com/ck.fly?locale=EN&amp;first_name1=C&amp;last_name1=FAIOLA&amp;departure_port=LUX&amp;departure_date=2016/09/23&amp;flight_number=759&amp;carrier=LX&amp;group=NO&amp;etkt=7242301648536#'>swiss.com</a>.\r\rSWISS.COM

Questo è un semplice esempio di come decodificare la struttura di un pass, le cose possono complicarsi un po' con la presenza di numeri formattati, valute, beacon, tag semantici, etc. Un processo alquanto laborioso da svolgere a mano per un singolo pass, immaginate di ripeterlo per 100 ed ecco che viene in aiuto dfPassJSON, strumento creato ad hoc per i pass in grado di fare in automatico tutto questo e generare i report nei formati di testo semplice (.txt), testo separato da virgola (.csv) e foglio di calcolo Excel (.xls e .xlsx).

dfPassJSON

dfPassJSON, è se così si può definire uno "spin-off" del progetto WhatsAppIZZA nato nel gennaio del 2021 come parser autonomo per i pass del Wallet di Apple.

Windows | Linux

E' uno strumento user-friendly, bastano pochi click e l'analisi dei pass è pronta.


Seleziona la lingua di localizzazione, per esempio it per l'italiano, spunta il checkbox "Full details" per ottenere più dettagli nel report e/o semplicemente seleziona il percorso della cartella principale "Passes/" dei pass.

Per finire, fai click sul pulsante "Export as" per generare il report finale e seleziona il formato, per esempio Excel 2007.

Questo è un piccolo esempio di report in formato Excel:


Read More