report.js 10 KB

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