report.js 9.2 KB

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