Huge thanks to our Platinum Members Endace and LiveAction,
and our Silver Member Veeam, for supporting the Wireshark Foundation and project.

Wireshark-dev: [Wireshark-dev] reassembly.c: fragment_set_partial_reassembly() for fragment_add

From: Richard van der Hoff <richardv@xxxxxxxxxxxxx>
Date: Thu, 15 Feb 2007 21:36:11 +0000
Hi guys,

I've been working on support for reassembly of protocols over RTP; for this I needed to be able to do fragment_set_partial_reassembly() on fragments added with fragment_add_seq().

Here are a bunch of patches... I'd be much obliged if they could be reviewed and hopefully committed; there will be a bunch of other patches to follow soon!

Cheers,

Richard


01_reassemble_test.patch
------------------------
I didn't want to do anything without some unit tests, so here they are. This allows a standalone binary, epan/reassemble_test, to be built; this can be run from the commandline and should end up printing out "success" if all goes well.

Incidentally: is it possible to get the buildbot to run things like this, exntest and tvbtest?

02_reassemble_refactor.patch
----------------------------
fragment_add_seq, fragment_add_dcerpc_dg and fragment_add_seq_check_work were all pretty much carbon-copies of each other. This patch factors out the common parts of the routines into a new routine,
fragment_add_seq_key().

03_reassemble_partial_reassembly.patch
---------------------------------------
This makes fragment_set_partial_reassembly() work for datagrams assembled with fragment_add_seq(). The patch itself is actually quite small, but it adds another unit test which is reasonably lengthy.
Index: epan/reassemble_test.c
===================================================================
--- epan/reassemble_test.c	(revision 0)
+++ epan/reassemble_test.c	(revision 0)
@@ -0,0 +1,791 @@
+/* Standalone program to test functionality of reassemble.h API
+ *
+ * These aren't particularly complete - they just test a few corners of
+ * functionality which I was interested in. In particular, they only test the
+ * fragment_add_seq_* (ie, FD_BLOCKSEQUENCE) family of routines. However,
+ * hopefully they will inspire people to write additional tests, and provide a
+ * useful basis on which to do so.
+ *
+ * $Id$
+ *
+ * Copyright (c) 2007 MX Telecom Ltd. <richardv@xxxxxxxxxxxxx>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@xxxxxxxxxxxxx>
+ * Copyright 1998
+ *
+ * This program 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 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <glib.h>
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <epan/emem.h>
+#include <epan/packet.h>
+#include <epan/packet_info.h>
+#include <epan/proto.h>
+#include <epan/tvbuff.h>
+#include <epan/reassemble.h>
+
+#include <epan/dissectors/packet-dcerpc.h>
+
+#define ASSERT(b) do_test((b),"Assertion failed at line %i: %s\n", __LINE__, #b)
+#define ASSERT_EQ(exp,act) do_test((exp)==(act),"Assertion failed at line %i: %s==%s (%i==%i)\n", __LINE__, #exp, #act, exp, act)
+#define ASSERT_NE(exp,act) do_test((exp)!=(act),"Assertion failed at line %i: %s!=%s (%i!=%i)\n", __LINE__, #exp, #act, exp, act)
+
+int failure = 0;
+
+void do_test(int condition, char *format, ...)
+{
+    va_list ap;
+    
+    if(condition)
+        return;
+    va_start(ap, format);
+    vfprintf(stderr, format, ap);
+    va_end(ap);
+    failure = 1;
+}
+
+#define DATA_LEN 256
+
+char *data;
+tvbuff_t *tvb;
+packet_info pinfo;
+
+/* fragment_table maps from datagram ids to head of fragment_data list
+   reassembled_table maps from <packet number,datagram id> to head of
+   fragment_data list */
+GHashTable *fragment_table = NULL, *reassembled_table = NULL;
+
+/**********************************************************************************
+ *
+ * fragment_add_seq
+ *
+ *********************************************************************************/
+
+/* Simple test case for fragment_add_seq.
+ * Adds three fragments (out of order, with one for a different datagram in between),
+ * and checks that they are reassembled correctly.
+ */
+static void test_simple_fragment_add_seq(void)
+{
+    fragment_data *fd_head, *fdh0;
+
+    printf("Starting test test_simple_fragment_add_seq\n");
+
+    pinfo.fd->num = 1;
+    fd_head=fragment_add_seq(tvb, 10, &pinfo, 12, fragment_table,
+                             0, 50, TRUE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* adding the same fragment again should do nothing, even with different
+     * offset etc */
+    pinfo.fd->flags.visited = 1;
+    fd_head=fragment_add_seq(tvb, 5, &pinfo, 12, fragment_table,
+                             0, 60, TRUE);
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* start another pdu (just to confuse things) */
+    pinfo.fd->flags.visited = 0;
+    pinfo.fd->num = 2;
+    fd_head=fragment_add_seq(tvb, 15, &pinfo, 13, fragment_table,
+                             0, 60, TRUE);
+    ASSERT_EQ(2,g_hash_table_size(fragment_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* now we add the terminal fragment of the first datagram */
+    pinfo.fd->num = 3;
+    fd_head=fragment_add_seq(tvb, 5, &pinfo, 12, fragment_table,
+                             2, 60, FALSE);
+
+    /* we haven't got all the fragments yet ... */
+    ASSERT_EQ(2,g_hash_table_size(fragment_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* finally, add the missing fragment */
+    pinfo.fd->num = 4;
+    fd_head=fragment_add_seq(tvb, 15, &pinfo, 12, fragment_table,
+                             1, 60, TRUE);
+
+    ASSERT_EQ(2,g_hash_table_size(fragment_table));
+    ASSERT_NE(NULL,fd_head);
+
+    /* check the contents of the structure */
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(170,fd_head->len); /* the length of data we have */
+    ASSERT_EQ(2,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(4,fd_head->reassembled_in);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_NE(NULL,fd_head->data);
+    ASSERT_NE(NULL,fd_head->next);
+
+    ASSERT_EQ(1,fd_head->next->frame);
+    ASSERT_EQ(0,fd_head->next->offset);  /* seqno */
+    ASSERT_EQ(50,fd_head->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->data);
+    ASSERT_NE(NULL,fd_head->next->next);
+
+    ASSERT_EQ(4,fd_head->next->next->frame);
+    ASSERT_EQ(1,fd_head->next->next->offset);  /* seqno */
+    ASSERT_EQ(60,fd_head->next->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->next->data);
+    ASSERT_NE(NULL,fd_head->next->next->next);
+
+    ASSERT_EQ(3,fd_head->next->next->next->frame);
+    ASSERT_EQ(2,fd_head->next->next->next->offset);  /* seqno */
+    ASSERT_EQ(60,fd_head->next->next->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->next->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->next->next->data);
+    ASSERT_EQ(NULL,fd_head->next->next->next->next);
+
+    /* test the actual reassembly */
+    ASSERT(!memcmp(fd_head->data,data+10,50));
+    ASSERT(!memcmp(fd_head->data+50,data+15,60));
+    ASSERT(!memcmp(fd_head->data+110,data+5,60));
+
+    /* what happens if we revisit the packets now? */
+    fdh0 = fd_head;
+    pinfo.fd->flags.visited = 1;
+    pinfo.fd->num = 1;
+    fd_head=fragment_add_seq(tvb, 10, &pinfo, 12, fragment_table,
+                             0, 50, TRUE);
+    /*
+     * this api relies on the caller to check fd_head -> reassembled_in
+     *
+     * Redoing all the tests seems like overkill - just check the pointer
+     */
+    ASSERT_EQ(fdh0,fd_head);
+
+    pinfo.fd->num = 3;
+    fd_head=fragment_add_seq(tvb, 5, &pinfo, 12, fragment_table,
+                             2, 60, FALSE);
+    ASSERT_EQ(fdh0,fd_head);
+
+    pinfo.fd->num = 4;
+    fd_head=fragment_add_seq(tvb, 15, &pinfo, 12, fragment_table,
+                             1, 60, TRUE);
+    ASSERT_EQ(fdh0,fd_head);
+}
+
+/* XXX ought to have some tests for overlapping fragments */
+
+/**********************************************************************************
+ *
+ * fragment_add_dcerpc_dg
+ *
+ *********************************************************************************/
+
+/* This can afford to be reasonably minimal, as it's just the same logic with a
+ * different hash key to fragment_add_seq
+ */
+static void test_fragment_add_dcerpc_dg(void)
+{
+    e_uuid_t act_id = {1,2,3,{4,5,6,7,8,9,10,11}};
+    
+    fragment_data *fd_head, *fdh0;
+    GHashTable *fragment_table = NULL;
+
+    printf("Starting test test_fragment_add_dcerpc_dg\n");
+    
+    /* we need our own fragment table */
+    dcerpc_fragment_table_init(&fragment_table);
+    fd_head=fragment_add_dcerpc_dg(tvb, 10, &pinfo, 12, &act_id, fragment_table,
+                                   0, 50, TRUE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* start another pdu (just to confuse things) */
+    pinfo.fd->num = 2;
+    fd_head=fragment_add_dcerpc_dg(tvb, 15, &pinfo, 13, &act_id, fragment_table,
+                             0, 60, TRUE);
+    ASSERT_EQ(2,g_hash_table_size(fragment_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* another pdu, with the same fragment_id, but a different act_id, to the
+     * first one */
+    pinfo.fd->num = 3;
+    act_id.Data1=2;
+    fd_head=fragment_add_dcerpc_dg(tvb, 15, &pinfo, 12, &act_id, fragment_table,
+                                   0, 60, TRUE);
+    ASSERT_EQ(3,g_hash_table_size(fragment_table));
+    ASSERT_EQ(NULL,fd_head);
+    act_id.Data1=1;
+
+    /* now we add the terminal fragment of the first datagram */
+    pinfo.fd->num = 4;
+    fd_head=fragment_add_dcerpc_dg(tvb, 5, &pinfo, 12, &act_id, fragment_table,
+                                   1, 60, FALSE);
+
+    ASSERT_EQ(3,g_hash_table_size(fragment_table));
+    ASSERT_NE(NULL,fd_head);
+
+    /* check the contents of the structure */
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(110,fd_head->len); /* the length of data we have */
+    ASSERT_EQ(1,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(4,fd_head->reassembled_in);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_NE(NULL,fd_head->data);
+    ASSERT_NE(NULL,fd_head->next);
+
+    /* test the actual reassembly */
+    ASSERT(!memcmp(fd_head->data,data+10,50));
+    ASSERT(!memcmp(fd_head->data+50,data+5,60));
+
+    /* what happens if we revisit the packets now? */
+    fdh0 = fd_head;
+    pinfo.fd->flags.visited = 1;
+    pinfo.fd->num = 1;
+    fd_head=fragment_add_dcerpc_dg(tvb, 10, &pinfo, 12, &act_id, fragment_table,
+                                   0, 50, TRUE);
+    /*
+     * this api relies on the caller to check fd_head -> reassembled_in
+     *
+     * Redoing all the tests seems like overkill - just check the pointer
+     */
+    ASSERT_EQ(fdh0,fd_head);
+}
+
+/**********************************************************************************
+ *
+ * fragment_add_seq_check
+ *
+ *********************************************************************************/
+
+
+/* This routine is used for both fragment_add_seq_802_11 and
+ * fragment_add_seq_check.
+ *
+ * Adds a couple of out-of-order fragments and checks their reassembly.
+ */
+static void test_fragment_add_seq_check_work(
+    fragment_data *(*fn)(tvbuff_t *, int, packet_info *, guint32, GHashTable *,
+                        GHashTable *, guint32, guint32, gboolean))
+{
+    fragment_data *fd_head;
+
+    pinfo.fd -> num = 1;
+    fd_head=fn(tvb, 10, &pinfo, 12, fragment_table,
+               reassembled_table, 0, 50, TRUE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* start another pdu (just to confuse things) */
+    pinfo.fd->num = 2;
+    fd_head=fn(tvb, 15, &pinfo, 13, fragment_table,
+               reassembled_table, 0, 60, TRUE);
+    ASSERT_EQ(2,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+    
+    /* add the terminal fragment of the first datagram */
+    pinfo.fd->num = 3;
+    fd_head=fn(tvb, 5, &pinfo, 12, fragment_table,
+               reassembled_table, 2, 60, FALSE);
+
+    /* we haven't got all the fragments yet ... */
+    ASSERT_EQ(2,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* finally, add the missing fragment */
+    pinfo.fd->num = 4;
+    fd_head=fn(tvb, 15, &pinfo, 12, fragment_table,
+               reassembled_table, 1, 60, TRUE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(3,g_hash_table_size(reassembled_table));
+    ASSERT_NE(NULL,fd_head);
+
+    /* check the contents of the structure */
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(170,fd_head->len); /* the length of data we have */
+    ASSERT_EQ(2,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(4,fd_head->reassembled_in);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_NE(NULL,fd_head->data);
+    ASSERT_NE(NULL,fd_head->next);
+
+    ASSERT_EQ(1,fd_head->next->frame);
+    ASSERT_EQ(0,fd_head->next->offset);  /* seqno */
+    ASSERT_EQ(50,fd_head->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->data);
+    ASSERT_NE(NULL,fd_head->next->next);
+
+    ASSERT_EQ(4,fd_head->next->next->frame);
+    ASSERT_EQ(1,fd_head->next->next->offset);  /* seqno */
+    ASSERT_EQ(60,fd_head->next->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->next->data);
+    ASSERT_NE(NULL,fd_head->next->next->next);
+
+    ASSERT_EQ(3,fd_head->next->next->next->frame);
+    ASSERT_EQ(2,fd_head->next->next->next->offset);  /* seqno */
+    ASSERT_EQ(60,fd_head->next->next->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->next->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->next->next->data);
+    ASSERT_EQ(NULL,fd_head->next->next->next->next);
+
+    /* test the actual reassembly */
+    ASSERT(!memcmp(fd_head->data,data+10,50));
+    ASSERT(!memcmp(fd_head->data+50,data+15,60));
+    ASSERT(!memcmp(fd_head->data+110,data+5,60));
+}
+
+/* Simple test case for fragment_add_seq_check
+ */
+static void test_fragment_add_seq_check(void)
+{
+    printf("Starting test test_fragment_add_seq_check\n");
+
+    test_fragment_add_seq_check_work(fragment_add_seq_check);
+}
+
+
+/* This tests the case that the 802.11 hack does something different for: when
+ * the terminal segment in a fragmented datagram arrives first.
+ */
+static void test_fragment_add_seq_check_1(void)
+{
+    fragment_data *fd_head;
+
+    printf("Starting test test_fragment_add_seq_check_1\n");
+
+    pinfo.fd->num = 1;
+    fd_head=fragment_add_seq_check(tvb, 10, &pinfo, 12, fragment_table,
+                                   reassembled_table, 1, 50, FALSE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* Now add the missing segment */
+    pinfo.fd->num = 2;
+    fd_head=fragment_add_seq_check(tvb, 5, &pinfo, 12, fragment_table,
+                                   reassembled_table, 0, 60, TRUE);
+
+    ASSERT_EQ(0,g_hash_table_size(fragment_table));
+    ASSERT_EQ(2,g_hash_table_size(reassembled_table));
+    ASSERT_NE(NULL,fd_head);
+
+    /* check the contents of the structure */
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(110,fd_head->len); /* the length of data we have */
+    ASSERT_EQ(1,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(2,fd_head->reassembled_in);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_NE(NULL,fd_head->data);
+    ASSERT_NE(NULL,fd_head->next);
+
+    ASSERT_EQ(2,fd_head->next->frame);
+    ASSERT_EQ(0,fd_head->next->offset);  /* seqno */
+    ASSERT_EQ(60,fd_head->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->data);
+    ASSERT_NE(NULL,fd_head->next->next);
+
+    ASSERT_EQ(1,fd_head->next->next->frame);
+    ASSERT_EQ(1,fd_head->next->next->offset);  /* seqno */
+    ASSERT_EQ(50,fd_head->next->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->next->data);
+    ASSERT_EQ(NULL,fd_head->next->next->next);
+
+    /* test the actual reassembly */
+    ASSERT(!memcmp(fd_head->data,data+5,60));
+    ASSERT(!memcmp(fd_head->data+60,data+10,50));
+}
+
+/**********************************************************************************
+ *
+ * fragment_add_seq_802_11
+ *
+ *********************************************************************************/
+
+/* Tests the 802.11 hack.
+ */
+static void test_fragment_add_seq_802_11_0(void)
+{
+    fragment_data *fd_head;
+
+    printf("Starting test test_fragment_add_seq_802_11_0\n");
+
+    /* the 802.11 hack is that some non-fragmented datagrams have non-zero
+     * fragment_number; test for this. */
+
+    pinfo.fd->num = 1;
+    fd_head=fragment_add_seq_802_11(tvb, 10, &pinfo, 12, fragment_table,
+                                    reassembled_table, 10, 50, FALSE);
+
+    ASSERT_EQ(0,g_hash_table_size(fragment_table));
+    ASSERT_EQ(1,g_hash_table_size(reassembled_table));
+    ASSERT_NE(NULL,fd_head);
+
+    /* check the contents of the structure */
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(0,fd_head->len);    /* unused */
+    ASSERT_EQ(0,fd_head->datalen); /* unused */
+    ASSERT_EQ(1,fd_head->reassembled_in);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_EQ(NULL,fd_head->data);
+    ASSERT_EQ(NULL,fd_head->next);
+}
+
+/* Reuse the fragment_add_seq_check testcases */
+static void test_fragment_add_seq_802_11_1(void)
+{
+    printf("Starting test test_fragment_add_seq_802_11_1\n");
+    test_fragment_add_seq_check_work(fragment_add_seq_802_11);
+}
+
+/**********************************************************************************
+ *
+ * fragment_add_seq_next
+ *
+ *********************************************************************************/
+
+/* Simple test case for fragment_add_seq_next.
+ * Adds a couple of fragments (with one for a different datagram in between),
+ * and checks that they are reassembled correctly.
+ */
+static void test_simple_fragment_add_seq_next(void)
+{
+    fragment_data *fd_head;
+
+    printf("Starting test test_simple_fragment_add_seq_next\n");
+
+    pinfo.fd->num = 1;
+    fd_head=fragment_add_seq_next(tvb, 10, &pinfo, 12, fragment_table,
+                                  reassembled_table, 50, TRUE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* adding the same fragment again should do nothing, even with different
+     * offset etc */
+    pinfo.fd->flags.visited = 1;
+    fd_head=fragment_add_seq_next(tvb, 5, &pinfo, 12, fragment_table,
+                                  reassembled_table, 60, TRUE);
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* start another pdu (just to confuse things) */
+    pinfo.fd->flags.visited = 0;
+    pinfo.fd->num = 2;
+    fd_head=fragment_add_seq_next(tvb, 15, &pinfo, 13, fragment_table,
+                                  reassembled_table, 60, TRUE);
+    ASSERT_EQ(2,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+    
+    
+    /* now we add the terminal fragment of the first datagram */
+    pinfo.fd->num = 3;
+    fd_head=fragment_add_seq_next(tvb, 5, &pinfo, 12, fragment_table,
+                                  reassembled_table, 60, FALSE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(2,g_hash_table_size(reassembled_table));
+    ASSERT_NE(NULL,fd_head);
+
+    /* check the contents of the structure */
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(110,fd_head->len); /* the length of data we have */
+    ASSERT_EQ(1,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(3,fd_head->reassembled_in);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_NE(NULL,fd_head->data);
+    ASSERT_NE(NULL,fd_head->next);
+
+    ASSERT_EQ(1,fd_head->next->frame);
+    ASSERT_EQ(0,fd_head->next->offset);  /* seqno */
+    ASSERT_EQ(50,fd_head->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->data);
+    ASSERT_NE(NULL,fd_head->next->next);
+
+    ASSERT_EQ(3,fd_head->next->next->frame);
+    ASSERT_EQ(1,fd_head->next->next->offset);  /* seqno */
+    ASSERT_EQ(60,fd_head->next->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->next->data);
+    ASSERT_EQ(NULL,fd_head->next->next->next);
+
+    /* test the actual reassembly */
+    ASSERT(!memcmp(fd_head->data,data+10,50));
+    ASSERT(!memcmp(fd_head->data+50,data+5,60));
+}
+
+
+/* This tests the case where some data is missing from one of the fragments.
+ * It should prevent reassembly.
+ */
+static void test_missing_data_fragment_add_seq_next(void)
+{
+    fragment_data *fd_head;
+
+    printf("Starting test test_missing_data_fragment_add_seq_next\n");
+
+    /* attempt to add a fragment which is longer than the data available */
+    pinfo.fd->num = 1;
+    fd_head=fragment_add_seq_next(tvb, 10, &pinfo, 12, fragment_table,
+                                  reassembled_table, DATA_LEN-9, TRUE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_NE(NULL,fd_head);
+
+    /* check the contents of the structure. Reassembly failed so everything
+     * should be null (meaning, just use the original tvb)  */
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(0,fd_head->len); /* the length of data we have */
+    ASSERT_EQ(0,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(0,fd_head->reassembled_in);
+    ASSERT_EQ(FD_BLOCKSEQUENCE,fd_head->flags & 0x1ff);
+    ASSERT_EQ(NULL,fd_head->data);
+    ASSERT_EQ(NULL,fd_head->next);
+
+    /* add another fragment (with all data present) */
+    pinfo.fd->num = 4;
+    fd_head=fragment_add_seq_next(tvb, 5, &pinfo, 12, fragment_table,
+                                  reassembled_table, 60, FALSE);
+
+    /* XXX: it's not clear that this is the right result; however it's what the
+     * code does...
+     */
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+
+    /* check what happens when we revisit the packets */
+    pinfo.fd->flags.visited = TRUE;
+    pinfo.fd->num = 1;
+
+    fd_head=fragment_add_seq_next(tvb, 10, &pinfo, 12, fragment_table,
+                                  reassembled_table, DATA_LEN-9, TRUE);
+
+    /* We just look in the reassembled_table for this packet. It never got put
+     * there, so this always returns null.
+     *
+     * That's crazy, because it means that the subdissector will see the data
+     * exactly once - on the first pass through the capture (well, assuming it
+     * doesn't bother to check fd_head->reassembled_in); however, that's
+     * what the code does...
+     */
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    pinfo.fd->num = 4;
+    fd_head=fragment_add_seq_next(tvb, 5, &pinfo, 12, fragment_table,
+                                  reassembled_table, 60, FALSE);
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+}
+
+
+/*
+ * we're going to do something similar now, but this time it is the second
+ * fragment which has something missing.
+ */
+static void test_missing_data_fragment_add_seq_next_2(void)
+{
+    fragment_data *fd_head;
+
+    printf("Starting test test_missing_data_fragment_add_seq_next_2\n");
+
+    pinfo.fd->num = 11;
+    fd_head=fragment_add_seq_next(tvb, 10, &pinfo, 24, fragment_table,
+                                  reassembled_table, 50, TRUE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    pinfo.fd->num = 12;
+    fd_head=fragment_add_seq_next(tvb, 5, &pinfo, 24, fragment_table,
+                                  reassembled_table, DATA_LEN-4, FALSE);
+
+    /* XXX: again, i'm really dubious about this. Surely this should return all
+     * the data we had, for a best-effort attempt at dissecting it?
+     * And it ought to go into the reassembled table?
+     */
+    ASSERT_EQ(0,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    /* check what happens when we revisit the packets */
+    pinfo.fd->flags.visited = TRUE;
+    pinfo.fd->num = 11;
+
+    fd_head=fragment_add_seq_next(tvb, 10, &pinfo, 24, fragment_table,
+                                  reassembled_table, 50, TRUE);
+
+    /* As before, this returns NULL because the fragment isn't in the
+     * reassembled_table. At least this is a bit more consistent than before.
+     */
+    ASSERT_EQ(0,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    pinfo.fd->num = 12;
+    fd_head=fragment_add_seq_next(tvb, 5, &pinfo, 24, fragment_table,
+                                  reassembled_table, DATA_LEN-4, FALSE);
+    ASSERT_EQ(0,g_hash_table_size(fragment_table));
+    ASSERT_EQ(0,g_hash_table_size(reassembled_table));
+    ASSERT_EQ(NULL,fd_head);
+
+}
+
+/*
+ * This time, our datagram only has one segment, but it has data missing.
+ */
+static void test_missing_data_fragment_add_seq_next_3(void)
+{
+    fragment_data *fd_head;
+
+    printf("Starting test test_missing_data_fragment_add_seq_next_3\n");
+
+    pinfo.fd->num = 20;
+    fd_head=fragment_add_seq_next(tvb, 5, &pinfo, 30, fragment_table,
+                                  reassembled_table, DATA_LEN-4, FALSE);
+
+    ASSERT_EQ(0,g_hash_table_size(fragment_table));
+    ASSERT_EQ(1,g_hash_table_size(reassembled_table));
+    ASSERT_NE(NULL,fd_head);
+
+    /* check the contents of the structure. */
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(0,fd_head->len); /* the length of data we have */
+    ASSERT_EQ(0,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(20,fd_head->reassembled_in);
+    ASSERT_EQ(FD_BLOCKSEQUENCE|FD_DEFRAGMENTED,fd_head->flags & 0x1ff);
+    ASSERT_EQ(NULL,fd_head->data);
+    ASSERT_EQ(NULL,fd_head->next);
+
+    /* revisiting the packet ought to produce the same result. */
+    pinfo.fd->flags.visited = TRUE;
+
+    pinfo.fd->num = 20;
+    fd_head=fragment_add_seq_next(tvb, 5, &pinfo, 30, fragment_table,
+                                  reassembled_table, DATA_LEN-4, FALSE);
+
+    ASSERT_EQ(0,g_hash_table_size(fragment_table));
+    ASSERT_EQ(1,g_hash_table_size(reassembled_table));
+    ASSERT_NE(NULL,fd_head);
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(0,fd_head->len); /* the length of data we have */
+    ASSERT_EQ(0,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(20,fd_head->reassembled_in);
+    ASSERT_EQ(FD_BLOCKSEQUENCE|FD_DEFRAGMENTED,fd_head->flags & 0x1ff);
+    ASSERT_EQ(NULL,fd_head->data);
+    ASSERT_EQ(NULL,fd_head->next);
+}
+
+
+/**********************************************************************************
+ *
+ * main
+ *
+ *********************************************************************************/
+
+int main(int argc, char **argv)
+{
+    frame_data fd;
+    char src[] = {1,2,3,4}, dst[] = {5,6,7,8};
+    unsigned int i;
+    void (*tests[])(void) = {
+        test_simple_fragment_add_seq,
+        test_fragment_add_dcerpc_dg,
+        test_fragment_add_seq_check,
+        test_fragment_add_seq_check_1,
+        test_fragment_add_seq_802_11_0,
+        test_fragment_add_seq_802_11_1,
+        test_simple_fragment_add_seq_next,
+        test_missing_data_fragment_add_seq_next,
+        test_missing_data_fragment_add_seq_next_2,
+        test_missing_data_fragment_add_seq_next_3
+    };
+    
+    /* we don't use our params */
+    argc=argc; argv=argv;
+    
+    /* initialise stuff */
+    ep_init_chunk();
+    tvbuff_init();
+    reassemble_init();
+        
+    /* a tvbuff for testing with */
+    data = g_malloc(DATA_LEN);
+    /* make sure it's full of stuff */
+    for(i=0; i<DATA_LEN; i++) {
+        data[i]=i & 0xFF;
+    }
+    tvb = tvb_new_real_data(data, DATA_LEN, DATA_LEN*2);
+
+    /* other test stuff */
+    pinfo.fd = &fd;
+    fd.flags.visited = 0;
+    SET_ADDRESS(&pinfo.src,AT_IPv4,4,src);
+    SET_ADDRESS(&pinfo.dst,AT_IPv4,4,dst);
+
+    /*************************************************************************/
+    for(i=0; i < sizeof(tests)/sizeof(tests[0]); i++ ) {
+        /* re-init the fragment tables */
+        fragment_table_init(&fragment_table);
+        ASSERT(fragment_table != NULL);
+    
+        reassembled_table_init(&reassembled_table);
+        ASSERT(reassembled_table != NULL);
+
+        pinfo.fd->flags.visited = FALSE;
+        
+        tests[i]();
+    }
+
+    printf(failure?"FAILURE\n":"SUCCESS\n");
+    return failure;
+}
Index: epan/Makefile.am
===================================================================
--- epan/Makefile.am	(revision 11846)
+++ epan/Makefile.am	(working copy)
@@ -75,6 +75,7 @@
 	make-sminmpec.pl \
 	radius_dict.l   \
 	tvbtest.c	\
+	reassemble_test.c \
 	uat_load.l	\
 	exntest.c	\
 	doxygen.cfg.in
@@ -104,6 +105,9 @@
 libwireshark_la_LIBADD = @G_ASCII_STRTOULL_LO@ @INET_ATON_LO@ @INET_PTON_LO@ @INET_NTOP_LO@ crypt/libairpdcap.la ftypes/libftypes.la dfilter/libdfilter.la dissectors/libdissectors.la $(wslua_lib) @ADNS_LIBS@ @LIBGNUTLS_LIBS@ @LIBICONV@ @KRB5_LIBS@ @SNMP_LIBS@ @SSL_LIBS@ -lm
 libwireshark_la_DEPENDENCIES = @G_ASCII_STRTOULL_LO@ @INET_ATON_LO@ @INET_PTON_LO@ @INET_NTOP_LO@ crypt/libairpdcap.la ftypes/libftypes.la dfilter/libdfilter.la dissectors/libdissectors.la $(wslua_lib)
 
+EXTRA_PROGRAMS = reassemble_test
+reassemble_test_LDADD = $(GLIB_LIBS) libwireshark.la
+
 tvbtest: tvbtest.o tvbuff.o except.o strutil.o emem.o
 	$(LINK) $^ $(GLIB_LIBS) -lz
 
Index: epan/reassemble.c
===================================================================
--- epan/reassemble.c	(revision 11846)
+++ epan/reassemble.c	(working copy)
@@ -64,6 +64,33 @@
 		fd_i->next=(fd);					\
 	}
 
+/* copy a fragment key to heap store to insert in the hash */
+static void *fragment_key_copy(const void *k)
+{
+	const fragment_key* key = (const fragment_key*) k;
+	fragment_key *new_key = g_mem_chunk_alloc(fragment_key_chunk);
+
+	COPY_ADDRESS(&new_key->src, &key->src);
+	COPY_ADDRESS(&new_key->dst, &key->dst);
+	new_key->id = key->id;
+	return new_key;
+}
+
+/* copy a dcerpc fragment key to heap store to insert in the hash */
+static void *dcerpc_fragment_key_copy(const void *k)
+{
+	const dcerpc_fragment_key* key = (const dcerpc_fragment_key*) k;
+	dcerpc_fragment_key *new_key = se_alloc(sizeof(dcerpc_fragment_key));
+
+	COPY_ADDRESS(&new_key->src, &key->src);
+	COPY_ADDRESS(&new_key->dst, &key->dst);
+	new_key->id = key->id;
+	new_key->act_id = key->act_id;
+    
+	return new_key;
+}
+
+
 static gint
 fragment_equal(gconstpointer k1, gconstpointer k2)
 {
@@ -1066,7 +1093,8 @@
 static gboolean
 fragment_add_seq_work(fragment_data *fd_head, tvbuff_t *tvb, int offset,
 	     packet_info *pinfo, guint32 frag_number,
-	     guint32 frag_data_len, gboolean more_frags)
+	     guint32 frag_data_len, gboolean more_frags,
+	     guint32 flags)
 {
 	fragment_data *fd;
 	fragment_data *fd_i;
@@ -1311,16 +1339,51 @@
 	     GHashTable *fragment_table, guint32 frag_number,
 	     guint32 frag_data_len, gboolean more_frags)
 {
-	fragment_key key, *new_key;
-	fragment_data *fd_head;
+	fragment_key key;
 
 	/* create key to search hash with */
 	key.src = pinfo->src;
 	key.dst = pinfo->dst;
 	key.id  = id;
 
-	fd_head = g_hash_table_lookup(fragment_table, &key);
+	return fragment_add_seq_key(tvb, offset, pinfo,
+				    &key, fragment_key_copy,
+				    fragment_table, frag_number,
+				    frag_data_len, more_frags, 0);
+}
+                                    
 
+fragment_data *
+fragment_add_dcerpc_dg(tvbuff_t *tvb, int offset, packet_info *pinfo, guint32 id,
+                    void *v_act_id,
+                    GHashTable *fragment_table, guint32 frag_number,
+                    guint32 frag_data_len, gboolean more_frags)
+{
+	e_uuid_t *act_id = (e_uuid_t *)v_act_id;
+	dcerpc_fragment_key key;
+
+	/* create key to search hash with */
+	key.src = pinfo->src;
+	key.dst = pinfo->dst;
+	key.id  = id;
+	key.act_id  = *act_id;
+
+	return fragment_add_seq_key(tvb, offset, pinfo,
+				    &key, dcerpc_fragment_key_copy,
+				    fragment_table, frag_number,
+				    frag_data_len, more_frags, 0);
+}
+
+fragment_data *
+fragment_add_seq_key(tvbuff_t *tvb, int offset, packet_info *pinfo,
+                     void *key, fragment_key_copier key_copier,
+                    GHashTable *fragment_table, guint32 frag_number,
+                    guint32 frag_data_len, gboolean more_frags,
+                    guint32 flags)
+{
+	fragment_data *fd_head;
+	fd_head = g_hash_table_lookup(fragment_table, key);
+
 	/* have we already seen this frame ?*/
 	if (pinfo->fd->flags.visited) {
 		if (fd_head != NULL && fd_head->flags & FD_DEFRAGMENTED) {
@@ -1348,21 +1411,89 @@
 		fd_head->data=NULL;
 		fd_head->reassembled_in=0;
 
+		if((flags & (REASSEMBLE_FLAGS_NO_FRAG_NUMBER|REASSEMBLE_FLAGS_802_11_HACK))
+		   && !more_frags) {
+			/*
+			 * This is the last fragment for this packet, and
+			 * is the only one we've seen.
+			 *
+			 * Either we don't have sequence numbers, in which
+			 * case we assume this is the first fragment for
+			 * this packet, or we're doing special 802.11
+			 * processing, in which case we assume it's one
+			 * of those reassembled packets with a non-zero
+			 * fragment number (see packet-80211.c); just
+			 * return a pointer to the head of the list;
+			 * fragment_add_seq_check will then add it to the table
+			 * of reassembled packets.
+			 */
+			fd_head->reassembled_in=pinfo->fd->num;
+			return fd_head;
+		}
+		       
 		/*
 		 * We're going to use the key to insert the fragment,
-		 * so allocate a structure for it, and copy the
-		 * addresses, allocating new buffers for the address
-		 * data.
+		 * so copy it to a long-term store.
 		 */
-		new_key = g_mem_chunk_alloc(fragment_key_chunk);
-		COPY_ADDRESS(&new_key->src, &key.src);
-		COPY_ADDRESS(&new_key->dst, &key.dst);
-		new_key->id = key.id;
-		g_hash_table_insert(fragment_table, new_key, fd_head);
+		if(key_copier != NULL)
+			key = key_copier(key);
+		g_hash_table_insert(fragment_table, key, fd_head);
+		
+		/*
+		 * If we weren't given an initial fragment number,
+		 * make it 0.
+		 */
+		if (flags & REASSEMBLE_FLAGS_NO_FRAG_NUMBER)
+			frag_number = 0;
+	} else {
+		if (flags & REASSEMBLE_FLAGS_NO_FRAG_NUMBER) {
+			fragment_data *fd;
+			/*
+			 * If we weren't given an initial fragment number,
+			 * use the next expected fragment number as the fragment
+			 * number for this fragment.
+			 */
+			for (fd = fd_head; fd != NULL; fd = fd->next) {
+				if (fd->next == NULL)
+					frag_number = fd->offset + 1;
+			}
+		}
 	}
 
+	/*
+	 * XXX I've copied this over from the old separate
+	 * fragment_add_seq_check_work, but I'm not convinced it's doing the
+	 * right thing -- rav
+	 *
+	 * If we don't have all the data that is in this fragment,
+	 * then we can't, and don't, do reassembly on it.
+	 *
+	 * If it's the first frame, handle it as an unfragmented packet.
+	 * Otherwise, just handle it as a fragment.
+	 *
+	 * If "more_frags" isn't set, we get rid of the entry in the
+	 * hash table for this reassembly, as we don't need it any more.
+	 */
+	if ((flags & REASSEMBLE_FLAGS_CHECK_DATA_PRESENT) &&
+		!tvb_bytes_exist(tvb, offset, frag_data_len)) {
+		if (!more_frags) {
+			gpointer orig_key;
+			/*
+			 * Remove this from the table of in-progress
+			 * reassemblies, and free up any memory used for
+			 * it in that table.
+			 */
+			if (g_hash_table_lookup_extended(fragment_table, key,
+							 &orig_key, NULL)) {
+				fragment_unhash(fragment_table, (fragment_key *)orig_key);
+			}
+		}
+		fd_head -> flags |= FD_DATA_NOT_PRESENT;
+		return frag_number == 0 ? fd_head : NULL;
+	}
+	
 	if (fragment_add_seq_work(fd_head, tvb, offset, pinfo,
-				  frag_number, frag_data_len, more_frags)) {
+				  frag_number, frag_data_len, more_frags, flags)) {
 		/*
 		 * Reassembly is complete.
 		 */
@@ -1375,79 +1506,6 @@
 	}
 }
 
-fragment_data *
-fragment_add_dcerpc_dg(tvbuff_t *tvb, int offset, packet_info *pinfo, guint32 id,
-                    void *v_act_id,
-                    GHashTable *fragment_table, guint32 frag_number,
-                    guint32 frag_data_len, gboolean more_frags)
-{
-       dcerpc_fragment_key key, *new_key;
-       fragment_data *fd_head;
-       e_uuid_t *act_id = (e_uuid_t *)v_act_id;
-
-       /* create key to search hash with */
-       key.src = pinfo->src;
-       key.dst = pinfo->dst;
-       key.id  = id;
-       key.act_id  = *act_id;
-
-       fd_head = g_hash_table_lookup(fragment_table, &key);
-
-       /* have we already seen this frame ?*/
-       if (pinfo->fd->flags.visited) {
-               if (fd_head != NULL && fd_head->flags & FD_DEFRAGMENTED) {
-                       return fd_head;
-               } else {
-                       return NULL;
-               }
-       }
-
-       if (fd_head==NULL){
-               /* not found, this must be the first snooped fragment for this
-                 * packet. Create list-head.
-                */
-               fd_head=g_mem_chunk_alloc(fragment_data_chunk);
-
-               /* head/first structure in list only holds no other data than
-                 * 'datalen' then we don't have to change the head of the list
-                 * even if we want to keep it sorted
-                 */
-               fd_head->next=NULL;
-               fd_head->datalen=0;
-               fd_head->offset=0;
-               fd_head->len=0;
-               fd_head->flags=FD_BLOCKSEQUENCE;
-               fd_head->data=NULL;
-               fd_head->reassembled_in=0;
-
-               /*
-                * We're going to use the key to insert the fragment,
-                * so allocate a structure for it, and copy the
-                * addresses, allocating new buffers for the address
-                * data.
-                */
-               new_key = se_alloc(sizeof(dcerpc_fragment_key));
-               COPY_ADDRESS(&new_key->src, &key.src);
-               COPY_ADDRESS(&new_key->dst, &key.dst);
-               new_key->id = key.id;
-               new_key->act_id = key.act_id;
-               g_hash_table_insert(fragment_table, new_key, fd_head);
-       }
-
-       if (fragment_add_seq_work(fd_head, tvb, offset, pinfo,
-                                 frag_number, frag_data_len, more_frags)) {
-               /*
-                * Reassembly is complete.
-                */
-               return fd_head;
-       } else {
-               /*
-                * Reassembly isn't complete.
-                */
-               return NULL;
-       }
-}
-
 /*
  * This does the work for "fragment_add_seq_check()" and
  * "fragment_add_seq_next()".
@@ -1468,10 +1526,10 @@
  *
  * This fragment is added to the linked list of fragments for this packet.
  *
- * If "more_frags" is false and "frag_802_11_hack" (as the name implies,
- * a special hack for 802.11) or "no_frag_number" (implying messages must be
- * in order since there's no sequence number) are true, then this (one
- * element) list is returned.
+ * If "more_frags" is false and REASSEMBLE_FLAGS_802_11_HACK (as the name
+ * implies, a special hack for 802.11) or REASSEMBLE_FLAGS_NO_FRAG_NUMBER
+ * (implying messages must be in order since there's no sequence number) are
+ * set in "flags", then this (one element) list is returned.
  *
  * If, after processing this fragment, we have all the fragments,
  * "fragment_add_seq_check_work()" removes that from the fragment hash
@@ -1487,12 +1545,11 @@
 	     guint32 id, GHashTable *fragment_table,
 	     GHashTable *reassembled_table, guint32 frag_number,
 	     guint32 frag_data_len, gboolean more_frags,
-	     gboolean no_frag_number, gboolean frag_802_11_hack)
+	     guint32 flags)
 {
 	reassembled_key reass_key;
-	fragment_key key, *new_key, *old_key;
-	gpointer orig_key, value;
-	fragment_data *fd_head, *fd;
+	fragment_key key;
+	fragment_data *fd_head;
 
 	/*
 	 * Have we already seen this frame?
@@ -1509,124 +1566,36 @@
 	key.dst = pinfo->dst;
 	key.id  = id;
 
-	if (!g_hash_table_lookup_extended(fragment_table, &key,
-					  &orig_key, &value)) {
-		/* not found, this must be the first snooped fragment for this
-                 * packet. Create list-head.
-		 */
-		fd_head=g_mem_chunk_alloc(fragment_data_chunk);
+	fd_head = fragment_add_seq_key(tvb, offset, pinfo,
+				       &key, fragment_key_copy,
+				       fragment_table, frag_number,
+				       frag_data_len, more_frags, flags|REASSEMBLE_FLAGS_CHECK_DATA_PRESENT);
+	if (fd_head) {
+		gpointer orig_key;
 
-		/* head/first structure in list only holds no other data than
-                 * 'datalen' then we don't have to change the head of the list
-                 * even if we want to keep it sorted
-                 */
-		fd_head->next=NULL;
-		fd_head->datalen=0;
-		fd_head->offset=0;
-		fd_head->len=0;
-		fd_head->flags=FD_BLOCKSEQUENCE;
-		fd_head->data=NULL;
-		fd_head->reassembled_in=0;
-
-		if ((no_frag_number || frag_802_11_hack) && !more_frags) {
-			/*
-			 * This is the last fragment for this packet, and
-			 * is the only one we've seen.
-			 *
-			 * Either we don't have sequence numbers, in which
-			 * case we assume this is the first fragment for
-			 * this packet, or we're doing special 802.11
-			 * processing, in which case we assume it's one
-			 * of those reassembled packets with a non-zero
-			 * fragment number (see packet-80211.c); just
-			 * add the fragment to the table of reassembled
-			 * packets, and return a pointer to the head of
-			 * the list.
-			 */
-			fragment_reassembled(fd_head, pinfo,
-			       reassembled_table, id);
+		if(fd_head->flags & FD_DATA_NOT_PRESENT) {
+			/* this is the first fragment of a datagram with
+			 * truncated fragments. Don't move it to the
+			 * reassembled table. */
 			return fd_head;
 		}
-
+		
 		/*
-		 * We're going to use the key to insert the fragment,
-		 * so allocate a structure for it, and copy the
-		 * addresses, allocating new buffers for the address
-		 * data.
-		 */
-		new_key = g_mem_chunk_alloc(fragment_key_chunk);
-		COPY_ADDRESS(&new_key->src, &key.src);
-		COPY_ADDRESS(&new_key->dst, &key.dst);
-		new_key->id = key.id;
-		g_hash_table_insert(fragment_table, new_key, fd_head);
-
-		orig_key = new_key;	/* for unhashing it later */
-
-		/*
-		 * If we weren't given an initial fragment number,
-		 * make it 0.
-		 */
-		if (no_frag_number)
-			frag_number = 0;
-	} else {
-		/*
-		 * We found it.
-		 */
-		fd_head = value;
-
-		/*
-		 * If we weren't given an initial fragment number,
-		 * use the next expected fragment number as the fragment
-		 * number for this fragment.
-		 */
-		if (no_frag_number) {
-			for (fd = fd_head; fd != NULL; fd = fd->next) {
-				if (fd->next == NULL)
-					frag_number = fd->offset + 1;
-			}
-		}
-	}
-
-	/*
-	 * If we don't have all the data that is in this fragment,
-	 * then we can't, and don't, do reassembly on it.
-	 *
-	 * If it's the first frame, handle it as an unfragmented packet.
-	 * Otherwise, just handle it as a fragment.
-	 *
-	 * If "more_frags" isn't set, we get rid of the entry in the
-	 * hash table for this reassembly, as we don't need it any more.
-	 */
-	if (!tvb_bytes_exist(tvb, offset, frag_data_len)) {
-		if (!more_frags) {
-			/*
-			 * Remove this from the table of in-progress
-			 * reassemblies, and free up any memory used for
-			 * it in that table.
-			 */
-			old_key = orig_key;
-			fragment_unhash(fragment_table, old_key);
-		}
-		return frag_number == 0 ? fd_head : NULL;
-	}
-
-	if (fragment_add_seq_work(fd_head, tvb, offset, pinfo,
-				  frag_number, frag_data_len, more_frags)) {
-		/*
 		 * Reassembly is complete.
 		 * Remove this from the table of in-progress
 		 * reassemblies, add it to the table of
 		 * reassembled packets, and return it.
 		 */
+		if (g_hash_table_lookup_extended(fragment_table, &key,
+						 &orig_key, NULL)) {
+			/*
+			 * Remove this from the table of in-progress reassemblies,
+			 * and free up any memory used for it in that table.
+			 */
+			fragment_unhash(fragment_table, (fragment_key *)orig_key);
+		}
 
 		/*
-		 * Remove this from the table of in-progress reassemblies,
-		 * and free up any memory used for it in that table.
-		 */
-		old_key = orig_key;
-		fragment_unhash(fragment_table, old_key);
-
-		/*
 		 * Add this item to the table of reassembled packets.
 		 */
 		fragment_reassembled(fd_head, pinfo, reassembled_table, id);
@@ -1647,7 +1616,7 @@
 {
 	return fragment_add_seq_check_work(tvb, offset, pinfo, id,
 	    fragment_table, reassembled_table, frag_number, frag_data_len,
-	    more_frags, FALSE, FALSE);
+	    more_frags, 0);
 }
 
 fragment_data *
@@ -1658,7 +1627,7 @@
 {
 	return fragment_add_seq_check_work(tvb, offset, pinfo, id,
 	    fragment_table, reassembled_table, frag_number, frag_data_len,
-	    more_frags, FALSE, TRUE);
+	    more_frags, REASSEMBLE_FLAGS_802_11_HACK);
 }
 
 fragment_data *
@@ -1669,7 +1638,7 @@
 {
 	return fragment_add_seq_check_work(tvb, offset, pinfo, id,
 	    fragment_table, reassembled_table, 0, frag_data_len,
-	    more_frags, TRUE, FALSE);
+	    more_frags, REASSEMBLE_FLAGS_NO_FRAG_NUMBER);
 }
 
 /*
@@ -1925,3 +1894,11 @@
 
 	return show_fragment_errs_in_col(fd_head, fit, pinfo);
 }
+
+/*
+ * Local Variables:
+ * c-basic-offset: 8
+ * indent-tabs-mode: t
+ * tab-width: 8
+ * End:
+ */
Index: epan/reassemble.h
===================================================================
--- epan/reassemble.h	(revision 11846)
+++ epan/reassemble.h	(working copy)
@@ -51,6 +51,13 @@
    into the defragmented packet */
 #define FD_BLOCKSEQUENCE        0x0100
 
+/* if REASSEMBLE_FLAGS_CHECK_DATA_PRESENT is set, and the first fragment is
+ * incomplete, this flag is set in the flags word on the fd_head returned.
+ *
+ * It's all a fudge to preserve historical behaviour.
+ */
+#define FD_DATA_NOT_PRESENT	0x0200
+
 typedef struct _fragment_data {
 	struct _fragment_data *next;
 	guint32 frame;
@@ -64,7 +71,27 @@
 	unsigned char *data;
 } fragment_data;
 
+
 /*
+ * Flags for fragment_add_seq_*
+ */
+
+/* we don't have any sequence numbers - fragments are assumed to appear in
+ * order */
+#define REASSEMBLE_FLAGS_NO_FRAG_NUMBER		0x0001
+
+/* a special fudge for the 802.11 dissector */
+#define REASSEMBLE_FLAGS_802_11_HACK		0x0002
+
+/* causes fragment_add_seq_key to check that all the fragment data is present
+ * in the tvb, and if not, do something a bit odd. */
+#define REASSEMBLE_FLAGS_CHECK_DATA_PRESENT	0x0004
+
+/* a function for copying hash keys */
+typedef void *(*fragment_key_copier)(const void *key);
+
+
+/*
  * Initialize a fragment table.
  */
 extern void fragment_table_init(GHashTable **fragment_table);
@@ -105,10 +132,51 @@
 
 /* same as fragment_add() but this one assumes frag_number is a block
    sequence number. note that frag_number is 0 for the first fragment. */
+
+/*
+ * These functions add a new fragment to the fragment hash table,
+ * assuming that frag_number is a block sequence number (starting from zero for
+ * the first fragment of each datagram).
+ *
+ * If this is the first fragment seen for this datagram, a new
+ * "fragment_data" structure is allocated to refer to the reassembled
+ * packet, and:
+ *
+ *	if "more_frags" is false, and either we have no sequence numbers, or
+ *	are using the 802.11 hack, it is assumed that this is the only fragment
+ *	in the datagram. The structure is not added to the hash
+ *	table, and not given any fragments to refer to, but is just returned.
+ *
+ *      In this latter case reassembly wasn't done (since there was only one
+ *      fragment in the packet); dissectors can check the 'next' pointer on the
+ *      returned list to see if this case was hit or not.
+ *
+ * Otherwise, this fragment is just added to the linked list of fragments
+ * for this packet; the fragment_data is also added to the fragment hash if
+ * necessary.
+ *
+ * If this packet completes assembly, these functions return the head of the
+ * fragment data; otherwise, they return null.
+ */
+
+/* "key" should be an arbitrary key used for indexing the fragment hash;
+ * "key_copier" is called to copy the key to a more appropriate store before
+ * inserting a new entry to the hash.
+ */
+extern fragment_data *
+fragment_add_seq_key(tvbuff_t *tvb, int offset, packet_info *pinfo,
+                     void *key, fragment_key_copier key_copier,
+                     GHashTable *fragment_table, guint32 frag_number,
+                     guint32 frag_data_len, gboolean more_frags,
+                     guint32 flags);
+
+/* a wrapper for fragment_add_seq_key - uses a key of source, dest and frame number */
 extern fragment_data *fragment_add_seq(tvbuff_t *tvb, int offset, packet_info *pinfo,
     guint32 id, GHashTable *fragment_table, guint32 frag_number,
     guint32 frag_data_len, gboolean more_frags);
 
+/* another wrapper for fragment_add_seq_key - uses a key of source, dest, frame
+ * number and act_id */
 extern fragment_data *
 fragment_add_dcerpc_dg(tvbuff_t *tvb, int offset, packet_info *pinfo, guint32 id,
 	void *act_id,
@@ -116,44 +184,12 @@
 	guint32 frag_data_len, gboolean more_frags);
 
 /*
- * These functions add a new fragment to the fragment hash table.
- * If this is the first fragment seen for this datagram, a new
- * "fragment_data" structure is allocated to refer to the reassembled,
- * packet, and:
+ * These routines extend fragment_add_seq_key to use a "reassembled_table".
  *
- *	in "fragment_add_seq_802_11()", if "more_frags" is false,
- *	the structure is not added to the hash table, and not given
- *	any fragments to refer to, but is just returned;
- *
- *	otherwise, this fragment is added to the linked list of fragments
- *	for this packet, and the "fragment_data" structure is put into
- *	the hash table.
- *
- * Otherwise, this fragment is just added to the linked list of fragments
- * for this packet.
- *
  * If, after processing this fragment, we have all the fragments, they
  * remove that from the fragment hash table if necessary and add it
  * to the table of reassembled fragments, and return a pointer to the
  * head of the fragment list.
- *
- * If this is the first fragment we've seen, and "more_frags" is false,
- * the fragment is added to the list.  "fragment_add_seq_check()" will
- * return NULL (waiting for the earlier sequence numbers) while
- * "fragment_add_seq_802_11()" (a special hack for the 802.11 dissector) and
- * "fragment_add_seq_next()" will return a pointer to the (one element) list.
- * In this latter case reassembly wasn't done (since there was only one
- * fragment in the packet); dissectors can check the 'next' pointer on the
- * returned list to see if this case was hit or not.
- *
- * Otherwise, they return NULL.
- *
- * "fragment_add_seq_check()" and "fragment_add_seq_802_11()" assume
- * frag_number is a block sequence number.
- * The bsn for the first block is 0.
- *
- * "fragment_add_seq_next()" is for protocols with no sequence number,
- * and assumes fragments always appear in sequence.
  */
 extern fragment_data *
 fragment_add_seq_check(tvbuff_t *tvb, int offset, packet_info *pinfo,
Index: epan/reassemble_test.c
===================================================================
--- epan/reassemble_test.c	(revision 11850)
+++ epan/reassemble_test.c	(working copy)
@@ -65,6 +65,12 @@
     vfprintf(stderr, format, ap);
     va_end(ap);
     failure = 1;
+
+    /* many of the tests assume this routine doesn't return on failure; if we
+     * do, it may provide more information, but may cause a segfault. Uncomment
+     * this line if you wish.
+     */
+    exit(1);
 }
 
 #define DATA_LEN 256
@@ -140,7 +146,7 @@
     ASSERT_EQ(170,fd_head->len); /* the length of data we have */
     ASSERT_EQ(2,fd_head->datalen); /* seqno of the last fragment we have */
     ASSERT_EQ(4,fd_head->reassembled_in);
-    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE|FD_DATALEN_SET,fd_head->flags);
     ASSERT_NE(NULL,fd_head->data);
     ASSERT_NE(NULL,fd_head->next);
 
@@ -196,6 +202,265 @@
 
 /* XXX ought to have some tests for overlapping fragments */
 
+/* This tests the functionality of fragment_set_partial_reassembly for
+ * FD_BLOCKSEQUENCE reassembly.
+ *
+ * We add a sequence of fragments thus:
+ *    seqno   frame  offset   len   (initial) more_frags
+ *    -----   -----  ------   ---   --------------------
+ *      0       1       10       50   false
+ *      1       2        0       40   true
+ *      1       3        0       40   true (a duplicate fragment)
+ *      2       4       20      100   false
+ *      3       5        0       40   false
+ */
+static void test_fragment_add_seq_partial_reassembly(void)
+{
+    fragment_data *fd_head, *fd;
+
+    printf("Starting test test_fragment_add_seq_partial_reassembly\n");
+
+    /* generally it's probably fair to assume that we will be called with
+     * more_frags=FALSE.
+     */
+    pinfo.fd->num = 1;
+    fd_head=fragment_add_seq(tvb, 10, &pinfo, 12, fragment_table,
+                             0, 50, FALSE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_NE(NULL,fd_head);
+
+    /* check the contents of the structure */
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(50,fd_head->len); /* the length of data we have */
+    ASSERT_EQ(0,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(1,fd_head->reassembled_in);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE|FD_DATALEN_SET,fd_head->flags);
+    ASSERT_NE(NULL,fd_head->data);
+    ASSERT_NE(NULL,fd_head->next);
+
+    ASSERT_EQ(1,fd_head->next->frame);
+    ASSERT_EQ(0,fd_head->next->offset);  /* seqno */
+    ASSERT_EQ(50,fd_head->next->len);    /* segment length */
+    ASSERT_EQ(0,fd_head->next->flags);
+    ASSERT_EQ(NULL,fd_head->next->data);
+    ASSERT_EQ(NULL,fd_head->next->next);
+
+    /* test the actual reassembly */
+    ASSERT(!memcmp(fd_head->data,data+10,50));
+
+    /* now we announce that the reassembly wasn't complete after all. */
+    fragment_set_partial_reassembly(&pinfo,12,fragment_table);
+
+    /* and add another segment. To mix things up slightly (and so that we can
+     * check on the state of things), we're going to set the more_frags flag
+     * here
+     */
+    pinfo.fd->num = 2;
+    fd_head=fragment_add_seq(tvb, 0, &pinfo, 12, fragment_table,
+                             1, 40, TRUE);
+
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(NULL,fd_head);
+
+    fd_head=fragment_get(&pinfo,12,fragment_table);
+    ASSERT_NE(NULL,fd_head);
+    
+    /* check the contents of the structure */
+    ASSERT_EQ(0,fd_head->frame);   /* unused */
+    ASSERT_EQ(0,fd_head->offset);  /* unused */
+    /* ASSERT_EQ(50,fd_head->len);     the length of data we have */
+    ASSERT_EQ(0,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(0,fd_head->reassembled_in);
+    ASSERT_EQ(FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_NE(NULL,fd_head->data);
+    ASSERT_NE(NULL,fd_head->next);
+
+    fd=fd_head->next;
+    ASSERT_EQ(1,fd->frame);
+    ASSERT_EQ(0,fd->offset);  /* seqno */
+    ASSERT_EQ(50,fd->len);    /* segment length */
+    ASSERT_EQ(FD_NOT_MALLOCED,fd->flags);
+    ASSERT_EQ(fd_head->data,fd->data);
+    ASSERT_NE(NULL,fd->next);
+
+    fd=fd->next;
+    ASSERT_EQ(2,fd->frame);
+    ASSERT_EQ(1,fd->offset);  /* seqno */
+    ASSERT_EQ(40,fd->len);    /* segment length */
+    ASSERT_EQ(0,fd->flags);
+    ASSERT_NE(NULL,fd->data);
+    ASSERT_EQ(NULL,fd->next);
+
+    /* Another copy of the second segment.
+     */
+    pinfo.fd->num = 3;
+    fd_head=fragment_add_seq(tvb, 0, &pinfo, 12, fragment_table,
+                             1, 40, TRUE);
+    
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_EQ(NULL,fd_head);
+    fd_head=fragment_get(&pinfo,12,fragment_table);
+    ASSERT_NE(NULL,fd_head);
+    ASSERT_EQ(0,fd_head->frame);   /* unused */
+    ASSERT_EQ(0,fd_head->offset);  /* unused */
+    /* ASSERT_EQ(50,fd_head->len);     the length of data we have */
+    ASSERT_EQ(0,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(0,fd_head->reassembled_in);
+    ASSERT_EQ(FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_NE(NULL,fd_head->data);
+    ASSERT_NE(NULL,fd_head->next);
+
+    fd=fd_head->next;
+    ASSERT_EQ(1,fd->frame);
+    ASSERT_EQ(0,fd->offset);  /* seqno */
+    ASSERT_EQ(50,fd->len);    /* segment length */
+    ASSERT_EQ(FD_NOT_MALLOCED,fd->flags);
+    ASSERT_EQ(fd_head->data,fd->data);
+    ASSERT_NE(NULL,fd->next);
+
+    fd=fd->next;
+    ASSERT_EQ(2,fd->frame);
+    ASSERT_EQ(1,fd->offset);  /* seqno */
+    ASSERT_EQ(40,fd->len);    /* segment length */
+    ASSERT_EQ(0,fd->flags);
+    ASSERT_NE(NULL,fd->data);
+    ASSERT_NE(NULL,fd->next);
+
+    fd=fd->next;
+    ASSERT_EQ(3,fd->frame);
+    ASSERT_EQ(1,fd->offset);  /* seqno */
+    ASSERT_EQ(40,fd->len);    /* segment length */
+    ASSERT_EQ(0,fd->flags);
+    ASSERT_NE(NULL,fd->data);
+    ASSERT_EQ(NULL,fd->next);
+
+    
+
+    /* have another go at wrapping things up */
+    pinfo.fd->num = 4;
+    fd_head=fragment_add_seq(tvb, 20, &pinfo, 12, fragment_table,
+                             2, 100, FALSE);
+    
+    ASSERT_EQ(1,g_hash_table_size(fragment_table));
+    ASSERT_NE(NULL,fd_head);
+    
+    /* check the contents of the structure */
+    ASSERT_EQ(0,fd_head->frame);  /* unused */
+    ASSERT_EQ(0,fd_head->offset); /* unused */
+    ASSERT_EQ(190,fd_head->len); /* the length of data we have */
+    ASSERT_EQ(2,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(4,fd_head->reassembled_in);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE|FD_DATALEN_SET|FD_OVERLAP,fd_head->flags);
+    ASSERT_NE(NULL,fd_head->data);
+    ASSERT_NE(NULL,fd_head->next);
+
+    fd=fd_head->next;
+    ASSERT_EQ(1,fd->frame);
+    ASSERT_EQ(0,fd->offset);  /* seqno */
+    ASSERT_EQ(50,fd->len);    /* segment length */
+    ASSERT_EQ(0,fd->flags);
+    ASSERT_EQ(NULL,fd->data);
+    ASSERT_NE(NULL,fd->next);
+
+    fd=fd->next;
+    ASSERT_EQ(2,fd->frame);
+    ASSERT_EQ(1,fd->offset);  /* seqno */
+    ASSERT_EQ(40,fd->len);    /* segment length */
+    ASSERT_EQ(0,fd->flags);
+    ASSERT_EQ(NULL,fd->data);
+    ASSERT_NE(NULL,fd->next);
+    
+    fd=fd->next;
+    ASSERT_EQ(3,fd->frame);
+    ASSERT_EQ(1,fd->offset);  /* seqno */
+    ASSERT_EQ(40,fd->len);    /* segment length */
+    ASSERT_EQ(FD_OVERLAP,fd->flags);
+    ASSERT_EQ(NULL,fd->data);
+    ASSERT_NE(NULL,fd->next);
+
+    fd=fd->next;
+    ASSERT_EQ(4,fd->frame);
+    ASSERT_EQ(2,fd->offset);  /* seqno */
+    ASSERT_EQ(100,fd->len);    /* segment length */
+    ASSERT_EQ(0,fd->flags);
+    ASSERT_EQ(NULL,fd->data);
+    ASSERT_EQ(NULL,fd->next);
+
+    /* test the actual reassembly */
+    ASSERT(!memcmp(fd_head->data,data+10,50));
+    ASSERT(!memcmp(fd_head->data+50,data,40));
+    ASSERT(!memcmp(fd_head->data+90,data+20,100));
+
+
+    /* do it again (this time it is more complicated, with an overlap in the
+     * reassembly) */
+
+    fragment_set_partial_reassembly(&pinfo,12,fragment_table);
+
+    pinfo.fd->num = 5;
+    fd_head=fragment_add_seq(tvb, 0, &pinfo, 12, fragment_table,
+                             3, 40, FALSE);
+
+    fd_head=fragment_get(&pinfo,12,fragment_table);
+    ASSERT_NE(NULL,fd_head);
+    ASSERT_EQ(0,fd_head->frame);   /* unused */
+    ASSERT_EQ(0,fd_head->offset);  /* unused */
+    ASSERT_EQ(230,fd_head->len);   /* the length of data we have */
+    ASSERT_EQ(3,fd_head->datalen); /* seqno of the last fragment we have */
+    ASSERT_EQ(5,fd_head->reassembled_in);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE|FD_DATALEN_SET|FD_OVERLAP,fd_head->flags);
+    ASSERT_NE(NULL,fd_head->data);
+    ASSERT_NE(NULL,fd_head->next);
+
+    fd=fd_head->next;
+    ASSERT_EQ(1,fd->frame);
+    ASSERT_EQ(0,fd->offset);  /* seqno */
+    ASSERT_EQ(50,fd->len);    /* segment length */
+    ASSERT_EQ(0,fd->flags);
+    ASSERT_EQ(NULL,fd->data);
+    ASSERT_NE(NULL,fd->next);
+
+    fd=fd->next;
+    ASSERT_EQ(2,fd->frame);
+    ASSERT_EQ(1,fd->offset);  /* seqno */
+    ASSERT_EQ(40,fd->len);    /* segment length */
+    ASSERT_EQ(0,fd->flags);
+    ASSERT_EQ(NULL,fd->data);
+    ASSERT_NE(NULL,fd->next);
+
+    fd=fd->next;
+    ASSERT_EQ(3,fd->frame);
+    ASSERT_EQ(1,fd->offset);  /* seqno */
+    ASSERT_EQ(40,fd->len);    /* segment length */
+    ASSERT_EQ(FD_OVERLAP,fd->flags);
+    ASSERT_EQ(NULL,fd->data);
+    ASSERT_NE(NULL,fd->next);
+
+    fd=fd->next;
+    ASSERT_EQ(4,fd->frame);
+    ASSERT_EQ(2,fd->offset);  /* seqno */
+    ASSERT_EQ(100,fd->len);   /* segment length */
+    ASSERT_EQ(0,fd->flags);
+    ASSERT_EQ(NULL,fd->data);
+    ASSERT_NE(NULL,fd->next);
+
+    fd=fd->next;
+    ASSERT_EQ(5,fd->frame);
+    ASSERT_EQ(3,fd->offset);  /* seqno */
+    ASSERT_EQ(40,fd->len);    /* segment length */
+    ASSERT_EQ(0,fd->flags);
+    ASSERT_EQ(NULL,fd->data);
+    ASSERT_EQ(NULL,fd->next);
+
+    /* test the actual reassembly */
+    ASSERT(!memcmp(fd_head->data,data+10,50));
+    ASSERT(!memcmp(fd_head->data+50,data,40));
+    ASSERT(!memcmp(fd_head->data+90,data+20,100));
+    ASSERT(!memcmp(fd_head->data+190,data,40));
+}
+
 /**********************************************************************************
  *
  * fragment_add_dcerpc_dg
@@ -253,7 +518,7 @@
     ASSERT_EQ(110,fd_head->len); /* the length of data we have */
     ASSERT_EQ(1,fd_head->datalen); /* seqno of the last fragment we have */
     ASSERT_EQ(4,fd_head->reassembled_in);
-    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE|FD_DATALEN_SET,fd_head->flags);
     ASSERT_NE(NULL,fd_head->data);
     ASSERT_NE(NULL,fd_head->next);
 
@@ -334,7 +599,7 @@
     ASSERT_EQ(170,fd_head->len); /* the length of data we have */
     ASSERT_EQ(2,fd_head->datalen); /* seqno of the last fragment we have */
     ASSERT_EQ(4,fd_head->reassembled_in);
-    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE|FD_DATALEN_SET,fd_head->flags);
     ASSERT_NE(NULL,fd_head->data);
     ASSERT_NE(NULL,fd_head->next);
 
@@ -407,7 +672,7 @@
     ASSERT_EQ(110,fd_head->len); /* the length of data we have */
     ASSERT_EQ(1,fd_head->datalen); /* seqno of the last fragment we have */
     ASSERT_EQ(2,fd_head->reassembled_in);
-    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE|FD_DATALEN_SET,fd_head->flags);
     ASSERT_NE(NULL,fd_head->data);
     ASSERT_NE(NULL,fd_head->next);
 
@@ -531,7 +796,7 @@
     ASSERT_EQ(110,fd_head->len); /* the length of data we have */
     ASSERT_EQ(1,fd_head->datalen); /* seqno of the last fragment we have */
     ASSERT_EQ(3,fd_head->reassembled_in);
-    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE,fd_head->flags);
+    ASSERT_EQ(FD_DEFRAGMENTED|FD_BLOCKSEQUENCE|FD_DATALEN_SET,fd_head->flags);
     ASSERT_NE(NULL,fd_head->data);
     ASSERT_NE(NULL,fd_head->next);
 
@@ -701,7 +966,7 @@
     ASSERT_EQ(0,fd_head->len); /* the length of data we have */
     ASSERT_EQ(0,fd_head->datalen); /* seqno of the last fragment we have */
     ASSERT_EQ(20,fd_head->reassembled_in);
-    ASSERT_EQ(FD_BLOCKSEQUENCE|FD_DEFRAGMENTED,fd_head->flags & 0x1ff);
+    ASSERT_EQ(FD_BLOCKSEQUENCE|FD_DEFRAGMENTED,fd_head->flags);
     ASSERT_EQ(NULL,fd_head->data);
     ASSERT_EQ(NULL,fd_head->next);
 
@@ -720,7 +985,7 @@
     ASSERT_EQ(0,fd_head->len); /* the length of data we have */
     ASSERT_EQ(0,fd_head->datalen); /* seqno of the last fragment we have */
     ASSERT_EQ(20,fd_head->reassembled_in);
-    ASSERT_EQ(FD_BLOCKSEQUENCE|FD_DEFRAGMENTED,fd_head->flags & 0x1ff);
+    ASSERT_EQ(FD_BLOCKSEQUENCE|FD_DEFRAGMENTED,fd_head->flags);
     ASSERT_EQ(NULL,fd_head->data);
     ASSERT_EQ(NULL,fd_head->next);
 }
@@ -739,6 +1004,7 @@
     unsigned int i;
     void (*tests[])(void) = {
         test_simple_fragment_add_seq,
+        test_fragment_add_seq_partial_reassembly,
         test_fragment_add_dcerpc_dg,
         test_fragment_add_seq_check,
         test_fragment_add_seq_check_1,
Index: epan/reassemble.c
===================================================================
--- epan/reassemble.c	(revision 11854)
+++ epan/reassemble.c	(working copy)
@@ -472,6 +472,7 @@
 
 	if(fd_head){
 		fd_head->datalen = tot_len;
+		fd_head->flags |= FD_DATALEN_SET;
 	}
 
 	return;
@@ -651,7 +652,7 @@
 			}
 			fd_i->flags &= (~FD_TOOLONGFRAGMENT) & (~FD_MULTIPLETAILS);
 		}
-		fd_head->flags ^= FD_DEFRAGMENTED|FD_PARTIAL_REASSEMBLY;
+		fd_head->flags &= ~(FD_DEFRAGMENTED|FD_PARTIAL_REASSEMBLY|FD_DATALEN_SET);
 		fd_head->flags &= (~FD_TOOLONGFRAGMENT) & (~FD_MULTIPLETAILS);
 		fd_head->datalen=0;
 		fd_head->reassembled_in=0;
@@ -661,7 +662,7 @@
 		/*
 		 * This is the tail fragment in the sequence.
 		 */
-		if (fd_head->datalen) {
+		if (fd_head->flags & FD_DATALEN_SET) {
 			/* ok we have already seen other tails for this packet
 			 * it might be a duplicate.
 			 */
@@ -677,6 +678,7 @@
 			 * length of the packet
 			 */
 			fd_head->datalen = fd->offset + fd->len;
+			fd_head->flags |= FD_DATALEN_SET;
 		}
 	}
 
@@ -723,7 +725,7 @@
 	LINK_FRAG(fd_head,fd);
 
 
-	if( !(fd_head->datalen) ){
+	if( !(fd_head->flags & FD_DATALEN_SET) ){
 		/* if we dont know the datalen, there are still missing
 		 * packets. Cheaper than the check below.
 		 */
@@ -1100,7 +1102,37 @@
 	fragment_data *fd_i;
 	fragment_data *last_fd;
 	guint32 max, dfpos, size;
+	void *old_data;
 
+	/* if the partial reassembly flag has been set, and we are extending
+	 * the pdu, un-reassemble the pdu. This means pointing old fds to malloc'ed data.
+	 */
+	if(fd_head->flags & FD_DEFRAGMENTED && frag_number >= fd_head->datalen &&
+		fd_head->flags & FD_PARTIAL_REASSEMBLY){
+		guint32 lastdfpos = 0;
+		dfpos = 0;
+		for(fd_i=fd_head->next; fd_i; fd_i=fd_i->next){
+			if( !fd_i->data ) {
+				if( fd_i->flags & FD_OVERLAP ) {
+					/* this is a duplicate of the previous
+					 * fragment. */
+					fd_i->data = fd_head->data + lastdfpos;
+				} else {
+					fd_i->data = fd_head->data + dfpos;
+					lastdfpos = dfpos;
+					dfpos += fd_i->len;
+				}
+				fd_i->flags |= FD_NOT_MALLOCED;
+			}
+			fd_i->flags &= (~FD_TOOLONGFRAGMENT) & (~FD_MULTIPLETAILS);
+		}
+		fd_head->flags &= ~(FD_DEFRAGMENTED|FD_PARTIAL_REASSEMBLY|FD_DATALEN_SET);
+		fd_head->flags &= (~FD_TOOLONGFRAGMENT) & (~FD_MULTIPLETAILS);
+		fd_head->datalen=0;
+		fd_head->reassembled_in=0;
+	}
+
+
 	/* create new fd describing this fragment */
 	fd = g_mem_chunk_alloc(fragment_data_chunk);
 	fd->next = NULL;
@@ -1114,7 +1146,7 @@
 		/*
 		 * This is the tail fragment in the sequence.
 		 */
-		if (fd_head->datalen) {
+		if (fd_head->flags&FD_DATALEN_SET) {
 			/* ok we have already seen other tails for this packet
 			 * it might be a duplicate.
 			 */
@@ -1131,6 +1163,7 @@
 			 * the length of the packet!)
 			 */
 			fd_head->datalen = fd->offset;
+			fd_head->flags |= FD_DATALEN_SET;
 		}
 	}
 
@@ -1237,7 +1270,7 @@
 	LINK_FRAG(fd_head,fd);
 
 
-	if( !(fd_head->datalen) ){
+	if( !(fd_head->flags & FD_DATALEN_SET) ){
 		/* if we dont know the sequence number of the last fragment,
 		 * there are definitely still missing packets. Cheaper than
 		 * the check below.
@@ -1281,6 +1314,8 @@
 	  }
 	  last_fd=fd_i;
 	}
+	/* store old data in case the fd_i->data pointers refer to it */
+	old_data=fd_head->data;
 	fd_head->data = g_malloc(size);
 	fd_head->len = size;		/* record size for caller	*/
 
@@ -1296,7 +1331,7 @@
 	      /* duplicate/retransmission/overlap */
 	      fd_i->flags    |= FD_OVERLAP;
 	      fd_head->flags |= FD_OVERLAP;
-	      if( (last_fd->len!=fd_i->datalen)
+	      if( (last_fd->len!=fd_i->len)
 		  || memcmp(last_fd->data, fd_i->data, last_fd->len) ){
 			fd->flags      |= FD_OVERLAPCONFLICT;
 			fd_head->flags |= FD_OVERLAPCONFLICT;
@@ -1308,12 +1343,16 @@
 
 	/* we have defragmented the pdu, now free all fragments*/
 	for (fd_i=fd_head->next;fd_i;fd_i=fd_i->next) {
-	  if(fd_i->data){
-	    g_free(fd_i->data);
-	    fd_i->data=NULL;
-	  }
+		if( fd_i->flags & FD_NOT_MALLOCED )
+			fd_i->flags &= ~FD_NOT_MALLOCED;
+		else
+			g_free(fd_i->data);
+		fd_i->data=NULL;
 	}
 
+	if( old_data )
+		g_free(old_data);
+
 	/* mark this packet as defragmented.
            allows us to skip any trailing fragments */
 	fd_head->flags |= FD_DEFRAGMENTED;
Index: epan/reassemble.h
===================================================================
--- epan/reassemble.h	(revision 11854)
+++ epan/reassemble.h	(working copy)
@@ -58,12 +58,21 @@
  */
 #define FD_DATA_NOT_PRESENT	0x0200
 
+/* This flag is set in the to denote that datalen has ben set to a valid value.
+ * It's implied by FD_DEFRAGMENTED (we must know the total length of the
+ * datagram if we have defragmented it...)
+ */
+#define FD_DATALEN_SET		0x0400
+
 typedef struct _fragment_data {
 	struct _fragment_data *next;
 	guint32 frame;
 	guint32	offset;
 	guint32	len;
-	guint32 datalen; /*Only valid in first item of list */
+	guint32 datalen; /* Only valid in first item of list and when
+                          * flags&FD_DATALEN_SET is set;
+                          * number of bytes or (if flags&FD_BLOCKSEQUENCE set)
+                          * segments in the datagram */
 	guint32 reassembled_in;	/* frame where this PDU was reassembled,
 				   only valid in the first item of the list
 				   and when FD_DEFRAGMENTED is set*/