report.js 11 KB

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