2018-09-19 20:56:44 +00:00
|
|
|
/*
|
|
|
|
* Supporting routines used in common by all the various components of
|
|
|
|
* the SSH system.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <assert.h>
|
2018-09-19 19:36:40 +00:00
|
|
|
#include <stdlib.h>
|
2018-09-19 20:56:44 +00:00
|
|
|
|
2018-09-19 19:36:40 +00:00
|
|
|
#include "putty.h"
|
2018-09-19 20:56:44 +00:00
|
|
|
#include "ssh.h"
|
|
|
|
#include "sshchan.h"
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------
|
|
|
|
* Implementation of PacketQueue.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void pq_base_push(PacketQueueBase *pqb, PacketQueueNode *node)
|
|
|
|
{
|
|
|
|
assert(!node->next);
|
|
|
|
assert(!node->prev);
|
|
|
|
node->next = &pqb->end;
|
|
|
|
node->prev = pqb->end.prev;
|
|
|
|
node->next->prev = node;
|
|
|
|
node->prev->next = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pq_base_push_front(PacketQueueBase *pqb, PacketQueueNode *node)
|
|
|
|
{
|
|
|
|
assert(!node->next);
|
|
|
|
assert(!node->prev);
|
|
|
|
node->prev = &pqb->end;
|
|
|
|
node->next = pqb->end.next;
|
|
|
|
node->next->prev = node;
|
|
|
|
node->prev->next = node;
|
|
|
|
}
|
|
|
|
|
|
|
|
static PktIn *pq_in_get(PacketQueueBase *pqb, int pop)
|
|
|
|
{
|
|
|
|
PacketQueueNode *node = pqb->end.next;
|
|
|
|
if (node == &pqb->end)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (pop) {
|
|
|
|
node->next->prev = node->prev;
|
|
|
|
node->prev->next = node->next;
|
|
|
|
node->prev = node->next = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FROMFIELD(node, PktIn, qnode);
|
|
|
|
}
|
|
|
|
|
|
|
|
static PktOut *pq_out_get(PacketQueueBase *pqb, int pop)
|
|
|
|
{
|
|
|
|
PacketQueueNode *node = pqb->end.next;
|
|
|
|
if (node == &pqb->end)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (pop) {
|
|
|
|
node->next->prev = node->prev;
|
|
|
|
node->prev->next = node->next;
|
|
|
|
node->prev = node->next = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return FROMFIELD(node, PktOut, qnode);
|
|
|
|
}
|
|
|
|
|
|
|
|
void pq_in_init(PktInQueue *pq)
|
|
|
|
{
|
|
|
|
pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
|
|
|
|
pq->get = pq_in_get;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pq_out_init(PktOutQueue *pq)
|
|
|
|
{
|
|
|
|
pq->pqb.end.next = pq->pqb.end.prev = &pq->pqb.end;
|
|
|
|
pq->get = pq_out_get;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pq_in_clear(PktInQueue *pq)
|
|
|
|
{
|
|
|
|
PktIn *pkt;
|
|
|
|
while ((pkt = pq_pop(pq)) != NULL)
|
|
|
|
ssh_unref_packet(pkt);
|
|
|
|
}
|
|
|
|
|
|
|
|
void pq_out_clear(PktOutQueue *pq)
|
|
|
|
{
|
|
|
|
PktOut *pkt;
|
|
|
|
while ((pkt = pq_pop(pq)) != NULL)
|
|
|
|
ssh_free_pktout(pkt);
|
|
|
|
}
|
|
|
|
|
2018-09-19 19:30:40 +00:00
|
|
|
/*
|
|
|
|
* Concatenate the contents of the two queues q1 and q2, and leave the
|
|
|
|
* result in qdest. qdest must be either empty, or one of the input
|
|
|
|
* queues.
|
|
|
|
*/
|
|
|
|
void pq_base_concatenate(PacketQueueBase *qdest,
|
|
|
|
PacketQueueBase *q1, PacketQueueBase *q2)
|
2018-09-19 20:56:44 +00:00
|
|
|
{
|
2018-09-19 19:30:40 +00:00
|
|
|
struct PacketQueueNode *head1, *tail1, *head2, *tail2;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Extract the contents from both input queues, and empty them.
|
|
|
|
*/
|
|
|
|
|
|
|
|
head1 = (q1->end.next == &q1->end ? NULL : q1->end.next);
|
|
|
|
tail1 = (q1->end.prev == &q1->end ? NULL : q1->end.prev);
|
|
|
|
head2 = (q2->end.next == &q2->end ? NULL : q2->end.next);
|
|
|
|
tail2 = (q2->end.prev == &q2->end ? NULL : q2->end.prev);
|
|
|
|
|
|
|
|
q1->end.next = q1->end.prev = &q1->end;
|
|
|
|
q2->end.next = q2->end.prev = &q2->end;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Link the two lists together, handling the case where one or
|
|
|
|
* both is empty.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (tail1)
|
|
|
|
tail1->next = head2;
|
|
|
|
else
|
|
|
|
head1 = head2;
|
|
|
|
|
|
|
|
if (head2)
|
|
|
|
head2->prev = tail1;
|
|
|
|
else
|
|
|
|
tail2 = head1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check the destination queue is currently empty. (If it was one
|
|
|
|
* of the input queues, then it will be, because we emptied both
|
|
|
|
* of those just a moment ago.)
|
|
|
|
*/
|
|
|
|
|
|
|
|
assert(qdest->end.next == &qdest->end);
|
|
|
|
assert(qdest->end.prev == &qdest->end);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If our concatenated list has anything in it, then put it in
|
|
|
|
* dest.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!head1) {
|
|
|
|
assert(!tail2);
|
|
|
|
} else {
|
|
|
|
assert(tail2);
|
|
|
|
qdest->end.next = head1;
|
|
|
|
qdest->end.prev = tail2;
|
|
|
|
head1->prev = &qdest->end;
|
|
|
|
tail2->next = &qdest->end;
|
|
|
|
}
|
2018-09-19 20:56:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------
|
|
|
|
* Low-level functions for the packet structures themselves.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void ssh_pkt_BinarySink_write(BinarySink *bs,
|
|
|
|
const void *data, size_t len);
|
|
|
|
PktOut *ssh_new_packet(void)
|
|
|
|
{
|
|
|
|
PktOut *pkt = snew(PktOut);
|
|
|
|
|
|
|
|
BinarySink_INIT(pkt, ssh_pkt_BinarySink_write);
|
|
|
|
pkt->data = NULL;
|
|
|
|
pkt->length = 0;
|
|
|
|
pkt->maxlen = 0;
|
|
|
|
pkt->downstream_id = 0;
|
|
|
|
pkt->additional_log_text = NULL;
|
|
|
|
pkt->qnode.next = pkt->qnode.prev = NULL;
|
|
|
|
|
|
|
|
return pkt;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh_pkt_ensure(PktOut *pkt, int length)
|
|
|
|
{
|
|
|
|
if (pkt->maxlen < length) {
|
|
|
|
pkt->maxlen = length + 256;
|
|
|
|
pkt->data = sresize(pkt->data, pkt->maxlen, unsigned char);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len)
|
|
|
|
{
|
|
|
|
pkt->length += len;
|
|
|
|
ssh_pkt_ensure(pkt, pkt->length);
|
|
|
|
memcpy(pkt->data + pkt->length - len, data, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh_pkt_BinarySink_write(BinarySink *bs,
|
|
|
|
const void *data, size_t len)
|
|
|
|
{
|
|
|
|
PktOut *pkt = BinarySink_DOWNCAST(bs, PktOut);
|
|
|
|
ssh_pkt_adddata(pkt, data, len);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ssh_unref_packet(PktIn *pkt)
|
|
|
|
{
|
|
|
|
if (--pkt->refcount <= 0)
|
|
|
|
sfree(pkt);
|
|
|
|
}
|
|
|
|
|
|
|
|
void ssh_free_pktout(PktOut *pkt)
|
|
|
|
{
|
|
|
|
sfree(pkt->data);
|
|
|
|
sfree(pkt);
|
|
|
|
}
|
2018-09-19 19:31:19 +00:00
|
|
|
/* ----------------------------------------------------------------------
|
|
|
|
* Implement zombiechan_new() and its trivial vtable.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void zombiechan_free(Channel *chan);
|
|
|
|
static int zombiechan_send(Channel *chan, int is_stderr, const void *, int);
|
|
|
|
static void zombiechan_set_input_wanted(Channel *chan, int wanted);
|
|
|
|
static void zombiechan_do_nothing(Channel *chan);
|
|
|
|
static void zombiechan_open_failure(Channel *chan, const char *);
|
|
|
|
static int zombiechan_want_close(Channel *chan, int sent_eof, int rcvd_eof);
|
|
|
|
static char *zombiechan_log_close_msg(Channel *chan) { return NULL; }
|
|
|
|
|
|
|
|
static const struct ChannelVtable zombiechan_channelvt = {
|
|
|
|
zombiechan_free,
|
|
|
|
zombiechan_do_nothing, /* open_confirmation */
|
|
|
|
zombiechan_open_failure,
|
|
|
|
zombiechan_send,
|
|
|
|
zombiechan_do_nothing, /* send_eof */
|
|
|
|
zombiechan_set_input_wanted,
|
|
|
|
zombiechan_log_close_msg,
|
|
|
|
zombiechan_want_close,
|
|
|
|
};
|
|
|
|
|
|
|
|
Channel *zombiechan_new(void)
|
|
|
|
{
|
|
|
|
Channel *chan = snew(Channel);
|
|
|
|
chan->vt = &zombiechan_channelvt;
|
|
|
|
chan->initial_fixed_window_size = 0;
|
|
|
|
return chan;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void zombiechan_free(Channel *chan)
|
|
|
|
{
|
|
|
|
assert(chan->vt == &zombiechan_channelvt);
|
|
|
|
sfree(chan);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void zombiechan_do_nothing(Channel *chan)
|
|
|
|
{
|
|
|
|
assert(chan->vt == &zombiechan_channelvt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void zombiechan_open_failure(Channel *chan, const char *errtext)
|
|
|
|
{
|
|
|
|
assert(chan->vt == &zombiechan_channelvt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int zombiechan_send(Channel *chan, int is_stderr,
|
|
|
|
const void *data, int length)
|
|
|
|
{
|
|
|
|
assert(chan->vt == &zombiechan_channelvt);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void zombiechan_set_input_wanted(Channel *chan, int enable)
|
|
|
|
{
|
|
|
|
assert(chan->vt == &zombiechan_channelvt);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int zombiechan_want_close(Channel *chan, int sent_eof, int rcvd_eof)
|
|
|
|
{
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2018-09-19 19:32:27 +00:00
|
|
|
/* ----------------------------------------------------------------------
|
|
|
|
* Centralised standard methods for other channel implementations to
|
|
|
|
* borrow.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void chan_remotely_opened_confirmation(Channel *chan)
|
|
|
|
{
|
|
|
|
assert(0 && "this channel type should never receive OPEN_CONFIRMATION");
|
|
|
|
}
|
|
|
|
|
|
|
|
void chan_remotely_opened_failure(Channel *chan, const char *errtext)
|
|
|
|
{
|
|
|
|
assert(0 && "this channel type should never receive OPEN_FAILURE");
|
|
|
|
}
|
|
|
|
|
|
|
|
int chan_no_eager_close(Channel *chan, int sent_local_eof, int rcvd_remote_eof)
|
|
|
|
{
|
|
|
|
return FALSE; /* default: never proactively ask for a close */
|
|
|
|
}
|
2018-09-19 19:36:40 +00:00
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------
|
|
|
|
* Common routine to marshal tty modes into an SSH packet.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void write_ttymodes_to_packet_from_conf(
|
|
|
|
BinarySink *bs, Frontend *frontend, Conf *conf,
|
|
|
|
int ssh_version, int ospeed, int ispeed)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Codes for terminal modes.
|
|
|
|
* Most of these are the same in SSH-1 and SSH-2.
|
|
|
|
* This list is derived from RFC 4254 and
|
|
|
|
* SSH-1 RFC-1.2.31.
|
|
|
|
*/
|
|
|
|
static const struct ssh_ttymode {
|
|
|
|
const char *mode;
|
|
|
|
int opcode;
|
|
|
|
enum { TTY_OP_CHAR, TTY_OP_BOOL } type;
|
|
|
|
} ssh_ttymodes[] = {
|
|
|
|
/* "V" prefix discarded for special characters relative to SSH specs */
|
|
|
|
{ "INTR", 1, TTY_OP_CHAR },
|
|
|
|
{ "QUIT", 2, TTY_OP_CHAR },
|
|
|
|
{ "ERASE", 3, TTY_OP_CHAR },
|
|
|
|
{ "KILL", 4, TTY_OP_CHAR },
|
|
|
|
{ "EOF", 5, TTY_OP_CHAR },
|
|
|
|
{ "EOL", 6, TTY_OP_CHAR },
|
|
|
|
{ "EOL2", 7, TTY_OP_CHAR },
|
|
|
|
{ "START", 8, TTY_OP_CHAR },
|
|
|
|
{ "STOP", 9, TTY_OP_CHAR },
|
|
|
|
{ "SUSP", 10, TTY_OP_CHAR },
|
|
|
|
{ "DSUSP", 11, TTY_OP_CHAR },
|
|
|
|
{ "REPRINT", 12, TTY_OP_CHAR },
|
|
|
|
{ "WERASE", 13, TTY_OP_CHAR },
|
|
|
|
{ "LNEXT", 14, TTY_OP_CHAR },
|
|
|
|
{ "FLUSH", 15, TTY_OP_CHAR },
|
|
|
|
{ "SWTCH", 16, TTY_OP_CHAR },
|
|
|
|
{ "STATUS", 17, TTY_OP_CHAR },
|
|
|
|
{ "DISCARD", 18, TTY_OP_CHAR },
|
|
|
|
{ "IGNPAR", 30, TTY_OP_BOOL },
|
|
|
|
{ "PARMRK", 31, TTY_OP_BOOL },
|
|
|
|
{ "INPCK", 32, TTY_OP_BOOL },
|
|
|
|
{ "ISTRIP", 33, TTY_OP_BOOL },
|
|
|
|
{ "INLCR", 34, TTY_OP_BOOL },
|
|
|
|
{ "IGNCR", 35, TTY_OP_BOOL },
|
|
|
|
{ "ICRNL", 36, TTY_OP_BOOL },
|
|
|
|
{ "IUCLC", 37, TTY_OP_BOOL },
|
|
|
|
{ "IXON", 38, TTY_OP_BOOL },
|
|
|
|
{ "IXANY", 39, TTY_OP_BOOL },
|
|
|
|
{ "IXOFF", 40, TTY_OP_BOOL },
|
|
|
|
{ "IMAXBEL", 41, TTY_OP_BOOL },
|
|
|
|
{ "IUTF8", 42, TTY_OP_BOOL },
|
|
|
|
{ "ISIG", 50, TTY_OP_BOOL },
|
|
|
|
{ "ICANON", 51, TTY_OP_BOOL },
|
|
|
|
{ "XCASE", 52, TTY_OP_BOOL },
|
|
|
|
{ "ECHO", 53, TTY_OP_BOOL },
|
|
|
|
{ "ECHOE", 54, TTY_OP_BOOL },
|
|
|
|
{ "ECHOK", 55, TTY_OP_BOOL },
|
|
|
|
{ "ECHONL", 56, TTY_OP_BOOL },
|
|
|
|
{ "NOFLSH", 57, TTY_OP_BOOL },
|
|
|
|
{ "TOSTOP", 58, TTY_OP_BOOL },
|
|
|
|
{ "IEXTEN", 59, TTY_OP_BOOL },
|
|
|
|
{ "ECHOCTL", 60, TTY_OP_BOOL },
|
|
|
|
{ "ECHOKE", 61, TTY_OP_BOOL },
|
|
|
|
{ "PENDIN", 62, TTY_OP_BOOL }, /* XXX is this a real mode? */
|
|
|
|
{ "OPOST", 70, TTY_OP_BOOL },
|
|
|
|
{ "OLCUC", 71, TTY_OP_BOOL },
|
|
|
|
{ "ONLCR", 72, TTY_OP_BOOL },
|
|
|
|
{ "OCRNL", 73, TTY_OP_BOOL },
|
|
|
|
{ "ONOCR", 74, TTY_OP_BOOL },
|
|
|
|
{ "ONLRET", 75, TTY_OP_BOOL },
|
|
|
|
{ "CS7", 90, TTY_OP_BOOL },
|
|
|
|
{ "CS8", 91, TTY_OP_BOOL },
|
|
|
|
{ "PARENB", 92, TTY_OP_BOOL },
|
|
|
|
{ "PARODD", 93, TTY_OP_BOOL }
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Miscellaneous other tty-related constants. */
|
|
|
|
enum {
|
|
|
|
/* The opcodes for ISPEED/OSPEED differ between SSH-1 and SSH-2. */
|
|
|
|
SSH1_TTY_OP_ISPEED = 192,
|
|
|
|
SSH1_TTY_OP_OSPEED = 193,
|
|
|
|
SSH2_TTY_OP_ISPEED = 128,
|
|
|
|
SSH2_TTY_OP_OSPEED = 129,
|
|
|
|
|
|
|
|
SSH_TTY_OP_END = 0
|
|
|
|
};
|
|
|
|
|
|
|
|
for (i = 0; i < lenof(ssh_ttymodes); i++) {
|
|
|
|
const struct ssh_ttymode *mode = ssh_ttymodes + i;
|
|
|
|
const char *sval = conf_get_str_str(conf, CONF_ttymodes, mode->mode);
|
|
|
|
char *to_free = NULL;
|
|
|
|
|
|
|
|
/* Every mode known to the current version of the code should be
|
|
|
|
* mentioned; this was ensured when settings were loaded. */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* sval[0] can be
|
|
|
|
* - 'V', indicating that an explicit value follows it;
|
|
|
|
* - 'A', indicating that we should pass the value through from
|
|
|
|
* the local environment via get_ttymode; or
|
|
|
|
* - 'N', indicating that we should explicitly not send this
|
|
|
|
* mode.
|
|
|
|
*/
|
|
|
|
if (sval[0] == 'A') {
|
|
|
|
sval = to_free = get_ttymode(frontend, mode->mode);
|
|
|
|
} else if (sval[0] == 'V') {
|
|
|
|
sval++; /* skip the 'V' */
|
|
|
|
} else {
|
|
|
|
/* else 'N', or something from the future we don't understand */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sval) {
|
|
|
|
/*
|
|
|
|
* Parse the string representation of the tty mode
|
|
|
|
* into the integer value it will take on the wire.
|
|
|
|
*/
|
|
|
|
unsigned ival = 0;
|
|
|
|
|
|
|
|
switch (mode->type) {
|
|
|
|
case TTY_OP_CHAR:
|
|
|
|
if (*sval) {
|
|
|
|
char *next = NULL;
|
|
|
|
/* We know ctrlparse won't write to the string, so
|
|
|
|
* casting away const is ugly but allowable. */
|
|
|
|
ival = ctrlparse((char *)sval, &next);
|
|
|
|
if (!next)
|
|
|
|
ival = sval[0];
|
|
|
|
} else {
|
|
|
|
ival = 255; /* special value meaning "don't set" */
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case TTY_OP_BOOL:
|
|
|
|
if (stricmp(sval, "yes") == 0 ||
|
|
|
|
stricmp(sval, "on") == 0 ||
|
|
|
|
stricmp(sval, "true") == 0 ||
|
|
|
|
stricmp(sval, "+") == 0)
|
|
|
|
ival = 1; /* true */
|
|
|
|
else if (stricmp(sval, "no") == 0 ||
|
|
|
|
stricmp(sval, "off") == 0 ||
|
|
|
|
stricmp(sval, "false") == 0 ||
|
|
|
|
stricmp(sval, "-") == 0)
|
|
|
|
ival = 0; /* false */
|
|
|
|
else
|
|
|
|
ival = (atoi(sval) != 0);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
assert(0 && "Bad mode->type");
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* And write it into the output packet. The parameter
|
|
|
|
* value is formatted as a byte in SSH-1, but a uint32
|
|
|
|
* in SSH-2.
|
|
|
|
*/
|
|
|
|
put_byte(bs, mode->opcode);
|
|
|
|
if (ssh_version == 1)
|
|
|
|
put_byte(bs, ival);
|
|
|
|
else
|
|
|
|
put_uint32(bs, ival);
|
|
|
|
}
|
|
|
|
|
|
|
|
sfree(to_free);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Finish off with the terminal speeds (which are formatted as
|
|
|
|
* uint32 in both protocol versions) and the end marker.
|
|
|
|
*/
|
|
|
|
put_byte(bs, ssh_version == 1 ? SSH1_TTY_OP_ISPEED : SSH2_TTY_OP_ISPEED);
|
|
|
|
put_uint32(bs, ispeed);
|
|
|
|
put_byte(bs, ssh_version == 1 ? SSH1_TTY_OP_OSPEED : SSH2_TTY_OP_OSPEED);
|
|
|
|
put_uint32(bs, ospeed);
|
|
|
|
put_byte(bs, SSH_TTY_OP_END);
|
|
|
|
}
|
2018-09-19 19:37:12 +00:00
|
|
|
|
|
|
|
/* ----------------------------------------------------------------------
|
|
|
|
* Routine for allocating a new channel ID, given a means of finding
|
|
|
|
* the index field in a given channel structure.
|
|
|
|
*/
|
|
|
|
|
|
|
|
unsigned alloc_channel_id_general(tree234 *channels, size_t localid_offset)
|
|
|
|
{
|
|
|
|
const unsigned CHANNEL_NUMBER_OFFSET = 256;
|
|
|
|
search234_state ss;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* First-fit allocation of channel numbers: we always pick the
|
|
|
|
* lowest unused one.
|
|
|
|
*
|
|
|
|
* Every channel before that, and no channel after it, has an ID
|
|
|
|
* exactly equal to its tree index plus CHANNEL_NUMBER_OFFSET. So
|
|
|
|
* we can use the search234 system to identify the length of that
|
|
|
|
* initial sequence, in a single log-time pass down the channels
|
|
|
|
* tree.
|
|
|
|
*/
|
|
|
|
search234_start(&ss, channels);
|
|
|
|
while (ss.element) {
|
|
|
|
unsigned localid = *(unsigned *)((char *)ss.element + localid_offset);
|
|
|
|
if (localid == ss.index + CHANNEL_NUMBER_OFFSET)
|
|
|
|
search234_step(&ss, +1);
|
|
|
|
else
|
|
|
|
search234_step(&ss, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Now ss.index gives exactly the number of channels in that
|
|
|
|
* initial sequence. So adding CHANNEL_NUMBER_OFFSET to it must
|
|
|
|
* give precisely the lowest unused channel number.
|
|
|
|
*/
|
|
|
|
return ss.index + CHANNEL_NUMBER_OFFSET;
|
|
|
|
}
|