blob: d0b21a291d5b55202ef7fc80f53b91b69b60404b [file] [log] [blame]
-- Copyright (c) 2015, Regents of the University of California.
--
-- This file is part of ndn-tools (Named Data Networking Essential Tools).
-- See AUTHORS.md for complete list of ndn-tools authors and contributors.
--
-- ndn-tools is free software: you can redistribute it and/or modify it under the terms
-- of the GNU General Public License as published by the Free Software Foundation,
-- either version 3 of the License, or (at your option) any later version.
--
-- ndn-tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-- without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
-- PURPOSE. See the GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License along with
-- ndn-tools, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
--
-- @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/>
-- inspect.lua (https://github.com/kikito/inspect.lua) can be used for debugging.
-- See more at http://stackoverflow.com/q/15175859/2150331
-- local inspect = require('inspect')
-- NDN protocol
p_ndnproto = Proto ("ndn", "Named Data Network (NDN)") -- to create a 'Proto' object
-- 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)
-- 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
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 ]
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
end
function dump_packet_status()
--print(inspect(pending_packets))
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
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
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
if ( is_original ) then
buf = optional_params["buf"]
length = buf:len()
else
raw_bytes = optional_params["raw_bytes"]
length = raw_bytes:len()
end
local current_pos = 0
local _size_num_including_header = 0
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]
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
-- print("type:" .. _type_uint)
if ( isFirst ) then
_size_num_including_header = _size_num_including_header + 1
end
current_pos = current_pos + 1
-- 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
if ( isFirst ) then
_size_num_including_header = _size_num_including_header + 1
end
current_pos = current_pos + 1
if ( _size_num == 253 ) then
if ( is_original ) then
_size_num = buf( current_pos, 2 ):uint()
else
_size_num = bytearray_to_int( raw_bytes, current_pos, 2 )
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()
else
_size_num = bytearray_to_int( raw_bytes, current_pos, 4 )
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
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 add_subtree( info, data )
local child_tree = { ["data"] = data, ["children"] = {} }
table.insert( info["children"], child_tree )
return child_tree
end
function create_empty_ndntlv_info()
return { ["data"] = nil, ["children"] = {} }
end
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 )
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
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
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
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 )
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 )
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 ( pending_packet_number <= packet_number ) then
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 = {}
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
table.insert( used_packet_numbers, temp_packet_number )
temp_packet_number = get_next_element( pending_packet_numbers, temp_packet_number )
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
--dump_packet_status()
end
end
-- Initialization routine
function p_ndnproto.init()
end
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) .. " : "
end
tmp = tmp .. (buf:range(i,1).." ")
if (i+1) % 16 == 0 then
tmp = tmp .. ("\n")
end
end
print(tmp)
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