1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182 | 1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
1x
| http = require 'http'
net = require 'net'
tls = require 'tls'
config = require "./config/config"
config.tcpAdapter = config.get('tcpAdapter')
logger = require "winston"
Channels = require('./model/channels')
Channel = Channels.Channel
Q = require "q"
tlsAuthentication = require "./middleware/tlsAuthentication"
authorisation = require "./middleware/authorisation"
tcpServers = []
newKey = 0
datastore = {}
process.on 'message', (msg) ->
if msg.type is 'start-tcp-channel'
logger.debug "Recieved message to start tcp channel: #{msg.channelID}"
exports.startupTCPServer msg.channelID, ->
else if msg.type is 'stop-tcp-channel'
logger.debug "Recieved message to stop tcp channel: #{msg.channelID}"
exports.stopServerForChannel msg.channelID, ->
exports.popTransaction = (key) ->
res = datastore["#{key}"]
delete datastore["#{key}"]
return res
startListening = (channel, tcpServer, host, port, callback) ->
tcpServer.listen port, host, ->
tcpServers.push { channelID: channel._id, server: tcpServer }
callback null
tcpServer.on 'error', (err) ->
logger.error err + ' Host: ' + host + ' Port: ' + port
exports.notifyMasterToStartTCPServer = (channelID, callback) ->
logger.debug "Sending message to master to start tcp channel: #{channelID}"
process.send
type: 'start-tcp-channel'
channelID: channelID
exports.startupTCPServer = (channelID, callback) ->
for existingServer in tcpServers
# server already running for channel
return callback null if existingServer.channelID.equals channelID
handler = (sock) ->
Channel.findById channelID, (err, channel) ->
return logger.error err if err
sock.on 'data', (data) -> adaptSocketRequest channel, sock, "#{data}"
sock.on 'error', (err) -> logger.error err
Channel.findById channelID, (err, channel) ->
host = channel.tcpHost or '0.0.0.0'
port = channel.tcpPort
return callback "Channel #{channel.name} (#{channel._id}): TCP port not defined" if not port
if channel.type is 'tls'
tlsAuthentication.getServerOptions true, (err, options) ->
return callback err if err
tcpServer = tls.createServer options, handler
startListening channel, tcpServer, host, port, (err) ->
if err
callback err
else
logger.info "Channel #{channel.name} (#{channel._id}): TLS server listening on port #{port}"
callback null
else if channel.type is 'tcp'
tcpServer = net.createServer handler
startListening channel, tcpServer, host, port, (err) ->
if err
callback err
else
logger.info "Channel #{channel.name} (#{channel._id}): TCP server listening on port #{port}"
callback null
else
return callback "Cannot handle #{channel.type} channels"
# Startup a TCP server for each TCP channel
exports.startupServers = (callback) ->
Channel.find { $or: [ {type: 'tcp'}, {type: 'tls'} ] }, (err, channels) ->
return callback err if err
promises = []
for channel in channels
do (channel) ->
if Channels.isChannelEnabled channel
defer = Q.defer()
exports.startupTCPServer channel._id, (err) ->
return callback err if err
defer.resolve()
promises.push defer.promise
(Q.all promises).then -> callback null
adaptSocketRequest = (channel, sock, socketData) ->
options =
hostname: config.tcpAdapter.httpReceiver.host
port: config.tcpAdapter.httpReceiver.httpPort
path: '/'
method: 'POST'
req = http.request options, (res) ->
response = ''
res.on 'data', (data) -> response += data
res.on 'end', ->
if sock.writable
sock.write response
req.on "error", (err) -> logger.error err
# don't write the actual data to the http receiver
# instead send a reference through (see popTransaction)
datastore["#{newKey}"] = {}
datastore["#{newKey}"].data = socketData
datastore["#{newKey}"].channel = channel
req.write "#{newKey}"
newKey++
# in case we've been running for a couple thousand years
newKey = 0 if newKey is Number.MAX_VALUE
req.end()
stopTCPServers = (servers, callback) ->
promises = []
for server in servers
do (server) ->
defer = Q.defer()
server.server.close (err) ->
if err
logger.error "Could not close tcp server: #{err}"
defer.reject err
else
logger.info "Channel #{server.channelID}: Stopped TCP/TLS server"
defer.resolve()
promises.push defer.promise
(Q.all promises).then -> callback()
exports.stopServers = (callback) ->
stopTCPServers tcpServers, ->
tcpServers = []
callback()
exports.notifyMasterToStopTCPServer = (channelID, callback) ->
logger.debug "Sending message to master to stop tcp channel: #{channelID}"
process.send
type: 'stop-tcp-channel'
channelID: channelID
exports.stopServerForChannel = (channelID, callback) ->
server = null
notStoppedTcpServers = []
for serverDetails in tcpServers
if serverDetails.channelID.equals channelID
server = serverDetails
else
# push all except the server we're stopping
notStoppedTcpServers.push serverDetails
return callback "Server for channel #{channelID} not running" if not server
tcpServers = notStoppedTcpServers
stopTCPServers [server], callback
if process.env.NODE_ENV == "test"
exports.tcpServers = tcpServers
|