From e02d15bf647e7c0121f684c82d7dbb977b2a4e7d Mon Sep 17 00:00:00 2001
From: Vasco Santos <vasco.santos@moxy.studio>
Date: Wed, 12 Feb 2020 10:16:57 +0100
Subject: [PATCH] feat: stream to multiaddr connection converter

---
 package.json                   | 11 +++++---
 src/stream-to-ma-conn.js       | 49 ++++++++++++++++++++++++++++++++++
 test/stream-to-ma-conn.spec.js | 38 ++++++++++++++++++++++++++
 3 files changed, 94 insertions(+), 4 deletions(-)
 create mode 100644 src/stream-to-ma-conn.js
 create mode 100644 test/stream-to-ma-conn.spec.js

diff --git a/package.json b/package.json
index f14851d..aeafb99 100644
--- a/package.json
+++ b/package.json
@@ -29,13 +29,16 @@
   },
   "homepage": "https://github.com/libp2p/js-libp2p-utils#readme",
   "devDependencies": {
-    "aegir": "^20.3.1",
+    "aegir": "^20.6.0",
     "chai": "^4.2.0",
-    "dirty-chai": "^2.0.1"
+    "dirty-chai": "^2.0.1",
+    "it-pair": "^1.0.0",
+    "multiaddr": "^7.3.0"
   },
   "dependencies": {
-    "ip-address": "^6.1.0",
-    "multiaddr": "^7.1.0"
+    "abortable-iterator": "^3.0.0",
+    "debug": "^4.1.1",
+    "ip-address": "^6.1.0"
   },
   "contributors": [
     "Vasco Santos <vasco.santos@moxy.studio>"
diff --git a/src/stream-to-ma-conn.js b/src/stream-to-ma-conn.js
new file mode 100644
index 0000000..d19e960
--- /dev/null
+++ b/src/stream-to-ma-conn.js
@@ -0,0 +1,49 @@
+'use strict'
+
+const abortable = require('abortable-iterator')
+const log = require('debug')('libp2p:stream:converter')
+
+// Convert a duplex iterable into a MultiaddrConnection
+// https://github.com/libp2p/interface-transport#multiaddrconnection
+module.exports = ({ stream, remoteAddr, localAddr }, options = {}) => {
+  const { sink, source } = stream
+  const maConn = {
+    async sink (source) {
+      if (options.signal) {
+        source = abortable(source, options.signal)
+      }
+
+      try {
+        await sink(source)
+      } catch (err) {
+        // If aborted we can safely ignore
+        if (err.type !== 'aborted') {
+          // If the source errored the socket will already have been destroyed by
+          // toIterable.duplex(). If the socket errored it will already be
+          // destroyed. There's nothing to do here except log the error & return.
+          log(err)
+        }
+      }
+      close()
+    },
+
+    source: options.signal ? abortable(source, options.signal) : source,
+    conn: stream,
+    localAddr,
+    remoteAddr,
+    timeline: { open: Date.now() },
+
+    close () {
+      sink([])
+      close()
+    }
+  }
+
+  function close () {
+    if (!maConn.timeline.close) {
+      maConn.timeline.close = Date.now()
+    }
+  }
+
+  return maConn
+}
diff --git a/test/stream-to-ma-conn.spec.js b/test/stream-to-ma-conn.spec.js
new file mode 100644
index 0000000..df5b250
--- /dev/null
+++ b/test/stream-to-ma-conn.spec.js
@@ -0,0 +1,38 @@
+/* eslint-env mocha */
+'use strict'
+
+const chai = require('chai')
+const dirtyChai = require('dirty-chai')
+const expect = chai.expect
+chai.use(dirtyChai)
+
+const pair = require('it-pair')
+const multiaddr = require('multiaddr')
+
+const streamToMaConn = require('../src/stream-to-ma-conn')
+
+describe('Convert stream into a multiaddr connection', () => {
+  it('converts a stream and adds the provided metadata', () => {
+    const stream = pair()
+    const localAddr = multiaddr('/ip4/101.45.75.219/tcp/6000')
+    const remoteAddr = multiaddr('/ip4/100.46.74.201/tcp/6002')
+
+    const maConn = streamToMaConn({
+      stream,
+      localAddr,
+      remoteAddr
+    })
+
+    expect(maConn).to.exist()
+    expect(maConn.sink).to.exist()
+    expect(maConn.source).to.exist()
+    expect(maConn.localAddr).to.eql(localAddr)
+    expect(maConn.remoteAddr).to.eql(remoteAddr)
+    expect(maConn.timeline).to.exist()
+    expect(maConn.timeline.open).to.exist()
+    expect(maConn.timeline.close).to.not.exist()
+
+    maConn.close()
+    expect(maConn.timeline.close).to.exist()
+  })
+})