Hi,

Yet another patch against 0.2.3, cleaning the previous patch up a bit. Now
failed requests are not COMMAND_RETURN()-ed as NULLs, but an exception
handler is installed, which sends failure messages on caught errors.

I can log in, and request for a pty, but no further testing has been done.

-- 
Bazsi
PGP info: KeyID 9AF8D0A9 Fingerprint CD27 CFB0 802C 0944 9CFD 804E C82C 8EB1
     url: http://www.balabit.hu/pgpkey.txt
diff -urN --exclude-from=diff-exclude lsh-0.2.3-orig/src/channel.c 
lsh-0.2.3-bazsi/src/channel.c
--- lsh-0.2.3-orig/src/channel.c        Wed Jan 12 23:29:06 2000
+++ lsh-0.2.3-bazsi/src/channel.c       Thu Jan 20 11:04:54 2000
@@ -80,32 +80,6 @@
        ; (start object connection_startup)))
 */
 
-/* ;; GABA:
-   (class
-     (name global_request_handler)
-     (super packet_handler)
-     (vars
-       (global_requests object alist)))
-*/
-
-/* ;; GABA:
-   (class
-     (name channel_open_handler)
-     (super packet_handler)
-     (vars
-       (channel_types object alist)))
-*/
-
-/* ;; GABA:
-   (class
-     (name channel_open_response)
-     (super channel_open_callback)
-     (vars
-       (remote_channel_number simple UINT32)
-       (window_size simple UINT32)
-       (max_packet simple UINT32)))
-*/
-
 struct lsh_string *format_global_failure(void)
 {
   return ssh_format("%c", SSH_MSG_REQUEST_FAILURE);
@@ -175,15 +149,6 @@
                    channel->channel_number, add);
 }
 
-/* ;; GABA:
-   (class
-     (name channel_exception)
-     (super exception)
-     (vars
-       (channel object ssh_channel)
-       (pending_close . int)))
-*/
-
 /* GABA:
    (class
      (name exc_finish_channel_handler)
@@ -421,7 +386,7 @@
 
 /* GABA:
    (class
-     (name global_request_status)
+     (name request_status)
      (vars
        ; -1 for still active requests,
        ; 0 for failure,
@@ -429,39 +394,30 @@
        (status . int)))
 */
 
-static struct global_request_status *make_global_request_status(void)
+static struct request_status *make_request_status(void)
 {
-  NEW(global_request_status, self);
+  NEW(request_status, self);
   self->status = -1;
 
   return self;
 }
 
-/* FIXME: Split into a continuation and an exception handler */
 /* GABA:
    (class
-     (name global_request_response)
-     (super global_request_callback)
+     (name global_request_continuation)
+     (super command_continuation)
      (vars
-       (active object global_request_status)))
+       (connection object ssh_connection)
+       (active object request_status)))
 */
 
-static void
-do_global_request_response(struct global_request_callback *c,
-                          int success)
+static void 
+send_global_request_responses(struct ssh_connection *connection, 
+                             struct object_queue *q)
 {
-  CAST(global_request_response, self, c);
-  struct object_queue *q = &self->super.connection->table->active_global_requests;
-
-  assert( self->active->status == -1);
-  assert( (success == 0) || (success == 1) );
-  assert( !object_queue_is_empty(q));
-         
-  self->active->status = success;
-
   for (;;)
     {
-      CAST(global_request_status, n, object_queue_peek_head(q));
+      CAST(request_status, n, object_queue_peek_head(q));
       if (!n || (n->status < 0))
        break;
 
@@ -469,27 +425,83 @@
 
       /* FIXME: Perhaps install some exception handler that cancels
        * the queue as soon as a write failes. */
-      C_WRITE(self->super.connection,
+      C_WRITE(connection,
              (n->status
               ? format_global_success()
               : format_global_failure()));
     }
 }
 
-static struct global_request_callback *
-make_global_request_response(struct ssh_connection *connection,
-                            struct global_request_status *active)
+static void
+do_global_request_response(struct command_continuation *s,
+                          struct lsh_object *x)
 {
-  NEW(global_request_response, self);
+  CAST(global_request_continuation, self, s);
+  struct object_queue *q = &self->connection->table->active_global_requests;
+
+  assert(self->active->status == -1);
+  assert(!object_queue_is_empty(q));
+  assert(!x);
+         
+  self->active->status = 1;
 
-  self->super.connection = connection;
-  self->super.response = do_global_request_response;
+  send_global_request_responses(self->connection, q);
+}
 
+static struct command_continuation *
+make_global_request_response(struct ssh_connection *connection,
+                            struct request_status *active)
+{
+  NEW(global_request_continuation, self);
+
+  self->super.c = do_global_request_response;
+  self->connection = connection;
   self->active = active;
 
   return &self->super;
 }
-     
+
+/* GABA:
+   (class
+     (name global_request_exception_handler)
+     (super exception_handler)
+     (vars
+       (connection object ssh_connection)
+       (active object request_status)))
+*/
+
+static void 
+do_global_request_handler(struct exception_handler *c,
+                         const struct exception *e)
+{
+  CAST(global_request_exception_handler, self, c);
+  struct object_queue *q = &self->connection->table->active_global_requests;
+
+  assert(self->active->status == -1);
+  assert(!object_queue_is_empty(q));
+
+  self->active->status = 0;
+  
+  send_global_request_responses(self->connection, q);
+
+  if (e->type != EXC_GLOBAL_REQUEST)
+    EXCEPTION_RAISE(c->parent, e);
+}
+
+static struct exception_handler *
+make_global_request_exception_handler(struct ssh_connection *connection,
+                                     struct request_status *active,
+                                     struct exception_handler *h)
+{
+  NEW(global_request_exception_handler, self);
+
+  self->super.raise = do_global_request_handler;
+  self->super.parent = h;
+  self->active = active;
+  self->connection = connection;
+  return &self->super;
+}
+
 static void do_global_request(struct packet_handler *s UNUSED,
                              struct ssh_connection *connection,
                              struct lsh_string *packet)
@@ -509,7 +521,8 @@
       && parse_boolean(&buffer, &want_reply))
     {
       struct global_request *req;
-      struct global_request_callback *c = NULL;
+      struct command_continuation *c = &discard_continuation;
+      struct exception_handler *e = connection->e;
       
       if (!name || !(req = ALIST_GET(connection->table->global_requests,
                                     name)))
@@ -523,14 +536,15 @@
        {
          if (want_reply)
            {
-             struct global_request_status *a = make_global_request_status();
+             struct request_status *a = make_request_status();
              
              object_queue_add_tail(&connection->table->active_global_requests,
                                    &a->super);
              
              c = make_global_request_response(connection, a);
+             e = make_global_request_exception_handler(connection, a, e);
            }
-         GLOBAL_REQUEST(req, connection, &buffer, c);
+         GLOBAL_REQUEST(req, connection, &buffer, c, e);
        }
     }
   else
@@ -783,6 +797,112 @@
   lsh_string_free(packet);
 }     
 
+/* GABA:
+   (class
+     (name channel_request_continuation)
+     (super command_continuation)
+     (vars
+       (connection object ssh_connection)
+       (channel object ssh_channel)
+       (active object request_status)))
+*/
+
+static void
+send_channel_request_responses(struct ssh_connection *connection,
+                              struct ssh_channel *channel,
+                              struct object_queue *q)
+{
+  for (;;)
+    {
+      CAST(request_status, n, object_queue_peek_head(q));
+      if (!n || (n->status < 0))
+       break;
+
+      object_queue_remove_head(q);
+
+      /* FIXME: Perhaps install some exception handler that cancels
+       * the queue as soon as a write failes. */
+      C_WRITE(connection,
+             (n->status
+              ? format_channel_success(channel->channel_number)
+              : format_channel_failure(channel->channel_number)));
+    }
+}
+
+static void
+do_channel_request_response(struct command_continuation *s,
+                           struct lsh_object *x)
+{
+  CAST(channel_request_continuation, self, s);
+  struct object_queue *q = &self->channel->active_requests;
+
+  assert(self->active->status == -1);
+  assert(!object_queue_is_empty(q));
+  assert(!x);
+         
+  self->active->status = 1;
+
+  send_channel_request_responses(self->connection, self->channel, q);
+}
+
+static struct command_continuation *
+make_channel_request_response(struct ssh_connection *connection,
+                             struct ssh_channel *channel,
+                             struct request_status *active)
+{
+  NEW(channel_request_continuation, self);
+
+  self->super.c = do_channel_request_response;
+  self->connection = connection;
+  self->channel = channel;
+  self->active = active;
+
+  return &self->super;
+}
+
+/* GABA:
+   (class
+     (name channel_request_exception_handler)
+     (super exception_handler)
+     (vars
+       (connection object ssh_connection)
+       (channel object ssh_channel)
+       (active object request_status)))
+*/
+
+static void 
+do_channel_request_handler(struct exception_handler *c,
+                          const struct exception *e)
+{
+  CAST(channel_request_exception_handler, self, c);
+  struct object_queue *q = &self->channel->active_requests;
+
+  assert(self->active->status == -1);
+  assert(!object_queue_is_empty(q));
+
+  self->active->status = 0;
+  
+  send_channel_request_responses(self->connection, self->channel, q);
+  if (e->type != EXC_CHANNEL_REQUEST)
+    EXCEPTION_RAISE(c->parent, e);
+}
+
+static struct exception_handler *
+make_channel_request_exception_handler(struct ssh_connection *connection,
+                                      struct ssh_channel *channel,
+                                      struct request_status *active,
+                                      struct exception_handler *h)
+{
+  NEW(channel_request_exception_handler, self);
+
+  self->super.raise = do_channel_request_handler;
+  self->super.parent = h;
+  self->connection = connection;
+  self->channel = channel;
+  self->active = active;
+  return &self->super;
+}
+
 static void
 do_channel_request(struct packet_handler *closure UNUSED,
                   struct ssh_connection *connection,
@@ -812,10 +932,25 @@
       if (channel)
        {
          struct channel_request *req;
+         struct command_continuation *c = &discard_continuation;
+         struct exception_handler *e = channel->e;
 
          if (type && channel->request_types 
              && ( (req = ALIST_GET(channel->request_types, type)) ))
-           CHANNEL_REQUEST(req, channel, connection, want_reply, &buffer);
+           {
+             if (want_reply)
+               {
+                 struct request_status *a = make_request_status();
+                 
+                 object_queue_add_tail(&channel->active_requests,
+                                       &a->super);
+                 
+                 c = make_channel_request_response(connection, channel, a);
+                 e = make_channel_request_exception_handler(connection, channel, a, 
+e);
+                 
+               }
+             CHANNEL_REQUEST(req, channel, connection, type, want_reply, &buffer, c, 
+e);
+           }
          else
            {
              if (want_reply)
@@ -1498,6 +1633,7 @@
   channel->resources = empty_resource_list();
   
   object_queue_init(&channel->pending_requests);
+  object_queue_init(&channel->active_requests);
 }
 
 struct lsh_string *channel_transmit_data(struct ssh_channel *channel,
diff -urN --exclude-from=diff-exclude lsh-0.2.3-orig/src/channel.h 
lsh-0.2.3-bazsi/src/channel.h
--- lsh-0.2.3-orig/src/channel.h        Wed Jan 12 23:31:03 2000
+++ lsh-0.2.3-bazsi/src/channel.h       Thu Jan 20 10:59:27 2000
@@ -124,7 +124,11 @@
        (open_continuation object command_continuation)
 
        ; Queue of channel requests that we expect replies on
-       (pending_requests struct object_queue)))
+       (pending_requests struct object_queue)
+
+       ; Channel requests that we have received, and should reply to
+       ; in the right order
+       (active_requests struct object_queue)))
        
        ; Reply from SSH_MSG_CHANNEL_REQUEST 
        ;; (channel_success method int)
@@ -201,30 +205,19 @@
        ))
 */
 
-
 /* SSH_MSG_GLOBAL_REQUEST */
 
 /* GABA:
    (class
-     (name global_request_callback)
-     (vars
-       (response method void "int success")
-       (connection object ssh_connection)))
-*/
-
-#define GLOBAL_REQUEST_CALLBACK(c, s) \
-((c)->response((c), (s)))
-
-/* GABA:
-   (class
      (name global_request)
      (vars
        (handler method void "struct ssh_connection *connection"
                             "struct simple_buffer *args"
-                           "struct global_request_callback *response")))
+                           "struct command_continuation *c"
+                           "struct exception_handler *e")))
 */
 
-#define GLOBAL_REQUEST(r, c, a, n) ((r)->handler((r), (c), (a), (n)))
+#define GLOBAL_REQUEST(r, c, a, n, e) ((r)->handler((r), (c), (a), (n), (e)))
 
 /* SSH_MSG_CHANNEL_OPEN */
 
@@ -264,20 +257,15 @@
        (handler method void
                "struct ssh_channel *channel"
                "struct ssh_connection *connection"
+               "UINT32 type"
                "int want_reply"
-               "struct simple_buffer *args")))
+               "struct simple_buffer *args"
+               "struct command_continuation *c"
+               "struct exception_handler *e")))
 */
 
-#define CHANNEL_REQUEST(s, c, conn, w, a) \
-((s)->handler((s), (c), (conn), (w), (a)))
-
-/* ;;GABA:
-   (class
-     (name connection_startup)
-     (vars
-       (start method int
-             "struct ssh_connection *connection")))
-*/
+#define CHANNEL_REQUEST(s, c, conn, t, w, a, n, e) \
+((s)->handler((s), (c), (conn), (t), (w), (a), (n), (e)))
 
 /* #define CONNECTION_START(c, s) ((c)->start((c), (s))) */
 
diff -urN --exclude-from=diff-exclude lsh-0.2.3-orig/src/client.c 
lsh-0.2.3-bazsi/src/client.c
--- lsh-0.2.3-orig/src/client.c Sun Dec 12 19:47:53 1999
+++ lsh-0.2.3-bazsi/src/client.c        Thu Jan 20 10:11:02 2000
@@ -228,8 +228,11 @@
 do_exit_status(struct channel_request *c,
               struct ssh_channel *channel,
               struct ssh_connection *connection UNUSED,
+              UINT32 type UNUSED,
               int want_reply,
-              struct simple_buffer *args)
+              struct simple_buffer *args,
+              struct command_continuation *s,
+              struct exception_handler *e)
 {
   CAST(exit_handler, closure, c);
   int status;
@@ -248,18 +251,24 @@
        * child process alive that we could talk to. */
 
       channel_eof(channel);
+      COMMAND_RETURN(s, NULL);
     }
   else
-    /* Invalid request */
-    PROTOCOL_ERROR(channel->e, "Invalid exit-status message");
+    {
+      /* Invalid request */
+      PROTOCOL_ERROR(e, "Invalid exit-status message");
+    }
 }
 
 static void
 do_exit_signal(struct channel_request *c,
               struct ssh_channel *channel,
               struct ssh_connection *connection UNUSED,
+              UINT32 type UNUSED,
               int want_reply,
-              struct simple_buffer *args)
+              struct simple_buffer *args,
+              struct command_continuation *s,
+              struct exception_handler *e)
 {
   CAST(exit_handler, closure, c);
 
@@ -299,10 +308,13 @@
        * child process alive that we could talk to. */
 
       channel_eof(channel);
+      COMMAND_RETURN(s, NULL);
     }
   else
-    /* Invalid request */
-    PROTOCOL_ERROR(channel->e, "Invalid exit-signal message");
+    {
+      /* Invalid request */
+      PROTOCOL_ERROR(e, "Invalid exit-signal message");
+    }
 }
 
 struct channel_request *make_handle_exit_status(int *exit_status)
diff -urN --exclude-from=diff-exclude lsh-0.2.3-orig/src/server_session.c 
lsh-0.2.3-bazsi/src/server_session.c
--- lsh-0.2.3-orig/src/server_session.c Wed Jan 12 23:50:00 2000
+++ lsh-0.2.3-bazsi/src/server_session.c        Thu Jan 20 11:05:41 2000
@@ -617,12 +617,18 @@
 
 #define USE_LOGIN_DASH_CONVENTION 1
 
+static struct exception shell_request_failed =
+STATIC_EXCEPTION(EXC_CHANNEL_REQUEST, "Shell request failed");
+
 static void
 do_spawn_shell(struct channel_request *c,
               struct ssh_channel *channel,
-              struct ssh_connection *connection,
-              int want_reply,
-              struct simple_buffer *args)
+              struct ssh_connection *connection UNUSED,
+              UINT32 type UNUSED,
+              int want_reply UNUSED,
+              struct simple_buffer *args,
+              struct command_continuation *s,
+              struct exception_handler *e)
 {
   CAST(shell_request, closure, c);
   struct server_session *session = (struct server_session *) channel;
@@ -637,7 +643,7 @@
 
   if (!parse_eod(args))
     {
-      PROTOCOL_ERROR(connection->e, "Invalid shell CHANNEL_REQUEST message.");
+      PROTOCOL_ERROR(e, "Invalid shell CHANNEL_REQUEST message.");
       return;
     }
     
@@ -899,9 +905,12 @@
          REMEMBER_RESOURCE
            (channel->resources, &session->err->super.super);
 
-       if (want_reply)
-         A_WRITE(channel->write,
-                 format_channel_success(channel->channel_number) );
+       COMMAND_RETURN(s, NULL);
+       /*
+         if (want_reply)
+           A_WRITE(channel->write,
+                   format_channel_success(channel->channel_number) );
+       */
 
        channel_start_receive(channel);
        return;
@@ -914,9 +923,13 @@
     close(in[1]);
   }
  fail:
-  if (want_reply)
+  EXCEPTION_RAISE(e, &shell_request_failed);
+  /* COMMAND_RETURN(s, NULL); */
+  
+  /*
     A_WRITE(channel->write,
-           format_channel_failure(channel->channel_number));
+            format_channel_failure(channel->channel_number));
+  */
 }
 
 struct channel_request *make_shell_handler(struct io_backend *backend,
@@ -932,13 +945,20 @@
 }
 
 #if WITH_PTY_SUPPORT
+
+static struct exception pty_request_failed =
+STATIC_EXCEPTION(EXC_CHANNEL_REQUEST, "pty request failed");
+
 /* pty_handler */
 static void
 do_alloc_pty(struct channel_request *c UNUSED,
             struct ssh_channel *channel,
             struct ssh_connection *connection UNUSED,
-            int want_reply,
-            struct simple_buffer *args)
+            UINT32 type UNUSED,
+            int want_reply UNUSED,
+            struct simple_buffer *args,
+            struct command_continuation *s,
+            struct exception_handler *e)
 {
   UINT32 width, height, width_p, height_p;
   UINT8 *mode;
@@ -988,9 +1008,12 @@
                  REMEMBER_RESOURCE(channel->resources, &pty->super);
 
                  verbose(" granted.\n");
-                 if (want_reply)
-                   A_WRITE(channel->write,
-                           format_channel_success(channel->channel_number) );
+                 COMMAND_RETURN(s, NULL);
+                 /*
+                   if (want_reply)
+                     A_WRITE(channel->write,
+                             format_channel_success(channel->channel_number) );
+                 */
                  return;
                }
              else
@@ -1003,10 +1026,12 @@
 
   verbose("Pty allocation failed.\n");
   lsh_string_free(term);
-
-  if (want_reply)
-    A_WRITE(channel->write,
-           format_channel_failure(channel->channel_number) );
+  /*
+    if (want_reply)
+      A_WRITE(channel->write,
+             format_channel_failure(channel->channel_number) );
+  */
+  EXCEPTION_RAISE(e, &pty_request_failed);
 }
 
 struct channel_request *make_pty_handler(void)
diff -urN --exclude-from=diff-exclude lsh-0.2.3-orig/src/tcpforward.c 
lsh-0.2.3-bazsi/src/tcpforward.c
--- lsh-0.2.3-orig/src/tcpforward.c     Wed Jan 12 23:52:09 2000
+++ lsh-0.2.3-bazsi/src/tcpforward.c    Thu Jan 20 11:07:27 2000
@@ -316,7 +316,7 @@
      (vars
        (connection object ssh_connection)
        (forward object local_port)
-       (c object global_request_callback)))
+       (c object command_continuation)))
 */
 
 /* FIXME: Split off an exception handler */
@@ -340,20 +340,20 @@
       assert(port);
       assert(port == self->forward);
       
-      GLOBAL_REQUEST_CALLBACK(self->c, 0);
+      COMMAND_RETURN(self->c, NULL);
     }
   
   REMEMBER_RESOURCE(self->connection->resources, &fd->super);
 
   self->forward->socket = fd;
 
-  GLOBAL_REQUEST_CALLBACK(self->c, 1);
+  COMMAND_RETURN(self->c, &self->forward->super.super);
 }
 
 static struct command_continuation *
 make_tcpip_forward_request_continuation(struct ssh_connection *connection,
                                        struct local_port *forward,
-                                       struct global_request_callback *c)
+                                       struct command_continuation *c)
 {
   NEW(tcpip_forward_request_continuation, self);
 
@@ -380,7 +380,8 @@
 do_tcpip_forward_request(struct global_request *s, 
                         struct ssh_connection *connection,
                         struct simple_buffer *args,
-                        struct global_request_callback *c)
+                        struct command_continuation *c,
+                        struct exception_handler *e)
 {
   CAST(tcpip_forward_request, self, s);
   struct lsh_string *bind_host;
@@ -396,15 +397,18 @@
       if (bind_port < 1024)
        {
          werror("Denying forwarding of privileged port %i.\n", bind_port);
-         GLOBAL_REQUEST_CALLBACK(c, 0);
+         COMMAND_RETURN(c, NULL);
          return;
        }
 
       if (lookup_forward(&connection->table->local_ports,
                         bind_host->length, bind_host->data, bind_port))
        {
+         static const struct exception again = 
+           STATIC_EXCEPTION(EXC_GLOBAL_REQUEST, "An already requested tcp-forward 
+requested again");
+
          verbose("An already requested tcp-forward requested again\n");
-         GLOBAL_REQUEST_CALLBACK(c, 0);
+         EXCEPTION_RAISE(e, &again);
          return;
        }
       
@@ -428,7 +432,7 @@
   else
     {
       werror("Incorrectly formatted tcpip-forward request\n");
-      PROTOCOL_ERROR(connection->e, "Invalid tcpip-forward message.");
+      PROTOCOL_ERROR(e, "Invalid tcpip-forward message.");
     }
 }
 
@@ -446,7 +450,8 @@
 do_tcpip_cancel_forward(struct global_request *s UNUSED, 
                        struct ssh_connection *connection,
                        struct simple_buffer *args,
-                       struct global_request_callback *c)
+                       struct command_continuation *c,
+                       struct exception_handler *e)
 {
   UINT32 bind_host_length;
   UINT8 *bind_host;
@@ -472,14 +477,16 @@
          close_fd(port->socket, 0);
          port->socket = NULL;
 
-         GLOBAL_REQUEST_CALLBACK(c, 1);
+         COMMAND_RETURN(c, NULL);
          return;
        }
       else
        {      
+         static const struct exception notfound = 
+           STATIC_EXCEPTION(EXC_GLOBAL_REQUEST, "Could not find tcpip-forward to 
+cancel");
          verbose("Could not find tcpip-forward to cancel\n");
 
-         GLOBAL_REQUEST_CALLBACK(c, 0);
+         EXCEPTION_RAISE(e, &notfound);
          return;
        }
     }
diff -urN --exclude-from=diff-exclude lsh-0.2.3-orig/src/zlib.c 
lsh-0.2.3-bazsi/src/zlib.c
--- lsh-0.2.3-orig/src/zlib.c   Sun Jan  9 19:02:27 2000
+++ lsh-0.2.3-bazsi/src/zlib.c  Wed Jan 19 14:41:04 2000
@@ -91,13 +91,15 @@
 }
 
 /* Estimates of the resulting packet sizes. We use fixnum arithmetic,
- * with one represented as 1<<10=1024. Only rates between 1/8 and 8
- * are used. */
+ * with one represented as 1<<10=1024. Only rates between 1/16 and 16
+ * are used. This may be a little too conservative; I have observed
+ * compression ratios of about 50. */
  
 #define RATE_UNIT 1024
-#define RATE_MAX (RATE_UNIT * 8)
-#define RATE_MIN (RATE_UNIT / 8)
+#define RATE_MAX (RATE_UNIT * 16)
+#define RATE_MIN (RATE_UNIT / 16)
 #define MARGIN 200
+#define INSIGNIFICANT 100
 
 static UINT32 estimate_size(UINT32 rate, UINT32 input, UINT32 max)
 {
@@ -108,17 +110,28 @@
 /* Assumes that input is nonzero */
 static UINT32 estimate_update(UINT32 rate, UINT32 input, UINT32 output)
 {
-  UINT32 estimate = output * rate / input;
-
-  if (estimate > RATE_MAX)
-    return RATE_MAX;
-  
   /* Decay old estimate */
   rate = rate * 15 / 16;
 
-  /* Follow the "envelope" */
-  rate = MAX(estimate, rate);
+  /* FIXME: Following the envelope is suboptimal for small inputs. We
+   * do it only for input packets of reasonable size. This method
+   * could be improved.
+   *
+   * Perhaps a linear combination k * rate + (1-k) estimate, where k
+   * depends on the size of the sample (i.e. input) would make sense?
+   * Or use different rate estimates for different lengths? */
+  
+  if (input > INSIGNIFICANT)
+    {
+      UINT32 estimate = output * RATE_UNIT / input;
+
+      if (estimate > RATE_MAX)
+       return RATE_MAX;
 
+      /* Follow the "envelope" */
+      rate = MAX(estimate, rate);
+    }
+  
   return MAX(rate, RATE_MIN);
 }
 
@@ -129,19 +142,30 @@
 {
   CAST(zlib_instance, self, c);
   struct string_buffer buffer;
-  UINT32 limit = self->max;
+  
+  /* LIMIT keeps track of the amount of storage we may still need to
+   * allocate. To detect that a packet grows unexpectedly large, we
+   * need a little extra buffer space beyond the maximum size. */
+  UINT32 limit = self->max + 1;
+
+  UINT32 estimate;
+  
+  debug("do_zlib: length in: %i\n", packet->length);
   
   if (!packet->length)
     {
       werror("do_zlib_deflate: Compressing empty packet.\n");
       return free ? packet : lsh_string_dup(packet);
     }
-  
+
+  estimate = estimate_size(self->rate, packet->length, self->max);
+  debug("do_zlib: estimate:  %i\n", estimate);
+
   string_buffer_init(&buffer, 
                     estimate_size(self->rate, packet->length, self->max));
 
   limit -= buffer.partial->length;
-  
+
   self->z.next_in = packet->data;
   self->z.avail_in = packet->length;
 
@@ -149,8 +173,6 @@
     {
       int rc;
       
-      assert(self->z.avail_in);
-      
       self->z.next_out = buffer.current;
       self->z.avail_out = buffer.left;
 
@@ -166,7 +188,32 @@
          return NULL;
        }
 
-      if (!self->z.avail_in)
+      /* NOTE: It's not enough to check that avail_in is zero to
+       * determine that all data have been flushed. avail_in == 0 and
+       * avail_out > 0 implies that all data has been flushed, but if
+       * avail_in == avail_out == 0, we have to allocate more output
+       * space. */
+        
+      if (!self->z.avail_in && !self->z.avail_out)
+       verbose("do_zlib: Both avail_in and avail_out are zero.\n");
+      
+      if (!self->z.avail_out)
+       { /* All output space consumed */  
+         if (!limit)
+           {
+             werror("do_zlib_deflate: Packet grew too large!\n");
+             if (free)
+               lsh_string_free(packet);
+
+             string_buffer_clear(&buffer);
+             return NULL;
+           }
+
+         /* Grow to about double size. */
+         string_buffer_grow(&buffer, MIN(limit, buffer.partial->length + buffer.total 
++ 100));
+         limit -= buffer.partial->length;
+       }
+      else if (!self->z.avail_in)
        { /* Compressed entire packet */
          UINT32 input = packet->length;
 
@@ -176,26 +223,17 @@
          packet =
            string_buffer_final(&buffer, self->z.avail_out);
 
-         self->rate = estimate_update(self->rate, input, packet->length);
+         assert(packet->length <= self->max);
 
-         return packet;
-       }
-      else
-       { /* All output space consumed */
-         assert(!self->z.avail_out);
-         
-         if (!limit)
-           {
-             werror("do_zlib_deflate: Packet grew too large!\n");
-             if (free)
-               lsh_string_free(packet);
+         debug("do_zlib: length out: %i\n", packet->length);
 
-             string_buffer_clear(&buffer);
-             return NULL;
-           }
+         if (packet->length > estimate)
+           verbose("do_zlib: Estimated size exceeded: input = %i, estimate = %i, 
+output = %i\n",
+                   input, estimate, packet->length);
+         
+         self->rate = estimate_update(self->rate, input, packet->length);
 
-         string_buffer_grow(&buffer, MIN(limit, packet->length + 100));
-         limit -= buffer.partial->length;
+         return packet;
        }
     }
 }

Reply via email to