+ ND_PRINT((ndo, " [|ubik]"));
+}
+
+/*
+ * Handle RX ACK packets.
+ */
+
+static void
+rx_ack_print(netdissect_options *ndo,
+ register const u_char *bp, int length)
+{
+ struct rx_ackPacket *rxa;
+ int i, start, last;
+ uint32_t firstPacket;
+
+ if (length < (int)sizeof(struct rx_header))
+ return;
+
+ bp += sizeof(struct rx_header);
+
+ /*
+ * This may seem a little odd .... the rx_ackPacket structure
+ * contains an array of individual packet acknowledgements
+ * (used for selective ack/nack), but since it's variable in size,
+ * we don't want to truncate based on the size of the whole
+ * rx_ackPacket structure.
+ */
+
+ ND_TCHECK2(bp[0], sizeof(struct rx_ackPacket) - RX_MAXACKS);
+
+ rxa = (struct rx_ackPacket *) bp;
+ bp += (sizeof(struct rx_ackPacket) - RX_MAXACKS);
+
+ /*
+ * Print out a few useful things from the ack packet structure
+ */
+
+ if (ndo->ndo_vflag > 2)
+ ND_PRINT((ndo, " bufspace %d maxskew %d",
+ (int) EXTRACT_16BITS(&rxa->bufferSpace),
+ (int) EXTRACT_16BITS(&rxa->maxSkew)));
+
+ firstPacket = EXTRACT_32BITS(&rxa->firstPacket);
+ ND_PRINT((ndo, " first %d serial %d reason %s",
+ firstPacket, EXTRACT_32BITS(&rxa->serial),
+ tok2str(rx_ack_reasons, "#%d", (int) rxa->reason)));
+
+ /*
+ * Okay, now we print out the ack array. The way _this_ works
+ * is that we start at "first", and step through the ack array.
+ * If we have a contiguous range of acks/nacks, try to
+ * collapse them into a range.
+ *
+ * If you're really clever, you might have noticed that this
+ * doesn't seem quite correct. Specifically, due to structure
+ * padding, sizeof(struct rx_ackPacket) - RX_MAXACKS won't actually
+ * yield the start of the ack array (because RX_MAXACKS is 255
+ * and the structure will likely get padded to a 2 or 4 byte
+ * boundary). However, this is the way it's implemented inside
+ * of AFS - the start of the extra fields are at
+ * sizeof(struct rx_ackPacket) - RX_MAXACKS + nAcks, which _isn't_
+ * the exact start of the ack array. Sigh. That's why we aren't
+ * using bp, but instead use rxa->acks[]. But nAcks gets added
+ * to bp after this, so bp ends up at the right spot. Go figure.
+ */
+
+ if (rxa->nAcks != 0) {
+
+ ND_TCHECK2(bp[0], rxa->nAcks);
+
+ /*
+ * Sigh, this is gross, but it seems to work to collapse
+ * ranges correctly.
+ */
+
+ for (i = 0, start = last = -2; i < rxa->nAcks; i++)
+ if (rxa->acks[i] == RX_ACK_TYPE_ACK) {
+
+ /*
+ * I figured this deserved _some_ explanation.
+ * First, print "acked" and the packet seq
+ * number if this is the first time we've
+ * seen an acked packet.
+ */
+
+ if (last == -2) {
+ ND_PRINT((ndo, " acked %d", firstPacket + i));
+ start = i;
+ }
+
+ /*
+ * Otherwise, if there is a skip in
+ * the range (such as an nacked packet in
+ * the middle of some acked packets),
+ * then print the current packet number
+ * seperated from the last number by
+ * a comma.
+ */
+
+ else if (last != i - 1) {
+ ND_PRINT((ndo, ",%d", firstPacket + i));
+ start = i;
+ }
+
+ /*
+ * We always set last to the value of
+ * the last ack we saw. Conversely, start
+ * is set to the value of the first ack
+ * we saw in a range.
+ */
+
+ last = i;
+
+ /*
+ * Okay, this bit a code gets executed when
+ * we hit a nack ... in _this_ case we
+ * want to print out the range of packets
+ * that were acked, so we need to print
+ * the _previous_ packet number seperated
+ * from the first by a dash (-). Since we
+ * already printed the first packet above,
+ * just print the final packet. Don't
+ * do this if there will be a single-length
+ * range.
+ */
+ } else if (last == i - 1 && start != last)
+ ND_PRINT((ndo, "-%d", firstPacket + i - 1));
+
+ /*
+ * So, what's going on here? We ran off the end of the
+ * ack list, and if we got a range we need to finish it up.
+ * So we need to determine if the last packet in the list
+ * was an ack (if so, then last will be set to it) and
+ * we need to see if the last range didn't start with the
+ * last packet (because if it _did_, then that would mean
+ * that the packet number has already been printed and
+ * we don't need to print it again).
+ */
+
+ if (last == i - 1 && start != last)
+ ND_PRINT((ndo, "-%d", firstPacket + i - 1));
+
+ /*
+ * Same as above, just without comments
+ */
+
+ for (i = 0, start = last = -2; i < rxa->nAcks; i++)
+ if (rxa->acks[i] == RX_ACK_TYPE_NACK) {
+ if (last == -2) {
+ ND_PRINT((ndo, " nacked %d", firstPacket + i));
+ start = i;
+ } else if (last != i - 1) {
+ ND_PRINT((ndo, ",%d", firstPacket + i));
+ start = i;
+ }
+ last = i;
+ } else if (last == i - 1 && start != last)
+ ND_PRINT((ndo, "-%d", firstPacket + i - 1));
+
+ if (last == i - 1 && start != last)
+ ND_PRINT((ndo, "-%d", firstPacket + i - 1));
+
+ bp += rxa->nAcks;
+ }
+
+
+ /*
+ * These are optional fields; depending on your version of AFS,
+ * you may or may not see them
+ */
+
+#define TRUNCRET(n) if (ndo->ndo_snapend - bp + 1 <= n) return;
+
+ if (ndo->ndo_vflag > 1) {
+ TRUNCRET(4);
+ ND_PRINT((ndo, " ifmtu"));
+ INTOUT();
+
+ TRUNCRET(4);
+ ND_PRINT((ndo, " maxmtu"));
+ INTOUT();
+
+ TRUNCRET(4);
+ ND_PRINT((ndo, " rwind"));
+ INTOUT();
+
+ TRUNCRET(4);
+ ND_PRINT((ndo, " maxpackets"));
+ INTOUT();
+ }
+
+ return;
+
+trunc:
+ ND_PRINT((ndo, " [|ack]"));