UNPKG

6.26 kBJavaScriptView Raw
1'use strict'
2
3const ip = require('./ip')
4const protocols = require('./protocols-table')
5const { CID } = require('multiformats/cid')
6const { base32 } = require('multiformats/bases/base32')
7const { base58btc } = require('multiformats/bases/base58')
8const Digest = require('multiformats/hashes/digest')
9const varint = require('varint')
10const { toString: uint8ArrayToString } = require('uint8arrays/to-string')
11const { fromString: uint8ArrayFromString } = require('uint8arrays/from-string')
12const { concat: uint8ArrayConcat } = require('uint8arrays/concat')
13
14module.exports = Convert
15
16// converts (serializes) addresses
17/**
18 * @param {string} proto
19 * @param {string | Uint8Array} a
20 */
21function Convert (proto, a) {
22 if (a instanceof Uint8Array) {
23 return Convert.toString(proto, a)
24 } else {
25 return Convert.toBytes(proto, a)
26 }
27}
28
29/**
30 * Convert [code,Uint8Array] to string
31 *
32 * @param {number|string} proto
33 * @param {Uint8Array} buf
34 * @returns {string}
35 */
36Convert.toString = function convertToString (proto, buf) {
37 const protocol = protocols(proto)
38 switch (protocol.code) {
39 case 4: // ipv4
40 case 41: // ipv6
41 return bytes2ip(buf)
42
43 case 6: // tcp
44 case 273: // udp
45 case 33: // dccp
46 case 132: // sctp
47 return bytes2port(buf).toString()
48
49 case 53: // dns
50 case 54: // dns4
51 case 55: // dns6
52 case 56: // dnsaddr
53 case 400: // unix
54 case 777: // memory
55 return bytes2str(buf)
56
57 case 421: // ipfs
58 return bytes2mh(buf)
59 case 444: // onion
60 return bytes2onion(buf)
61 case 445: // onion3
62 return bytes2onion(buf)
63 default:
64 return uint8ArrayToString(buf, 'base16') // no clue. convert to hex
65 }
66}
67
68Convert.toBytes = function convertToBytes (/** @type {string | number } */ proto, /** @type {string} */ str) {
69 const protocol = protocols(proto)
70 switch (protocol.code) {
71 case 4: // ipv4
72 return ip2bytes(str)
73 case 41: // ipv6
74 return ip2bytes(str)
75
76 case 6: // tcp
77 case 273: // udp
78 case 33: // dccp
79 case 132: // sctp
80 return port2bytes(parseInt(str, 10))
81
82 case 53: // dns
83 case 54: // dns4
84 case 55: // dns6
85 case 56: // dnsaddr
86 case 400: // unix
87 case 777: // memory
88 return str2bytes(str)
89
90 case 421: // ipfs
91 return mh2bytes(str)
92 case 444: // onion
93 return onion2bytes(str)
94 case 445: // onion3
95 return onion32bytes(str)
96 default:
97 return uint8ArrayFromString(str, 'base16') // no clue. convert from hex
98 }
99}
100
101/**
102 * @param {string} ipString
103 */
104function ip2bytes (ipString) {
105 if (!ip.isIP(ipString)) {
106 throw new Error('invalid ip address')
107 }
108 return ip.toBytes(ipString)
109}
110
111/**
112 * @param {Uint8Array} ipBuff
113 */
114function bytes2ip (ipBuff) {
115 const ipString = ip.toString(ipBuff)
116 if (!ipString || !ip.isIP(ipString)) {
117 throw new Error('invalid ip address')
118 }
119 return ipString
120}
121
122/**
123 * @param {number} port
124 */
125function port2bytes (port) {
126 const buf = new ArrayBuffer(2)
127 const view = new DataView(buf)
128 view.setUint16(0, port)
129
130 return new Uint8Array(buf)
131}
132
133/**
134 * @param {Uint8Array} buf
135 */
136function bytes2port (buf) {
137 const view = new DataView(buf.buffer)
138 return view.getUint16(buf.byteOffset)
139}
140
141/**
142 * @param {string} str
143 */
144function str2bytes (str) {
145 const buf = uint8ArrayFromString(str)
146 const size = Uint8Array.from(varint.encode(buf.length))
147 return uint8ArrayConcat([size, buf], size.length + buf.length)
148}
149
150/**
151 * @param {Uint8Array} buf
152 */
153function bytes2str (buf) {
154 const size = varint.decode(buf)
155 buf = buf.slice(varint.decode.bytes)
156
157 if (buf.length !== size) {
158 throw new Error('inconsistent lengths')
159 }
160
161 return uint8ArrayToString(buf)
162}
163
164/**
165 * @param {string} hash - base58btc string
166 */
167function mh2bytes (hash) {
168 let mh
169
170 if (hash[0] === 'Q' || hash[0] === '1') {
171 mh = Digest.decode(base58btc.decode(`z${hash}`)).bytes
172 } else {
173 mh = CID.parse(hash).multihash.bytes
174 }
175
176 // the address is a varint prefixed multihash string representation
177 const size = Uint8Array.from(varint.encode(mh.length))
178 return uint8ArrayConcat([size, mh], size.length + mh.length)
179}
180
181/**
182 * Converts bytes to bas58btc string
183 *
184 * @param {Uint8Array} buf
185 * @returns {string} base58btc string
186 */
187function bytes2mh (buf) {
188 const size = varint.decode(buf)
189 const address = buf.slice(varint.decode.bytes)
190
191 if (address.length !== size) {
192 throw new Error('inconsistent lengths')
193 }
194
195 return uint8ArrayToString(address, 'base58btc')
196}
197
198/**
199 * @param {string} str
200 */
201function onion2bytes (str) {
202 const addr = str.split(':')
203 if (addr.length !== 2) {
204 throw new Error('failed to parse onion addr: ' + addr + ' does not contain a port number')
205 }
206 if (addr[0].length !== 16) {
207 throw new Error('failed to parse onion addr: ' + addr[0] + ' not a Tor onion address.')
208 }
209
210 // onion addresses do not include the multibase prefix, add it before decoding
211 const buf = base32.decode('b' + addr[0])
212
213 // onion port number
214 const port = parseInt(addr[1], 10)
215 if (port < 1 || port > 65536) {
216 throw new Error('Port number is not in range(1, 65536)')
217 }
218 const portBuf = port2bytes(port)
219 return uint8ArrayConcat([buf, portBuf], buf.length + portBuf.length)
220}
221
222/**
223 * @param {string} str
224 */
225function onion32bytes (str) {
226 const addr = str.split(':')
227 if (addr.length !== 2) {
228 throw new Error('failed to parse onion addr: ' + addr + ' does not contain a port number')
229 }
230 if (addr[0].length !== 56) {
231 throw new Error('failed to parse onion addr: ' + addr[0] + ' not a Tor onion3 address.')
232 }
233 // onion addresses do not include the multibase prefix, add it before decoding
234 const buf = base32.decode('b' + addr[0])
235
236 // onion port number
237 const port = parseInt(addr[1], 10)
238 if (port < 1 || port > 65536) {
239 throw new Error('Port number is not in range(1, 65536)')
240 }
241 const portBuf = port2bytes(port)
242 return uint8ArrayConcat([buf, portBuf], buf.length + portBuf.length)
243}
244
245/**
246 * @param {Uint8Array} buf
247 */
248function bytes2onion (buf) {
249 const addrBytes = buf.slice(0, buf.length - 2)
250 const portBytes = buf.slice(buf.length - 2)
251 const addr = uint8ArrayToString(addrBytes, 'base32')
252 const port = bytes2port(portBytes)
253 return addr + ':' + port
254}