# All files / src tcpAdapter.coffee

32.91% Statements 26/79
0% Branches 0/2
0% Functions 0/35
32.91% Lines 26/79

    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
    1821x
    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