     return "DATA -> #{toStr(@to)}" unless @from
     "#{fromStr(@from)} -> #{toStr(@to)}"
 
+  legacyToIp: (event, payload) ->
+    # No need to wrap modern IP Objects
+    return payload if payload and typeof payload is 'object' and payload.isIP
+
+    # Wrap legacy events into appropriate IP objects
+    switch event
+      when 'connect'
+        return new IP 'openBracket', null
+      when 'connect', 'begingroup'
+        return new IP 'openBracket', payload
+      when 'disconnect', 'endgroup'
+        return new IP 'closeBracket'
+      else
+        return new IP 'data', payload
+
+  ipToLegacy: (ip) ->
+    switch ip.type
+      when 'openBracket'
+        if ip.data is null
+          return legacy =
+            event: 'connect'
+            payload: @
+        return legacy =
+          event: 'begingroup'
+          payload: ip.data
+      when 'data'
+        return legacy =
+          event: 'data'
+          payload: ip.data
+      when 'closeBracket'
+        if ip.data is null
+          return legacy =
+            event: 'disconnect'
+            payload: @
+        return legacy =
+          event: 'endgroup'
+          payload: ip.data
+
+  handleSocketEvent: (event, payload, autoConnect = true) ->
+    ip = @legacyToIp event, payload
+
+    # Handle state transitions
+    if ip.type is 'data' and not @connected and autoConnect
+      # Connect before sending data
+      @handleSocketEvent 'connect', null
+
+    if ip.type is 'openBracket' and ip.data
+      if not @connected and autoConnect
+        # Connect before sending bracket
+        @handleSocketEvent 'connect', null
+      # Append bracket to socket group list
+      @groups.push ip.data
+
+    if ip.type is 'openBracket' and ip.data is null
+      # If we're already connected, no need to connect again
+      return if @connected
+      @connected = true
+
+    if ip.type is 'closeBracket'
+      if @groups.length
+        # Add group name to bracket
+        ip.data = @groups.pop()
+      else
+        # If we were already disconnected, no need to disconnect again
+        return unless @connected
+        # Last bracket was closed, we're disconnected
+        @connected = false
+        ip.data = null
+
+    # Emit the IP Object
+    @emitEvent 'ip', ip
+
+    # Emit the legacy event
+    legacyEvent = @ipToLegacy ip
+    @emitEvent legacyEvent.event, legacyEvent.payload
+
 exports.InternalSocket = InternalSocket
 
 exports.createSocket = -> new InternalSocket

  #         # Otherwise, call same method recursively
  #         @readBuffer fd, position, size, buffer
  connect: ->
    return if @connected
    @connected = true
    @emitEvent 'connect', @

  disconnect: ->
    return unless @connected
    @connected = false
    @emitEvent 'disconnect', @

  isConnected: -> @connected


  # can be constructed with more flexibility, as file buffers or
  # message queues can be used as additional packet relay mechanisms.
  send: (data) ->
    data = @dataDelegate() if data is undefined and typeof @dataDelegate is 'function'
    @handleSocketEvent 'data', data

  # ## Sending information packets without open bracket
  #
