report.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. 'use strict';
  2. const logger = require('../logger');
  3. const nantum = require('../modules/nantum');
  4. const { v4 } = require('uuid');
  5. // const reportSubscriptionParameters = {
  6. // dataGranularitySeconds: 60,
  7. // reportBackSeconds: 60,
  8. // subscriptionDurationSeconds: 60 * 60,
  9. // resubscribeDurationSeconds: 58 * 60,
  10. // resubscribeAfterNoDataForSeconds: 90
  11. // };
  12. const reportSubscriptionParameters = {
  13. dataGranularitySeconds: 60,
  14. reportBackSeconds: 60,
  15. subscriptionDurationSeconds: 4 * 60,
  16. resubscribeDurationSeconds: 2 * 60,
  17. resubscribeAfterNoDataForSeconds: 90,
  18. };
  19. function getSecondsSince(property, reportMetadata) {
  20. const value = reportMetadata[property];
  21. if (value != null) {
  22. const millisDiff = new Date().getTime() - new Date(value).getTime();
  23. if (millisDiff > 0) {
  24. return Math.round(millisDiff / 1000);
  25. }
  26. }
  27. }
  28. async function pollForReports(
  29. oadrPoll,
  30. clientCertificateCn,
  31. clientCertificateFingerprint,
  32. ) {
  33. logger.info(
  34. 'pollForReports',
  35. oadrPoll,
  36. clientCertificateCn,
  37. clientCertificateFingerprint,
  38. );
  39. const report = await nantum.fetchReport(clientCertificateFingerprint);
  40. const createRequests = [];
  41. if (report.venReportMetadata) {
  42. for (const reportMetadata of report.venReportMetadata) {
  43. let sendCreate = false;
  44. if (!reportMetadata.lastSentCreate) {
  45. // if we've never sent a subscription request, do it
  46. // logger.info('sending create because we never have', reportMetadata.reportSpecifierId);
  47. sendCreate = true;
  48. } else {
  49. // have sent a create > 5s ago, not received a created
  50. if (
  51. !reportMetadata.lastReceivedCreated &&
  52. getSecondsSince('lastSentCreate', reportMetadata) > 5
  53. ) {
  54. // logger.info('no reply to creation request, send another', reportMetadata.reportSpecifierId);
  55. sendCreate = true;
  56. }
  57. }
  58. if (
  59. getSecondsSince('lastReceivedUpdate', reportMetadata) >
  60. reportSubscriptionParameters.resubscribeAfterNoDataForSeconds
  61. ) {
  62. // previously received data, silent now
  63. sendCreate = true;
  64. }
  65. if (
  66. !reportMetadata.lastReceivedUpdate &&
  67. getSecondsSince('lastReceivedCreated', reportMetadata) >
  68. reportSubscriptionParameters.reportBackSeconds + 5
  69. ) {
  70. // if we haven't received any data but we've waited long enough for one data interval + 5 seconds
  71. // logger.info('sending create because have not received data', reportMetadata.reportSpecifierId);
  72. sendCreate = true;
  73. }
  74. if (
  75. getSecondsSince('lastReceivedCreated', reportMetadata) >
  76. reportSubscriptionParameters.resubscribeDurationSeconds
  77. ) {
  78. // when we're close to the end of the subscription, trigger a resubscribe
  79. // logger.info('sending create because close to end of subscription', reportMetadata.reportSpecifierId);
  80. sendCreate = true;
  81. }
  82. if (sendCreate) {
  83. const newReportRequestId = v4();
  84. // track the last 5 registration ids
  85. reportMetadata.reportRequestIds = [
  86. newReportRequestId,
  87. ...reportMetadata.reportRequestIds,
  88. ].slice(0, 5);
  89. createRequests.push({
  90. reportRequestId: newReportRequestId,
  91. reportSpecifierId: reportMetadata.reportSpecifierId,
  92. granularityDuration: `PT${reportSubscriptionParameters.dataGranularitySeconds}S`,
  93. reportBackDuration: `PT${reportSubscriptionParameters.reportBackSeconds}S`,
  94. startDate: new Date().toISOString(),
  95. duration: `PT${reportSubscriptionParameters.subscriptionDurationSeconds}S`,
  96. specifiers: reportMetadata.descriptions.map(description => ({
  97. reportId: description.reportId,
  98. readingType: 'x-notApplicable',
  99. })),
  100. });
  101. reportMetadata.lastSentCreate = new Date().toISOString();
  102. }
  103. }
  104. if (createRequests.length > 0) {
  105. const createReport = {
  106. _type: 'oadrCreateReport',
  107. requestId: v4(),
  108. requests: createRequests,
  109. };
  110. await nantum.updateReport(clientCertificateFingerprint, report);
  111. return createReport;
  112. }
  113. }
  114. }
  115. async function registerReports(
  116. oadrRegisterReport,
  117. clientCertificateCn,
  118. clientCertificateFingerprint,
  119. ) {
  120. logger.info(
  121. 'registerReports',
  122. oadrRegisterReport,
  123. clientCertificateCn,
  124. clientCertificateFingerprint,
  125. );
  126. const requestVenId = oadrRegisterReport.venId;
  127. validateVenId(requestVenId, clientCertificateFingerprint, false);
  128. const venReportMetadata = (oadrRegisterReport.reports || []).map(report => {
  129. const { reportSpecifierId, descriptions } = report;
  130. const lastReceivedRegister = new Date().toISOString();
  131. const reportRequestId = v4();
  132. return {
  133. reportRequestIds: [reportRequestId],
  134. reportSpecifierId,
  135. descriptions,
  136. lastReceivedRegister,
  137. };
  138. });
  139. //TODO: whitelist based off Nantum API sensors
  140. await nantum.updateReport(clientCertificateFingerprint, {
  141. venReportMetadata,
  142. });
  143. return {
  144. _type: 'oadrRegisteredReport',
  145. responseCode: '200',
  146. responseRequestId: oadrRegisterReport.requestId,
  147. responseDescription: 'OK',
  148. requests: [],
  149. };
  150. }
  151. async function createdReports(
  152. oadrCreatedReport,
  153. clientCertificateCn,
  154. clientCertificateFingerprint,
  155. ) {
  156. logger.info(
  157. 'createdReports',
  158. oadrCreatedReport,
  159. clientCertificateCn,
  160. clientCertificateFingerprint,
  161. );
  162. validateVenId(oadrCreatedReport.venId, clientCertificateFingerprint, false);
  163. if (oadrCreatedReport.pendingReports) {
  164. // flag reports as having been created
  165. const report = await nantum.fetchReport(clientCertificateFingerprint);
  166. if (report.venReportMetadata) {
  167. for (const pendingReport of oadrCreatedReport.pendingReports) {
  168. const reportRequestId = pendingReport['reportRequestId'];
  169. const match = report.venReportMetadata.filter(x =>
  170. x.reportRequestIds.includes(reportRequestId),
  171. )[0];
  172. if (match) {
  173. match.lastReceivedCreated = new Date().toISOString();
  174. } else {
  175. logger.info(
  176. 'could not match',
  177. reportRequestId,
  178. report.venReportMetadata,
  179. );
  180. }
  181. }
  182. }
  183. await nantum.updateReport(clientCertificateFingerprint, report);
  184. }
  185. return {
  186. _type: 'oadrResponse',
  187. responseCode: '200',
  188. responseDescription: 'OK',
  189. venId: clientCertificateFingerprint,
  190. };
  191. }
  192. async function receiveReportData(
  193. oadrUpdateReport,
  194. clientCertificateCn,
  195. clientCertificateFingerprint,
  196. ) {
  197. logger.info(
  198. 'receiveReportData',
  199. oadrUpdateReport,
  200. clientCertificateCn,
  201. clientCertificateFingerprint,
  202. );
  203. const requestVenId = oadrUpdateReport.venId;
  204. validateVenId(requestVenId, clientCertificateFingerprint, false);
  205. const report = await nantum.fetchReport(clientCertificateFingerprint);
  206. if (report.venReportMetadata) {
  207. for (const updateReport of oadrUpdateReport.reports) {
  208. const reportRequestId = updateReport.reportRequestId;
  209. const match = report.venReportMetadata.filter(x =>
  210. x.reportRequestIds.includes(reportRequestId),
  211. )[0];
  212. if (!match) {
  213. logger.info(
  214. 'could not match',
  215. reportRequestId,
  216. report.venReportMetadata,
  217. );
  218. continue;
  219. }
  220. match.lastReceivedUpdate = new Date().toISOString();
  221. for (const interval of updateReport.intervals || []) {
  222. const reportId = interval.reportPayloads[0].reportId;
  223. const date = interval.startDate;
  224. if (interval.reportPayloads[0].payloadFloat) {
  225. const value = interval.reportPayloads[0].payloadFloat;
  226. logger.info('received report', [
  227. date,
  228. clientCertificateFingerprint,
  229. updateReport.reportSpecifierId,
  230. updateReport.reportName,
  231. reportRequestId,
  232. reportId,
  233. value,
  234. ]);
  235. }
  236. if (
  237. interval.reportPayloads[0].payloadStatus &&
  238. interval.reportPayloads[0].payloadStatus.loadControlState
  239. ) {
  240. const loadControlState =
  241. interval.reportPayloads[0].payloadStatus.loadControlState;
  242. Object.keys(loadControlState).forEach(type => {
  243. const typeObj = loadControlState[type];
  244. Object.keys(typeObj).forEach(subType => {
  245. const value = typeObj[subType];
  246. logger.info('received report', [
  247. date,
  248. clientCertificateFingerprint,
  249. updateReport.reportSpecifierId,
  250. updateReport.reportName,
  251. reportRequestId,
  252. reportId,
  253. type,
  254. subType,
  255. value,
  256. ]);
  257. });
  258. });
  259. }
  260. }
  261. }
  262. }
  263. await nantum.updateReport(clientCertificateFingerprint, report);
  264. return {
  265. _type: 'oadrUpdatedReport',
  266. responseCode: '200',
  267. responseRequestId: oadrUpdateReport.requestId,
  268. responseDescription: 'OK',
  269. venId: clientCertificateFingerprint,
  270. };
  271. }
  272. function validateVenId(requestVenId, clientCertificateFingerprint, required) {
  273. if (requestVenId === clientCertificateFingerprint) {
  274. return;
  275. }
  276. if (!required && requestVenId == null) {
  277. return;
  278. }
  279. if (required && requestVenId == null) {
  280. const error = new Error('VenID is missing');
  281. error.responseCode = 452;
  282. throw error;
  283. }
  284. const error = new Error('VenID does not match certificate');
  285. error.responseCode = 452;
  286. throw error;
  287. }
  288. module.exports = {
  289. registerReports,
  290. createdReports,
  291. pollForReports,
  292. receiveReportData,
  293. };