dissect-wireshark: Refactored dissector implementation

Change-Id: I913044e5f5e9f42dc62d5d81da758f12177bb149
Refs: #3092
diff --git a/tests/dissect-wireshark/README.md b/tests/dissect-wireshark/README.md
new file mode 100644
index 0000000..6de2a99
--- /dev/null
+++ b/tests/dissect-wireshark/README.md
@@ -0,0 +1,98 @@
+Testing Instructions
+====================
+
+This folder contains several crafted tcpdump traces that can be used to manually check
+correctness of NDN Packet Dissector for Wireshark in several scenarios.
+
+To use the dissector, follow the instructions in
+[dissector's README.md](../../tools/dissect-wireshark/README.md).
+
+`-r <trace-file>` command-line flag can be used to directly open a specific trace file in
+WireShark.  For example:
+
+    wireshark -X lua_script:../../tools/dissect-wireshark/ndn.lua -r ipv4-udp-fragmented.pcap
+
+## Test Cases / Trace File Description
+
+### 1. IPv4 UDP
+
+Trace file: `ipv4-udp-fragmented.pcap`
+
+Trace summary: several IPv4 UDP packets, carrying NDN interests and data packets. One
+datagram is fragmented into several IPv4 packets.
+
+Expected result of the dissection:
+
+- NDN interests are dissected from packets 1, 2, 3, and 8.
+- NDN data packet is dissected from defragmented packets 4, 5, 6, and 7.
+
+### 2. IPv6 UDP
+
+Trace file: `ipv6-udp-fragmented.pcap`
+
+Trace summary: several IPv6 UDP packets, carrying NDN interests and data packets. One
+datagram is fragmented into several IPv6 packets.
+
+Expected result of the dissection:
+
+- NDN interests are dissected from packets 1, and 2.
+- NDN data packet is dissected from defragmented packets 3, 4, 5, and 6.
+
+### 3. IPv4 TCP
+
+#### 3.1. De-segmentation and dissection
+
+  Trace file: `ipv4-tcp-segmented.pcap`
+
+  Trace summary: several IPv4 TCP packets, carrying NDN interest and a data packets. The data
+  packet spans several TCP segments.
+
+  Expected result of the dissection:
+  - interest packet is properly dissected from packet 2.
+  - data packet is properly dissected after de-segmentation of payloads in packets 4, 5, 6, and 7.
+
+#### 3.2. Dissection of TCP segments containing multiple NDN packets
+
+  Trace file: `ipv4-tcp-multi-ndn-packets-in-segment.pcap`
+
+  Trace summary: Several IPv4 TCP packets, payload of one containing several NDN interests.
+
+  Expected result of the dissection:
+  - a single interest packet is dissected from packet 1.
+  - four interest packets are dissected from packet 3.
+
+### 4. IPv6 TCP
+
+Trace file: `ipv6-tcp-segmented.pcap`
+
+Trace summary: several IPv6 TCP packets, carrying an NDN data packet that spans several
+TCP segments.
+
+Expected result of the dissection:
+- data packet is properly dissected after de-segmentation of payloads in packets 2, 3, 4, and 5.
+
+### 5. IPv4 TCP/WebSocket
+
+Trace file: `ipv4-websocket-segmented.pcap`
+
+Trace summary: Partial capture of a live IPv4 WebSocket session with a single NDN interest
+retrieving large (~5k) NDN data packet.
+
+Expected result of the dissection:
+- interest packet is dissected after a partial reconstruction of WebSocket session at
+  packet 16.
+- data packet is properly dissected after a partial reconstruction of WebSocket
+  conversation at packet 22.
+
+### 6. IPv6 TCP/WebSocket
+
+Trace file: `ipv6-websocket-segmented.pcap`
+
+Trace summary: Partial capture of a live IPv6 WebSocket session with a single NDN interest
+retrieving large (~5k) NDN data packet.
+
+Expected result of the dissection:
+- interest packet is dissected after a partial reconstruction of WebSocket session at
+  packet 6.
+- data packet is properly dissected after a partial reconstruction of WebSocket
+  conversation at packet 12.
diff --git a/tests/dissect-wireshark/ipv4-tcp-multi-ndn-packets-in-segment.pcap b/tests/dissect-wireshark/ipv4-tcp-multi-ndn-packets-in-segment.pcap
new file mode 100644
index 0000000..5123fa8
--- /dev/null
+++ b/tests/dissect-wireshark/ipv4-tcp-multi-ndn-packets-in-segment.pcap
Binary files differ
diff --git a/tests/dissect-wireshark/ipv4-tcp-segmented.pcap b/tests/dissect-wireshark/ipv4-tcp-segmented.pcap
new file mode 100644
index 0000000..6d5fa29
--- /dev/null
+++ b/tests/dissect-wireshark/ipv4-tcp-segmented.pcap
Binary files differ
diff --git a/tests/dissect-wireshark/ipv4-udp-fragmented.pcap b/tests/dissect-wireshark/ipv4-udp-fragmented.pcap
new file mode 100644
index 0000000..ad8de2b
--- /dev/null
+++ b/tests/dissect-wireshark/ipv4-udp-fragmented.pcap
Binary files differ
diff --git a/tests/dissect-wireshark/ipv4-websocket-segmented.pcap b/tests/dissect-wireshark/ipv4-websocket-segmented.pcap
new file mode 100644
index 0000000..e1ac4d0
--- /dev/null
+++ b/tests/dissect-wireshark/ipv4-websocket-segmented.pcap
Binary files differ
diff --git a/tests/dissect-wireshark/ipv6-tcp-segmented.pcap b/tests/dissect-wireshark/ipv6-tcp-segmented.pcap
new file mode 100644
index 0000000..be21ca2
--- /dev/null
+++ b/tests/dissect-wireshark/ipv6-tcp-segmented.pcap
Binary files differ
diff --git a/tests/dissect-wireshark/ipv6-udp-fragmented.pcap b/tests/dissect-wireshark/ipv6-udp-fragmented.pcap
new file mode 100644
index 0000000..8d2a195
--- /dev/null
+++ b/tests/dissect-wireshark/ipv6-udp-fragmented.pcap
Binary files differ
diff --git a/tests/dissect-wireshark/ipv6-websocket-segmented.pcap b/tests/dissect-wireshark/ipv6-websocket-segmented.pcap
new file mode 100644
index 0000000..8277a38
--- /dev/null
+++ b/tests/dissect-wireshark/ipv6-websocket-segmented.pcap
Binary files differ
diff --git a/tools/dissect-wireshark/README.md b/tools/dissect-wireshark/README.md
index 3d6b07d..6284650 100644
--- a/tools/dissect-wireshark/README.md
+++ b/tools/dissect-wireshark/README.md
@@ -1,18 +1,49 @@
-ndn-dissect-wireshark
-=====================
+NDN Packet Dissector for Wireshark
+==================================
 
-A Wireshark dissector for [Named Data Networking (NDN) packets](http://named-data.net/doc/ndn-tlv/).
+**NDN packet dissector requires at least version 1.12.6 of Wireshark with LUA support enabled**
 
-The dissector is able to process and visualize structure of NDN packets encapsulated in
-IPv4/IPv6 UDP packets with source of destination port 6363, IPv4/IPv6 TCP packets with
-source or destination port 6363, IPv4/IPv6 TCP/HTTP WebSocket packets (any port).
+The dissection of [Named Data Networking (NDN) packets](http://named-data.net/doc/ndn-tlv/) is
+supported in the following cases:
 
-Note that when UDP packet is fragmented, only the first fragment is getting dissected.
-For TCP packets, the dissector assumes that NDN packet starts at the packet boundary,
-therefore some NDN packets will not be properly dissected.  The same limitation applies to
-WebSocket packets.
+- NDN packets are encapsulated in IPv4/IPv6 UDP packets with source or destination port
+  6363 or 56363.
 
-Currently, the dissector does not support NDNLPv2 packets, Link, SelectedDelegation fields.
+- NDN packets are encapsulated in IPv4/IPv6 TCP segments with source or destination
+  port 6363.
+
+- NDN packets are encapsulated in IPv4/IPv6 TCP/HTTP WebSocket packets with source or
+  destination port 9696.
+
+## Available dissection features
+
+- When UDP packet is fragmented, the dissection is performed after the full IP reassembly.
+  If the full reassembly is not possible (e.g., a wrong checksum or missing segments),
+  dissection is not performed.
+
+- When multiple NDN packets are part of a single UDP datagram, TCP segment, or WebSocket
+  payload, all NDN packets are dissected.
+
+- When a single NDN packet is scattered across multiple TCP segments or WebSocket
+  payloads, it is dissected after the successful reconstruction of the necessary portion
+  of the TCP stream.  If the reconstruction of the necessary portion of the TCP stream is
+  not possible (e.g., missing segments), the dissection is not performed.
+
+- When an NDN packet is not aligned to the segment or payload boundary, the dissector
+  searches for any valid NDN packet within the segment using heuristics defined by the
+  following pseudocode:
+
+        for each offset in range (0, packet length)
+          type <- read TLV VarNumber from (buffer + offset)
+          length <- read TLV VarNumber from (buffer + offset + length of type field)
+
+          if type is either 5 or 6  // Type of NDN Interest of Data packet)
+             and length is less 8800 // Current (soft) limit for NDN packet size
+          then
+             dissect NDN packet from (buffer + offset)
+          end if
+
+Currently, the dissector does not support NDNLPv2 packets.
 
 ## Usage
 
diff --git a/tools/dissect-wireshark/ndn.lua b/tools/dissect-wireshark/ndn.lua
index d0b21a2..643bcb0 100644
--- a/tools/dissect-wireshark/ndn.lua
+++ b/tools/dissect-wireshark/ndn.lua
@@ -17,6 +17,7 @@
 -- @author Qi Zhao       <https://www.linkedin.com/pub/qi-zhao/73/835/9a3>
 -- @author Seunghyun Yoo <http://relue2718.com/>
 -- @author Seungbae Kim  <https://sites.google.com/site/sbkimcv/>
+-- @author Alexander Afanasyev <http://lasr.cs.ucla.edu/afanasyev/index.html>
 
 
 -- inspect.lua (https://github.com/kikito/inspect.lua) can be used for debugging.
@@ -24,594 +25,376 @@
 -- local inspect = require('inspect')
 
 -- NDN protocol
-p_ndnproto = Proto ("ndn", "Named Data Network (NDN)") -- to create a 'Proto' object
+ndn = Proto("ndn", "Named Data Networking (NDN)")
 
--- Type and Length fields
-local f_packet_type = ProtoField.uint16("ndn.type", "Type", base.DEC_HEX)
-local f_packet_size = ProtoField.uint16("ndn.length", "Length", base.DEC_HEX)
+-----------------------------------------------------
+-----------------------------------------------------
+-- Field formatting helpers
 
--- Interest or Data packets
-local f_interest = ProtoField.string("ndn.interest", "Interest", FT_STRING)
-local f_data = ProtoField.string("ndn.data", "Data", FT_STRING)
-
--- Name field
-local f_name = ProtoField.string("ndn.name", "Name", FT_STRING)
-local f_namecomponent = ProtoField.string("ndn.namecomponent", "Name Component", FT_STRING)
-local f_implicitSHA = ProtoField.string("ndn.implicitsha", "Implicit SHA 256 Digest Component", FT_STRING)
-
--- Sub-fields of Interest packet
-local f_interest_selector = ProtoField.string("ndn.selector", "Selector", FT_STRING)
-local f_interest_nonce = ProtoField.uint16("ndn.nonce", "Nonce", base.DEC_HEX)
-local f_interest_scope = ProtoField.string("ndn.scope", "Scope", FT_STRING)
-local f_interest_interestlifetime = ProtoField.uint16("ndn.interestlifetime", "Interest Life Time", base.DEC_HEX)
-
--- Sub-fields of Interest/Selector field
-local f_interest_selector_minsuffix = ProtoField.uint16("ndn.minsuffix", "Min Suffix Components", base.DEC_HEX)
-local f_interest_selector_maxsuffix = ProtoField.uint16("ndn.maxsuffix", "Max Suffix Components", base.DEC_HEX)
-local f_interest_selector_keylocator = ProtoField.string("ndn.keylocator", "Publisher Public Key Locator", FT_STRING)
-local f_interest_selector_exclude = ProtoField.string("ndn.exclude", "Exclude", FT_STRING)
-local f_interest_selector_childselector = ProtoField.uint16("ndn.childselector", "Child Selector", base.DEC_HEX)
-local f_interest_selector_mustbefresh = ProtoField.string("ndn.mustbefresh", "Must Be Fresh", FT_STRING)
-local f_interest_selector_any = ProtoField.string("ndn.any", "Any", FT_STRING)
-
--- Sub-fields of Data packet
-local f_data_metainfo = ProtoField.string("ndn.metainfo", "Meta Info", FT_STRING)
-local f_data_content = ProtoField.string("ndn.content", "Content", FT_STRING)
-local f_data_signatureinfo = ProtoField.string("ndn.signatureinfo", "Signature Info", FT_STRING)
-local f_data_signaturevalue = ProtoField.string("ndn.signaturevalue", "Signature Value", FT_STRING)
-
--- Sub-fields of Data/MetaInfo field
-local f_data_metainfo_contenttype = ProtoField.uint16("ndn.contenttype", "Content Type", base.DEC_HEX)
-local f_data_metainfo_freshnessperiod = ProtoField.uint16("ndn.freshnessperiod", "Freshness Period", base.DEC_HEX)
-local f_data_metainfo_finalblockid = ProtoField.string("ndn.finalblockid", "Final Block ID", FT_STRING)
-
--- Sub-fields of Data/Signature field
-local f_data_signature_signaturetype = ProtoField.uint16("ndn.signaturetype", "Signature Type", base.DEC_HEX)
-local f_data_signature_keylocator = ProtoField.string("ndn.keylocator", "Key Locator", FT_STRING)
-local f_data_signature_keydigest = ProtoField.string("ndn.keydigest", "Key Digest", FT_STRING)
-
--- Add protofields in NDN protocol
-p_ndnproto.fields = {f_packet_type, f_packet_size, f_data, f_interest, f_name, f_namecomponent, f_implicitSHA, f_interest_selector, f_interest_nonce, f_interest_scope, f_interest_interestlifetime, f_interest_selector_mustbefresh, f_interest_selector_minsuffix, f_interest_selector_maxsuffix, f_interest_selector_keylocator, f_interest_selector_exclude, f_interest_selector_childselector, f_interest_selector_any, f_data_metainfo, f_data_content, f_data_signatureinfo, f_data_signaturevalue, f_data_metainfo_contenttype, f_data_metainfo_freshnessperiod, f_data_metainfo_finalblockid, f_data_signature_signaturetype, f_data_signature_keylocator, f_data_signature_keydigest}
-
--- ndntlv_info = { data: { field, type, string }, children: {} }
-
--- To handle the fragmented packets
--- type: map
--- * key: (host ip address, host port number)
--- * value: type: map
---          * key: packet number
---          * value: packet status
-local pending_packets = {}
-local CONST_STR_TRUNCATED = "TRUNCATED"
-local CONST_STR_NDNTLV = "NDNTLV"
-local GLOBAL_PACKET_INDEX = 0
-
-function set_packet_status( packet_key, packet_number, status_key, status_value )
-  if type( pending_packets[ packet_key ] ) ~= "table" then
-    pending_packets[ packet_key ] = {}
-  end
-  if type( pending_packets[ packet_key ][ packet_number ] ) ~= "table" then
-    pending_packets[ packet_key ][ packet_number ] = {}
-  end
-  pending_packets[ packet_key ][ packet_number ][ status_key ] = status_value
+-- Borrowed from http://lua-users.org/wiki/StringRecipes
+function escapeString(str)
+   if (str) then
+      str = string.gsub(str, "\n", "\r\n")
+      str = string.gsub(str, "([^%w %-%_%.%~])",
+                        function (c) return string.format ("%%%02X", string.byte(c)) end)
+      str = string.gsub(str, " ", "+")
+   end
+   return str
 end
 
-function get_packet_status( packet_key, packet_number, status_key )
-  if type( pending_packets[ packet_key ] ) ~= "table" then
-    return nil
-  end
-  if type( pending_packets[ packet_key ][ packet_number ] ) ~= "table" then
-    return nil
-  end
-  return pending_packets[ packet_key ][ packet_number ][ status_key ]
+function getUriFromNameComponent(block)
+   -- @todo Implement proper proper URL escaping
+   return block.tvb(block.offset + block.typeLen + block.lengthLen, block.length):string()
 end
 
-function get_keys_from( table )
-  local keyset = {}
-  local n = 0
-  for k, v in pairs( table ) do
-    n = n + 1
-    keyset[n] = k
-  end
-  return keyset
+function getUriFromName(nameBlock)
+   if (nameBlock.elements == nil) then
+      return ""
+   else
+      components = {}
+      for i, block in pairs(nameBlock.elements) do
+         table.insert(components, getUriFromNameComponent(block))
+      end
+
+      return "/" .. table.concat(components, "/")
+   end
 end
 
-function dump_packet_status()
-  --print(inspect(pending_packets))
+function getNonNegativeInteger(b)
+   if (b.length == 1) then
+      return b.tvb(b.offset + b.typeLen + b.lengthLen, 1):uint()
+   elseif (b.length == 2) then
+      return b.tvb(b.offset + b.typeLen + b.lengthLen, 2):uint()
+   elseif (b.length == 4) then
+      return b.tvb(b.offset + b.typeLen + b.lengthLen, 4):uint()
+   -- Something strange with uint64, not supporting it for now
+   -- elseif (b.length == 8) then
+   --    return b.tvb(b.offset + b.typeLen + b.lengthLen, 8):uint64()
+   else
+      return 0xFFFFFFFF;
+   end
 end
 
-function bytearray_to_int( raw_bytes, offset, length )
-  local ret = 0
-  for i = offset, offset + length - 1 do
-    ret = ret * 256 + raw_bytes:get_index( i )
-  end
-  return ret
+function getUriFromExclude(block)
+   -- @todo
+   return ""
 end
 
-function deepcopy(orig)
-    local orig_type = type(orig)
-    local copy
-    if orig_type == 'table' then
-        copy = {}
-        for orig_key, orig_value in next, orig, nil do
-            copy[deepcopy(orig_key)] = deepcopy(orig_value)
-        end
-        setmetatable(copy, deepcopy(getmetatable(orig)))
-    else -- number, string, boolean, etc
-        copy = orig
-    end
-    return copy
+function getTrue(block)
+   return "Yes"
 end
 
-function parse_ndn_tlv( packet_key, packet_number, is_original, max_size, optional_params, ndntlv_info )
-  local raw_bytes = nil
-  local buf = nil
-  local length = nil
+local AppPrivateBlock1 = 128
+local AppPrivateBlock2 = 32767
 
-  if ( is_original ) then
-    buf = optional_params["buf"]
-    length = buf:len()
-  else
-    raw_bytes = optional_params["raw_bytes"]
-    length = raw_bytes:len()
-  end
+function getGenericBlockInfo(block)
+   local name = ""
 
-  local current_pos = 0
-  local _size_num_including_header = 0
+   if (block.type < AppPrivateBlock1) then
+      name = "RESERVED_1"
+   elseif (AppPrivateBlock1 <= block.type and block.type < 253) then
+      name = "APP_TAG_1"
+   elseif (253 <= block.type and block.type < AppPrivateBlock2) then
+      name = "RESERVED_3"
+   else
+      name = "APP_TAG_3"
+   end
 
-  local ret = true -- a result of a ndn-tlv parser
-  local isFirst = false -- flag that is going to be enabled when the first buffer arrives [BUGGY]
+   return name .. ", Type: " .. block.type .. ", Length: " .. block.length
+end
 
-  while ( current_pos < length ) do
-    isFirst = ( current_pos == 0 )
+-----------------------------------------------------
+-----------------------------------------------------
 
-    -- extract TYPE
-    local _type_uint = nil
-    if ( is_original ) then
-      _type_uint = buf( current_pos, 1 ):uint()
-    else
-      _type_uint = bytearray_to_int( raw_bytes, current_pos, 1 )
-    end
+local NDN_DICT = {
+   -- Interest or Data packets
+   [5]  = {name = "Interest"                     , summary = true},
+   [6]  = {name = "Data"                         , summary = true},
 
-    -- print("type:" .. _type_uint)
+   -- Name field
+   [7]  = {name = "Name"                         , field = ProtoField.string("ndn.name", "Name")                                   , value = getUriFromName},
+   [1]  = {name = "ImplicitSha256DigestComponent", field = ProtoField.string("ndn.implicitsha256", "ImplicitSha256DigestComponent"), value = getUriFromNameComponent},
+   [8]  = {name = "NameComponent"                , field = ProtoField.string("ndn.namecomponent", "NameComponent")                 , value = getUriFromNameComponent},
 
-    if ( isFirst ) then
-      _size_num_including_header = _size_num_including_header + 1
-    end
-    current_pos = current_pos + 1
+   -- Sub-fields of Interest packet
+   [9]  = {name = "Selectors"                    , summary = true},
+   [10] = {name = "Nonce"                        , field = ProtoField.bytes("ndn.nonce", "Nonce")},
+   [12] = {name = "InterestLifetime"             , field = ProtoField.uint32("ndn.interestlifetime", "InterestLifetime", base.DEC) , value = getNonNegativeInteger},
 
-    -- extract SIZE
-    local _size_num = nil
-    if ( is_original ) then
-      _size_num = buf( current_pos, 1 ):uint()
-    else
-      _size_num = bytearray_to_int( raw_bytes, current_pos, 1 )
-    end
+   -- Sub-fields of Interest/Selector field
+   [13] = {name = "MinSuffixComponents"          , field = ProtoField.uint32("ndn.minsuffix", "MinSuffixComponents")               , value = getNonNegativeInteger},
+   [14] = {name = "MaxSuffixComponents"          , field = ProtoField.uint32("ndn.maxsuffix", "MaxSuffixComponents")               , value = getNonNegativeInteger},
+   [15] = {name = "PublisherPublicKeyLocator"    , summary = true},
+   [16] = {name = "Exclude"                      , field = ProtoField.string("ndn.exclude", "Exclude")                             , value = getUriFromExclude},
+   [17] = {name = "ChildSelector"                , field = ProtoField.uint32("ndn.childselector", "ChildSelector", base.DEC)       , value = getNonNegativeInteger},
+   [18] = {name = "MustBeFresh"                  , field = ProtoField.string("ndn.mustbefresh", "MustBeFresh")                     , value = getTrue},
+   [19] = {name = "Any"                          , field = ProtoField.string("ndn.any", "Any")                                     , value = getTrue},
 
-    if ( isFirst ) then
-        _size_num_including_header = _size_num_including_header + 1
-    end
-    current_pos = current_pos + 1
+   -- Sub-fields of Data packet
+   [20] = {name = "MetaInfo"                     , summary = true},
+   [21] = {name = "Content"                      , field = ProtoField.string("ndn.content", "Content")},
+   [22] = {name = "SignatureInfo"                , summary = true},
+   [23] = {name = "SignatureValue"               , field = ProtoField.bytes("ndn.signaturevalue", "SignatureValue")},
 
-    if ( _size_num == 253 ) then
-      if ( is_original ) then
-        _size_num = buf( current_pos, 2 ):uint()
+   -- Sub-fields of Data/MetaInfo field
+   [24] = {name = "ContentType"                  , field = ProtoField.uint32("ndn.contenttype", "Content Type", base.DEC)          , value = getNonNegativeInteger},
+   [25] = {name = "FreshnessPeriod"              , field = ProtoField.uint32("ndn.freshnessperiod", "FreshnessPeriod", base.DEC)   , value = getNonNegativeInteger},
+   [26] = {name = "FinalBlockId"                 , field = ProtoField.string("ndn.finalblockid", "FinalBlockId")                   , value = getUriFromNameComponent},
+
+   -- Sub-fields of Data/Signature field
+   [27] = {name = "SignatureType"                , field = ProtoField.uint32("ndn.signaturetype", "SignatureType", base.DEC)       , value = getNonNegativeInteger},
+   [28] = {name = "KeyLocator"                   , summary = true},
+   [29] = {name = "KeyDigest"                    , field = ProtoField.bytes("ndn.keydigest", "KeyDigest")},
+
+   -- Other fields
+   [30] = {name = "LinkPreference"               , field = ProtoField.uint32("ndn.link_preference", "LinkPreference", base.DEC)    , value = getNonNegativeInteger},
+   [31] = {name = "LinkDelegation"               , summary = true},
+   [32] = {name = "SelectedDelegation"           , field = ProtoField.uint32("ndn.selected_delegation", "SelectedDelegation", base.DEC), value = getNonNegativeInteger},
+}
+
+
+-- -- Add protofields in NDN protocol
+ndn.fields = {
+}
+for key, value in pairs(NDN_DICT) do
+   table.insert(ndn.fields, value.field)
+end
+
+
+-----------------------------------------------------
+-----------------------------------------------------
+
+-- block
+-- .tvb
+-- .offset
+-- .type
+-- .typeLen
+-- .length
+-- .lengthLen
+-- .size = .typeLen + .lengthLen + .length
+
+function addInfo(block, root) -- may be add additional context later
+   local info = NDN_DICT[block.type]
+
+   if (info == nil) then
+      info = {}
+      info.value = getGenericBlockInfo
+   end
+
+   local treeInfo
+   if (info.value == nil) then
+
+      if (info.field ~= nil) then
+         treeInfo = root:add(info.field, block.tvb(block.offset, block.size))
       else
-        _size_num = bytearray_to_int( raw_bytes, current_pos, 2 )
+         treeInfo = root:add(block.tvb(block.offset, block.size), info.name)
       end
-      if ( isFirst ) then
-        _size_num_including_header = _size_num_including_header + _size_num + 2
-      end
-      current_pos = current_pos + 2
-    elseif ( _size_num == 254 ) then
-      if ( is_original ) then
-        _size_num = buf( current_pos, 4 ):uint()
+
+      treeInfo:append_text(", Type: " .. block.type .. ", Length: " .. block.length)
+   else
+      block.value = info.value(block)
+
+      if (info.field ~= nil) then
+         treeInfo = root:add(info.field, block.tvb(block.offset, block.size), block.value)
       else
-        _size_num = bytearray_to_int( raw_bytes, current_pos, 4 )
+         treeInfo = root:add(block.tvb(block.offset, block.size), info.name)
       end
-      if ( isFirst ) then
-        _size_num_including_header = _size_num_including_header + _size_num + 4
-      end
-      current_pos = current_pos + 4
-    elseif ( _size_num == 255 ) then
-      print("## error ## lua doesn't support 8 bytes of number variables.")
-      if ( is_original ) then
-        _size_num = buf( current_pos, 8 ):uint64() -- can lua number be larger than 32 bits? -- the type 'userdata'
-      else
-        _size_num = bytearray_to_int( raw_bytes, current_pos, 8 )
-      end
-      if ( isFirst ) then
-        _size_num_including_header = _size_num_including_header + _size_num + 8
-      end
-      current_pos = current_pos + 8
-    else
-      if ( isFirst ) then
-        _size_num_including_header = _size_num_including_header + _size_num
-      end
-    end
-
-    -- subtree:add( f_packet_size, _size )
-    local type_size_info = " (Type: " .. _type_uint .. ", Size: " .. _size_num .. ")"
-
-    -- need to check which one should be used: either _size_num or _size_num_including_header
-    if ( max_size ~= -1 and max_size < _size_num ) then
-      if ( is_original ) then
-        set_packet_status( packet_key, packet_number, "error", "The size of sub ndn-tlv packet can't exceed the parent's one." )
-      end
-      ret = false
-      break
-    end
-
-    if ( isFirst ) then
-      if ( is_original ) then
-        set_packet_status( packet_key, packet_number, "expected_size", _size_num_including_header )
-      end
-    end
-
-    if ( _type_uint == 18 ) then
-      if ( is_original ) then
-        set_packet_status( packet_key, packet_number, "error", "the type of field is 18 (but why is this an error?).")
-      end
-      return ret
-    end
-
-    if ( current_pos + _size_num > length ) then
-      if ( is_original ) then
-        set_packet_status( packet_key, packet_number, "status", CONST_STR_TRUNCATED)
-      end
-      ret = false
-      break
-    end
-
-    local _payload = nil
-    local new_optional_params = {}
-    if ( is_original ) then
-      _payload = buf( current_pos, _size_num )
-      new_optional_params["buf"] = _payload
-    else
-      new_optional_params["raw_bytes"] = raw_bytes:subset( current_pos, _size_num )
-    end
-    current_pos = current_pos + _size_num
-
-    local child_tree = nil
-
-    if ( _type_uint == 5 ) then -- interest packet can contain sub NDN-TLV packets
-      -- Interest packet
-      if ( is_original ) then
-        child_tree = add_subtree( ndntlv_info, { f_interest, _payload, type_size_info } )
-      end
-      ret = ret and parse_ndn_tlv( packet_key, packet_number, is_original, _size_num, new_optional_params, child_tree )
-    elseif ( _type_uint == 6 ) then
-      -- Data packet
-      if ( is_original ) then
-        child_tree = add_subtree( ndntlv_info, { f_data, _payload, type_size_info } )
-      end
-      ret = ret and parse_ndn_tlv( packet_key, packet_number, is_original, _size_num, new_optional_params, child_tree )
-    elseif ( _type_uint == 7 ) then
-      -- Name
-      if ( is_original ) then
-        child_tree = add_subtree( ndntlv_info, { f_name, _payload, type_size_info } )
-      end
-      ret = ret and parse_ndn_tlv( packet_key, packet_number, is_original, _size_num, new_optional_params, child_tree )
-    elseif ( _type_uint == 8 ) then
-      -- Name Component
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_namecomponent, _payload, _payload:string(ENC_UTF_8) .. type_size_info } )
-      end
-    elseif ( _type_uint == 1 ) then
-      -- Implicit SHA 256 Digest Component
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_implicitSHA, _payload, _payload:string() .. type_size_info } )
-      end
-    elseif ( _type_uint == 9 ) then
-      -- Selectors
-      if ( is_original ) then
-        child_tree = add_subtree( ndntlv_info, { f_interest_selector, _payload, type_size_info } )
-      end
-      ret = ret and parse_ndn_tlv( packet_key, packet_number, is_original, _size_num, new_optional_params, child_tree )
-    elseif ( _type_uint == 10 ) then
-      -- Nonce
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_interest_nonce, _payload, _payload:uint(), nil, type_size_info } )
-      end
-    elseif ( _type_uint == 11 ) then
-      -- Scope
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_interest_scope, _payload, _payload:string() .. type_size_info } )
-      end
-    elseif ( _type_uint == 12 ) then
-      -- Interest Lifetime
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_interest_interestlifetime, _payload, _payload:uint(), nil, type_size_info } )
-      end
-    elseif ( _type_uint == 13 ) then
-      -- Selectors / Min Suffix Components
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_interest_selector_minsuffix, _payload, _payload:uint(), nil, type_size_info } )
-      end
-    elseif ( _type_uint == 14 ) then
-      -- Selectors / Max Suffix Components
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_interest_selector_maxsuffix, _payload, _payload:uint(), nil, type_size_info } )
-      end
-    elseif ( _type_uint == 15 ) then
-      -- Selectors / Publish Key Locator
-      if ( is_original ) then
-        child_tree = add_subtree( ndntlv_info, { f_interest_selector_keylocator, _payload, type_size_info } )
-      end
-      ret = ret and parse_ndn_tlv( packet_key, packet_number, is_original, _size_num, new_optional_params, child_tree )
-    elseif ( _type_uint == 16 ) then
-      -- Selectors / Exclude
-      if ( is_original ) then
-        child_tree = add_subtree( ndntlv_info, { f_interest_selector_exclude, _payload, type_size_info } )
-      end
-      ret = ret and parse_ndn_tlv( packet_key, packet_number, is_original, _size_num, new_optional_params, child_tree )
-    elseif ( _type_uint == 17 ) then
-      -- Selectors / Child Selector
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_interest_selector_childselector, _payload, _payload:uint(), nil, type_size_info } )
-      end
-    elseif ( _type_uint == 18 ) then
-      -- Selectors / Must be Fresh
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_interest_selector_mustbefresh, _payload, _payload:string() .. type_size_info } )
-      end
-    elseif ( _type_uint == 19 ) then
-      -- Selectors / Any
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_interest_selector_any, _payload, _payload:string() .. type_size_info } )
-      end
-    elseif ( _type_uint == 20 ) then
-      -- MetaInfo
-      if ( is_original ) then
-        child_tree = add_subtree( ndntlv_info, { f_data_metainfo, _payload, type_size_info } )
-      end
-      ret = ret and parse_ndn_tlv( packet_key, packet_number, is_original, _size_num, new_optional_params, child_tree )
-    elseif ( _type_uint == 21 ) then
-      -- Content
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_data_content, _payload, _payload:string() .. type_size_info } )
-      end
-    elseif ( _type_uint == 22 ) then
-      -- SignatureInfo
-      if ( is_original ) then
-        child_tree = add_subtree( ndntlv_info, { f_data_signatureinfo, _payload, type_size_info } )
-      end
-      ret = ret and parse_ndn_tlv( packet_key, packet_number, is_original, _size_num, new_optional_params, child_tree )
-    elseif ( _type_uint == 23 ) then
-      -- SignatureValue
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_data_signaturevalue, _payload, _payload:string() .. type_size_info } )
-      end
-    elseif ( _type_uint == 24 ) then
-      -- MetaInfo / ContentType
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_data_metainfo_contenttype, _payload, _payload:uint(), nil, type_size_info } )
-      end
-    elseif ( _type_uint == 25 ) then
-      -- MetaInfo / FreshnessPeriod
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_data_metainfo_freshnessperiod, _payload, _payload:uint(), nil, type_size_info } )
-      end
-    elseif ( _type_uint == 26 ) then
-      -- MetaInfo / FinalBlockId
-      if ( is_original ) then
-        child_tree = add_subtree( ndntlv_info, { f_data_metainfo_finalblockid, _payload, type_size_info } )
-      end
-      ret = ret and parse_ndn_tlv( packet_key, packet_number, is_original, _size_num, new_optional_params, child_tree )
-    elseif ( _type_uint == 27 ) then
-      -- Signature / SignatureType
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_data_signature_signaturetype, _payload, _payload:uint(), nil, type_size_info } )
-      end
-    elseif ( _type_uint == 28 ) then
-      -- Signature / KeyLocator
-      if ( is_original ) then
-        child_tree = add_subtree( ndntlv_info, { f_data_signature_keylocator, _payload, type_size_info } )
-      end
-      ret = ret and parse_ndn_tlv( packet_key, packet_number, is_original, _size_num, new_optional_params, child_tree )
-    elseif ( _type_uint == 29 ) then
-      -- Signature / KeyDigest
-      if ( is_original ) then
-        add_subtree( ndntlv_info, { f_data_signature_keydigest, _payload, _payload:string() .. type_size_info } );
-      end
-    else
-      --print("## warning ## unhandled type_uint: ", _type_uint)
-      ret = false
-      -- if the packet seems to be a NDN packet, it would be better idea to add some warning messages in the subtress instead of returning false.
-    end
-  end
-  return ret
+   end
+   block.root = treeInfo
+   return block.root
 end
 
-function create_subtree_from( info, subtree )
-  for k, v in pairs( info["children"] ) do
-    local data = v["data"]
-    if type(data) == "table" then
-      local child_tree = subtree:add( unpack( data ) )
-      create_subtree_from( v, child_tree )
-    end
-  end
-end
+function addSummary(block)
+   if (block.elements == nil) then
+      return
+   end
 
-function add_subtree( info, data )
-  local child_tree = { ["data"] = data, ["children"] = {} }
-  table.insert( info["children"], child_tree )
-  return child_tree
-end
+   local info = NDN_DICT[block.type]
+   if (info == nil or info.summary == nil) then
+      return
+   end
 
-function create_empty_ndntlv_info()
-  return { ["data"] = nil, ["children"] = {} }
-end
+   local summary = {}
 
-function parse_buffer_and_update( packet_key, packet_number, is_original, pkt, root, optional_params )
-  -- TODO: need to set the maximum length
-  local ndntlv_info = create_empty_ndntlv_info()
-  local was_ndntlv_packet = parse_ndn_tlv( packet_key, packet_number, is_original, -1, optional_params, ndntlv_info )
-
-  if was_ndntlv_packet then
-    local buf = nil
-    if ( is_original ) then
-      buf = optional_params["buf"]
-      set_packet_status( packet_key, packet_number, "ndntlv_info", ndntlv_info )
-      set_packet_status( packet_key, packet_number, "status", CONST_STR_NDNTLV )
-    else
-      buf = ByteArray.tvb( optional_params["raw_bytes"], optional_params["tvb_name"] )
-      ndntlv_info = create_empty_ndntlv_info()
-      parse_ndn_tlv( packet_key, packet_number, true, -1, { ["buf"] = buf }, ndntlv_info )
-
-      local used_packet_numbers = optional_params["used_packet_numbers"]
-
-      for k,v in pairs(used_packet_numbers) do
-        set_packet_status( packet_key, v, "ndntlv_info", ndntlv_info )
-        set_packet_status( packet_key, v, "status", CONST_STR_NDNTLV )
+   for k, subblock in pairs(block.elements) do
+      if (subblock.value ~= nil) then
+         local info = NDN_DICT[subblock.type]
+         if (info ~= nil) then
+            table.insert(summary, info.name .. ": " .. subblock.value)
+         end
       end
-    end
-  end
+   end
 
-  -- print( packet_key .. "--" .. packet_number .. ".." .. tostring(was_ndntlv_packet) )
-
-  -- It needs to check whether the packet type is NDN-TLV.
-  local saved_ndntlv_info = get_packet_status( packet_key, packet_number, "ndntlv_info" )
-  local parsed = get_packet_status( packet_key, packet_number, "parsed" )
-  if saved_ndntlv_info ~= nil then
-    pkt.cols.protocol = p_ndnproto.name -- set the protocol name to NDN
-    if ( parsed ~= true ) then
-      set_packet_status( packet_key, packet_number, "parsed", true )
-      local subtree = root:add( p_ndnproto, buf ) -- create subtree for ndnproto
-      create_subtree_from( saved_ndntlv_info, subtree )
-    end
-  end
+   if (#summary > 0) then
+      block.summary = table.concat(summary, ", ")
+      if (block.value == nil) then
+         block.value = block.summary
+      end
+      block.root:append_text(", " .. block.summary)
+   end
 end
 
--- # not efficient
--- # lua -- doesn't support the random access...?
-function get_next_element( tbl, current_value )
-  for k, v in pairs( tbl ) do
-    if ( v > current_value ) then
-      return v
-    end
-  end
-  return current_value
+-----------------------------------------------------
+-----------------------------------------------------
+
+function readVarNumber(tvb, offset)
+   local firstOctet = tvb(offset, 1):uint()
+   if (firstOctet < 253) then
+      return firstOctet, 1
+   elseif (firstOctet == 253) then
+      return tvb(offset + 1, 2):uint(), 3
+   elseif (firstOctet == 254) then
+      return tvb(offset + 1, 4):uint(), 5
+   elseif (firstOctet == 255) then
+      return tvb(offset + 1, 8):uint64(), 6
+   end
 end
 
--- # not efficient
-function get_previous_element( tbl, current_value )
-  local prev = current_value
-  for k, v in pairs( tbl ) do
-    if ( v < current_value ) then
-      prev = v
-    else
-      break
-    end
-  end
-  return prev
+function getBlock(tvb, offset)
+   local block = {}
+   block.tvb = tvb
+   block.offset = offset
+
+   block.type, block.typeLen = readVarNumber(block.tvb, block.offset)
+   block.length, block.lengthLen = readVarNumber(block.tvb, block.offset + block.typeLen)
+
+   block.size = block.typeLen + block.lengthLen + block.length
+
+   return block
 end
 
--- ndnproto dissector function
-function p_ndnproto.dissector( buf, pkt, root )
-  -- validate packet length is adequate, otherwise quit
-  local length = buf:len()
-  local packet_number = pkt.number -- an unique serial for each packet
-  local packet_key = tostring(pkt.src) .. ":" .. tostring(pkt.src_port) .. ":" .. tostring(pkt.dst) .. ":" .. tostring(pkt.dst_port)
-  print("## info ## packet[" .. packet_number .. "], length = " .. length )
-  set_packet_status( packet_key, packet_number, "parsed", false )
+function findNdnPacket(tvb)
+   offset = 0
 
-  if length == 0 then
-  else
-    local raw_bytes = buf:range():bytes()
-    parse_buffer_and_update( packet_key, packet_number, true, pkt, root, { ["buf"] = buf } )
-    set_packet_status( packet_key, packet_number, "buffer", raw_bytes )
+   while offset + 2 < tvb:len() do
+      local block = getBlock(tvb, offset)
 
-    local pending_packet_numbers = get_keys_from( pending_packets[ packet_key ] )
-    for k, v in pairs( pending_packet_numbers ) do
-      local pending_packet_number = v
+      if ((block.type == 5 or block.type == 6) and block.length <= 8800) then
+         return block
+      end
 
-      if ( pending_packet_number <= packet_number ) then
+      offset = offset + 1
+   end
 
-        local status = get_packet_status( packet_key, pending_packet_number, "status" )
-        local expected_size = get_packet_status( packet_key, pending_packet_number, "expected_size" )
-        local used_packet_numbers = {}
+   return nil
+end
 
-        if ( status == CONST_STR_TRUNCATED ) then
-          local merged_temp_buf = ByteArray.new()
-          local temp_packet_number = pending_packet_number
-          local pending_packet_number_end = 0
-          while (merged_temp_buf:len() < expected_size) do
-            local temp_buf = get_packet_status( packet_key, temp_packet_number, "buffer" )
-            if ( temp_buf == nil ) then
-              break
-            else
-              merged_temp_buf:append( temp_buf )
-              pending_packet_number_end = temp_packet_number
+function getSubBlocks(block)
+   local valueLeft = block.length
+   local subBlocks = {}
 
-              table.insert( used_packet_numbers, temp_packet_number )
-              temp_packet_number = get_next_element( pending_packet_numbers, temp_packet_number )
+   while valueLeft > 0 do
+      local child = getBlock(block.tvb,
+                             block.offset + block.typeLen + block.lengthLen + (block.length - valueLeft))
+
+      valueLeft = valueLeft - child.size
+      table.insert(subBlocks, child)
+   end
+
+   if (valueLeft == 0) then
+      return subBlocks
+   else
+      return nil
+   end
+end
+
+-----------------------------------------------------
+-----------------------------------------------------
+
+-- NDN protocol dissector function
+function ndn.dissector(tvb, pInfo, root) -- Tvb, Pinfo, TreeItem
+
+   if (tvb:len() ~= tvb:reported_len()) then
+      return 0 -- ignore partially captured packets
+      -- this can/may be re-enabled only for unfragmented UDP packets
+   end
+
+   local ok, block = pcall(findNdnPacket, tvb)
+   if (not ok) then
+      return 0
+   end
+
+   if (block == nil or block.offset == nil) then
+      -- no valid NDN packets found
+      return 0
+   end
+
+   local nBytesLeft = tvb:len() - block.offset
+   -- print (pInfo.number .. ":: Found block: " .. block.type .. " of length " .. block.size .. " bytesLeft: " .. nBytesLeft)
+
+   while (block.size <= nBytesLeft) do
+      -- Create TreeItems
+      block.tree = root:add(ndn, tvb(block.offset, block.size))
+
+      local queue = {block}
+      while (#queue > 0) do
+         local block = queue[1]
+         table.remove(queue, 1)
+
+         block.elements = getSubBlocks(block)
+         local subtree = addInfo(block, block.tree)
+
+         if (block.elements ~= nil) then
+            for i, subBlock in pairs(block.elements) do
+               subBlock.tree = subtree
+               table.insert(queue, subBlock)
             end
-          end
-          if ( merged_temp_buf:len() >= expected_size ) then
-            local merged_tvb_name = "Reassembled (" .. pending_packet_number .. "-" .. pending_packet_number_end .. ")"
-            local merged_parser_option = {
-              ["raw_bytes"] = merged_temp_buf,
-              ["tvb_name"] = merged_tvb_name,
-              ["pending_packet_number"] = pending_packet_number,
-              ["pending_packet_number_end"] = pending_packet_number_end,
-              ["used_packet_numbers"] = used_packet_numbers,
-            }
-            print(pending_packet_number .. ".." .. pending_packet_number_end)
-            parse_buffer_and_update( packet_key, packet_number, false, pkt, root, merged_parser_option )
-          end
-        end
+         end
       end
-    end
-    --dump_packet_status()
-  end
-end
 
--- Initialization routine
-function p_ndnproto.init()
-end
+      -- Make summaries
+      local queue = {block}
+      while (#queue > 0) do
+         local block = queue[1]
+         if (block.visited ~= nil or block.elements == nil) then
+            -- try to make summary
+            table.remove(queue, 1)
 
-local websocket_dissector_table = DissectorTable.get("ws.port")
-websocket_dissector_table:add("1-65535", p_ndnproto)
-
-local tcp_dissector_table = DissectorTable.get("tcp.port")
-tcp_dissector_table:add("6363", p_ndnproto)
-
-local udp_dissector_table = DissectorTable.get("udp.port")
-udp_dissector_table:add("6363", p_ndnproto)
-
-print("ndntlv.lua is successfully loaded.")
-
-----------------------------------------------------------------------
--- helper functions
-----------------------------------------------------------------------
-function dump_buf(buf)
-  print("buffer.length = "..buf:len())
-  local tmp = ""
-  for i=0, buf:len()-1 do
-      if i % 16 == 0 then
-          tmp = tmp .. string.format("%04d",i) .. " : "
+            addSummary(block)
+         else
+            for i, subBlock in pairs(block.elements) do
+               table.insert(queue, 1, subBlock)
+            end
+            block.visited = true
+         end
       end
-      tmp = tmp .. (buf:range(i,1).." ")
-      if (i+1) % 16 == 0 then
-        tmp = tmp .. ("\n")
+
+      local info = NDN_DICT[block.type]
+      if (info ~= nil) then
+         block.tree:append_text(", " .. NDN_DICT[block.type].name .. ", " .. block.summary)
       end
-  end
-  print(tmp)
+
+      nBytesLeft = nBytesLeft - block.size
+
+      if (nBytesLeft > 0) then
+         ok, block = pcall(getBlock, tvb, tvb:len() - nBytesLeft)
+         if (not ok) then
+            break
+         end
+      end
+   end
+
+   pInfo.cols.protocol = tostring(pInfo.cols.protocol) .. " (" .. ndn.name .. ")"
+
+   if (nBytesLeft > 0 and block.size > nBytesLeft) then
+      pInfo.desegment_offset = tvb:len() - nBytesLeft
+
+      -- Originally, I set desegment_len to the exact lenght, but it mysteriously didn't work for TCP
+      -- pInfo.desegment_len = block.size -- this will not work to desegment TCP streams
+      pInfo.desegment_len = DESEGMENT_ONE_MORE_SEGMENT
+   end
 end
 
-function print_table(tbl, indent)
-  if not indent then indent = 0 end
-  for k, v in pairs(tbl) do
-    formatting = string.rep("  ", indent) .. k .. ": "
-    if type(v) == "table" then
-      print(formatting)
-      print_table(v, indent+1)
-    elseif type(v) == 'boolean' then
-      print(formatting , tostring(v))
-    else
-      print(formatting , v)
-    end
-  end
-end
+local udpDissectorTable = DissectorTable.get("udp.port")
+udpDissectorTable:add("6363", ndn)
+udpDissectorTable:add("56363", ndn)
+
+local tcpDissectorTable = DissectorTable.get("tcp.port")
+tcpDissectorTable:add("6363", ndn)
+
+local websocketDissectorTable = DissectorTable.get("ws.port")
+-- websocketDissectorTable:add("9696", ndn)
+websocketDissectorTable:add("1-65535", ndn)
+
+-- print("ndn.lua is successfully loaded")
+io.stderr:write("ndn.lua is successfully loaded\n")