@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.
@@ -9841,11 +9841,16 @@ function CreateWarning(message, level = "low" /* low */) {
9841
9841
  message
9842
9842
  };
9843
9843
  }
9844
- function CreateError(message, level = "high" /* high */) {
9845
- return {
9844
+ function CreateError(message, level = "high" /* high */, code, details) {
9845
+ const error = {
9846
9846
  level,
9847
9847
  message
9848
9848
  };
9849
+ if (code !== void 0)
9850
+ error.code = code;
9851
+ if (details !== void 0)
9852
+ error.details = details;
9853
+ return error;
9849
9854
  }
9850
9855
  function isISessionCryptoResult1(obj) {
9851
9856
  return isObject(obj) && obj["status"] !== void 0;
@@ -10831,6 +10836,20 @@ var ACrypt = class {
10831
10836
  newText.classList.remove("overEntry");
10832
10837
  };
10833
10838
  }
10839
+ // Records why a verification step failed as structured data: a stable reason
10840
+ // key (localized via i18n.json and machine-switchable by the GUI) plus an
10841
+ // optional, language-neutral technical detail. Presentation is left to the GUI.
10842
+ AddVerificationError(cryptoResult, reasonKey, detail) {
10843
+ const details = detail instanceof Error ? detail.message : typeof detail === "string" ? detail : void 0;
10844
+ (cryptoResult.errors ??= []).push(
10845
+ CreateError(
10846
+ this.chargy.GetMultilanguageText(reasonKey),
10847
+ "high" /* high */,
10848
+ reasonKey,
10849
+ details
10850
+ )
10851
+ );
10852
+ }
10834
10853
  };
10835
10854
  var Alfen = class {
10836
10855
  chargy;
@@ -11057,8 +11076,7 @@ var Alfen = class {
11057
11076
  "connectors": [{
11058
11077
  "type": asString(connector?.["type"]) ?? "",
11059
11078
  "cable": {
11060
- "length": asNumber(connector?.["cableLength"]) ?? 0,
11061
- "looses": asNumber(connector?.["cableLooses"]) ?? 0
11079
+ "length": asNumber(connector?.["cableLength"]) ?? 0
11062
11080
  }
11063
11081
  }],
11064
11082
  "energyMeters": [
@@ -11246,9 +11264,15 @@ var AlfenCrypt01 = class extends ACrypt {
11246
11264
  cryptoResult.publicKey = meter.publicKeys[0]?.value;
11247
11265
  cryptoResult.publicKeyFormat = meter.publicKeys[0]?.format;
11248
11266
  cryptoResult.publicKeySignatures = meter.publicKeys[0]?.signatures;
11267
+ let publicKey;
11268
+ try {
11269
+ publicKey = buf2hex(this.chargy.base32Decode(cryptoResult.publicKey ?? "", "RFC4648"));
11270
+ } catch (exception) {
11271
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
11272
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
11273
+ }
11274
+ let result = false;
11249
11275
  try {
11250
- const publicKey = buf2hex(this.chargy.base32Decode(cryptoResult.publicKey ?? "", "RFC4648"));
11251
- let result = false;
11252
11276
  switch (meter.publicKeys[0]?.algorithm ?? "") {
11253
11277
  case "secp192r1":
11254
11278
  cryptoResult.hashValue = await sha256(cryptoBuffer);
@@ -11293,25 +11317,29 @@ var AlfenCrypt01 = class extends ACrypt {
11293
11317
  );
11294
11318
  break;
11295
11319
  }
11296
- if (result) {
11297
- return setResult("ValidSignature" /* ValidSignature */);
11298
- }
11299
- return setResult("InvalidSignature" /* InvalidSignature */);
11300
- } catch {
11320
+ } catch (exception) {
11321
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMalformed", exception);
11301
11322
  return setResult("InvalidSignature" /* InvalidSignature */);
11302
11323
  }
11303
- } catch {
11324
+ if (result)
11325
+ return setResult("ValidSignature" /* ValidSignature */);
11326
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMismatch");
11327
+ return setResult("InvalidSignature" /* InvalidSignature */);
11328
+ } catch (exception) {
11329
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
11304
11330
  return setResult("InvalidPublicKey" /* InvalidPublicKey */);
11305
11331
  }
11306
11332
  } else
11307
11333
  return setResult("PublicKeyNotFound" /* PublicKeyNotFound */);
11308
11334
  } else
11309
11335
  return setResult("EnergyMeterNotFound" /* EnergyMeterNotFound */);
11310
- } catch {
11336
+ } catch (exception) {
11337
+ this.AddVerificationError(cryptoResult, "Verification_UnexpectedError", exception);
11311
11338
  return setResult("InvalidSignature" /* InvalidSignature */);
11312
11339
  }
11313
11340
  }
11314
- return {};
11341
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMissing");
11342
+ return setResult("InvalidSignature" /* InvalidSignature */);
11315
11343
  }
11316
11344
  async ViewMeasurement(measurementValue, _errorDiv, introDiv, infoDiv, PlainTextDiv, HashedPlainTextDiv, PublicKeyDiv, SignatureExpectedDiv, SignatureCheckDiv) {
11317
11345
  const result = measurementValue.result;
@@ -12150,33 +12178,46 @@ var BSMCrypt01 = class extends ACrypt {
12150
12178
  const publicKeyDER = ASN1_PublicKey.decode(Buffer.from(meter.publicKeys[0]?.value ?? "", "hex"), "der");
12151
12179
  publicKey = buf2hex(publicKeyDER.publicKey.data).toLowerCase();
12152
12180
  }
12181
+ let keyPair;
12153
12182
  try {
12154
- if (this.curve.keyFromPublic(publicKey, "hex").verify(
12155
- cryptoResult.sha256value,
12156
- cryptoResult.signature
12157
- )) {
12158
- if (measurementValue.errors && measurementValue.errors.length > 0)
12159
- return setResult("ValidationError" /* ValidationError */);
12160
- return setResult("ValidSignature" /* ValidSignature */);
12161
- }
12162
- if (measurementValue.errors && measurementValue.errors.length > 0)
12163
- return setResult("ValidationError" /* ValidationError */);
12164
- return setResult("InvalidSignature" /* InvalidSignature */);
12165
- } catch {
12183
+ keyPair = this.curve.keyFromPublic(publicKey, "hex");
12184
+ } catch (exception) {
12185
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
12186
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12187
+ }
12188
+ const keyValidation = keyPair.validate();
12189
+ if (!keyValidation.result) {
12190
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyNotOnCurve", keyValidation.reason ?? void 0);
12191
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12192
+ }
12193
+ let signatureValid;
12194
+ try {
12195
+ signatureValid = keyPair.verify(cryptoResult.sha256value, cryptoResult.signature);
12196
+ } catch (exception) {
12197
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMalformed", exception);
12166
12198
  return setResult("InvalidSignature" /* InvalidSignature */);
12167
12199
  }
12168
- } catch {
12200
+ if (measurementValue.errors && measurementValue.errors.length > 0)
12201
+ return setResult("ValidationError" /* ValidationError */);
12202
+ if (signatureValid)
12203
+ return setResult("ValidSignature" /* ValidSignature */);
12204
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMismatch");
12205
+ return setResult("InvalidSignature" /* InvalidSignature */);
12206
+ } catch (exception) {
12207
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
12169
12208
  return setResult("InvalidPublicKey" /* InvalidPublicKey */);
12170
12209
  }
12171
12210
  } else
12172
12211
  return setResult("PublicKeyNotFound" /* PublicKeyNotFound */);
12173
12212
  } else
12174
12213
  return setResult("EnergyMeterNotFound" /* EnergyMeterNotFound */);
12175
- } catch {
12214
+ } catch (exception) {
12215
+ this.AddVerificationError(cryptoResult, "Verification_UnexpectedError", exception);
12176
12216
  return setResult("InvalidSignature" /* InvalidSignature */);
12177
12217
  }
12178
12218
  }
12179
- return {};
12219
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMissing");
12220
+ return setResult("InvalidSignature" /* InvalidSignature */);
12180
12221
  }
12181
12222
  async ViewMeasurement(measurementValue, _errorDiv, introDiv, infoDiv, PlainTextDiv, HashedPlainTextDiv, PublicKeyDiv, SignatureExpectedDiv, SignatureCheckDiv) {
12182
12223
  if (measurementValue.measurement === void 0)
@@ -12355,35 +12396,7 @@ var BSMCrypt01 = class extends ACrypt {
12355
12396
  if ((value & 1 << 29) != 0) events.push("OEM 15");
12356
12397
  return events;
12357
12398
  }
12358
- // private DecodeStatus(statusValue: string) : Array<string>
12359
- // {
12360
- // const statusArray:string[] = [];
12361
- // try
12362
- // {
12363
- // const status = parseInt(statusValue);
12364
- // if ((status & 1) == 1)
12365
- // statusArray.push("Fehler erkannt");
12366
- // if ((status & 2) == 2)
12367
- // statusArray.push("Synchrone Messwertübermittlung");
12368
- // // Bit 3 is reserved!
12369
- // if ((status & 8) == 8)
12370
- // statusArray.push("System-Uhr ist synchron");
12371
- // else
12372
- // statusArray.push("System-Uhr ist nicht synchron");
12373
- // if ((status & 16) == 16)
12374
- // statusArray.push("Rücklaufsperre aktiv");
12375
- // if ((status & 32) == 32)
12376
- // statusArray.push("Energierichtung -A");
12377
- // if ((status & 64) == 64)
12378
- // statusArray.push("Magnetfeld erkannt");
12379
- // }
12380
- // catch
12381
- // {
12382
- // statusArray.push("Invalid status!");
12383
- // }
12384
- // return statusArray;
12385
- // }
12386
- // //#endregion
12399
+ //#endregion
12387
12400
  };
12388
12401
 
12389
12402
  // src/interfaces/IPublicKeyInfo.ts
@@ -13842,29 +13855,44 @@ var EMHCrypt01 = class extends ACrypt {
13842
13855
  cryptoResult.publicKey = publicKey?.value.toLowerCase();
13843
13856
  cryptoResult.publicKeyFormat = publicKey?.format;
13844
13857
  cryptoResult.publicKeySignatures = publicKey?.signatures;
13858
+ let keyPair;
13845
13859
  try {
13846
- if (this.curve.keyFromPublic(cryptoResult.publicKey ?? "", "hex").verify(
13847
- cryptoResult.sha256value,
13848
- cryptoResult.signature
13849
- )) {
13850
- return setResult("ValidSignature" /* ValidSignature */);
13851
- }
13852
- return setResult("InvalidSignature" /* InvalidSignature */);
13853
- } catch {
13860
+ keyPair = this.curve.keyFromPublic(cryptoResult.publicKey ?? "", "hex");
13861
+ } catch (exception) {
13862
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
13863
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
13864
+ }
13865
+ const keyValidation = keyPair.validate();
13866
+ if (!keyValidation.result) {
13867
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyNotOnCurve", keyValidation.reason ?? void 0);
13868
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
13869
+ }
13870
+ let signatureValid;
13871
+ try {
13872
+ signatureValid = keyPair.verify(cryptoResult.sha256value, cryptoResult.signature);
13873
+ } catch (exception) {
13874
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMalformed", exception);
13854
13875
  return setResult("InvalidSignature" /* InvalidSignature */);
13855
13876
  }
13856
- } catch {
13877
+ if (signatureValid)
13878
+ return setResult("ValidSignature" /* ValidSignature */);
13879
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMismatch");
13880
+ return setResult("InvalidSignature" /* InvalidSignature */);
13881
+ } catch (exception) {
13882
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
13857
13883
  return setResult("InvalidPublicKey" /* InvalidPublicKey */);
13858
13884
  }
13859
13885
  } else
13860
13886
  return setResult("PublicKeyNotFound" /* PublicKeyNotFound */);
13861
13887
  } else
13862
13888
  return setResult("EnergyMeterNotFound" /* EnergyMeterNotFound */);
13863
- } catch {
13889
+ } catch (exception) {
13890
+ this.AddVerificationError(cryptoResult, "Verification_UnexpectedError", exception);
13864
13891
  return setResult("InvalidSignature" /* InvalidSignature */);
13865
13892
  }
13866
13893
  }
13867
- return {};
13894
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMissing");
13895
+ return setResult("InvalidSignature" /* InvalidSignature */);
13868
13896
  }
13869
13897
  async ViewMeasurement(measurementValue, _errorDiv, introDiv, infoDiv, PlainTextDiv, HashedPlainTextDiv, PublicKeyDiv, SignatureExpectedDiv, SignatureCheckDiv) {
13870
13898
  if (measurementValue.measurement?.chargingSession?.authorizationStart?.timestamp === void 0) {
@@ -13972,7 +14000,7 @@ var EMHCrypt01 = class extends ACrypt {
13972
14000
  DecodeStatus(statusValue) {
13973
14001
  const statusArray = [];
13974
14002
  try {
13975
- const status = parseInt(statusValue);
14003
+ const status = parseInt(statusValue, 16);
13976
14004
  if ((status & 1) == 1)
13977
14005
  statusArray.push("Fehler erkannt");
13978
14006
  if ((status & 2) == 2)
@@ -14074,29 +14102,44 @@ var GDFCrypt01 = class extends ACrypt {
14074
14102
  cryptoResult.publicKey = publicKey?.value.toLowerCase();
14075
14103
  cryptoResult.publicKeyFormat = publicKey?.format;
14076
14104
  cryptoResult.publicKeySignatures = publicKey?.signatures;
14105
+ let keyPair;
14077
14106
  try {
14078
- if (this.curve.keyFromPublic(cryptoResult.publicKey ?? "", "hex").verify(
14079
- cryptoResult.sha256value,
14080
- cryptoResult.signature
14081
- )) {
14082
- return setResult("ValidSignature" /* ValidSignature */);
14083
- }
14084
- return setResult("InvalidSignature" /* InvalidSignature */);
14085
- } catch {
14107
+ keyPair = this.curve.keyFromPublic(cryptoResult.publicKey ?? "", "hex");
14108
+ } catch (exception) {
14109
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
14110
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
14111
+ }
14112
+ const keyValidation = keyPair.validate();
14113
+ if (!keyValidation.result) {
14114
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyNotOnCurve", keyValidation.reason ?? void 0);
14115
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
14116
+ }
14117
+ let signatureValid;
14118
+ try {
14119
+ signatureValid = keyPair.verify(cryptoResult.sha256value, cryptoResult.signature);
14120
+ } catch (exception) {
14121
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMalformed", exception);
14086
14122
  return setResult("InvalidSignature" /* InvalidSignature */);
14087
14123
  }
14088
- } catch {
14124
+ if (signatureValid)
14125
+ return setResult("ValidSignature" /* ValidSignature */);
14126
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMismatch");
14127
+ return setResult("InvalidSignature" /* InvalidSignature */);
14128
+ } catch (exception) {
14129
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
14089
14130
  return setResult("InvalidPublicKey" /* InvalidPublicKey */);
14090
14131
  }
14091
14132
  } else
14092
14133
  return setResult("PublicKeyNotFound" /* PublicKeyNotFound */);
14093
14134
  } else
14094
14135
  return setResult("EnergyMeterNotFound" /* EnergyMeterNotFound */);
14095
- } catch {
14136
+ } catch (exception) {
14137
+ this.AddVerificationError(cryptoResult, "Verification_UnexpectedError", exception);
14096
14138
  return setResult("InvalidSignature" /* InvalidSignature */);
14097
14139
  }
14098
14140
  }
14099
- return {};
14141
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMissing");
14142
+ return setResult("InvalidSignature" /* InvalidSignature */);
14100
14143
  }
14101
14144
  async ViewMeasurement(measurementValue, _errorDiv, introDiv, infoDiv, PlainTextDiv, HashedPlainTextDiv, PublicKeyDiv, SignatureExpectedDiv, SignatureCheckDiv) {
14102
14145
  if (measurementValue.measurement?.chargingSession?.authorizationStart?.timestamp === void 0) {
@@ -14435,19 +14478,38 @@ var MennekesCrypt01 = class extends ACrypt {
14435
14478
  );
14436
14479
  cryptoResult.hashValue = (await sha256(new DataView(signedDataBuffer))).substring(0, 48);
14437
14480
  const publicKey = cleanHex(meter.publicKeys.at(0)?.value ?? measurement.publicKey);
14438
- if (publicKey.length !== 96)
14481
+ if (publicKey.length !== 96) {
14482
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed");
14439
14483
  return setResult("InvalidPublicKey" /* InvalidPublicKey */);
14440
- const result = this.curve192r1.keyFromPublic("04" + publicKey, "hex").verify(
14441
- cryptoResult.hashValue.toUpperCase(),
14442
- {
14484
+ }
14485
+ let keyPair;
14486
+ try {
14487
+ keyPair = this.curve192r1.keyFromPublic("04" + publicKey, "hex");
14488
+ } catch (exception) {
14489
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyDecodingFailed", exception);
14490
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
14491
+ }
14492
+ const keyValidation = keyPair.validate();
14493
+ if (!keyValidation.result) {
14494
+ this.AddVerificationError(cryptoResult, "Verification_PublicKeyNotOnCurve", keyValidation.reason ?? void 0);
14495
+ return setResult("InvalidPublicKey" /* InvalidPublicKey */);
14496
+ }
14497
+ let signatureValid;
14498
+ try {
14499
+ signatureValid = keyPair.verify(cryptoResult.hashValue.toUpperCase(), {
14443
14500
  r: signatureExpected.r,
14444
14501
  s: signatureExpected.s
14445
- }
14446
- );
14447
- return setResult(
14448
- result ? "ValidSignature" /* ValidSignature */ : "InvalidSignature" /* InvalidSignature */
14449
- );
14450
- } catch {
14502
+ });
14503
+ } catch (exception) {
14504
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMalformed", exception);
14505
+ return setResult("InvalidSignature" /* InvalidSignature */);
14506
+ }
14507
+ if (signatureValid)
14508
+ return setResult("ValidSignature" /* ValidSignature */);
14509
+ this.AddVerificationError(cryptoResult, "Verification_SignatureMismatch");
14510
+ return setResult("InvalidSignature" /* InvalidSignature */);
14511
+ } catch (exception) {
14512
+ this.AddVerificationError(cryptoResult, "Verification_UnexpectedError", exception);
14451
14513
  return setResult("InvalidSignature" /* InvalidSignature */);
14452
14514
  }
14453
14515
  }
@@ -14653,6 +14715,132 @@ function numberToBytesBE(value, length) {
14653
14715
  }
14654
14716
  return bytes;
14655
14717
  }
14718
+ var OCMFBonnTariffParseError = class extends Error {
14719
+ tariffText;
14720
+ constructor(tariffText, message) {
14721
+ super(message);
14722
+ this.name = "OCMFBonnTariffParseError";
14723
+ this.tariffText = tariffText;
14724
+ }
14725
+ };
14726
+ function parseCents(value, tariffText, fieldName) {
14727
+ if (!/^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/.test(value))
14728
+ throw new OCMFBonnTariffParseError(tariffText, `${fieldName} must be a non-negative decimal number`);
14729
+ const parsedValue = Number(value);
14730
+ if (!Number.isFinite(parsedValue))
14731
+ throw new OCMFBonnTariffParseError(tariffText, `${fieldName} is outside the supported numeric range`);
14732
+ return parsedValue;
14733
+ }
14734
+ function parseOCMFBonnTariffText(tariffText) {
14735
+ const fields = tariffText.split(";");
14736
+ const code = fields[0];
14737
+ if (fields[1] !== "EUR")
14738
+ throw new OCMFBonnTariffParseError(tariffText, "currency must be EUR");
14739
+ switch (code) {
14740
+ case "001":
14741
+ if (fields.length !== 6)
14742
+ throw new OCMFBonnTariffParseError(tariffText, "profile 001 must contain six fields");
14743
+ return {
14744
+ raw: tariffText,
14745
+ code,
14746
+ currency: "EUR",
14747
+ startFeeCents: parseCents(fields[2] ?? "", tariffText, "W"),
14748
+ energyFeeCentsPerKWh: parseCents(fields[3] ?? "", tariffText, "X"),
14749
+ blockingFeeCentsPerMinute: parseCents(fields[4] ?? "", tariffText, "Y"),
14750
+ blockingFeeStartMinute: parseCents(fields[5] ?? "", tariffText, "Z")
14751
+ };
14752
+ case "002":
14753
+ if (fields.length !== 5)
14754
+ throw new OCMFBonnTariffParseError(tariffText, "profile 002 must contain five fields");
14755
+ return {
14756
+ raw: tariffText,
14757
+ code,
14758
+ currency: "EUR",
14759
+ startFeeCents: parseCents(fields[2] ?? "", tariffText, "W"),
14760
+ energyFeeCentsPerKWh: parseCents(fields[3] ?? "", tariffText, "X"),
14761
+ blockingFeeCentsPerMinute: parseCents(fields[4] ?? "", tariffText, "Y"),
14762
+ blockingFeeStartsAfterCharging: true
14763
+ };
14764
+ case "003":
14765
+ if (fields.length !== 4)
14766
+ throw new OCMFBonnTariffParseError(tariffText, "profile 003 must contain four fields");
14767
+ return {
14768
+ raw: tariffText,
14769
+ code,
14770
+ currency: "EUR",
14771
+ startFeeCents: parseCents(fields[2] ?? "", tariffText, "W"),
14772
+ timeFeeCentsPerMinute: parseCents(fields[3] ?? "", tariffText, "X")
14773
+ };
14774
+ default:
14775
+ throw new OCMFBonnTariffParseError(tariffText, "unknown Bonn tariff profile");
14776
+ }
14777
+ }
14778
+ function tryParseOCMFBonnTariffText(tariffText) {
14779
+ try {
14780
+ return parseOCMFBonnTariffText(tariffText);
14781
+ } catch (error) {
14782
+ if (error instanceof OCMFBonnTariffParseError)
14783
+ return void 0;
14784
+ throw error;
14785
+ }
14786
+ }
14787
+ function priceComponent(type, price, stepSize) {
14788
+ return {
14789
+ type,
14790
+ price,
14791
+ step_size: stepSize
14792
+ };
14793
+ }
14794
+ function eurosFromCents(cents) {
14795
+ return new Decimal(cents).dividedBy(100);
14796
+ }
14797
+ function eurosPerHourFromCentsPerMinute(cents) {
14798
+ return eurosFromCents(cents).times(60);
14799
+ }
14800
+ function ocmfBonnTariffToChargingTariff(tariff) {
14801
+ const baseComponents = new Array(
14802
+ priceComponent("FLAT", eurosFromCents(tariff.startFeeCents), 1)
14803
+ );
14804
+ const elements = new Array();
14805
+ switch (tariff.code) {
14806
+ case "001":
14807
+ baseComponents.push(priceComponent("ENERGY", eurosFromCents(tariff.energyFeeCentsPerKWh), 1));
14808
+ elements.push(
14809
+ { price_components: baseComponents },
14810
+ {
14811
+ price_components: [
14812
+ priceComponent("PARKING_TIME", eurosPerHourFromCentsPerMinute(tariff.blockingFeeCentsPerMinute), 60)
14813
+ ],
14814
+ restrictions: {
14815
+ min_duration: tariff.blockingFeeStartMinute * 60
14816
+ }
14817
+ }
14818
+ );
14819
+ break;
14820
+ case "002":
14821
+ baseComponents.push(priceComponent("ENERGY", eurosFromCents(tariff.energyFeeCentsPerKWh), 1));
14822
+ elements.push(
14823
+ { price_components: baseComponents },
14824
+ {
14825
+ price_components: [
14826
+ priceComponent("PARKING_TIME", eurosPerHourFromCentsPerMinute(tariff.blockingFeeCentsPerMinute), 60)
14827
+ ]
14828
+ }
14829
+ );
14830
+ break;
14831
+ case "003":
14832
+ baseComponents.push(priceComponent("TIME", eurosPerHourFromCentsPerMinute(tariff.timeFeeCentsPerMinute), 60));
14833
+ elements.push({ price_components: baseComponents });
14834
+ break;
14835
+ }
14836
+ return {
14837
+ "@id": tariff.raw,
14838
+ currency: tariff.currency,
14839
+ elements
14840
+ };
14841
+ }
14842
+
14843
+ // src/OCMF.ts
14656
14844
  var OCMFv1_x = class extends ACrypt {
14657
14845
  curve = new this.chargy.elliptic.ec("p256");
14658
14846
  constructor(chargy) {
@@ -14742,6 +14930,8 @@ var OCMFv1_x = class extends ACrypt {
14742
14930
  return "Reading Current Type";
14743
14931
  case "CL":
14744
14932
  return "Cumulated Loss";
14933
+ case "EI":
14934
+ return "Error Index";
14745
14935
  case "EF":
14746
14936
  return "Error Flags";
14747
14937
  case "ST":
@@ -14949,10 +15139,39 @@ var OCMF = class {
14949
15139
  const identificationType = firstOCMDJSONDocument.payload.IT;
14950
15140
  const identificationData = firstOCMDJSONDocument.payload.ID;
14951
15141
  const tariffText = firstOCMDJSONDocument.payload.TT;
14952
- const controlerFirmwareVersion = firstOCMDJSONDocument.payload.CF;
15142
+ const tariffTextInterpretation = typeof tariffText === "string" ? tryParseOCMFBonnTariffText(tariffText) : void 0;
15143
+ const chargingTariff = typeof tariffText === "string" && tariffText.length > 0 ? tariffTextInterpretation !== void 0 ? ocmfBonnTariffToChargingTariff(tariffTextInterpretation) : { "@id": tariffText } : void 0;
15144
+ const controllerFirmwareVersion = firstOCMDJSONDocument.payload.CF;
14953
15145
  const lossCompensation = firstOCMDJSONDocument.payload.LC;
15146
+ const signedCable = lossCompensation !== void 0 && typeof lossCompensation.LR === "number" && Number.isFinite(lossCompensation.LR) && typeof lossCompensation.LU === "string" && lossCompensation.LU.length > 0 ? {
15147
+ ...typeof lossCompensation.LN === "string" ? { lossCompensation: lossCompensation.LN } : {},
15148
+ ...typeof lossCompensation.LI === "number" ? { lossCompensationId: lossCompensation.LI.toString() } : {},
15149
+ resistance: lossCompensation.LR,
15150
+ resistanceUnit: lossCompensation.LU
15151
+ } : void 0;
14954
15152
  const chargePointIdType = firstOCMDJSONDocument.payload.CT;
14955
15153
  const chargePointId = firstOCMDJSONDocument.payload.CI;
15154
+ let signedChargingStationId;
15155
+ let signedEVSEId;
15156
+ let signedConnectorId;
15157
+ if (typeof chargePointId === "string" && chargePointId.trim().length > 0) {
15158
+ const normalizedChargePointId = chargePointId.trim();
15159
+ switch (chargePointIdType?.toUpperCase()) {
15160
+ case void 0:
15161
+ break;
15162
+ case "EVSEID":
15163
+ signedEVSEId = normalizedChargePointId;
15164
+ break;
15165
+ case "CBIDC": {
15166
+ const cbidcMatch = /^(\S+)\s+(\S+)$/.exec(normalizedChargePointId);
15167
+ if (cbidcMatch?.[1] !== void 0 && cbidcMatch[2] !== void 0) {
15168
+ signedChargingStationId = cbidcMatch[1];
15169
+ signedConnectorId = cbidcMatch[2];
15170
+ }
15171
+ break;
15172
+ }
15173
+ }
15174
+ }
14956
15175
  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,
14957
15176
  // Charge Point and Public Key" section makes the serial-number fields conditionally
14958
15177
  // mandatory. KEBA KCP30 records identify the signing gateway via GS and omit MS.
@@ -14965,7 +15184,7 @@ var OCMF = class {
14965
15184
  // IT is 1..1 in the user-assignment table for transaction records. Some OCMF 1.0
14966
15185
  // implementations omit it when no user is assigned (IS=false, ID empty). We tolerate
14967
15186
  // this vendor compatibility case instead of rejecting otherwise valid signatures.
14968
- isOptionalString(identificationType) && isOptionalString(identificationData) && isOptionalString(tariffText) && isOptionalString(controlerFirmwareVersion) && isOptionalJSONObject(lossCompensation) && isOptionalString(chargePointIdType) && isOptionalString(chargePointId)) {
15187
+ isOptionalString(identificationType) && isOptionalString(identificationData) && isOptionalString(tariffText) && isOptionalString(controllerFirmwareVersion) && isOptionalJSONObject(lossCompensation) && isOptionalString(chargePointIdType) && isOptionalString(chargePointId)) {
14969
15188
  const paginationPrefix = paging.length > 0 ? paging.charAt(0).toLowerCase() : null;
14970
15189
  const transactionType = paginationPrefix === "t" ? "transaction" /* transaction */ : paginationPrefix === "f" ? "fiscal" /* fiscal */ : "undefined" /* undefined */;
14971
15190
  const pagination = paging.length > 1 ? parseNumber(paging.substring(1)) : null;
@@ -14997,11 +15216,22 @@ var OCMF = class {
14997
15216
  "@id": identificationData ?? "?",
14998
15217
  "type": identificationType ?? "?"
14999
15218
  },
15219
+ "chargingTariffs": chargingTariff !== void 0 ? [chargingTariff] : void 0,
15000
15220
  "ocmf": {
15001
15221
  "formatVersion": formatVersion,
15002
15222
  "gatewayInformation": gatewayInformation,
15003
15223
  "gatewaySerial": gatewaySerial,
15004
- "gatewayVersion": gatewayVersion
15224
+ "gatewayVersion": gatewayVersion,
15225
+ "meterVendor": meterVendor,
15226
+ "meterModel": meterModel,
15227
+ "meterSerial": meterSerial,
15228
+ "meterFirmware": meterFirmware,
15229
+ "tariffText": tariffText,
15230
+ "tariffTextInterpretation": tariffTextInterpretation,
15231
+ "controllerFirmwareVersion": controllerFirmwareVersion,
15232
+ "lossCompensation": lossCompensation,
15233
+ "chargePointIdentificationType": chargePointIdType,
15234
+ "chargePointIdentification": chargePointId
15005
15235
  },
15006
15236
  // "chargingStationOperators": [{
15007
15237
  // "chargingPools": [{
@@ -15068,6 +15298,11 @@ var OCMF = class {
15068
15298
  "@context": "https://open.charging.cloud/contexts/SessionSignatureFormats/OCMFv1.0+json",
15069
15299
  "begin": "?",
15070
15300
  "end": "?",
15301
+ "chargingStationId": signedChargingStationId,
15302
+ "EVSEId": signedEVSEId,
15303
+ "ConnectorId": signedConnectorId,
15304
+ "tariffId": chargingTariff?.["@id"],
15305
+ "chargingTariffs": chargingTariff !== void 0 ? [chargingTariff] : void 0,
15071
15306
  "authorizationStart": {
15072
15307
  "@id": identificationData ?? "?",
15073
15308
  "type": identificationType ?? "?",
@@ -15080,19 +15315,50 @@ var OCMF = class {
15080
15315
  }],
15081
15316
  "certainty": 1
15082
15317
  };
15083
- if (ContainerInfos?.chargingStations !== void 0)
15318
+ const resolvedChargingStationId = signedChargingStationId ?? containerChargingStation?.["@id"];
15319
+ if (resolvedChargingStationId !== void 0) {
15320
+ const matchingContainerStation = containerChargingStation?.["@id"] === resolvedChargingStationId ? containerChargingStation : void 0;
15321
+ const resolvedChargingStation = {
15322
+ ...matchingContainerStation ?? { "@id": resolvedChargingStationId },
15323
+ ...controllerFirmwareVersion !== void 0 ? {
15324
+ firmware: {
15325
+ ...matchingContainerStation?.firmware,
15326
+ version: controllerFirmwareVersion
15327
+ }
15328
+ } : {}
15329
+ };
15330
+ CTR.chargingStations = [
15331
+ resolvedChargingStation,
15332
+ ...ContainerInfos?.chargingStations?.filter((station) => station["@id"] !== resolvedChargingStationId) ?? []
15333
+ ];
15334
+ if (CTR.chargingSessions?.[0] !== void 0) {
15335
+ CTR.chargingSessions[0].chargingStationId ??= resolvedChargingStationId;
15336
+ CTR.chargingSessions[0].chargingStation = resolvedChargingStation;
15337
+ }
15338
+ } else if (ContainerInfos?.chargingStations !== void 0)
15084
15339
  CTR.chargingStations = ContainerInfos.chargingStations;
15085
- if (containerChargingStation !== void 0 && CTR.chargingSessions?.[0] !== void 0) {
15086
- CTR.chargingSessions[0].chargingStationId = containerChargingStation["@id"];
15087
- CTR.chargingSessions[0].chargingStation = containerChargingStation;
15088
- }
15089
15340
  if (containerEVSE !== void 0 && CTR.chargingSessions?.[0] !== void 0) {
15090
- CTR.chargingSessions[0].EVSEId = containerEVSE["@id"];
15091
- CTR.chargingSessions[0].EVSE = containerEVSE;
15092
- }
15093
- if (containerConnector !== void 0 && CTR.chargingSessions?.[0] !== void 0) {
15094
- CTR.chargingSessions[0].ConnectorId = containerConnector["@id"];
15095
- CTR.chargingSessions[0].Connector = containerConnector;
15341
+ const chargingSession = CTR.chargingSessions[0];
15342
+ chargingSession.EVSEId ??= containerEVSE["@id"];
15343
+ if (chargingSession.EVSEId === containerEVSE["@id"])
15344
+ chargingSession.EVSE = containerEVSE;
15345
+ }
15346
+ const resolvedConnectorId = signedConnectorId ?? containerConnector?.["@id"];
15347
+ if ((resolvedConnectorId !== void 0 || signedCable !== void 0) && CTR.chargingSessions?.[0] !== void 0) {
15348
+ const matchingContainerConnector = containerConnector?.["@id"] === resolvedConnectorId ? containerConnector : void 0;
15349
+ const resolvedConnector = {
15350
+ ...matchingContainerConnector,
15351
+ ...resolvedConnectorId !== void 0 ? { "@id": resolvedConnectorId } : {},
15352
+ ...signedCable !== void 0 ? {
15353
+ cable: {
15354
+ ...matchingContainerConnector?.cable,
15355
+ ...signedCable
15356
+ }
15357
+ } : {}
15358
+ };
15359
+ const chargingSession = CTR.chargingSessions[0];
15360
+ chargingSession.ConnectorId ??= resolvedConnectorId;
15361
+ chargingSession.Connector = resolvedConnector;
15096
15362
  }
15097
15363
  const measurementsByKey = /* @__PURE__ */ new Map();
15098
15364
  for (const ocmfJSONDocument of OCMFJSONDocuments) {
@@ -15106,12 +15372,13 @@ var OCMF = class {
15106
15372
  const readingUnit = effectiveReading.RU;
15107
15373
  const readingCurrentType = effectiveReading.RT;
15108
15374
  const cumulatedLoss = effectiveReading.CL;
15375
+ const errorIndex = effectiveReading.EI;
15109
15376
  const errorFlags = effectiveReading.EF;
15110
15377
  const status = effectiveReading.ST;
15111
15378
  inheritedReading = effectiveReading;
15112
15379
  if (isMandatoryString(time) && isOptionalString(transaction) && isMandatoryDecimal(readingValue) && // Note: Some vendors use a JSON string here!
15113
15380
  isOptionalString(readingIdentification) && isMandatoryString(readingUnit) && isOptionalString(readingCurrentType) && // chargyLib.isOptionalDecimal (cumulatedLoss) &&
15114
- isOptionalString(errorFlags) && isMandatoryString(status)) {
15381
+ isOptionalNumber(errorIndex) && isOptionalString(errorFlags) && isMandatoryString(status)) {
15115
15382
  const timeSplit = time.split(" ");
15116
15383
  if (timeSplit.length != 2) return {
15117
15384
  status: "InvalidSessionFormat" /* InvalidSessionFormat */,
@@ -15210,6 +15477,7 @@ var OCMF = class {
15210
15477
  // "T" ToDo: Serialize this to a string!
15211
15478
  "pagination": pagination,
15212
15479
  // "9289"
15480
+ "errorIndex": errorIndex,
15213
15481
  "errorFlags": errorFlags,
15214
15482
  // ""
15215
15483
  "cumulatedLoss": cumulatedLoss != null && cumulatedLoss !== 0 ? new Decimal(cumulatedLoss) : void 0,
@@ -15219,17 +15487,17 @@ var OCMF = class {
15219
15487
  // "value": ocmfJSONDocument.signature["SD"]
15220
15488
  // }],
15221
15489
  "result": {
15222
- "status": ocmfJSONDocument.validationStatus ?? "Unvalidated" /* Unvalidated */
15490
+ "status": ocmfJSONDocument.validationStatus ?? "Unvalidated" /* Unvalidated */,
15491
+ // Surface the per-document verification diagnostics on the measurement value.
15492
+ ...ocmfJSONDocument.validationErrors && ocmfJSONDocument.validationErrors.length > 0 ? { errors: ocmfJSONDocument.validationErrors } : {}
15223
15493
  },
15224
15494
  "ocmfDocument": ocmfJSONDocument
15225
15495
  });
15226
15496
  }
15227
15497
  }
15228
15498
  }
15229
- if (ContainerInfos?.chargingStations !== void 0)
15230
- CTR.chargingStations = ContainerInfos.chargingStations;
15231
15499
  if (ContainerInfos?.warnings !== void 0)
15232
- CTR.warnings = [...CTR.warnings ?? [], ...ContainerInfos.warnings];
15500
+ CTR.warnings = (CTR.warnings ?? []).concat(ContainerInfos.warnings);
15233
15501
  CTR.status = OCMFJSONDocuments.every((ocmfJSONDocument) => ocmfJSONDocument.validationStatus === "ValidSignature" /* ValidSignature */) ? "ValidSignature" /* ValidSignature */ : "InvalidSignature" /* InvalidSignature */;
15234
15502
  if (CTR.chargingSessions != null && CTR.chargingSessions.length > 0 && CTR.chargingSessions[0]) {
15235
15503
  CTR.begin = CTR.chargingSessions[0].begin;
@@ -15366,6 +15634,20 @@ var OCMF = class {
15366
15634
  // return mergedCTR;
15367
15635
  // }
15368
15636
  // //#endregion
15637
+ // Records why a verification step failed as structured data: a stable reason
15638
+ // key (localized via i18n.json and machine-switchable by the GUI) plus an
15639
+ // optional, language-neutral technical detail. Presentation is left to the GUI.
15640
+ AddValidationError(OCMFJSONDocument, reasonKey, detail) {
15641
+ const details = detail instanceof Error ? detail.message : typeof detail === "string" ? detail : void 0;
15642
+ (OCMFJSONDocument.validationErrors ??= []).push(
15643
+ CreateError(
15644
+ this.chargy.GetMultilanguageText(reasonKey),
15645
+ "high" /* high */,
15646
+ reasonKey,
15647
+ details
15648
+ )
15649
+ );
15650
+ }
15369
15651
  //#region (private) validateOCMFSignature(OCMFJSONDocument, PublicKey, PublicKeyEncoding?)
15370
15652
  async validateOCMFSignature(OCMFJSONDocument, PublicKey, PublicKeyEncoding) {
15371
15653
  try {
@@ -15405,7 +15687,8 @@ var OCMF = class {
15405
15687
  curve = new this.chargy.elliptic.ec("p256");
15406
15688
  break;
15407
15689
  }
15408
- } catch {
15690
+ } catch (exception) {
15691
+ this.AddValidationError(OCMFJSONDocument, "Verification_UnknownSignatureFormat", exception);
15409
15692
  OCMFJSONDocument.validationStatus = "UnknownSignatureFormat" /* UnknownSignatureFormat */;
15410
15693
  return OCMFJSONDocument.validationStatus;
15411
15694
  }
@@ -15482,20 +15765,26 @@ var OCMF = class {
15482
15765
  y: OCMFJSONDocument.publicKey.y
15483
15766
  }, "hex");
15484
15767
  }
15485
- } catch {
15768
+ } catch (exception) {
15769
+ this.AddValidationError(OCMFJSONDocument, "Verification_PublicKeyDecodingFailed", exception);
15486
15770
  OCMFJSONDocument.validationStatus = "InvalidPublicKey" /* InvalidPublicKey */;
15487
15771
  return OCMFJSONDocument.validationStatus;
15488
15772
  }
15489
15773
  try {
15490
15774
  if (publicKey === null)
15491
15775
  throw new Error("Missing public key!");
15492
- OCMFJSONDocument.validationStatus = publicKey.verify(OCMFJSONDocument.hashValue, OCMFJSONDocument.signatureRS) ? "ValidSignature" /* ValidSignature */ : "InvalidSignature" /* InvalidSignature */;
15776
+ const signatureValid = publicKey.verify(OCMFJSONDocument.hashValue, OCMFJSONDocument.signatureRS);
15777
+ if (!signatureValid)
15778
+ this.AddValidationError(OCMFJSONDocument, "Verification_SignatureMismatch");
15779
+ OCMFJSONDocument.validationStatus = signatureValid ? "ValidSignature" /* ValidSignature */ : "InvalidSignature" /* InvalidSignature */;
15493
15780
  return await Promise.resolve(OCMFJSONDocument.validationStatus);
15494
- } catch {
15781
+ } catch (exception) {
15782
+ this.AddValidationError(OCMFJSONDocument, "Verification_SignatureMalformed", exception);
15495
15783
  OCMFJSONDocument.validationStatus = "InvalidSignature" /* InvalidSignature */;
15496
15784
  return OCMFJSONDocument.validationStatus;
15497
15785
  }
15498
- } catch {
15786
+ } catch (exception) {
15787
+ this.AddValidationError(OCMFJSONDocument, "Verification_UnexpectedError", exception);
15499
15788
  OCMFJSONDocument.validationStatus = "InvalidSignature" /* InvalidSignature */;
15500
15789
  return OCMFJSONDocument.validationStatus;
15501
15790
  }
@@ -15724,6 +16013,11 @@ var OCMF = class {
15724
16013
  }
15725
16014
  if (ocmfJSONDocumentGroup[0]) {
15726
16015
  switch (ocmfJSONDocumentGroup[0].payload.FV) {
16016
+ // FV has cardinality 0..1 in OCMF. All supported 1.x
16017
+ // versions use the same parser, so an omitted version
16018
+ // is parsed as generic OCMF without changing the
16019
+ // signed payload or inventing a concrete version.
16020
+ case void 0:
15727
16021
  case "0.1":
15728
16022
  // OCMF 0.1 SAFE reference data uses a few legacy field names/forms (VI/VV,
15729
16023
  // string based IS values), but the compact signed document structure is close
@@ -16745,6 +17039,184 @@ function readDERInteger(bytes, getOffset, setOffset) {
16745
17039
  return hex;
16746
17040
  }
16747
17041
 
17042
+ // src/PTBContainer.ts
17043
+ var base64RegExp = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
17044
+ var formatVersionRegExp = /^1(?:\.[0-9]+)?$/;
17045
+ var PTB = class {
17046
+ chargy;
17047
+ constructor(chargy) {
17048
+ this.chargy = chargy;
17049
+ }
17050
+ async TryToParsePTBContainer(container) {
17051
+ const validation = this.validateContainer(container);
17052
+ if (!validation.valid)
17053
+ return this.validationError(validation.issues);
17054
+ const ptbContainer = validation.container;
17055
+ const containerInfos = {
17056
+ chargingStations: [{
17057
+ "@id": ptbContainer.chargeboxIdentifier,
17058
+ address: this.normalizeAddress(ptbContainer.address),
17059
+ geoLocation: {
17060
+ lat: ptbContainer.geoLocation.lat,
17061
+ lng: ptbContainer.geoLocation.lng
17062
+ },
17063
+ EVSEs: [{
17064
+ "@id": ptbContainer.chargeboxIdentifier
17065
+ }]
17066
+ }]
17067
+ };
17068
+ return new OCMF(this.chargy).TryToParseOCMFDocuments(
17069
+ [ptbContainer.ocmfBegin, ptbContainer.ocmfEnd],
17070
+ ptbContainer.publicKey,
17071
+ "base64",
17072
+ containerInfos
17073
+ );
17074
+ }
17075
+ validateContainer(container) {
17076
+ const issues = [];
17077
+ if (!isMandatoryJSONObject(container))
17078
+ return {
17079
+ valid: false,
17080
+ issues: [{
17081
+ path: "$",
17082
+ message: "must be an object"
17083
+ }]
17084
+ };
17085
+ this.requireConstantString(container, "format", "ptb", issues);
17086
+ this.requireString(container, "publicKey", issues);
17087
+ this.requireString(container, "chargeboxIdentifier", issues);
17088
+ this.requireString(container, "ocmfBegin", issues);
17089
+ this.requireString(container, "ocmfEnd", issues);
17090
+ const formatVersion = container["formatVersion"];
17091
+ if (formatVersion !== void 0 && (typeof formatVersion !== "string" || !formatVersionRegExp.test(formatVersion))) {
17092
+ issues.push({
17093
+ path: "$.formatVersion",
17094
+ message: "must match ^1(?:\\.[0-9]+)?$"
17095
+ });
17096
+ }
17097
+ const publicKey = container["publicKey"];
17098
+ if (typeof publicKey === "string" && publicKey.length > 0 && !base64RegExp.test(publicKey))
17099
+ issues.push({
17100
+ path: "$.publicKey",
17101
+ message: "must be a base64 encoded string"
17102
+ });
17103
+ for (const propertyName of ["ocmfBegin", "ocmfEnd"]) {
17104
+ const ocmfDocument = container[propertyName];
17105
+ if (typeof ocmfDocument === "string" && (ocmfDocument.length < 10 || !ocmfDocument.startsWith("OCMF|"))) {
17106
+ issues.push({
17107
+ path: "$." + propertyName,
17108
+ message: "must be an unmodified OCMF record beginning with OCMF|"
17109
+ });
17110
+ }
17111
+ }
17112
+ const address = container["address"];
17113
+ if (!isMandatoryJSONObject(address))
17114
+ issues.push({
17115
+ path: "$.address",
17116
+ message: "must be an object"
17117
+ });
17118
+ else
17119
+ this.validateAddress(address, issues);
17120
+ const geoLocation = container["geoLocation"];
17121
+ if (!isMandatoryJSONObject(geoLocation))
17122
+ issues.push({
17123
+ path: "$.geoLocation",
17124
+ message: "must be an object"
17125
+ });
17126
+ else
17127
+ this.validateGeoLocation(geoLocation, issues);
17128
+ if (issues.length > 0)
17129
+ return {
17130
+ valid: false,
17131
+ issues
17132
+ };
17133
+ return {
17134
+ valid: true,
17135
+ container
17136
+ };
17137
+ }
17138
+ validateAddress(address, issues) {
17139
+ this.requireString(address, "street", issues, "$.address");
17140
+ for (const propertyName of ["houseNumber", "zipCode", "postalCode", "town", "city", "country"]) {
17141
+ const propertyValue = address[propertyName];
17142
+ if (propertyValue !== void 0 && typeof propertyValue !== "string")
17143
+ issues.push({
17144
+ path: "$.address." + propertyName,
17145
+ message: "must be a string"
17146
+ });
17147
+ else if ((propertyName === "town" || propertyName === "city") && propertyValue === "")
17148
+ issues.push({
17149
+ path: "$.address." + propertyName,
17150
+ message: "must be a non-empty string"
17151
+ });
17152
+ }
17153
+ const town = address["town"];
17154
+ const city = address["city"];
17155
+ if ((typeof town !== "string" || town.length === 0) && (typeof city !== "string" || city.length === 0)) {
17156
+ issues.push({
17157
+ path: "$.address",
17158
+ message: "must contain a non-empty town or city"
17159
+ });
17160
+ }
17161
+ }
17162
+ validateGeoLocation(geoLocation, issues) {
17163
+ const latitude = geoLocation["lat"];
17164
+ const longitude = geoLocation["lng"];
17165
+ if (typeof latitude !== "number" || !Number.isFinite(latitude) || latitude < -90 || latitude > 90)
17166
+ issues.push({
17167
+ path: "$.geoLocation.lat",
17168
+ message: "must be a number between -90 and 90"
17169
+ });
17170
+ if (typeof longitude !== "number" || !Number.isFinite(longitude) || longitude < -180 || longitude > 180)
17171
+ issues.push({
17172
+ path: "$.geoLocation.lng",
17173
+ message: "must be a number between -180 and 180"
17174
+ });
17175
+ for (const propertyName of Object.keys(geoLocation))
17176
+ if (propertyName !== "lat" && propertyName !== "lng")
17177
+ issues.push({
17178
+ path: "$.geoLocation." + propertyName,
17179
+ message: "is not allowed"
17180
+ });
17181
+ }
17182
+ requireString(json, propertyName, issues, parentPath = "$") {
17183
+ const value = json[propertyName];
17184
+ if (typeof value !== "string" || value.length === 0)
17185
+ issues.push({
17186
+ path: parentPath + "." + propertyName,
17187
+ message: "must be a non-empty string"
17188
+ });
17189
+ }
17190
+ requireConstantString(json, propertyName, expectedValue, issues) {
17191
+ if (json[propertyName] !== expectedValue)
17192
+ issues.push({
17193
+ path: "$." + propertyName,
17194
+ message: "must equal " + expectedValue
17195
+ });
17196
+ }
17197
+ normalizeAddress(address) {
17198
+ return {
17199
+ city: address.city ?? address.town,
17200
+ street: address.street,
17201
+ houseNumber: address.houseNumber,
17202
+ postalCode: address.postalCode ?? address.zipCode,
17203
+ country: address.country
17204
+ };
17205
+ }
17206
+ validationError(issues) {
17207
+ return {
17208
+ format: "ptb",
17209
+ status: "InvalidSessionFormat" /* InvalidSessionFormat */,
17210
+ message: this.chargy.GetMultilanguageText("Invalid PTB OCMF container!"),
17211
+ certainty: 1,
17212
+ issues,
17213
+ errors: issues.map((issue) => CreateError(
17214
+ this.chargy.GetMultilanguageText(issue.path + " " + issue.message)
17215
+ ))
17216
+ };
17217
+ }
17218
+ };
17219
+
16748
17220
  // src/SAFE_XML.ts
16749
17221
  var SAFEXML = class _SAFEXML {
16750
17222
  chargy;
@@ -19261,6 +19733,26 @@ var Chargy = class {
19261
19733
  return textContent.replace(/\s+/g, "");
19262
19734
  return void 0;
19263
19735
  }
19736
+ TryToCreatePublicKeyLookup(processedFiles) {
19737
+ if (processedFiles.length === 0)
19738
+ return void 0;
19739
+ const publicKeys = new Array();
19740
+ for (const processedFile of processedFiles) {
19741
+ if (IsAChargeTransparencyRecord(processedFile.result) || IsAChargeTransparencyLiveLink(processedFile.result)) {
19742
+ return void 0;
19743
+ }
19744
+ if (IsAPublicKey(processedFile.result))
19745
+ publicKeys.push(processedFile.result);
19746
+ else if (IsAPublicKeyLookup(processedFile.result))
19747
+ publicKeys.push(...processedFile.result.publicKeys);
19748
+ else
19749
+ return void 0;
19750
+ }
19751
+ if (processedFiles.length === 1 && IsAPublicKeyLookup(processedFiles[0]?.result)) {
19752
+ return processedFiles[0].result;
19753
+ }
19754
+ return { publicKeys };
19755
+ }
19264
19756
  //#endregion
19265
19757
  //#region QR code image files...
19266
19758
  normalizeMIMEType(mimeType) {
@@ -19977,7 +20469,9 @@ var Chargy = class {
19977
20469
  if (IsAChargeTransparencyLiveLink(JSONContent)) {
19978
20470
  JSONContent.timestamp ??= (/* @__PURE__ */ new Date()).toISOString();
19979
20471
  processedFile.result = JSONContent;
19980
- } else if (isMandatoryString(JSONContext)) {
20472
+ } else if (JSONContent["format"] === "ptb")
20473
+ processedFile.result = await new PTB(this).TryToParsePTBContainer(JSONContent);
20474
+ else if (isMandatoryString(JSONContext)) {
19981
20475
  if (JSONContext.startsWith("https://open.charging.cloud/contexts/CTR+json"))
19982
20476
  processedFile.result = JSONContent;
19983
20477
  else if (JSONContext.startsWith("https://open.charging.cloud/contexts/publicKey+json"))
@@ -20017,18 +20511,15 @@ var Chargy = class {
20017
20511
  }
20018
20512
  processedFiles.push(processedFile);
20019
20513
  }
20514
+ const publicKeyLookup = this.TryToCreatePublicKeyLookup(processedFiles);
20515
+ if (publicKeyLookup != null)
20516
+ return publicKeyLookup;
20020
20517
  if (processedFiles.length == 1) {
20021
20518
  const processedFile = getFirstArrayElement(processedFiles, "Missing processed file");
20022
20519
  if (IsAChargeTransparencyRecord(processedFile.result))
20023
20520
  return this.processChargeTransparencyRecord(processedFile.result);
20024
20521
  if (IsAChargeTransparencyLiveLink(processedFile.result))
20025
20522
  return processedFile.result;
20026
- if (IsAPublicKeyLookup(processedFile.result))
20027
- return {
20028
- status: "InvalidSessionFormat" /* InvalidSessionFormat */,
20029
- message: this.GetMultilanguageText("UnknownOrInvalidChargeTransparencyRecord"),
20030
- certainty: 0
20031
- };
20032
20523
  return processedFile.result;
20033
20524
  } else if (processedFiles.length > 1) {
20034
20525
  const mergedCTR = {
@@ -20450,6 +20941,6 @@ buffer/index.js:
20450
20941
  *)
20451
20942
  */
20452
20943
 
20453
- 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 };
20944
+ 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 };
20454
20945
  //# sourceMappingURL=index.js.map
20455
20946
  //# sourceMappingURL=index.js.map