static int tipc_link_advance_transmq(struct tipc_link *l, struct tipc_link *r,
				     u16 acked, u16 gap,
				     struct tipc_gap_ack_blks *ga,
				     struct sk_buff_head *xmitq,
				     bool *retransmitted, int *rc)
{
	struct tipc_gap_ack_blks *last_ga = r->last_ga, *this_ga = NULL;
	struct tipc_gap_ack *gacks = NULL;
	struct sk_buff *skb, *_skb, *tmp;
	struct tipc_msg *hdr;
	u32 qlen = skb_queue_len(&l->transmq);
	u16 nacked = acked, ngap = gap, gack_cnt = 0;
	u16 bc_ack = l->bc_rcvlink->rcv_nxt - 1;
	u16 ack = l->rcv_nxt - 1;
	u16 seqno, n = 0;
	u16 end = r->acked, start = end, offset = r->last_gap;
	u16 si = (last_ga) ? last_ga->start_index : 0;
	bool is_uc = !link_is_bc_sndlink(l);
	bool bc_has_acked = false;

	trace_tipc_link_retrans(r, acked + 1, acked + gap, &l->transmq);

	/* Determine Gap ACK blocks if any for the particular link */
	if (ga && is_uc) {
		/* Get the Gap ACKs, uc part */
		gack_cnt = ga->ugack_cnt;
		gacks = &ga->gacks[ga->bgack_cnt];
	} else if (ga) {
		/* Copy the Gap ACKs, bc part, for later renewal if needed */
		this_ga = kmemdup(ga, struct_size(ga, gacks, ga->bgack_cnt),
				  GFP_ATOMIC);
		if (likely(this_ga)) {
			this_ga->start_index = 0;
			/* Start with the bc Gap ACKs */
			gack_cnt = this_ga->bgack_cnt;
			gacks = &this_ga->gacks[0];
		} else {
			/* Hmm, we can get in trouble..., simply ignore it */
			pr_warn_ratelimited("Ignoring bc Gap ACKs, no memory\n");
		}
	}

	/* Advance the link transmq */
	skb_queue_walk_safe(&l->transmq, skb, tmp) {
		seqno = buf_seqno(skb);

next_gap_ack:
		if (less_eq(seqno, nacked)) {
			if (is_uc)
				goto release;
			/* Skip packets peer has already acked */
			if (!more(seqno, r->acked))
				continue;
			/* Get the next of last Gap ACK blocks */
			while (more(seqno, end)) {
				if (!last_ga || si >= last_ga->bgack_cnt)
					break;
				start = end + offset + 1;
				end = ntohs(last_ga->gacks[si].ack);
				offset = ntohs(last_ga->gacks[si].gap);
				si++;
				WARN_ONCE(more(start, end) ||
					  (!offset &&
					   si < last_ga->bgack_cnt) ||
					  si > MAX_GAP_ACK_BLKS,
					  "Corrupted Gap ACK: %d %d %d %d %d\n",
					  start, end, offset, si,
					  last_ga->bgack_cnt);
			}
			/* Check against the last Gap ACK block */
			if (in_range(seqno, start, end))
				continue;
			/* Update/release the packet peer is acking */
			bc_has_acked = true;
			if (--TIPC_SKB_CB(skb)->ackers)
				continue;
release:
			/* release skb */
			__skb_unlink(skb, &l->transmq);
			kfree_skb(skb);
		} else if (less_eq(seqno, nacked + ngap)) {
			/* First gap: check if repeated retrans failures? */
			if (unlikely(seqno == acked + 1 &&
				     link_retransmit_failure(l, r, rc))) {
				/* Ignore this bc Gap ACKs if any */
				kfree(this_ga);
				this_ga = NULL;
				break;
			}
			/* retransmit skb if unrestricted*/
			if (time_before(jiffies, TIPC_SKB_CB(skb)->nxt_retr))
				continue;
			tipc_link_set_skb_retransmit_time(skb, l);
			_skb = pskb_copy(skb, GFP_ATOMIC);
			if (!_skb)
				continue;
			hdr = buf_msg(_skb);
			msg_set_ack(hdr, ack);
			msg_set_bcast_ack(hdr, bc_ack);
			_skb->priority = TC_PRIO_CONTROL;
			__skb_queue_tail(xmitq, _skb);
			l->stats.retransmitted++;
			if (!is_uc)
				r->stats.retransmitted++;
			*retransmitted = true;
			/* Increase actual retrans counter & mark first time */
			if (!TIPC_SKB_CB(skb)->retr_cnt++)
				TIPC_SKB_CB(skb)->retr_stamp = jiffies;
		} else {
			/* retry with Gap ACK blocks if any */
			if (n >= gack_cnt)
				break;
			nacked = ntohs(gacks[n].ack);
			ngap = ntohs(gacks[n].gap);
			n++;
			goto next_gap_ack;
		}
	}

	/* Renew last Gap ACK blocks for bc if needed */
	if (bc_has_acked) {
		if (this_ga) {
			kfree(last_ga);
			r->last_ga = this_ga;
			r->last_gap = gap;
		} else if (last_ga) {
			if (less(acked, start)) {
				si--;
				offset = start - acked - 1;
			} else if (less(acked, end)) {
				acked = end;
			}
			if (si < last_ga->bgack_cnt) {
				last_ga->start_index = si;
				r->last_gap = offset;
			} else {
				kfree(last_ga);
				r->last_ga = NULL;
				r->last_gap = 0;
			}
		} else {
			r->last_gap = 0;
		}
		r->acked = acked;
	} else {
		kfree(this_ga);
	}

	return qlen - skb_queue_len(&l->transmq);
}