@open-charging-cloud/chargy-core 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8078,11 +8078,16 @@ function CreateWarning(message, level = "low" /* low */) {
8078
8078
  message
8079
8079
  };
8080
8080
  }
8081
- function CreateError(message, level = "high" /* high */) {
8082
- return {
8081
+ function CreateError(message, level = "high" /* high */, code, details) {
8082
+ const error = {
8083
8083
  level,
8084
8084
  message
8085
8085
  };
8086
+ if (code !== void 0)
8087
+ error.code = code;
8088
+ if (details !== void 0)
8089
+ error.details = details;
8090
+ return error;
8086
8091
  }
8087
8092
  function isISessionCryptoResult1(obj) {
8088
8093
  return isObject(obj) && obj["status"] !== void 0;
@@ -9068,6 +9073,20 @@ var ACrypt = class {
9068
9073
  newText.classList.remove("overEntry");
9069
9074
  };
9070
9075
  }
9076
+ // Records why a verification step failed as structured data: a stable reason
9077
+ // key (localized via i18n.json and machine-switchable by the GUI) plus an
9078
+ // optional, language-neutral technical detail. Presentation is left to the GUI.
9079
+ AddVerificationError(cryptoResult, reasonKey, detail) {
9080
+ const details = detail instanceof Error ? detail.message : typeof detail === "string" ? detail : void 0;
9081
+ (cryptoResult.errors ??= []).push(
9082
+ CreateError(
9083
+ this.chargy.GetMultilanguageText(reasonKey),
9084
+ "high" /* high */,
9085
+ reasonKey,
9086
+ details
9087
+ )
9088
+ );
9089
+ }
9071
9090
  };
9072
9091
  var Alfen = class {
9073
9092
  chargy;
@@ -9294,8 +9313,7 @@ var Alfen = class {
9294
9313
  "connectors": [{
9295
9314
  "type": asString(connector?.["type"]) ?? "",
9296
9315
  "cable": {
9297
- "length": asNumber(connector?.["cableLength"]) ?? 0,
9298
- "looses": asNumber(connector?.["cableLooses"]) ?? 0
9316
+ "length": asNumber(connector?.["cableLength"]) ?? 0
9299
9317
  }
9300
9318
  }],
9301
9319
  "energyMeters": [
@@ -9483,9 +9501,15 @@ var AlfenCrypt01 = class extends ACrypt {
9483
9501
  cryptoResult.publicKey = meter.publicKeys[0]?.value;
9484
9502
  cryptoResult.publicKeyFormat = meter.publicKeys[0]?.format;
9485
9503
  cryptoResult.publicKeySignatures = meter.publicKeys[0]?.signatures;
9504
+ let publicKey;
9505
+ try {
9506
+ publicKey = buf2hex(this.chargy.base32Decode(cryptoResult.publicKey ?? "", "RFC4648"));
9507
+ } catch (exception) {
9508
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
9509
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
9510
+ }
9511
+ let result = false;
9486
9512
  try {
9487
- const publicKey = buf2hex(this.chargy.base32Decode(cryptoResult.publicKey ?? "", "RFC4648"));
9488
- let result = false;
9489
9513
  switch (meter.publicKeys[0]?.algorithm ?? "") {
9490
9514
  case "secp192r1":
9491
9515
  cryptoResult.hashValue = await sha256(cryptoBuffer);
@@ -9530,25 +9554,29 @@ var AlfenCrypt01 = class extends ACrypt {
9530
9554
  );
9531
9555
  break;
9532
9556
  }
9533
- if (result) {
9534
- return setResult("ValidSignature" /* ValidSignature */);
9535
- }
9536
- return setResult("InvalidSignature" /* InvalidSignature */);
9537
- } catch {
9557
+ } catch (exception) {
9558
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMalformed", exception);
9538
9559
  return setResult("InvalidSignature" /* InvalidSignature */);
9539
9560
  }
9540
- } catch {
9561
+ if (result)
9562
+ return setResult("ValidSignature" /* ValidSignature */);
9563
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMismatch");
9564
+ return setResult("InvalidSignature" /* InvalidSignature */);
9565
+ } catch (exception) {
9566
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
9541
9567
  return setResult("InvalidPublicKey" /* InvalidPublicKey */);
9542
9568
  }
9543
9569
  } else
9544
9570
  return setResult("PublicKeyNotFound" /* PublicKeyNotFound */);
9545
9571
  } else
9546
9572
  return setResult("EnergyMeterNotFound" /* EnergyMeterNotFound */);
9547
- } catch {
9573
+ } catch (exception) {
9574
+ this.AddVerificationError(cryptoResult, "Verification_UnexpectedError", exception);
9548
9575
  return setResult("InvalidSignature" /* InvalidSignature */);
9549
9576
  }
9550
9577
  }
9551
- return {};
9578
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMissing");
9579
+ return setResult("InvalidSignature" /* InvalidSignature */);
9552
9580
  }
9553
9581
  async ViewMeasurement(measurementValue, _errorDiv, introDiv, infoDiv, PlainTextDiv, HashedPlainTextDiv, PublicKeyDiv, SignatureExpectedDiv, SignatureCheckDiv) {
9554
9582
  const result = measurementValue.result;
@@ -10387,33 +10415,46 @@ var BSMCrypt01 = class extends ACrypt {
10387
10415
  const publicKeyDER = ASN1_PublicKey.decode(Buffer.from(meter.publicKeys[0]?.value ?? "", "hex"), "der");
10388
10416
  publicKey = buf2hex(publicKeyDER.publicKey.data).toLowerCase();
10389
10417
  }
10418
+ let keyPair;
10390
10419
  try {
10391
- if (this.curve.keyFromPublic(publicKey, "hex").verify(
10392
- cryptoResult.sha256value,
10393
- cryptoResult.signature
10394
- )) {
10395
- if (measurementValue.errors && measurementValue.errors.length > 0)
10396
- return setResult("ValidationError" /* ValidationError */);
10397
- return setResult("ValidSignature" /* ValidSignature */);
10398
- }
10399
- if (measurementValue.errors && measurementValue.errors.length > 0)
10400
- return setResult("ValidationError" /* ValidationError */);
10401
- return setResult("InvalidSignature" /* InvalidSignature */);
10402
- } catch {
10420
+ keyPair = this.curve.keyFromPublic(publicKey, "hex");
10421
+ } catch (exception) {
10422
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
10423
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
10424
+ }
10425
+ const keyValidation = keyPair.validate();
10426
+ if (!keyValidation.result) {
10427
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyNotOnCurve", keyValidation.reason ?? void 0);
10428
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
10429
+ }
10430
+ let signatureValid;
10431
+ try {
10432
+ signatureValid = keyPair.verify(cryptoResult.sha256value, cryptoResult.signature);
10433
+ } catch (exception) {
10434
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMalformed", exception);
10403
10435
  return setResult("InvalidSignature" /* InvalidSignature */);
10404
10436
  }
10405
- } catch {
10437
+ if (measurementValue.errors && measurementValue.errors.length > 0)
10438
+ return setResult("ValidationError" /* ValidationError */);
10439
+ if (signatureValid)
10440
+ return setResult("ValidSignature" /* ValidSignature */);
10441
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMismatch");
10442
+ return setResult("InvalidSignature" /* InvalidSignature */);
10443
+ } catch (exception) {
10444
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
10406
10445
  return setResult("InvalidPublicKey" /* InvalidPublicKey */);
10407
10446
  }
10408
10447
  } else
10409
10448
  return setResult("PublicKeyNotFound" /* PublicKeyNotFound */);
10410
10449
  } else
10411
10450
  return setResult("EnergyMeterNotFound" /* EnergyMeterNotFound */);
10412
- } catch {
10451
+ } catch (exception) {
10452
+ this.AddVerificationError(cryptoResult, "Verification_UnexpectedError", exception);
10413
10453
  return setResult("InvalidSignature" /* InvalidSignature */);
10414
10454
  }
10415
10455
  }
10416
- return {};
10456
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMissing");
10457
+ return setResult("InvalidSignature" /* InvalidSignature */);
10417
10458
  }
10418
10459
  async ViewMeasurement(measurementValue, _errorDiv, introDiv, infoDiv, PlainTextDiv, HashedPlainTextDiv, PublicKeyDiv, SignatureExpectedDiv, SignatureCheckDiv) {
10419
10460
  if (measurementValue.measurement === void 0)
@@ -10592,35 +10633,7 @@ var BSMCrypt01 = class extends ACrypt {
10592
10633
  if ((value & 1 << 29) != 0) events.push("OEM 15");
10593
10634
  return events;
10594
10635
  }
10595
- // private DecodeStatus(statusValue: string) : Array<string>
10596
- // {
10597
- // const statusArray:string[] = [];
10598
- // try
10599
- // {
10600
- // const status = parseInt(statusValue);
10601
- // if ((status & 1) == 1)
10602
- // statusArray.push("Fehler erkannt");
10603
- // if ((status & 2) == 2)
10604
- // statusArray.push("Synchrone Messwertübermittlung");
10605
- // // Bit 3 is reserved!
10606
- // if ((status & 8) == 8)
10607
- // statusArray.push("System-Uhr ist synchron");
10608
- // else
10609
- // statusArray.push("System-Uhr ist nicht synchron");
10610
- // if ((status & 16) == 16)
10611
- // statusArray.push("Rücklaufsperre aktiv");
10612
- // if ((status & 32) == 32)
10613
- // statusArray.push("Energierichtung -A");
10614
- // if ((status & 64) == 64)
10615
- // statusArray.push("Magnetfeld erkannt");
10616
- // }
10617
- // catch
10618
- // {
10619
- // statusArray.push("Invalid status!");
10620
- // }
10621
- // return statusArray;
10622
- // }
10623
- // //#endregion
10636
+ //#endregion
10624
10637
  };
10625
10638
 
10626
10639
  // src/interfaces/IPublicKeyInfo.ts
@@ -12079,29 +12092,44 @@ var EMHCrypt01 = class extends ACrypt {
12079
12092
  cryptoResult.publicKey = publicKey?.value.toLowerCase();
12080
12093
  cryptoResult.publicKeyFormat = publicKey?.format;
12081
12094
  cryptoResult.publicKeySignatures = publicKey?.signatures;
12095
+ let keyPair;
12082
12096
  try {
12083
- if (this.curve.keyFromPublic(cryptoResult.publicKey ?? "", "hex").verify(
12084
- cryptoResult.sha256value,
12085
- cryptoResult.signature
12086
- )) {
12087
- return setResult("ValidSignature" /* ValidSignature */);
12088
- }
12089
- return setResult("InvalidSignature" /* InvalidSignature */);
12090
- } catch {
12097
+ keyPair = this.curve.keyFromPublic(cryptoResult.publicKey ?? "", "hex");
12098
+ } catch (exception) {
12099
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
12100
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12101
+ }
12102
+ const keyValidation = keyPair.validate();
12103
+ if (!keyValidation.result) {
12104
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyNotOnCurve", keyValidation.reason ?? void 0);
12105
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12106
+ }
12107
+ let signatureValid;
12108
+ try {
12109
+ signatureValid = keyPair.verify(cryptoResult.sha256value, cryptoResult.signature);
12110
+ } catch (exception) {
12111
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMalformed", exception);
12091
12112
  return setResult("InvalidSignature" /* InvalidSignature */);
12092
12113
  }
12093
- } catch {
12114
+ if (signatureValid)
12115
+ return setResult("ValidSignature" /* ValidSignature */);
12116
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMismatch");
12117
+ return setResult("InvalidSignature" /* InvalidSignature */);
12118
+ } catch (exception) {
12119
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
12094
12120
  return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12095
12121
  }
12096
12122
  } else
12097
12123
  return setResult("PublicKeyNotFound" /* PublicKeyNotFound */);
12098
12124
  } else
12099
12125
  return setResult("EnergyMeterNotFound" /* EnergyMeterNotFound */);
12100
- } catch {
12126
+ } catch (exception) {
12127
+ this.AddVerificationError(cryptoResult, "Verification_UnexpectedError", exception);
12101
12128
  return setResult("InvalidSignature" /* InvalidSignature */);
12102
12129
  }
12103
12130
  }
12104
- return {};
12131
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMissing");
12132
+ return setResult("InvalidSignature" /* InvalidSignature */);
12105
12133
  }
12106
12134
  async ViewMeasurement(measurementValue, _errorDiv, introDiv, infoDiv, PlainTextDiv, HashedPlainTextDiv, PublicKeyDiv, SignatureExpectedDiv, SignatureCheckDiv) {
12107
12135
  if (measurementValue.measurement?.chargingSession?.authorizationStart?.timestamp === void 0) {
@@ -12209,7 +12237,7 @@ var EMHCrypt01 = class extends ACrypt {
12209
12237
  DecodeStatus(statusValue) {
12210
12238
  const statusArray = [];
12211
12239
  try {
12212
- const status = parseInt(statusValue);
12240
+ const status = parseInt(statusValue, 16);
12213
12241
  if ((status & 1) == 1)
12214
12242
  statusArray.push("Fehler erkannt");
12215
12243
  if ((status & 2) == 2)
@@ -12311,29 +12339,44 @@ var GDFCrypt01 = class extends ACrypt {
12311
12339
  cryptoResult.publicKey = publicKey?.value.toLowerCase();
12312
12340
  cryptoResult.publicKeyFormat = publicKey?.format;
12313
12341
  cryptoResult.publicKeySignatures = publicKey?.signatures;
12342
+ let keyPair;
12314
12343
  try {
12315
- if (this.curve.keyFromPublic(cryptoResult.publicKey ?? "", "hex").verify(
12316
- cryptoResult.sha256value,
12317
- cryptoResult.signature
12318
- )) {
12319
- return setResult("ValidSignature" /* ValidSignature */);
12320
- }
12321
- return setResult("InvalidSignature" /* InvalidSignature */);
12322
- } catch {
12344
+ keyPair = this.curve.keyFromPublic(cryptoResult.publicKey ?? "", "hex");
12345
+ } catch (exception) {
12346
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
12347
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12348
+ }
12349
+ const keyValidation = keyPair.validate();
12350
+ if (!keyValidation.result) {
12351
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyNotOnCurve", keyValidation.reason ?? void 0);
12352
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12353
+ }
12354
+ let signatureValid;
12355
+ try {
12356
+ signatureValid = keyPair.verify(cryptoResult.sha256value, cryptoResult.signature);
12357
+ } catch (exception) {
12358
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMalformed", exception);
12323
12359
  return setResult("InvalidSignature" /* InvalidSignature */);
12324
12360
  }
12325
- } catch {
12361
+ if (signatureValid)
12362
+ return setResult("ValidSignature" /* ValidSignature */);
12363
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMismatch");
12364
+ return setResult("InvalidSignature" /* InvalidSignature */);
12365
+ } catch (exception) {
12366
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
12326
12367
  return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12327
12368
  }
12328
12369
  } else
12329
12370
  return setResult("PublicKeyNotFound" /* PublicKeyNotFound */);
12330
12371
  } else
12331
12372
  return setResult("EnergyMeterNotFound" /* EnergyMeterNotFound */);
12332
- } catch {
12373
+ } catch (exception) {
12374
+ this.AddVerificationError(cryptoResult, "Verification_UnexpectedError", exception);
12333
12375
  return setResult("InvalidSignature" /* InvalidSignature */);
12334
12376
  }
12335
12377
  }
12336
- return {};
12378
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMissing");
12379
+ return setResult("InvalidSignature" /* InvalidSignature */);
12337
12380
  }
12338
12381
  async ViewMeasurement(measurementValue, _errorDiv, introDiv, infoDiv, PlainTextDiv, HashedPlainTextDiv, PublicKeyDiv, SignatureExpectedDiv, SignatureCheckDiv) {
12339
12382
  if (measurementValue.measurement?.chargingSession?.authorizationStart?.timestamp === void 0) {
@@ -12672,19 +12715,38 @@ var MennekesCrypt01 = class extends ACrypt {
12672
12715
  );
12673
12716
  cryptoResult.hashValue = (await sha256(new DataView(signedDataBuffer))).substring(0, 48);
12674
12717
  const publicKey = cleanHex(meter.publicKeys.at(0)?.value ?? measurement.publicKey);
12675
- if (publicKey.length !== 96)
12718
+ if (publicKey.length !== 96) {
12719
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed");
12676
12720
  return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12677
- const result = this.curve192r1.keyFromPublic("04" + publicKey, "hex").verify(
12678
- cryptoResult.hashValue.toUpperCase(),
12679
- {
12721
+ }
12722
+ let keyPair;
12723
+ try {
12724
+ keyPair = this.curve192r1.keyFromPublic("04" + publicKey, "hex");
12725
+ } catch (exception) {
12726
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
12727
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12728
+ }
12729
+ const keyValidation = keyPair.validate();
12730
+ if (!keyValidation.result) {
12731
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyNotOnCurve", keyValidation.reason ?? void 0);
12732
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12733
+ }
12734
+ let signatureValid;
12735
+ try {
12736
+ signatureValid = keyPair.verify(cryptoResult.hashValue.toUpperCase(), {
12680
12737
  r: signatureExpected.r,
12681
12738
  s: signatureExpected.s
12682
- }
12683
- );
12684
- return setResult(
12685
- result ? "ValidSignature" /* ValidSignature */ : "InvalidSignature" /* InvalidSignature */
12686
- );
12687
- } catch {
12739
+ });
12740
+ } catch (exception) {
12741
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMalformed", exception);
12742
+ return setResult("InvalidSignature" /* InvalidSignature */);
12743
+ }
12744
+ if (signatureValid)
12745
+ return setResult("ValidSignature" /* ValidSignature */);
12746
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMismatch");
12747
+ return setResult("InvalidSignature" /* InvalidSignature */);
12748
+ } catch (exception) {
12749
+ this.AddVerificationError(cryptoResult, "Verification_UnexpectedError", exception);
12688
12750
  return setResult("InvalidSignature" /* InvalidSignature */);
12689
12751
  }
12690
12752
  }
@@ -12890,6 +12952,132 @@ function numberToBytesBE(value, length) {
12890
12952
  }
12891
12953
  return bytes;
12892
12954
  }
12955
+ var OCMFBonnTariffParseError = class extends Error {
12956
+ tariffText;
12957
+ constructor(tariffText, message) {
12958
+ super(message);
12959
+ this.name = "OCMFBonnTariffParseError";
12960
+ this.tariffText = tariffText;
12961
+ }
12962
+ };
12963
+ function parseCents(value, tariffText, fieldName) {
12964
+ if (!/^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(value))
12965
+ throw new OCMFBonnTariffParseError(tariffText, `${fieldName} must be a non-negative decimal number`);
12966
+ const parsedValue = Number(value);
12967
+ if (!Number.isFinite(parsedValue))
12968
+ throw new OCMFBonnTariffParseError(tariffText, `${fieldName} is outside the supported numeric range`);
12969
+ return parsedValue;
12970
+ }
12971
+ function parseOCMFBonnTariffText(tariffText) {
12972
+ const fields = tariffText.split(";");
12973
+ const code = fields[0];
12974
+ if (fields[1] !== "EUR")
12975
+ throw new OCMFBonnTariffParseError(tariffText, "currency must be EUR");
12976
+ switch (code) {
12977
+ case "001":
12978
+ if (fields.length !== 6)
12979
+ throw new OCMFBonnTariffParseError(tariffText, "profile 001 must contain six fields");
12980
+ return {
12981
+ raw: tariffText,
12982
+ code,
12983
+ currency: "EUR",
12984
+ startFeeCents: parseCents(fields[2] ?? "", tariffText, "W"),
12985
+ energyFeeCentsPerKWh: parseCents(fields[3] ?? "", tariffText, "X"),
12986
+ blockingFeeCentsPerMinute: parseCents(fields[4] ?? "", tariffText, "Y"),
12987
+ blockingFeeStartMinute: parseCents(fields[5] ?? "", tariffText, "Z")
12988
+ };
12989
+ case "002":
12990
+ if (fields.length !== 5)
12991
+ throw new OCMFBonnTariffParseError(tariffText, "profile 002 must contain five fields");
12992
+ return {
12993
+ raw: tariffText,
12994
+ code,
12995
+ currency: "EUR",
12996
+ startFeeCents: parseCents(fields[2] ?? "", tariffText, "W"),
12997
+ energyFeeCentsPerKWh: parseCents(fields[3] ?? "", tariffText, "X"),
12998
+ blockingFeeCentsPerMinute: parseCents(fields[4] ?? "", tariffText, "Y"),
12999
+ blockingFeeStartsAfterCharging: true
13000
+ };
13001
+ case "003":
13002
+ if (fields.length !== 4)
13003
+ throw new OCMFBonnTariffParseError(tariffText, "profile 003 must contain four fields");
13004
+ return {
13005
+ raw: tariffText,
13006
+ code,
13007
+ currency: "EUR",
13008
+ startFeeCents: parseCents(fields[2] ?? "", tariffText, "W"),
13009
+ timeFeeCentsPerMinute: parseCents(fields[3] ?? "", tariffText, "X")
13010
+ };
13011
+ default:
13012
+ throw new OCMFBonnTariffParseError(tariffText, "unknown Bonn tariff profile");
13013
+ }
13014
+ }
13015
+ function tryParseOCMFBonnTariffText(tariffText) {
13016
+ try {
13017
+ return parseOCMFBonnTariffText(tariffText);
13018
+ } catch (error) {
13019
+ if (error instanceof OCMFBonnTariffParseError)
13020
+ return void 0;
13021
+ throw error;
13022
+ }
13023
+ }
13024
+ function priceComponent(type, price, stepSize) {
13025
+ return {
13026
+ type,
13027
+ price,
13028
+ step_size: stepSize
13029
+ };
13030
+ }
13031
+ function eurosFromCents(cents) {
13032
+ return new Decimal(cents).dividedBy(100);
13033
+ }
13034
+ function eurosPerHourFromCentsPerMinute(cents) {
13035
+ return eurosFromCents(cents).times(60);
13036
+ }
13037
+ function ocmfBonnTariffToChargingTariff(tariff) {
13038
+ const baseComponents = new Array(
13039
+ priceComponent("FLAT", eurosFromCents(tariff.startFeeCents), 1)
13040
+ );
13041
+ const elements = new Array();
13042
+ switch (tariff.code) {
13043
+ case "001":
13044
+ baseComponents.push(priceComponent("ENERGY", eurosFromCents(tariff.energyFeeCentsPerKWh), 1));
13045
+ elements.push(
13046
+ { price_components: baseComponents },
13047
+ {
13048
+ price_components: [
13049
+ priceComponent("PARKING_TIME", eurosPerHourFromCentsPerMinute(tariff.blockingFeeCentsPerMinute), 60)
13050
+ ],
13051
+ restrictions: {
13052
+ min_duration: tariff.blockingFeeStartMinute * 60
13053
+ }
13054
+ }
13055
+ );
13056
+ break;
13057
+ case "002":
13058
+ baseComponents.push(priceComponent("ENERGY", eurosFromCents(tariff.energyFeeCentsPerKWh), 1));
13059
+ elements.push(
13060
+ { price_components: baseComponents },
13061
+ {
13062
+ price_components: [
13063
+ priceComponent("PARKING_TIME", eurosPerHourFromCentsPerMinute(tariff.blockingFeeCentsPerMinute), 60)
13064
+ ]
13065
+ }
13066
+ );
13067
+ break;
13068
+ case "003":
13069
+ baseComponents.push(priceComponent("TIME", eurosPerHourFromCentsPerMinute(tariff.timeFeeCentsPerMinute), 60));
13070
+ elements.push({ price_components: baseComponents });
13071
+ break;
13072
+ }
13073
+ return {
13074
+ "@id": tariff.raw,
13075
+ currency: tariff.currency,
13076
+ elements
13077
+ };
13078
+ }
13079
+
13080
+ // src/OCMF.ts
12893
13081
  var OCMFv1_x = class extends ACrypt {
12894
13082
  curve = new this.chargy.elliptic.ec("p256");
12895
13083
  constructor(chargy) {
@@ -12979,6 +13167,8 @@ var OCMFv1_x = class extends ACrypt {
12979
13167
  return "Reading Current Type";
12980
13168
  case "CL":
12981
13169
  return "Cumulated Loss";
13170
+ case "EI":
13171
+ return "Error Index";
12982
13172
  case "EF":
12983
13173
  return "Error Flags";
12984
13174
  case "ST":
@@ -13186,10 +13376,39 @@ var OCMF = class {
13186
13376
  const identificationType = firstOCMDJSONDocument.payload.IT;
13187
13377
  const identificationData = firstOCMDJSONDocument.payload.ID;
13188
13378
  const tariffText = firstOCMDJSONDocument.payload.TT;
13189
- const controlerFirmwareVersion = firstOCMDJSONDocument.payload.CF;
13379
+ const tariffTextInterpretation = typeof tariffText === "string" ? tryParseOCMFBonnTariffText(tariffText) : void 0;
13380
+ const chargingTariff = typeof tariffText === "string" && tariffText.length > 0 ? tariffTextInterpretation !== void 0 ? ocmfBonnTariffToChargingTariff(tariffTextInterpretation) : { "@id": tariffText } : void 0;
13381
+ const controllerFirmwareVersion = firstOCMDJSONDocument.payload.CF;
13190
13382
  const lossCompensation = firstOCMDJSONDocument.payload.LC;
13383
+ const signedCable = lossCompensation !== void 0 && typeof lossCompensation.LR === "number" && Number.isFinite(lossCompensation.LR) && typeof lossCompensation.LU === "string" && lossCompensation.LU.length > 0 ? {
13384
+ ...typeof lossCompensation.LN === "string" ? { lossCompensation: lossCompensation.LN } : {},
13385
+ ...typeof lossCompensation.LI === "number" ? { lossCompensationId: lossCompensation.LI.toString() } : {},
13386
+ resistance: lossCompensation.LR,
13387
+ resistanceUnit: lossCompensation.LU
13388
+ } : void 0;
13191
13389
  const chargePointIdType = firstOCMDJSONDocument.payload.CT;
13192
13390
  const chargePointId = firstOCMDJSONDocument.payload.CI;
13391
+ let signedChargingStationId;
13392
+ let signedEVSEId;
13393
+ let signedConnectorId;
13394
+ if (typeof chargePointId === "string" && chargePointId.trim().length > 0) {
13395
+ const normalizedChargePointId = chargePointId.trim();
13396
+ switch (chargePointIdType?.toUpperCase()) {
13397
+ case void 0:
13398
+ break;
13399
+ case "EVSEID":
13400
+ signedEVSEId = normalizedChargePointId;
13401
+ break;
13402
+ case "CBIDC": {
13403
+ const cbidcMatch = /^(\S+)\s+(\S+)$/.exec(normalizedChargePointId);
13404
+ if (cbidcMatch?.[1] !== void 0 && cbidcMatch[2] !== void 0) {
13405
+ signedChargingStationId = cbidcMatch[1];
13406
+ signedConnectorId = cbidcMatch[2];
13407
+ }
13408
+ break;
13409
+ }
13410
+ }
13411
+ }
13193
13412
  if (isOptionalString(formatVersion) && isOptionalString(gatewayInformation) && isOptionalString(gatewaySerial) && isOptionalString(gatewayVersion) && isMandatoryString(paging) && isOptionalString(meterVendor) && isOptionalString(meterModel) && // OCMF 1.0 table 3 lists MS as 1..1, but the later "Relation of Serial Numbers,
13194
13413
  // Charge Point and Public Key" section makes the serial-number fields conditionally
13195
13414
  // mandatory. KEBA KCP30 records identify the signing gateway via GS and omit MS.
@@ -13202,7 +13421,7 @@ var OCMF = class {
13202
13421
  // IT is 1..1 in the user-assignment table for transaction records. Some OCMF 1.0
13203
13422
  // implementations omit it when no user is assigned (IS=false, ID empty). We tolerate
13204
13423
  // this vendor compatibility case instead of rejecting otherwise valid signatures.
13205
- isOptionalString(identificationType) && isOptionalString(identificationData) && isOptionalString(tariffText) && isOptionalString(controlerFirmwareVersion) && isOptionalJSONObject(lossCompensation) && isOptionalString(chargePointIdType) && isOptionalString(chargePointId)) {
13424
+ isOptionalString(identificationType) && isOptionalString(identificationData) && isOptionalString(tariffText) && isOptionalString(controllerFirmwareVersion) && isOptionalJSONObject(lossCompensation) && isOptionalString(chargePointIdType) && isOptionalString(chargePointId)) {
13206
13425
  const paginationPrefix = paging.length > 0 ? paging.charAt(0).toLowerCase() : null;
13207
13426
  const transactionType = paginationPrefix === "t" ? "transaction" /* transaction */ : paginationPrefix === "f" ? "fiscal" /* fiscal */ : "undefined" /* undefined */;
13208
13427
  const pagination = paging.length > 1 ? parseNumber(paging.substring(1)) : null;
@@ -13234,11 +13453,22 @@ var OCMF = class {
13234
13453
  "@id": identificationData ?? "?",
13235
13454
  "type": identificationType ?? "?"
13236
13455
  },
13456
+ "chargingTariffs": chargingTariff !== void 0 ? [chargingTariff] : void 0,
13237
13457
  "ocmf": {
13238
13458
  "formatVersion": formatVersion,
13239
13459
  "gatewayInformation": gatewayInformation,
13240
13460
  "gatewaySerial": gatewaySerial,
13241
- "gatewayVersion": gatewayVersion
13461
+ "gatewayVersion": gatewayVersion,
13462
+ "meterVendor": meterVendor,
13463
+ "meterModel": meterModel,
13464
+ "meterSerial": meterSerial,
13465
+ "meterFirmware": meterFirmware,
13466
+ "tariffText": tariffText,
13467
+ "tariffTextInterpretation": tariffTextInterpretation,
13468
+ "controllerFirmwareVersion": controllerFirmwareVersion,
13469
+ "lossCompensation": lossCompensation,
13470
+ "chargePointIdentificationType": chargePointIdType,
13471
+ "chargePointIdentification": chargePointId
13242
13472
  },
13243
13473
  // "chargingStationOperators": [{
13244
13474
  // "chargingPools": [{
@@ -13305,6 +13535,11 @@ var OCMF = class {
13305
13535
  "@context": "https://open.charging.cloud/contexts/SessionSignatureFormats/OCMFv1.0+json",
13306
13536
  "begin": "?",
13307
13537
  "end": "?",
13538
+ "chargingStationId": signedChargingStationId,
13539
+ "EVSEId": signedEVSEId,
13540
+ "ConnectorId": signedConnectorId,
13541
+ "tariffId": chargingTariff?.["@id"],
13542
+ "chargingTariffs": chargingTariff !== void 0 ? [chargingTariff] : void 0,
13308
13543
  "authorizationStart": {
13309
13544
  "@id": identificationData ?? "?",
13310
13545
  "type": identificationType ?? "?",
@@ -13317,19 +13552,50 @@ var OCMF = class {
13317
13552
  }],
13318
13553
  "certainty": 1
13319
13554
  };
13320
- if (ContainerInfos?.chargingStations !== void 0)
13555
+ const resolvedChargingStationId = signedChargingStationId ?? containerChargingStation?.["@id"];
13556
+ if (resolvedChargingStationId !== void 0) {
13557
+ const matchingContainerStation = containerChargingStation?.["@id"] === resolvedChargingStationId ? containerChargingStation : void 0;
13558
+ const resolvedChargingStation = {
13559
+ ...matchingContainerStation ?? { "@id": resolvedChargingStationId },
13560
+ ...controllerFirmwareVersion !== void 0 ? {
13561
+ firmware: {
13562
+ ...matchingContainerStation?.firmware,
13563
+ version: controllerFirmwareVersion
13564
+ }
13565
+ } : {}
13566
+ };
13567
+ CTR.chargingStations = [
13568
+ resolvedChargingStation,
13569
+ ...ContainerInfos?.chargingStations?.filter((station) => station["@id"] !== resolvedChargingStationId) ?? []
13570
+ ];
13571
+ if (CTR.chargingSessions?.[0] !== void 0) {
13572
+ CTR.chargingSessions[0].chargingStationId ??= resolvedChargingStationId;
13573
+ CTR.chargingSessions[0].chargingStation = resolvedChargingStation;
13574
+ }
13575
+ } else if (ContainerInfos?.chargingStations !== void 0)
13321
13576
  CTR.chargingStations = ContainerInfos.chargingStations;
13322
- if (containerChargingStation !== void 0 && CTR.chargingSessions?.[0] !== void 0) {
13323
- CTR.chargingSessions[0].chargingStationId = containerChargingStation["@id"];
13324
- CTR.chargingSessions[0].chargingStation = containerChargingStation;
13325
- }
13326
13577
  if (containerEVSE !== void 0 && CTR.chargingSessions?.[0] !== void 0) {
13327
- CTR.chargingSessions[0].EVSEId = containerEVSE["@id"];
13328
- CTR.chargingSessions[0].EVSE = containerEVSE;
13329
- }
13330
- if (containerConnector !== void 0 && CTR.chargingSessions?.[0] !== void 0) {
13331
- CTR.chargingSessions[0].ConnectorId = containerConnector["@id"];
13332
- CTR.chargingSessions[0].Connector = containerConnector;
13578
+ const chargingSession = CTR.chargingSessions[0];
13579
+ chargingSession.EVSEId ??= containerEVSE["@id"];
13580
+ if (chargingSession.EVSEId === containerEVSE["@id"])
13581
+ chargingSession.EVSE = containerEVSE;
13582
+ }
13583
+ const resolvedConnectorId = signedConnectorId ?? containerConnector?.["@id"];
13584
+ if ((resolvedConnectorId !== void 0 || signedCable !== void 0) && CTR.chargingSessions?.[0] !== void 0) {
13585
+ const matchingContainerConnector = containerConnector?.["@id"] === resolvedConnectorId ? containerConnector : void 0;
13586
+ const resolvedConnector = {
13587
+ ...matchingContainerConnector,
13588
+ ...resolvedConnectorId !== void 0 ? { "@id": resolvedConnectorId } : {},
13589
+ ...signedCable !== void 0 ? {
13590
+ cable: {
13591
+ ...matchingContainerConnector?.cable,
13592
+ ...signedCable
13593
+ }
13594
+ } : {}
13595
+ };
13596
+ const chargingSession = CTR.chargingSessions[0];
13597
+ chargingSession.ConnectorId ??= resolvedConnectorId;
13598
+ chargingSession.Connector = resolvedConnector;
13333
13599
  }
13334
13600
  const measurementsByKey = /* @__PURE__ */ new Map();
13335
13601
  for (const ocmfJSONDocument of OCMFJSONDocuments) {
@@ -13343,12 +13609,13 @@ var OCMF = class {
13343
13609
  const readingUnit = effectiveReading.RU;
13344
13610
  const readingCurrentType = effectiveReading.RT;
13345
13611
  const cumulatedLoss = effectiveReading.CL;
13612
+ const errorIndex = effectiveReading.EI;
13346
13613
  const errorFlags = effectiveReading.EF;
13347
13614
  const status = effectiveReading.ST;
13348
13615
  inheritedReading = effectiveReading;
13349
13616
  if (isMandatoryString(time) && isOptionalString(transaction) && isMandatoryDecimal(readingValue) && // Note: Some vendors use a JSON string here!
13350
13617
  isOptionalString(readingIdentification) && isMandatoryString(readingUnit) && isOptionalString(readingCurrentType) && // chargyLib.isOptionalDecimal (cumulatedLoss) &&
13351
- isOptionalString(errorFlags) && isMandatoryString(status)) {
13618
+ isOptionalNumber(errorIndex) && isOptionalString(errorFlags) && isMandatoryString(status)) {
13352
13619
  const timeSplit = time.split(" ");
13353
13620
  if (timeSplit.length != 2) return {
13354
13621
  status: "InvalidSessionFormat" /* InvalidSessionFormat */,
@@ -13447,6 +13714,7 @@ var OCMF = class {
13447
13714
  // "T" ToDo: Serialize this to a string!
13448
13715
  "pagination": pagination,
13449
13716
  // "9289"
13717
+ "errorIndex": errorIndex,
13450
13718
  "errorFlags": errorFlags,
13451
13719
  // ""
13452
13720
  "cumulatedLoss": cumulatedLoss != null && cumulatedLoss !== 0 ? new Decimal(cumulatedLoss) : void 0,
@@ -13456,17 +13724,17 @@ var OCMF = class {
13456
13724
  // "value": ocmfJSONDocument.signature["SD"]
13457
13725
  // }],
13458
13726
  "result": {
13459
- "status": ocmfJSONDocument.validationStatus ?? "Unvalidated" /* Unvalidated */
13727
+ "status": ocmfJSONDocument.validationStatus ?? "Unvalidated" /* Unvalidated */,
13728
+ // Surface the per-document verification diagnostics on the measurement value.
13729
+ ...ocmfJSONDocument.validationErrors && ocmfJSONDocument.validationErrors.length > 0 ? { errors: ocmfJSONDocument.validationErrors } : {}
13460
13730
  },
13461
13731
  "ocmfDocument": ocmfJSONDocument
13462
13732
  });
13463
13733
  }
13464
13734
  }
13465
13735
  }
13466
- if (ContainerInfos?.chargingStations !== void 0)
13467
- CTR.chargingStations = ContainerInfos.chargingStations;
13468
13736
  if (ContainerInfos?.warnings !== void 0)
13469
- CTR.warnings = [...CTR.warnings ?? [], ...ContainerInfos.warnings];
13737
+ CTR.warnings = (CTR.warnings ?? []).concat(ContainerInfos.warnings);
13470
13738
  CTR.status = OCMFJSONDocuments.every((ocmfJSONDocument) => ocmfJSONDocument.validationStatus === "ValidSignature" /* ValidSignature */) ? "ValidSignature" /* ValidSignature */ : "InvalidSignature" /* InvalidSignature */;
13471
13739
  if (CTR.chargingSessions != null && CTR.chargingSessions.length > 0 && CTR.chargingSessions[0]) {
13472
13740
  CTR.begin = CTR.chargingSessions[0].begin;
@@ -13603,6 +13871,20 @@ var OCMF = class {
13603
13871
  // return mergedCTR;
13604
13872
  // }
13605
13873
  // //#endregion
13874
+ // Records why a verification step failed as structured data: a stable reason
13875
+ // key (localized via i18n.json and machine-switchable by the GUI) plus an
13876
+ // optional, language-neutral technical detail. Presentation is left to the GUI.
13877
+ AddValidationError(OCMFJSONDocument, reasonKey, detail) {
13878
+ const details = detail instanceof Error ? detail.message : typeof detail === "string" ? detail : void 0;
13879
+ (OCMFJSONDocument.validationErrors ??= []).push(
13880
+ CreateError(
13881
+ this.chargy.GetMultilanguageText(reasonKey),
13882
+ "high" /* high */,
13883
+ reasonKey,
13884
+ details
13885
+ )
13886
+ );
13887
+ }
13606
13888
  //#region (private) validateOCMFSignature(OCMFJSONDocument, PublicKey, PublicKeyEncoding?)
13607
13889
  async validateOCMFSignature(OCMFJSONDocument, PublicKey, PublicKeyEncoding) {
13608
13890
  try {
@@ -13642,7 +13924,8 @@ var OCMF = class {
13642
13924
  curve = new this.chargy.elliptic.ec("p256");
13643
13925
  break;
13644
13926
  }
13645
- } catch {
13927
+ } catch (exception) {
13928
+ this.AddValidationError(OCMFJSONDocument, "Verification_UnknownSignatureFormat", exception);
13646
13929
  OCMFJSONDocument.validationStatus = "UnknownSignatureFormat" /* UnknownSignatureFormat */;
13647
13930
  return OCMFJSONDocument.validationStatus;
13648
13931
  }
@@ -13719,20 +14002,26 @@ var OCMF = class {
13719
14002
  y: OCMFJSONDocument.publicKey.y
13720
14003
  }, "hex");
13721
14004
  }
13722
- } catch {
14005
+ } catch (exception) {
14006
+ this.AddValidationError(OCMFJSONDocument, "Verification_PublicKeyDecodingFailed", exception);
13723
14007
  OCMFJSONDocument.validationStatus = "InvalidPublicKey" /* InvalidPublicKey */;
13724
14008
  return OCMFJSONDocument.validationStatus;
13725
14009
  }
13726
14010
  try {
13727
14011
  if (publicKey === null)
13728
14012
  throw new Error("Missing public key!");
13729
- OCMFJSONDocument.validationStatus = publicKey.verify(OCMFJSONDocument.hashValue, OCMFJSONDocument.signatureRS) ? "ValidSignature" /* ValidSignature */ : "InvalidSignature" /* InvalidSignature */;
14013
+ const signatureValid = publicKey.verify(OCMFJSONDocument.hashValue, OCMFJSONDocument.signatureRS);
14014
+ if (!signatureValid)
14015
+ this.AddValidationError(OCMFJSONDocument, "Verification_SignatureMismatch");
14016
+ OCMFJSONDocument.validationStatus = signatureValid ? "ValidSignature" /* ValidSignature */ : "InvalidSignature" /* InvalidSignature */;
13730
14017
  return await Promise.resolve(OCMFJSONDocument.validationStatus);
13731
- } catch {
14018
+ } catch (exception) {
14019
+ this.AddValidationError(OCMFJSONDocument, "Verification_SignatureMalformed", exception);
13732
14020
  OCMFJSONDocument.validationStatus = "InvalidSignature" /* InvalidSignature */;
13733
14021
  return OCMFJSONDocument.validationStatus;
13734
14022
  }
13735
- } catch {
14023
+ } catch (exception) {
14024
+ this.AddValidationError(OCMFJSONDocument, "Verification_UnexpectedError", exception);
13736
14025
  OCMFJSONDocument.validationStatus = "InvalidSignature" /* InvalidSignature */;
13737
14026
  return OCMFJSONDocument.validationStatus;
13738
14027
  }
@@ -13961,6 +14250,11 @@ var OCMF = class {
13961
14250
  }
13962
14251
  if (ocmfJSONDocumentGroup[0]) {
13963
14252
  switch (ocmfJSONDocumentGroup[0].payload.FV) {
14253
+ // FV has cardinality 0..1 in OCMF. All supported 1.x
14254
+ // versions use the same parser, so an omitted version
14255
+ // is parsed as generic OCMF without changing the
14256
+ // signed payload or inventing a concrete version.
14257
+ case void 0:
13964
14258
  case "0.1":
13965
14259
  // OCMF 0.1 SAFE reference data uses a few legacy field names/forms (VI/VV,
13966
14260
  // string based IS values), but the compact signed document structure is close
@@ -14982,6 +15276,184 @@ function readDERInteger(bytes, getOffset, setOffset) {
14982
15276
  return hex;
14983
15277
  }
14984
15278
 
15279
+ // src/PTBContainer.ts
15280
+ var base64RegExp = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
15281
+ var formatVersionRegExp = /^1(?:\.[0-9]+)?$/;
15282
+ var PTB = class {
15283
+ chargy;
15284
+ constructor(chargy) {
15285
+ this.chargy = chargy;
15286
+ }
15287
+ async TryToParsePTBContainer(container) {
15288
+ const validation = this.validateContainer(container);
15289
+ if (!validation.valid)
15290
+ return this.validationError(validation.issues);
15291
+ const ptbContainer = validation.container;
15292
+ const containerInfos = {
15293
+ chargingStations: [{
15294
+ "@id": ptbContainer.chargeboxIdentifier,
15295
+ address: this.normalizeAddress(ptbContainer.address),
15296
+ geoLocation: {
15297
+ lat: ptbContainer.geoLocation.lat,
15298
+ lng: ptbContainer.geoLocation.lng
15299
+ },
15300
+ EVSEs: [{
15301
+ "@id": ptbContainer.chargeboxIdentifier
15302
+ }]
15303
+ }]
15304
+ };
15305
+ return new OCMF(this.chargy).TryToParseOCMFDocuments(
15306
+ [ptbContainer.ocmfBegin, ptbContainer.ocmfEnd],
15307
+ ptbContainer.publicKey,
15308
+ "base64",
15309
+ containerInfos
15310
+ );
15311
+ }
15312
+ validateContainer(container) {
15313
+ const issues = [];
15314
+ if (!isMandatoryJSONObject(container))
15315
+ return {
15316
+ valid: false,
15317
+ issues: [{
15318
+ path: "$",
15319
+ message: "must be an object"
15320
+ }]
15321
+ };
15322
+ this.requireConstantString(container, "format", "ptb", issues);
15323
+ this.requireString(container, "publicKey", issues);
15324
+ this.requireString(container, "chargeboxIdentifier", issues);
15325
+ this.requireString(container, "ocmfBegin", issues);
15326
+ this.requireString(container, "ocmfEnd", issues);
15327
+ const formatVersion = container["formatVersion"];
15328
+ if (formatVersion !== void 0 && (typeof formatVersion !== "string" || !formatVersionRegExp.test(formatVersion))) {
15329
+ issues.push({
15330
+ path: "$.formatVersion",
15331
+ message: "must match ^1(?:\\.[0-9]+)?$"
15332
+ });
15333
+ }
15334
+ const publicKey = container["publicKey"];
15335
+ if (typeof publicKey === "string" && publicKey.length > 0 && !base64RegExp.test(publicKey))
15336
+ issues.push({
15337
+ path: "$.publicKey",
15338
+ message: "must be a base64 encoded string"
15339
+ });
15340
+ for (const propertyName of ["ocmfBegin", "ocmfEnd"]) {
15341
+ const ocmfDocument = container[propertyName];
15342
+ if (typeof ocmfDocument === "string" && (ocmfDocument.length < 10 || !ocmfDocument.startsWith("OCMF|"))) {
15343
+ issues.push({
15344
+ path: "$." + propertyName,
15345
+ message: "must be an unmodified OCMF record beginning with OCMF|"
15346
+ });
15347
+ }
15348
+ }
15349
+ const address = container["address"];
15350
+ if (!isMandatoryJSONObject(address))
15351
+ issues.push({
15352
+ path: "$.address",
15353
+ message: "must be an object"
15354
+ });
15355
+ else
15356
+ this.validateAddress(address, issues);
15357
+ const geoLocation = container["geoLocation"];
15358
+ if (!isMandatoryJSONObject(geoLocation))
15359
+ issues.push({
15360
+ path: "$.geoLocation",
15361
+ message: "must be an object"
15362
+ });
15363
+ else
15364
+ this.validateGeoLocation(geoLocation, issues);
15365
+ if (issues.length > 0)
15366
+ return {
15367
+ valid: false,
15368
+ issues
15369
+ };
15370
+ return {
15371
+ valid: true,
15372
+ container
15373
+ };
15374
+ }
15375
+ validateAddress(address, issues) {
15376
+ this.requireString(address, "street", issues, "$.address");
15377
+ for (const propertyName of ["houseNumber", "zipCode", "postalCode", "town", "city", "country"]) {
15378
+ const propertyValue = address[propertyName];
15379
+ if (propertyValue !== void 0 && typeof propertyValue !== "string")
15380
+ issues.push({
15381
+ path: "$.address." + propertyName,
15382
+ message: "must be a string"
15383
+ });
15384
+ else if ((propertyName === "town" || propertyName === "city") && propertyValue === "")
15385
+ issues.push({
15386
+ path: "$.address." + propertyName,
15387
+ message: "must be a non-empty string"
15388
+ });
15389
+ }
15390
+ const town = address["town"];
15391
+ const city = address["city"];
15392
+ if ((typeof town !== "string" || town.length === 0) && (typeof city !== "string" || city.length === 0)) {
15393
+ issues.push({
15394
+ path: "$.address",
15395
+ message: "must contain a non-empty town or city"
15396
+ });
15397
+ }
15398
+ }
15399
+ validateGeoLocation(geoLocation, issues) {
15400
+ const latitude = geoLocation["lat"];
15401
+ const longitude = geoLocation["lng"];
15402
+ if (typeof latitude !== "number" || !Number.isFinite(latitude) || latitude < -90 || latitude > 90)
15403
+ issues.push({
15404
+ path: "$.geoLocation.lat",
15405
+ message: "must be a number between -90 and 90"
15406
+ });
15407
+ if (typeof longitude !== "number" || !Number.isFinite(longitude) || longitude < -180 || longitude > 180)
15408
+ issues.push({
15409
+ path: "$.geoLocation.lng",
15410
+ message: "must be a number between -180 and 180"
15411
+ });
15412
+ for (const propertyName of Object.keys(geoLocation))
15413
+ if (propertyName !== "lat" && propertyName !== "lng")
15414
+ issues.push({
15415
+ path: "$.geoLocation." + propertyName,
15416
+ message: "is not allowed"
15417
+ });
15418
+ }
15419
+ requireString(json, propertyName, issues, parentPath = "$") {
15420
+ const value = json[propertyName];
15421
+ if (typeof value !== "string" || value.length === 0)
15422
+ issues.push({
15423
+ path: parentPath + "." + propertyName,
15424
+ message: "must be a non-empty string"
15425
+ });
15426
+ }
15427
+ requireConstantString(json, propertyName, expectedValue, issues) {
15428
+ if (json[propertyName] !== expectedValue)
15429
+ issues.push({
15430
+ path: "$." + propertyName,
15431
+ message: "must equal " + expectedValue
15432
+ });
15433
+ }
15434
+ normalizeAddress(address) {
15435
+ return {
15436
+ city: address.city ?? address.town,
15437
+ street: address.street,
15438
+ houseNumber: address.houseNumber,
15439
+ postalCode: address.postalCode ?? address.zipCode,
15440
+ country: address.country
15441
+ };
15442
+ }
15443
+ validationError(issues) {
15444
+ return {
15445
+ format: "ptb",
15446
+ status: "InvalidSessionFormat" /* InvalidSessionFormat */,
15447
+ message: this.chargy.GetMultilanguageText("Invalid PTB OCMF container!"),
15448
+ certainty: 1,
15449
+ issues,
15450
+ errors: issues.map((issue) => CreateError(
15451
+ this.chargy.GetMultilanguageText(issue.path + " " + issue.message)
15452
+ ))
15453
+ };
15454
+ }
15455
+ };
15456
+
14985
15457
  // src/SAFE_XML.ts
14986
15458
  var SAFEXML = class _SAFEXML {
14987
15459
  chargy;
@@ -17492,6 +17964,26 @@ var Chargy = class {
17492
17964
  return textContent.replace(/\s+/g, "");
17493
17965
  return void 0;
17494
17966
  }
17967
+ TryToCreatePublicKeyLookup(processedFiles) {
17968
+ if (processedFiles.length === 0)
17969
+ return void 0;
17970
+ const publicKeys = new Array();
17971
+ for (const processedFile of processedFiles) {
17972
+ if (IsAChargeTransparencyRecord(processedFile.result) || IsAChargeTransparencyLiveLink(processedFile.result)) {
17973
+ return void 0;
17974
+ }
17975
+ if (IsAPublicKey(processedFile.result))
17976
+ publicKeys.push(processedFile.result);
17977
+ else if (IsAPublicKeyLookup(processedFile.result))
17978
+ publicKeys.push(...processedFile.result.publicKeys);
17979
+ else
17980
+ return void 0;
17981
+ }
17982
+ if (processedFiles.length === 1 && IsAPublicKeyLookup(processedFiles[0]?.result)) {
17983
+ return processedFiles[0].result;
17984
+ }
17985
+ return { publicKeys };
17986
+ }
17495
17987
  //#endregion
17496
17988
  //#region QR code image files...
17497
17989
  normalizeMIMEType(mimeType) {
@@ -18208,7 +18700,9 @@ var Chargy = class {
18208
18700
  if (IsAChargeTransparencyLiveLink(JSONContent)) {
18209
18701
  JSONContent.timestamp ??= (/* @__PURE__ */ new Date()).toISOString();
18210
18702
  processedFile.result = JSONContent;
18211
- } else if (isMandatoryString(JSONContext)) {
18703
+ } else if (JSONContent["format"] === "ptb")
18704
+ processedFile.result = await new PTB(this).TryToParsePTBContainer(JSONContent);
18705
+ else if (isMandatoryString(JSONContext)) {
18212
18706
  if (JSONContext.startsWith("https://open.charging.cloud/contexts/CTR+json"))
18213
18707
  processedFile.result = JSONContent;
18214
18708
  else if (JSONContext.startsWith("https://open.charging.cloud/contexts/publicKey+json"))
@@ -18248,18 +18742,15 @@ var Chargy = class {
18248
18742
  }
18249
18743
  processedFiles.push(processedFile);
18250
18744
  }
18745
+ const publicKeyLookup = this.TryToCreatePublicKeyLookup(processedFiles);
18746
+ if (publicKeyLookup != null)
18747
+ return publicKeyLookup;
18251
18748
  if (processedFiles.length == 1) {
18252
18749
  const processedFile = getFirstArrayElement(processedFiles, "Missing processed file");
18253
18750
  if (IsAChargeTransparencyRecord(processedFile.result))
18254
18751
  return this.processChargeTransparencyRecord(processedFile.result);
18255
18752
  if (IsAChargeTransparencyLiveLink(processedFile.result))
18256
18753
  return processedFile.result;
18257
- if (IsAPublicKeyLookup(processedFile.result))
18258
- return {
18259
- status: "InvalidSessionFormat" /* InvalidSessionFormat */,
18260
- message: this.GetMultilanguageText("UnknownOrInvalidChargeTransparencyRecord"),
18261
- certainty: 0
18262
- };
18263
18754
  return processedFile.result;
18264
18755
  } else if (processedFiles.length > 1) {
18265
18756
  const mergedCTR = {
@@ -18668,6 +19159,6 @@ var Chargy = class {
18668
19159
  }
18669
19160
  };
18670
19161
 
18671
- export { ACrypt, Alfen, AlfenCrypt01, BSMCrypt01, CanonicalJSONError, ChargeIT, ChargePoint, ChargePointCrypt01, IChargeTransparencyLiveLink_exports as ChargeTransparencyLiveLink, ChargeTransparencyLiveLinkContext, IChargeTransparencyRecord_exports as ChargeTransparencyRecord, Chargy, chargyInterfaces_exports as ChargyInterfaces, Clone, CloneCTR, ConcatenateBuffers, CreateDiv, CreateDiv2, CreateError, CreateWarning, CryptoAlgorithms, CryptoHashAlgorithms, DayOfWeek, DisplayPrefixes, EDL40, EDL40Crypt01, EDL40ValidationError, EDL40_OBIS, EDL40_SESSION_CONTEXT, EDL40_SIGNATURE_CONTEXT, EMHCrypt01, ErrorLevel, GDFCrypt01, IECCurves, IEncoding, InformationRelevance, InformationRelevanceToString, IsAChargeTransparencyLiveLink, IsAChargeTransparencyRecord, IsAPublicKey, IsAPublicKeyLookup, IsAPublicKeySignature, IsAPublicKeyXY, IsASessionCryptoResult, IsNullOrEmpty, JSONSignatureVerificationStatus, MENNEKES_EDL40_OBIS, MENNEKES_EDL40_XMLNS, Mennekes, MennekesCrypt01, OBIS2Hex, OBIS2MeasurementName, OBIS_RegExpr, OCMF, OCMFTransactionTypes, OCMFv1_x, OCPI, OIDInfo, PCDF, PCDFCrypt01, PCDFParseError, PCDFValidationError, PCDF_FIELD_ORDER, PCDF_PREFIX, ParseJSON_LD, PublicKeyFormats, IPublicKeyInfo_exports as PublicKeyInfo, SAFEXML, SessionVerificationResult, SetHex, SetInt8, SetText, SetText_withLength, SetTimestamp, SetTimestamp32, SetUInt32, SetUInt32_withCode, SetUInt64, SetUInt64D, SignMessage, SignatureFormats, TimeStatusTypes, UTC2human, VerificationResult, VerifyJSONMessageSignatures, WarningLevel, WhenNullOrEmpty, XMLContainer, asJSONArray, asJSONObject, asNumber, asString, base64ToBytes, buf2hex, buildEDL40Signature, buildIsaSignature, buildMennekesSignatureData, bytesToBase64, bytesToHex, canParseEDL40, canonicalJSONBytes, canonicalJSONStringify, cleanHex, closeFullscreen, createHexString, dateToMennekesLocalEpochSeconds, decodeSmlMessages, extractMennekesChargingProcesses, findEntryByObis, findGetListRes, firstKey, firstValue, getArrayElement, getArrayLikeElement, getDirectChildByLocalName, getDirectChildrenByLocalName, getElementsByLocalName, getFirstArrayElement, getInt16Bytes, getInt32Bytes, getInt64Bytes, getInt8Bytes, getLastArrayElement, getTrimmedTextContent, hashFile, hex2bin, hex32, hexToArrayBuffer, hexToBytes, intFromBytes, isEncodedValue, isGeoLocation, isI18NString, isICryptoResult, isIFileInfo, isISessionCryptoResult1, isISessionCryptoResult2, isJSONLDObject, isMandatoryArrayOfStrings, isMandatoryBoolean, isMandatoryDecimal, isMandatoryJSONArray, isMandatoryJSONObject, isMandatoryNumber, isMandatoryString, isMandatoryURL, isOIDInfo, isObject, isOptionalArrayOfStrings, isOptionalDecimal, isOptionalJSONArray, isOptionalJSONArrayError, isOptionalJSONArrayOk, isOptionalJSONObject, isOptionalNumber, isOptionalString, isOptionalStringArray, isOptionalStringOrOIDInfo, isOptionalURL, isPCDFText, isPublicKeySubject, isString, isStringArray, isStringOrOIDInfo, isStringOrStringArray, isaListNameContext, jsonPrettyPrinter, measurementName2human, normalizePCDFPublicKeyHex, normalizeXMLText, openFullscreen, pad, parseAndVerifyJSONSignatures, parseDescription, parseEDL40, parseHexString, parseMennekesXMLDocument, parseNumber, parseOBIS, parsePCDFDocument, parsePCDFPublicKey, parsePCDFSignature, parseSmlTime, parseUTC, readQRCodeTextFromImage, readQRCodeTextFromImageData, readTLV, secp224k1, setUILocale, sha256, sha256____, sha384, sha384____, sha512, sha512____, signJSONMessage, signMessage, stripPCDFControlCharacters, stripTransport, time2human, toArrayBuffer, toSessionVerificationResults, toUint8Array, transformEDL40Status, unquotePCDFText, validatePCDFFields, verifyEDL40Document, verifyJSONMessageSignatureResults, verifyJSONMessageSignatures, verifyJSONSignature, verifyJSONSignatureResult, verifyPCDFDocument };
19162
+ export { ACrypt, Alfen, AlfenCrypt01, BSMCrypt01, CanonicalJSONError, ChargeIT, ChargePoint, ChargePointCrypt01, IChargeTransparencyLiveLink_exports as ChargeTransparencyLiveLink, ChargeTransparencyLiveLinkContext, IChargeTransparencyRecord_exports as ChargeTransparencyRecord, Chargy, chargyInterfaces_exports as ChargyInterfaces, Clone, CloneCTR, ConcatenateBuffers, CreateDiv, CreateDiv2, CreateError, CreateWarning, CryptoAlgorithms, CryptoHashAlgorithms, DayOfWeek, DisplayPrefixes, EDL40, EDL40Crypt01, EDL40ValidationError, EDL40_OBIS, EDL40_SESSION_CONTEXT, EDL40_SIGNATURE_CONTEXT, EMHCrypt01, ErrorLevel, GDFCrypt01, IECCurves, IEncoding, InformationRelevance, InformationRelevanceToString, IsAChargeTransparencyLiveLink, IsAChargeTransparencyRecord, IsAPublicKey, IsAPublicKeyLookup, IsAPublicKeySignature, IsAPublicKeyXY, IsASessionCryptoResult, IsNullOrEmpty, JSONSignatureVerificationStatus, MENNEKES_EDL40_OBIS, MENNEKES_EDL40_XMLNS, Mennekes, MennekesCrypt01, OBIS2Hex, OBIS2MeasurementName, OBIS_RegExpr, OCMF, OCMFBonnTariffParseError, OCMFTransactionTypes, OCMFv1_x, OCPI, OIDInfo, PCDF, PCDFCrypt01, PCDFParseError, PCDFValidationError, PCDF_FIELD_ORDER, PCDF_PREFIX, PTB, ParseJSON_LD, PublicKeyFormats, IPublicKeyInfo_exports as PublicKeyInfo, SAFEXML, SessionVerificationResult, SetHex, SetInt8, SetText, SetText_withLength, SetTimestamp, SetTimestamp32, SetUInt32, SetUInt32_withCode, SetUInt64, SetUInt64D, SignMessage, SignatureFormats, TimeStatusTypes, UTC2human, VerificationResult, VerifyJSONMessageSignatures, WarningLevel, WhenNullOrEmpty, XMLContainer, asJSONArray, asJSONObject, asNumber, asString, base64ToBytes, buf2hex, buildEDL40Signature, buildIsaSignature, buildMennekesSignatureData, bytesToBase64, bytesToHex, canParseEDL40, canonicalJSONBytes, canonicalJSONStringify, cleanHex, closeFullscreen, createHexString, dateToMennekesLocalEpochSeconds, decodeSmlMessages, extractMennekesChargingProcesses, findEntryByObis, findGetListRes, firstKey, firstValue, getArrayElement, getArrayLikeElement, getDirectChildByLocalName, getDirectChildrenByLocalName, getElementsByLocalName, getFirstArrayElement, getInt16Bytes, getInt32Bytes, getInt64Bytes, getInt8Bytes, getLastArrayElement, getTrimmedTextContent, hashFile, hex2bin, hex32, hexToArrayBuffer, hexToBytes, intFromBytes, isEncodedValue, isGeoLocation, isI18NString, isICryptoResult, isIFileInfo, isISessionCryptoResult1, isISessionCryptoResult2, isJSONLDObject, isMandatoryArrayOfStrings, isMandatoryBoolean, isMandatoryDecimal, isMandatoryJSONArray, isMandatoryJSONObject, isMandatoryNumber, isMandatoryString, isMandatoryURL, isOIDInfo, isObject, isOptionalArrayOfStrings, isOptionalDecimal, isOptionalJSONArray, isOptionalJSONArrayError, isOptionalJSONArrayOk, isOptionalJSONObject, isOptionalNumber, isOptionalString, isOptionalStringArray, isOptionalStringOrOIDInfo, isOptionalURL, isPCDFText, isPublicKeySubject, isString, isStringArray, isStringOrOIDInfo, isStringOrStringArray, isaListNameContext, jsonPrettyPrinter, measurementName2human, normalizePCDFPublicKeyHex, normalizeXMLText, ocmfBonnTariffToChargingTariff, openFullscreen, pad, parseAndVerifyJSONSignatures, parseDescription, parseEDL40, parseHexString, parseMennekesXMLDocument, parseNumber, parseOBIS, parseOCMFBonnTariffText, parsePCDFDocument, parsePCDFPublicKey, parsePCDFSignature, parseSmlTime, parseUTC, readQRCodeTextFromImage, readQRCodeTextFromImageData, readTLV, secp224k1, setUILocale, sha256, sha256____, sha384, sha384____, sha512, sha512____, signJSONMessage, signMessage, stripPCDFControlCharacters, stripTransport, time2human, toArrayBuffer, toSessionVerificationResults, toUint8Array, transformEDL40Status, tryParseOCMFBonnTariffText, unquotePCDFText, validatePCDFFields, verifyEDL40Document, verifyJSONMessageSignatureResults, verifyJSONMessageSignatures, verifyJSONSignature, verifyJSONSignatureResult, verifyPCDFDocument };
18672
19163
  //# sourceMappingURL=index.js.map
18673
19164
  //# sourceMappingURL=index.js.map