commit 6e61fb898c94f408de30725585a748171a46057f Author: Dave Horton Date: Fri Aug 16 22:32:09 2019 -0400 initial checkin diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..ab1cfb4 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +test/* diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..d81ce6d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,126 @@ +{ + "env": { + "node": true, + "es6": true + }, + "parserOptions": { + "ecmaFeatures": { + "jsx": false, + "modules": false + }, + "ecmaVersion": 2017 + }, + "plugins": ["promise"], + "rules": { + "promise/always-return": "error", + "promise/no-return-wrap": "error", + "promise/param-names": "error", + "promise/catch-or-return": "error", + "promise/no-native": "off", + "promise/no-nesting": "warn", + "promise/no-promise-in-callback": "warn", + "promise/no-callback-in-promise": "warn", + "promise/no-return-in-finally": "warn", + + // Possible Errors + // http://eslint.org/docs/rules/#possible-errors + "comma-dangle": [2, "only-multiline"], + "no-control-regex": 2, + "no-debugger": 2, + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-empty-character-class": 2, + "no-ex-assign": 2, + "no-extra-boolean-cast" : 2, + "no-extra-parens": [2, "functions"], + "no-extra-semi": 2, + "no-func-assign": 2, + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-negated-in-lhs": 2, + "no-obj-calls": 2, + "no-proto": 2, + "no-unexpected-multiline": 2, + "no-unreachable": 2, + "use-isnan": 2, + "valid-typeof": 2, + + // Best Practices + // http://eslint.org/docs/rules/#best-practices + "no-fallthrough": 2, + "no-octal": 2, + "no-redeclare": 2, + "no-self-assign": 2, + "no-unused-labels": 2, + + // Strict Mode + // http://eslint.org/docs/rules/#strict-mode + "strict": [2, "never"], + + // Variables + // http://eslint.org/docs/rules/#variables + "no-delete-var": 2, + "no-undef": 2, + "no-unused-vars": [2, {"args": "none"}], + + // Node.js and CommonJS + // http://eslint.org/docs/rules/#nodejs-and-commonjs + "no-mixed-requires": 2, + "no-new-require": 2, + "no-path-concat": 2, + "no-restricted-modules": [2, "sys", "_linklist"], + + // Stylistic Issues + // http://eslint.org/docs/rules/#stylistic-issues + "comma-spacing": 2, + "eol-last": 2, + "indent": [2, 2, {"SwitchCase": 1}], + "keyword-spacing": 2, + "max-len": [2, 120, 2], + "new-parens": 2, + "no-mixed-spaces-and-tabs": 2, + "no-multiple-empty-lines": [2, {"max": 2}], + "no-trailing-spaces": [2, {"skipBlankLines": false }], + "quotes": [2, "single", "avoid-escape"], + "semi": 2, + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, "never"], + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-unary-ops": 2, + + // ECMAScript 6 + // http://eslint.org/docs/rules/#ecmascript-6 + "arrow-parens": [2, "always"], + "arrow-spacing": [2, {"before": true, "after": true}], + "constructor-super": 2, + "no-class-assign": 2, + "no-confusing-arrow": 2, + "no-const-assign": 2, + "no-dupe-class-members": 2, + "no-new-symbol": 2, + "no-this-before-super": 2, + "prefer-const": 2 + }, + "globals": { + "DTRACE_HTTP_CLIENT_REQUEST" : false, + "LTTNG_HTTP_CLIENT_REQUEST" : false, + "COUNTER_HTTP_CLIENT_REQUEST" : false, + "DTRACE_HTTP_CLIENT_RESPONSE" : false, + "LTTNG_HTTP_CLIENT_RESPONSE" : false, + "COUNTER_HTTP_CLIENT_RESPONSE" : false, + "DTRACE_HTTP_SERVER_REQUEST" : false, + "LTTNG_HTTP_SERVER_REQUEST" : false, + "COUNTER_HTTP_SERVER_REQUEST" : false, + "DTRACE_HTTP_SERVER_RESPONSE" : false, + "LTTNG_HTTP_SERVER_RESPONSE" : false, + "COUNTER_HTTP_SERVER_RESPONSE" : false, + "DTRACE_NET_STREAM_END" : false, + "LTTNG_NET_STREAM_END" : false, + "COUNTER_NET_SERVER_CONNECTION_CLOSE" : false, + "DTRACE_NET_SERVER_CONNECTION" : false, + "LTTNG_NET_SERVER_CONNECTION" : false, + "COUNTER_NET_SERVER_CONNECTION" : false + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..421c8b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Logs +logs +*.log +package-lock.json + +# Runtime data +pids +*.pid +*.seed + +# github pages site +_site + +#transient test cases +examples/nosave.*.js + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +.nyc_output/ + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +.DS_Store + +examples/* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9a7af18 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 jambonz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3a0fcd4 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# sbc-inbound \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..0cff83a --- /dev/null +++ b/app.js @@ -0,0 +1,19 @@ +const Srf = require('drachtio-srf'); +const srf = new Srf(); +const config = require('config'); +const logger = require('pino')(config.get('logging')); +const {auth} = require('./lib/middleware'); + +// disable logging in test mode +if (process.env.NODE_ENV === 'test') { + const noop = () => {}; + logger.info = logger.debug = noop; + logger.child = () => {return {info: noop, error: noop, debug: noop};}; +} + +srf.listen(config.get('drachtio')); + +srf.request('invite', auth); +srf.invite(require('./lib/invite')({logger})); + +module.exports = {srf}; diff --git a/config/default.json b/config/default.json new file mode 100644 index 0000000..22e0a7f --- /dev/null +++ b/config/default.json @@ -0,0 +1,45 @@ +{ + "drachtio": { + "port": 3002, + "secret": "cymru" + }, + "rtpengine": { + "host": "127.0.0.1", + "port": 22222 + }, + "logging": { + "level": "info" + }, + "authCallback": { + "uri": "http://example.com/auth", + "auth": { + "username": "foo", + "password": "bar" + } + }, + "trunks": { + "inbound": [ + { + "name": "carrier1", + "host": ["10.123.22.3"] + } + ], + "appserver": ["sip:10.10.120.1"] + }, + "transcoding": { + "rtpCharacteristics" : { + "transport protocol": "RTP/AVP", + "DTLS": "off", + "SDES": "off", + "ICE": "remove", + "rtcp-mux": ["demux"] + }, + "srtpCharacteristics": { + "transport-protocol": "UDP/TLS/RTP/SAVPF", + "ICE": "force", + "SDES": "off", + "flags": ["generate mid", "SDES-no"], + "rtcp-mux": ["require"] + } + } +} \ No newline at end of file diff --git a/config/local-test.json b/config/local-test.json new file mode 100644 index 0000000..d3e2f26 --- /dev/null +++ b/config/local-test.json @@ -0,0 +1,10 @@ +{ + "drachtio": { + "host": "127.0.0.1", + "port": 9060, + "secret": "cymru" + }, + "logging": { + "level": "info" + } +} \ No newline at end of file diff --git a/lib/invite.js b/lib/invite.js new file mode 100644 index 0000000..271078f --- /dev/null +++ b/lib/invite.js @@ -0,0 +1,63 @@ +const config = require('config'); +const Client = require('rtpengine-client').Client ; +const rtpengine = new Client(); +const offer = rtpengine.offer.bind(rtpengine, config.get('rtpengine')); +const answer = rtpengine.answer.bind(rtpengine, config.get('rtpengine')); +const del = rtpengine.delete.bind(rtpengine, config.get('rtpengine')); +const {getAppserver, isWSS} = require('./utils'); + +module.exports = handler; + +function handler({logger}) { + return async(req, res) => { + const srf = req.srf; + const rtpEngineOpts = makeRtpEngineOpts(req, isWSS(req), false); + const rtpEngineResource = {destroy: del.bind(rtpengine, rtpEngineOpts.common)}; + const uri = getAppserver(); + logger.info(`received inbound INVITE from ${req.protocol}/${req.source_address}:${req.source_port}`); + try { + const response = await offer(rtpEngineOpts.offer); + if ('ok' !== response.result) { + res.send(480); + throw new Error(`failed allocating rtpengine endpoint: ${JSON.stringify(response)}`); + } + + const {uas, uac} = await srf.createB2BUA(req, res, { + proxy: uri, + localSdpB: response.sdp, + localSdpA: (sdp, res) => { + const opts = Object.assign({sdp, 'to-tag': res.getParsedHeader('To').params.tag}, + rtpEngineOpts.answer); + return answer(opts) + .then((response) => { + if ('ok' !== response.result) throw new Error('error allocating rtpengine'); + return response.sdp; + }); + } + }); + logger.info('call connected'); + + [uas, uac].forEach((dlg) => dlg.on('destroy', () => { + logger.info('call ended'); + dlg.other.destroy(); + rtpEngineResource.destroy(); + })); + } catch (err) { + logger.error(err, 'Error connecting call'); + rtpEngineResource.destroy(); + } + }; +} + +function makeRtpEngineOpts(req, srcIsUsingSrtp, dstIsUsingSrtp) { + const from = req.getParsedHeader('from'); + const common = {'call-id': req.get('Call-ID'), 'from-tag': from.params.tag}; + const rtpCharacteristics = config.get('transcoding.rtpCharacteristics'); + const srtpCharacteristics = config.get('transcoding.srtpCharacteristics'); + return { + common, + offer: Object.assign({'sdp': req.body, 'replace': ['origin', 'session-connection']}, common, + dstIsUsingSrtp ? srtpCharacteristics : rtpCharacteristics), + answer: Object.assign({}, common, srcIsUsingSrtp ? srtpCharacteristics : rtpCharacteristics) + }; +} diff --git a/lib/middleware.js b/lib/middleware.js new file mode 100644 index 0000000..dba3175 --- /dev/null +++ b/lib/middleware.js @@ -0,0 +1,12 @@ +const {fromInboundTrunk} = require('./utils'); +const config = require('config'); +const authenticator = require('drachtio-http-authenticator')(config.get('authCallback')); + +function auth(req, res, next) { + if (fromInboundTrunk) return next(); + authenticator(req, res, next); +} + +module.exports = { + auth +}; diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 0000000..40a89a7 --- /dev/null +++ b/lib/utils.js @@ -0,0 +1,24 @@ +const config = require('config'); +let idx = 0; + +function fromInboundTrunk(req) { + const trunks = config.has('trunks.inbound') ? + config.get('trunks.inbound') : []; + if (isWSS(req)) return false; + return trunks.find((trunk) => trunk.host.includes(req.source_address)); +} + +function isWSS(req) { + return req.getParsedHeader('Via')[0].protocol.toLowerCase().startsWith('ws'); +} + +function getAppserver() { + const len = config.get('trunks.appserver').length; + return config.get('trunks.appserver')[ idx++ % len]; +} + +module.exports = { + fromInboundTrunk, + isWSS, + getAppserver +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..9fb5d5c --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "sbc-inbound", + "version": "0.0.1", + "main": "app.js", + "engines": { + "node": ">= 8.10.0" + }, + "keywords": [ + "sip", + "drachtio" + ], + "author": "", + "license": "", + "scripts": { + "start": "node app", + "test": "NODE_ENV=test node test/ | ./node_modules/.bin/tap-spec", + "coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test", + "jslint": "eslint app.js lib" + }, + "dependencies": { + "config": "^3.2.2", + "drachtio-http-authenticator": "0.0.2", + "drachtio-srf": "^4.4.14", + "pino": "^5.13.2", + "rtpengine-client": "0.0.8" + }, + "devDependencies": { + "blue-tape": "^1.0.0", + "clear-module": "^4.0.0", + "eslint": "^6.1.0", + "eslint-plugin-promise": "^4.2.1", + "nyc": "^14.1.1", + "tap": "^14.6.1", + "tap-dot": "^2.0.0", + "tap-spec": "^5.0.0" + } +} diff --git a/test/docker-compose-testbed.yaml b/test/docker-compose-testbed.yaml new file mode 100644 index 0000000..3e9d94f --- /dev/null +++ b/test/docker-compose-testbed.yaml @@ -0,0 +1,32 @@ +version: '3' + +networks: + sbc-inbound: + driver: bridge + ipam: + config: + - subnet: 172.38.0.0/16 + +services: + drachtio: + image: drachtio/drachtio-server:latest + command: drachtio --contact "sip:*;transport=udp" --loglevel debug --sofia-loglevel 9 + container_name: drachtio + ports: + - "9060:9022/tcp" + networks: + sbc-inbound: + ipv4_address: 172.38.0.10 + + + + sipp-uas: + image: drachtio/sipp:latest + command: sipp -sf /tmp/uas.xml + container_name: sipp-uas.local + volumes: + - ./scenarios:/tmp + tty: true + networks: + sbc-inbound: + ipv4_address: 172.38.0.12 diff --git a/test/docker_start.js b/test/docker_start.js new file mode 100644 index 0000000..ccfa001 --- /dev/null +++ b/test/docker_start.js @@ -0,0 +1,12 @@ +const test = require('tape').test ; +const exec = require('child_process').exec ; +const async = require('async'); + +test('starting docker network..', (t) => { + exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml up -d`, (err, stdout, stderr) => { + t.end(err); + }); + +}); + + diff --git a/test/docker_stop.js b/test/docker_stop.js new file mode 100644 index 0000000..dd7a249 --- /dev/null +++ b/test/docker_stop.js @@ -0,0 +1,12 @@ +const test = require('tape').test ; +const exec = require('child_process').exec ; + +test('stopping docker network..', (t) => { + t.timeoutAfter(10000); + exec(`docker-compose -f ${__dirname}/docker-compose-testbed.yaml down`, (err, stdout, stderr) => { + //console.log(`stderr: ${stderr}`); + process.exit(0); + }); + t.end() ; +}); + diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..4b44985 --- /dev/null +++ b/test/index.js @@ -0,0 +1,3 @@ +require('./docker_start'); +require('./sip-tests'); +require('./docker_stop'); diff --git a/test/scenarios/pcap/g711a.pcap b/test/scenarios/pcap/g711a.pcap new file mode 100644 index 0000000..bafea38 Binary files /dev/null and b/test/scenarios/pcap/g711a.pcap differ diff --git a/test/scenarios/uac-info-expect-480.xml b/test/scenarios/uac-info-expect-480.xml new file mode 100644 index 0000000..07c4ca9 --- /dev/null +++ b/test/scenarios/uac-info-expect-480.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [service] + Call-ID: [call_id] + CSeq: 1 INFO + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + + + + + + diff --git a/test/scenarios/uac-message-expect-480.xml b/test/scenarios/uac-message-expect-480.xml new file mode 100644 index 0000000..5339c52 --- /dev/null +++ b/test/scenarios/uac-message-expect-480.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [service] + Call-ID: [call_id] + CSeq: 1 MESSAGE + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + + + + + + diff --git a/test/scenarios/uac-options-expect-480.xml b/test/scenarios/uac-options-expect-480.xml new file mode 100644 index 0000000..acefbc5 --- /dev/null +++ b/test/scenarios/uac-options-expect-480.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [service] + Call-ID: [call_id] + CSeq: 1 OPTIONS + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + + + + + + diff --git a/test/scenarios/uac-pcap.xml b/test/scenarios/uac-pcap.xml new file mode 100644 index 0000000..bdc2efc --- /dev/null +++ b/test/scenarios/uac-pcap.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag09[call_number] + To: [service] + Call-ID: [call_id] + CSeq: 1 INVITE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[local_ip_type] [local_ip] + t=0 0 + m=audio [auto_media_port] RTP/AVP 8 101 + a=rtpmap:8 PCMA/8000 + a=rtpmap:101 telephone-event/8000 + a=fmtp:101 0-11,16 + + ]]> + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag09[call_number] + To: [service] [peer_tag_param] + Call-ID: [call_id] + CSeq: 1 ACK + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag09[call_number] + To: [service] [peer_tag_param] + Call-ID: [call_id] + CSeq: 2 BYE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + + + + + + + + + + + + + diff --git a/test/scenarios/uac-publish-expect-480.xml b/test/scenarios/uac-publish-expect-480.xml new file mode 100644 index 0000000..6027b86 --- /dev/null +++ b/test/scenarios/uac-publish-expect-480.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [service] + Call-ID: [call_id] + CSeq: 1 PUBLISH + Contact: ;expires=3600 + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + + + + + + diff --git a/test/scenarios/uac-register-expect-480.xml b/test/scenarios/uac-register-expect-480.xml new file mode 100644 index 0000000..43a5188 --- /dev/null +++ b/test/scenarios/uac-register-expect-480.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [service] + Call-ID: [call_id] + CSeq: 1 REGISTER + Contact: ;expires=3600 + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + + + + + + diff --git a/test/scenarios/uac-subscribe-expect-480.xml b/test/scenarios/uac-subscribe-expect-480.xml new file mode 100644 index 0000000..ab2ae3d --- /dev/null +++ b/test/scenarios/uac-subscribe-expect-480.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [service] + Call-ID: [call_id] + CSeq: 1 SUBSCRIBE + Contact: ;expires=3600 + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + + + + + + diff --git a/test/scenarios/uac.xml b/test/scenarios/uac.xml new file mode 100644 index 0000000..db57c48 --- /dev/null +++ b/test/scenarios/uac.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [service] + Call-ID: [call_id] + CSeq: 1 INVITE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + + + + + + + + + + + + + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [service] [peer_tag_param] + Call-ID: [call_id] + CSeq: 1 ACK + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + + + + + + + + + ;tag=[pid]SIPpTag00[call_number] + To: [service] [peer_tag_param] + Call-ID: [call_id] + CSeq: 2 BYE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + + + + + + + + + + + + + diff --git a/test/scenarios/uas.xml b/test/scenarios/uas.xml new file mode 100644 index 0000000..51035f6 --- /dev/null +++ b/test/scenarios/uas.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + + + + + + + + + + + Content-Length: 0 + + ]]> + + + + + + + + + + + + + + + diff --git a/test/sip-tests.js b/test/sip-tests.js new file mode 100644 index 0000000..5e2d075 --- /dev/null +++ b/test/sip-tests.js @@ -0,0 +1,40 @@ +const test = require('blue-tape'); +const { output, sippUac } = require('./sipp')('test_sbc-inbound'); +const debug = require('debug')('drachtio:sbc-inbound'); +const clearModule = require('clear-module'); + +process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); +}); + +function connect(connectable) { + return new Promise((resolve, reject) => { + connectable.on('connect', () => { + return resolve(); + }); + }); +} + +test('invite handler', (t) => { + clearModule('../app'); + const {srf, disconnectMs} = require('../app'); + + connect(srf) + .then(() => { + return sippUac('uac-pcap.xml'); + }) + .then(() => { + t.pass('successfully connected call'); + if (srf.locals.lb) srf.locals.lb.disconnect(); + srf.disconnect(); + t.end(); + return; + }) + .catch((err) => { + if (srf.locals.lb) srf.locals.lb.disconnect(); + if (srf) srf.disconnect(); + console.log(`error received: ${err}`); + t.error(err); + }); +}); + diff --git a/test/sipp.js b/test/sipp.js new file mode 100644 index 0000000..2f580ec --- /dev/null +++ b/test/sipp.js @@ -0,0 +1,65 @@ +const { spawn } = require('child_process'); +//const debug = require('debug')('test:sipp'); +let network; +const obj = {}; +let output = ''; +let idx = 1; + +function clearOutput() { + output = ''; +} + +function addOutput(str) { + for (let i = 0; i < str.length; i++) { + if (str.charCodeAt(i) < 128) output += str.charAt(i); + } +} + +module.exports = (networkName) => { + network = networkName ; + return obj; +}; + +obj.output = () => { + return output; +}; + +obj.sippUac = (file) => { + const cmd = 'docker'; + const args = [ + 'run', '-ti', '--rm', '--net', `${network}`, + '-v', `${__dirname}/scenarios:/tmp/scenarios`, + 'drachtio/sipp', 'sipp', '-sf', `/tmp/scenarios/${file}`, + '-m', '1', + '-sleep', '250ms', + '-nostdin', + '-cid_str', `%u-%p@%s-${idx++}`, + 'drachtio' + ]; + + clearOutput(); + + return new Promise((resolve, reject) => { + const child_process = spawn(cmd, args, {stdio: ['inherit', 'pipe', 'pipe']}); + + child_process.on('exit', (code, signal) => { + if (code === 0) { + return resolve(); + } + console.log(`sipp exited with non-zero code ${code} signal ${signal}`); + reject(code); + }); + child_process.on('error', (error) => { + console.log(`error spawing child process for docker: ${args}`); + }); + + child_process.stdout.on('data', (data) => { + //debug(`stdout: ${data}`); + addOutput(data.toString()); + }); + child_process.stdout.on('data', (data) => { + //debug(`stdout: ${data}`); + addOutput(data.toString()); + }); + }); +};