From 5b8024ca9cc188ab68b1c722e42b5ec7c8f92391 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr>
Date: Fri, 17 Dec 2021 13:23:49 +0100
Subject: [PATCH] [ivette/server] restore ZMQ client

Failing due to non-context aware native modules
---
 ivette/.eslintignore             |   2 -
 ivette/package.json              |   4 +-
 ivette/src/frama-c/client_zmq.ts | 277 ++++++++++++-------------------
 ivette/src/frama-c/server.ts     |   1 +
 ivette/tsconfig.json             |   3 +-
 ivette/yarn.lock                 |  25 +++
 6 files changed, 140 insertions(+), 172 deletions(-)

diff --git a/ivette/.eslintignore b/ivette/.eslintignore
index 8744f886e55..80c4dad31d6 100644
--- a/ivette/.eslintignore
+++ b/ivette/.eslintignore
@@ -9,5 +9,3 @@ lib
 # don't lint the generated API
 api
 
-# ZMQ Server is not working (yet)
-src/frama-c/client_zmq.ts
diff --git a/ivette/package.json b/ivette/package.json
index 1077ae6437f..52655b7d59c 100644
--- a/ivette/package.json
+++ b/ivette/package.json
@@ -29,6 +29,7 @@
     "@types/react": "^16",
     "@types/react-dom": "^16",
     "@types/react-virtualized": "^9.21.0",
+    "@types/zeromq": "^5.2.1",
     "@typescript-eslint/eslint-plugin": "",
     "@typescript-eslint/parser": "",
     "babel-loader": "^8.2.3",
@@ -77,6 +78,7 @@
     "react-virtualized": "^9.22.3",
     "react-window": "",
     "source-map-support": "^0.5.21",
-    "tippy.js": ""
+    "tippy.js": "",
+    "zeromq": "^5.2.8"
   }
 }
diff --git a/ivette/src/frama-c/client_zmq.ts b/ivette/src/frama-c/client_zmq.ts
index 88128454f15..afe15ed62de 100644
--- a/ivette/src/frama-c/client_zmq.ts
+++ b/ivette/src/frama-c/client_zmq.ts
@@ -20,23 +20,20 @@
 /*                                                                          */
 /* ************************************************************************ */
 
-import Emitter from 'events';
-import { Request as ZmqRequest } from 'zeromq';
-import { json } from 'dome/data/json';
+import * as ZMQ from 'zeromq';
+import { Debug } from 'dome';
 import { Client } from './client';
 
-const pollingTimeout = 50;
+const D = new Debug('ZmqServer');
 
 // --------------------------------------------------------------------------
 // --- Frama-C Server API
 // --------------------------------------------------------------------------
 
-class ZmqClient implements Client {
+class ZmqClient extends Client {
 
-  events = new Emitter();
-  queueCmd: string[] = [];
-  queueId: string[] = [];
-  zmqSocket: ZmqRequest | undefined;
+  queue: string[] = [];
+  zmqSocket: ZMQ.Socket | undefined;
   zmqIsBusy = false;
 
   /** Server CLI */
@@ -46,18 +43,18 @@ class ZmqClient implements Client {
 
   /** Connection */
   connect(sockaddr: string): void {
-    if (!this.zmqSocket) {
+    if (this.zmqSocket) {
       this.zmqSocket.close();
     }
-    this.zmqSocket = new ZmqRequest();
+    this.zmqSocket = new ZMQ.Socket('req');
     this.zmqIsBusy = false;
-    this.zmqSocket.connect(sockaddr);
+    this.zmqSocket.connect(`ipc://${sockaddr}`);
+    this.zmqSocket.on('message', (msg: string[]) => this._receive(msg));
   }
 
   disconnect(): void {
     this.zmqIsBusy = false;
-    this.queueCmd = [];
-    this.queueId = [];
+    this.queue = [];
     if (this.zmqSocket) {
       this.zmqSocket.close();
       this.zmqSocket = undefined;
@@ -66,191 +63,137 @@ class ZmqClient implements Client {
 
   /** Send Request */
   send(kind: string, id: string, request: string, data: any): void {
-    this.queueCmd.push(kind, id, request, data);
-    this.queueId.push(id);
-    this._flush();
+    if (this.zmqSocket) {
+      this.queue.push(kind, id, request, data);
+      this._flush();
+    }
   }
 
   /** Signal ON */
-  sigOn(id: string): void { this.queueCmd.push('SIGON', id); this._flush(); }
+  sigOn(id: string): void {
+    if (this.zmqSocket) {
+      this.queue.push('SIGON', id);
+      this._flush();
+    }
+  }
 
   /** Signal ON */
-  sigOff(id: string): void { this.queueCmd.push('SIGOFF', id); this._flush(); }
+  sigOff(id: string): void {
+    if (this.zmqSocket) {
+      this.queue.push('SIGOFF', id);
+      this._flush();
+    }
+  }
 
   /** Kill Request */
   kill(id: string): void {
     if (this.zmqSocket) {
-      this.queueCmd.push('KILL', id);
+      this.queue.push('KILL', id);
       this._flush();
     }
   }
 
   /** Polling */
-  poll(): void { }
-
-  /** Shutdown the server */
-  shutdown(): void {
-    this._reset();
+  poll(): void {
+    if (this.zmqSocket && this.queue.length == 0) {
+      this.queue.push('POLL');
+    }
     this._flush();
-    this.queueCmd.push('SHUTDOWN');
-  }
-
-  /** Request data callback */
-  onData(callback: (id: string, data: json) => void): void {
-    this.events.on('DATA', callback);
-  }
-
-  /** Rejected request callback */
-  onRejected(callback: (id: string, err: string) => void): void {
-    this.events.on('REJECT', callback);
-  }
-
-  /** Request error callback */
-  onError(callback: (msg: string) => void): void {
-    this.events.on('ERROR', callback);
-  }
-
-  /** Killed request callback */
-  onKilled(callback: (id: string) => void): void {
-    this.events.on('KILL', callback);
   }
 
-  /** Signal callback */
-  onSignal(callback: (id: string) => void): void {
-    this.events.on('SIGNAL', callback);
-  }
-
-  /** Idle callback */
-  onIdle(callback: () => void): void {
-    this.events.on('CALLBACK', callback);
+  /** Shutdown the server */
+  shutdown(): void {
+    this.queue = [];
+    if (this.zmqSocket) {
+      this.queue.push('SHUTDOWN');
+      this._flush();
+    }
   }
 
   // --------------------------------------------------------------------------
   // --- Low-Level Management
   // --------------------------------------------------------------------------
 
-  pollingTimer: NodeJS.Timeout | undefined;
-  flushingTimer: NodeJS.Immediate | undefined;
-
-  _reset() {
-    if (this.flushingTimer) {
-      clearImmediate(this.flushingTimer);
-      this.flushingTimer = undefined;
-    }
-    if (this.pollingTimer) {
-      clearTimeout(this.pollingTimer);
-      this.pollingTimer = undefined;
-    }
-  }
-
   _flush() {
-    if (!this.flushingTimer) {
-      this.flushingTimer = setImmediate(() => {
-        this.flushingTimer = undefined;
-        this._send();
-      });
-    }
-  }
-
-  _poll() {
-    if (!this.pollingTimer) {
-      this.pollingTimer = setTimeout(() => {
-        this.pollingTimer = undefined;
-        this._send();
-      }, pollingTimeout);
+    const socket = this.zmqSocket;
+    if (socket) {
+      const cmds = this.queue;
+      if (cmds && !this.zmqIsBusy) {
+        try {
+          this.queue = [];
+          socket.send(cmds);
+          this.zmqIsBusy = true;
+        } catch (err) {
+          D.error('ZmqSocket', err);
+          this.zmqIsBusy = false;
+        }
+      }
+    } else {
+      this.queue = [];
     }
   }
 
-  async _send() {
-    // when busy, will be eventually re-triggered
-    if (!this.zmqIsBusy) {
-      const cmds = this.queueCmd;
-      if (!cmds.length) {
-        this.queueCmd.push('POLL');
-        this.events.emit('IDLE');
-      }
-      this.zmqIsBusy = true;
-      const ids = this.queueId;
-      this.queueCmd = [];
-      this.queueId = [];
-      try {
-        await this.zmqSocket?.send(cmds);
-        const resp = await this.zmqSocket?.receive();
-        this._receive(resp);
-      } catch (error) {
-        this._error(`Error in send/receive on ZMQ socket. ${error.toString()}`);
-        const err = 'Canceled request';
-        ids.forEach((rid) => this._reject(rid, err));
-      }
+  _receive(resp: string[]) {
+    try {
+      this._decode(resp);
+    } catch (err) {
+      D.error('ZmqSocket', err);
+    } finally {
       this.zmqIsBusy = false;
-      this.events.emit('IDLE');
+      setImmediate(() => this._flush());
     }
   }
 
-  _data(id: string, data: any) {
-    this.events.emit('DATA', id, data);
-  }
-
-  _reject(id: string, error: string) {
-    this.events.emit('REJECT', id, error);
-  }
-
-  _signal(id: string) {
-    this.events.emit('SIGNAL', id);
-  }
-
-  _error(err: any) {
-    this.events.emit('ERROR', err);
-  }
-
-  _receive(resp: any) {
-    try {
-      let rid;
-      let data;
-      let err;
-      let cmd;
-      const shift = () => resp.shift().toString();
-      let unknownResponse = false;
-      while (resp.length && !unknownResponse) {
-        cmd = shift();
-        switch (cmd) {
-          case 'NONE':
-            break;
-          case 'DATA':
-            rid = shift();
-            data = shift();
-            this._data(rid, data);
-            break;
-          case 'KILLED':
-            rid = shift();
-            this._reject(rid, 'Killed');
-            break;
-          case 'ERROR':
-            rid = shift();
-            err = shift();
-            this._reject(rid, err);
-            break;
-          case 'REJECTED':
-            rid = shift();
-            this._reject(rid, 'Rejected');
-            break;
-          case 'SIGNAL':
-            rid = shift();
-            this._signal(rid);
-            break;
-          case 'WRONG':
-            err = shift();
-            this._error(`ZMQ Protocol Error: ${err}`);
-            break;
-          default:
-            this._error(`Unknown Response: ${cmd}`);
-            unknownResponse = true;
-            break;
-        }
+  /* eslint-disable @typescript-eslint/indent */
+  _decode(resp: string[]) {
+    const shift = () => resp.shift() ?? '';
+    while (resp.length) {
+      const cmd = shift();
+      switch (cmd) {
+        case 'NONE':
+          break;
+        case 'DATA':
+          {
+            const rid = shift();
+            const data = JSON.parse(shift());
+            this.emitData(rid, data);
+          }
+          break;
+        case 'KILLED':
+          {
+            const rid = shift();
+            this.emitKilled(rid);
+          }
+          break;
+        case 'ERROR':
+          {
+            const rid = shift();
+            const msg = shift();
+            this.emitError(rid, msg);
+          }
+          break;
+        case 'REJECTED':
+          {
+            const rid = shift();
+            this.emitRejected(rid);
+          }
+          break;
+        case 'SIGNAL':
+          {
+            const rid = shift();
+            this.emitSignal(rid);
+          }
+          break;
+        case 'WRONG':
+          {
+            const err = shift();
+            D.error(`ZMQ Protocol Error: ${err}`);
+          }
+          break;
+        default:
+          D.error(`Unknown Response: ${cmd}`);
+          return;
       }
-    } finally {
-      if (this.queueCmd.length) this._flush();
-      else this._poll();
     }
   }
 
diff --git a/ivette/src/frama-c/server.ts b/ivette/src/frama-c/server.ts
index 198300d0fdc..8c84ddcfb27 100644
--- a/ivette/src/frama-c/server.ts
+++ b/ivette/src/frama-c/server.ts
@@ -38,6 +38,7 @@ import * as Json from 'dome/data/json';
 import { RichTextBuffer } from 'dome/text/buffers';
 import { ChildProcess } from 'child_process';
 import { client } from './client_socket';
+//import { client } from './client_zmq';
 
 // --------------------------------------------------------------------------
 // --- Events
diff --git a/ivette/tsconfig.json b/ivette/tsconfig.json
index 1d43b92a486..432ed41d037 100644
--- a/ivette/tsconfig.json
+++ b/ivette/tsconfig.json
@@ -86,8 +86,7 @@
     "node_modules",
     "dist",
     "src/dome/doc",
-    "src/dome/template",
-    "src/frama-c/client_zmq.ts"
+    "src/dome/template"
   ],
   "typedocOptions": {
     "name": "Ivette Documentation",
diff --git a/ivette/yarn.lock b/ivette/yarn.lock
index 979d6e22b45..cf9d9194ab9 100644
--- a/ivette/yarn.lock
+++ b/ivette/yarn.lock
@@ -1364,6 +1364,13 @@
   dependencies:
     "@types/yargs-parser" "*"
 
+"@types/zeromq@^5.2.1":
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/@types/zeromq/-/zeromq-5.2.1.tgz#f4316166e90fbe01e25ec6a6efa84e9bc8f91314"
+  integrity sha512-B6fUT5ZanWEyKSmLLEA0K3zNPvVedrjL6LNZWAt1eOpNg0J2lw14ismPJeOrgPf3SzRuNhPReQWibHbnMrtRow==
+  dependencies:
+    "@types/node" "*"
+
 "@typescript-eslint/eslint-plugin@":
   version "5.6.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.6.0.tgz#efd8668b3d6627c46ce722c2afe813928fe120a0"
@@ -6221,6 +6228,11 @@ multicast-dns@^6.0.1:
     dns-packet "^1.3.1"
     thunky "^1.0.2"
 
+nan@2.14.2:
+  version "2.14.2"
+  resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
+  integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
+
 nan@^2.12.1:
   version "2.15.0"
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
@@ -6286,6 +6298,11 @@ node-forge@^0.10.0:
   resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3"
   integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==
 
+node-gyp-build@^4.2.3:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
+  integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q==
+
 node-libs-browser@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425"
@@ -9145,3 +9162,11 @@ yauzl@^2.10.0:
   dependencies:
     buffer-crc32 "~0.2.3"
     fd-slicer "~1.1.0"
+
+zeromq@^5.2.8:
+  version "5.2.8"
+  resolved "https://registry.yarnpkg.com/zeromq/-/zeromq-5.2.8.tgz#94b0b85e4152e98b8bb163f1db4a34280d44d9d0"
+  integrity sha512-bXzsk7KOmgLSv1tC0Ms1VXBy90+Rz27ZYf27cLuldRYbpqYpuWJfxxHFhO710t22zgWBnmdUP0m3SKFpLI0u5g==
+  dependencies:
+    nan "2.14.2"
+    node-gyp-build "^4.2.3"
-- 
GitLab