do
    -- Protocol
    local p_foo = Proto("foo", "FOO Protocol")

    local TYPE_PING = 1
    local TYPE_PONG = 2

    -- Value Strings
    local foo_type_vals = {
        [TYPE_PING] = "Ping",
        [TYPE_PONG] = "Pong"
    }

    -- Fields
    local pf = {
        hdr = ProtoField.bytes("foo.hdr", "Foo Header", base.NONE),
        hdr_magic = ProtoField.uint32("foo.hdr.magic", "Magic", base.HEX),
        hdr_type = ProtoField.uint16("foo.hdr.type", "Type", base.DEC, foo_type_vals),
        hdr_len = ProtoField.uint16("foo.hdr.len", "Length", base.DEC),
        msg = ProtoField.string("foo.msg", "Message", base.ASCII),
        data = ProtoField.bytes("foo.data", "Data", base.NONE)
    }

    p_foo.fields = pf

    -- Dissection
    function p_foo.dissector(tvbuf, pinfo, tree)
        local foo_tree = tree:add(p_foo, tvbuf(0, -1))
        local hdr_tree
        local msg_type
        local length

        pinfo.cols.protocol:set("FOO")

        hdr_tree = foo_tree:add(pf.hdr, tvbuf(0, 8))
        hdr_tree:add(pf.hdr_magic, tvbuf(0, 4))
        hdr_tree:add(pf.hdr_type, tvbuf(4, 2))
        hdr_tree:add(pf.hdr_len, tvbuf(6, 2))

        msg_type = tvbuf(4, 2):uint()
        length = tvbuf(6, 2):uint()
        pinfo.cols.info:set(foo_type_vals[msg_type] or "Unknown")
        if msg_type == TYPE_PING or msg_type == TYPE_PONG then
            foo_tree:add(pf.msg, tvbuf(8, length))
            pinfo.cols.info:append(": " .. tvbuf(8, length):string())
        else
            foo_tree:add(pf.data, tvbuf(8, length))
        end
    end

    -- Registration
    local udp_table = DissectorTable.get("udp.port")
    udp_table:add(33333, p_foo)
end
