Index: pjproject/trunk/pjlib-util/include/pjlib-util/stun_endpoint.h
===================================================================
--- pjproject/trunk/pjlib-util/include/pjlib-util/stun_endpoint.h	(revision 1032)
+++ pjproject/trunk/pjlib-util/include/pjlib-util/stun_endpoint.h	(revision 1034)
@@ -46,12 +46,44 @@
 typedef struct pj_stun_endpoint
 {
+    /**
+     * Pool factory to be used by the STUN endpoint and all objects created
+     * that use this STUN endpoint.
+     */
     pj_pool_factory	*pf;
+
+    /**
+     * Ioqueue used by this endpoint.
+     */
     pj_ioqueue_t	*ioqueue;
+
+    /**
+     * Timer heap instance used by this endpoint.
+     */
     pj_timer_heap_t	*timer_heap;
+
+    /**
+     * Internal pool used by this endpoint. This shouldn't be used by
+     * application.
+     */
+    pj_pool_t		*pool;
+
+    /**
+     * Options.
+     */
     unsigned		 options;
 
+    /**
+     * The default initial STUN round-trip time estimation in msecs.
+     * The value normally is PJ_STUN_RTO_VALUE.
+     */
     unsigned		 rto_msec;
 
-    pj_pool_t		*pool;
+    /**
+     * The interval to cache outgoing  STUN response in the STUN session,
+     * in miliseconds. 
+     *
+     * Default 10000 (10 seconds).
+     */
+    unsigned		 res_cache_msec;
 
 } pj_stun_endpoint;
Index: pjproject/trunk/pjlib-util/include/pjlib-util/stun_session.h
===================================================================
--- pjproject/trunk/pjlib-util/include/pjlib-util/stun_session.h	(revision 1032)
+++ pjproject/trunk/pjlib-util/include/pjlib-util/stun_session.h	(revision 1034)
@@ -24,4 +24,5 @@
 #include <pjlib-util/stun_transaction.h>
 #include <pj/list.h>
+#include <pj/timer.h>
 
 PJ_BEGIN_DECL
@@ -140,5 +141,6 @@
 
     pj_stun_client_tsx	*client_tsx;	/**< Client STUN transaction.	    */
-    pj_uint8_t		 client_key[12];/**< Client transaction key.	    */
+    pj_uint32_t		 msg_magic;	/**< Message magic.		    */
+    pj_uint8_t		 msg_key[12];	/**< Message/transaction key.	    */
 
     void		*pkt;		/**< The STUN packet.		    */
@@ -146,6 +148,9 @@
     unsigned		 pkt_size;	/**< The actual length of STUN pkt. */
 
+    unsigned		 options;	/**< Options specified when sending */
     unsigned		 addr_len;	/**< Length of destination address. */
     const pj_sockaddr_t	*dst_addr;	/**< Destination address.	    */
+
+    pj_timer_entry	 res_timer;	/**< Response cache timer.	    */
 };
 
@@ -155,5 +160,5 @@
  * messages. These options may be specified as bitmask.
  */
-enum pj_stun_session_option
+enum pj_stun_session_send_option
 {
     /**
@@ -172,5 +177,13 @@
      * Add STUN fingerprint to the message.
      */
-    PJ_STUN_USE_FINGERPRINT	= 4
+    PJ_STUN_USE_FINGERPRINT	= 4,
+
+    /**
+     * Instruct the session to cache outgoing response. This can only be 
+     * used when sending outgoing response message, and when it's specified,
+     * the session will use \a res_cache_msec settings in pj_stun_endpoint
+     * as the duration of the cache.
+     */
+    PJ_STUN_CACHE_RESPONSE	= 8
 };
 
@@ -258,4 +271,5 @@
 					  const pj_str_t *user,
 					  const pj_str_t *passwd);
+
 
 /**
@@ -396,5 +410,5 @@
  *
  * @param sess	    The STUN session instance.
- * @param options   Optional flags, from pj_stun_session_option.
+ * @param options   Optional flags, from pj_stun_session_send_option.
  * @param dst_addr  The destination socket address.
  * @param addr_len  Length of destination address.
Index: pjproject/trunk/pjlib-util/src/pjlib-util/stun_endpoint.c
===================================================================
--- pjproject/trunk/pjlib-util/src/pjlib-util/stun_endpoint.c	(revision 1032)
+++ pjproject/trunk/pjlib-util/src/pjlib-util/stun_endpoint.c	(revision 1034)
@@ -48,4 +48,5 @@
     endpt->timer_heap = timer_heap;
     endpt->rto_msec = PJ_STUN_RTO_VALUE;
+    endpt->res_cache_msec = 10000;
 
     *p_endpt = endpt;
Index: pjproject/trunk/pjlib-util/src/pjlib-util/stun_session.c
===================================================================
--- pjproject/trunk/pjlib-util/src/pjlib-util/stun_session.c	(revision 1032)
+++ pjproject/trunk/pjlib-util/src/pjlib-util/stun_session.c	(revision 1034)
@@ -38,4 +38,5 @@
 
     pj_stun_tx_data	 pending_request_list;
+    pj_stun_tx_data	 cached_response_list;
 };
 
@@ -104,6 +105,7 @@
     tdata = sess->pending_request_list.next;
     while (tdata != &sess->pending_request_list) {
-	pj_assert(sizeof(tdata->client_key)==sizeof(msg->hdr.tsx_id));
-	if (pj_memcmp(tdata->client_key, msg->hdr.tsx_id, 
+	pj_assert(sizeof(tdata->msg_key)==sizeof(msg->hdr.tsx_id));
+	if (tdata->msg_magic == msg->hdr.magic &&
+	    pj_memcmp(tdata->msg_key, msg->hdr.tsx_id, 
 		      sizeof(msg->hdr.tsx_id))==0)
 	{
@@ -159,6 +161,7 @@
 
     /* copy the request's transaction ID as the transaction key. */
-    pj_assert(sizeof(tdata->client_key)==sizeof(tdata->msg->hdr.tsx_id));
-    pj_memcpy(tdata->client_key, tdata->msg->hdr.tsx_id,
+    pj_assert(sizeof(tdata->msg_key)==sizeof(tdata->msg->hdr.tsx_id));
+    tdata->msg_magic = tdata->msg->hdr.magic;
+    pj_memcpy(tdata->msg_key, tdata->msg->hdr.tsx_id,
 	      sizeof(tdata->msg->hdr.tsx_id));
 
@@ -175,5 +178,9 @@
 	tdata->client_tsx = NULL;
     }
-
+    if (tdata->res_timer.id != PJ_FALSE) {
+	pj_timer_heap_cancel(tdata->sess->endpt->timer_heap, 
+			     &tdata->res_timer);
+	tdata->res_timer.id = PJ_FALSE;
+    }
     pj_pool_release(tdata->pool);
 }
@@ -187,4 +194,22 @@
     PJ_UNUSED_ARG(sess);
     destroy_tdata(tdata);
+}
+
+
+/* Timer callback to be called when it's time to destroy response cache */
+static void on_cache_timeout(pj_timer_heap_t *timer_heap,
+			     struct pj_timer_entry *entry)
+{
+    pj_stun_tx_data *tdata;
+
+    PJ_UNUSED_ARG(timer_heap);
+
+    entry->id = PJ_FALSE;
+    tdata = (pj_stun_tx_data*) entry->user_data;
+
+    PJ_LOG(5,(SNAME(tdata->sess), "Response cache deleted"));
+
+    pj_list_erase(tdata);
+    pj_stun_msg_destroy_tdata(tdata->sess, tdata);
 }
 
@@ -329,4 +354,5 @@
 
     pj_list_init(&sess->pending_request_list);
+    pj_list_init(&sess->cached_response_list);
 
     status = pj_mutex_create_recursive(pool, name, &sess->mutex);
@@ -500,6 +526,7 @@
 
     /* copy the request's transaction ID as the transaction key. */
-    pj_assert(sizeof(tdata->client_key)==sizeof(req->hdr.tsx_id));
-    pj_memcpy(tdata->client_key, req->hdr.tsx_id, sizeof(req->hdr.tsx_id));
+    pj_assert(sizeof(tdata->msg_key)==sizeof(req->hdr.tsx_id));
+    tdata->msg_magic = req->hdr.magic;
+    pj_memcpy(tdata->msg_key, req->hdr.tsx_id, sizeof(req->hdr.tsx_id));
 
     *p_tdata = tdata;
@@ -553,4 +580,6 @@
     PJ_ASSERT_RETURN(sess && addr_len && server && tdata, PJ_EINVAL);
 
+    tdata->options = options;
+
     /* Allocate packet */
     tdata->max_len = PJ_STUN_MAX_PKT_LEN;
@@ -612,4 +641,31 @@
 
     } else {
+	if ((options & PJ_STUN_CACHE_RESPONSE) && 
+	    (PJ_STUN_IS_RESPONSE(tdata->msg->hdr.type) ||
+	     PJ_STUN_IS_ERROR_RESPONSE(tdata->msg->hdr.type))) 
+	{
+	    /* Requested to keep the response in the cache */
+	    pj_time_val timeout;
+	    
+	    pj_memset(&tdata->res_timer, 0, sizeof(tdata->res_timer));
+	    pj_timer_entry_init(&tdata->res_timer, PJ_TRUE, tdata, 
+				&on_cache_timeout);
+
+	    timeout.sec = sess->endpt->res_cache_msec / 1000;
+	    timeout.msec = sess->endpt->res_cache_msec % 1000;
+
+	    status = pj_timer_heap_schedule(sess->endpt->timer_heap, 
+					    &tdata->res_timer,
+					    &timeout);
+	    if (status != PJ_SUCCESS) {
+		pj_stun_msg_destroy_tdata(sess, tdata);
+		pj_mutex_unlock(sess->mutex);
+		LOG_ERR_(sess, "Error scheduling response timer", status);
+		return status;
+	    }
+
+	    pj_list_push_back(&sess->cached_response_list, tdata);
+	}
+    
 	/* Otherwise for non-request message, send directly to transport. */
 	status = sess->cb.on_send_msg(sess, tdata->pkt, tdata->pkt_size,
@@ -620,6 +676,8 @@
 	}
 
-	/* Destroy */
-	pj_stun_msg_destroy_tdata(sess, tdata);
+	/* Destroy only when response is not cached*/
+	if (tdata->res_timer.id == 0) {
+	    pj_stun_msg_destroy_tdata(sess, tdata);
+	}
     }
 
@@ -640,6 +698,7 @@
     tdata = tsx_lookup(sess, msg);
     if (tdata == NULL) {
-	LOG_ERR_(sess, "STUN error finding transaction", PJ_ENOTFOUND);
-	return PJ_ENOTFOUND;
+	PJ_LOG(4,(SNAME(sess), 
+		  "Transaction not found, response silently discarded"));
+	return PJ_SUCCESS;
     }
 
@@ -708,5 +767,31 @@
 				       unsigned src_addr_len)
 {
+    pj_stun_tx_data *t;
     pj_status_t status;
+
+    /* First lookup response in response cache */
+    t = sess->cached_response_list.next;
+    while (t != &sess->cached_response_list) {
+	if (t->msg_magic == msg->hdr.magic &&
+	    pj_memcmp(t->msg_key, msg->hdr.tsx_id, 
+		      sizeof(msg->hdr.tsx_id))==0)
+	{
+	    break;
+	}
+	t = t->next;
+    }
+
+    if (t != &sess->cached_response_list) {
+	/* Found response in the cache */
+	unsigned options;
+
+	PJ_LOG(5,(SNAME(sess), 
+		 "Request retransmission, sending cached response"));
+
+	options = t->options;
+	options &= ~PJ_STUN_CACHE_RESPONSE;
+	pj_stun_session_send_msg(sess, options, src_addr, src_addr_len, t);
+	return PJ_SUCCESS;
+    }
 
     /* Distribute to handler, or respond with Bad Request */
@@ -726,5 +811,5 @@
     }
 
-    return status;    
+    return status;
 }
 
Index: pjproject/trunk/pjlib-util/src/pjstun-srv-test/server_main.c
===================================================================
--- pjproject/trunk/pjlib-util/src/pjstun-srv-test/server_main.c	(revision 1032)
+++ pjproject/trunk/pjlib-util/src/pjstun-srv-test/server_main.c	(revision 1034)
@@ -157,5 +157,6 @@
 
     /* Send */
-    status = pj_stun_session_send_msg(sess, 0, src_addr, src_addr_len, tdata);
+    status = pj_stun_session_send_msg(sess, PJ_STUN_CACHE_RESPONSE, 
+				      src_addr, src_addr_len, tdata);
     return status;
 }
