root/net/6lowpan/nhc_udp.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *      6LoWPAN IPv6 UDP compression according to RFC6282
 *
 *      Authors:
 *      Alexander Aring <aar@pengutronix.de>
 *
 *      Original written by:
 *      Alexander Smirnov <alex.bluesman.smirnov@gmail.com>
 *      Jon Smirl <jonsmirl@gmail.com>
 */

#include "nhc.h"

#define LOWPAN_NHC_UDP_MASK             0xF8
#define LOWPAN_NHC_UDP_ID               0xF0

#define LOWPAN_NHC_UDP_4BIT_PORT        0xF0B0
#define LOWPAN_NHC_UDP_4BIT_MASK        0xFFF0
#define LOWPAN_NHC_UDP_8BIT_PORT        0xF000
#define LOWPAN_NHC_UDP_8BIT_MASK        0xFF00

/* values for port compression, _with checksum_ ie bit 5 set to 0 */

/* all inline */
#define LOWPAN_NHC_UDP_CS_P_00  0xF0
/* source 16bit inline, dest = 0xF0 + 8 bit inline */
#define LOWPAN_NHC_UDP_CS_P_01  0xF1
/* source = 0xF0 + 8bit inline, dest = 16 bit inline */
#define LOWPAN_NHC_UDP_CS_P_10  0xF2
/* source & dest = 0xF0B + 4bit inline */
#define LOWPAN_NHC_UDP_CS_P_11  0xF3
/* checksum elided */
#define LOWPAN_NHC_UDP_CS_C     0x04

static int udp_uncompress(struct sk_buff *skb, size_t needed)
{
        u8 tmp = 0, val = 0;
        struct udphdr uh;
        bool fail;
        int err;

        fail = lowpan_fetch_skb(skb, &tmp, sizeof(tmp));

        pr_debug("UDP header uncompression\n");
        switch (tmp & LOWPAN_NHC_UDP_CS_P_11) {
        case LOWPAN_NHC_UDP_CS_P_00:
                fail |= lowpan_fetch_skb(skb, &uh.source, sizeof(uh.source));
                fail |= lowpan_fetch_skb(skb, &uh.dest, sizeof(uh.dest));
                break;
        case LOWPAN_NHC_UDP_CS_P_01:
                fail |= lowpan_fetch_skb(skb, &uh.source, sizeof(uh.source));
                fail |= lowpan_fetch_skb(skb, &val, sizeof(val));
                uh.dest = htons(val + LOWPAN_NHC_UDP_8BIT_PORT);
                break;
        case LOWPAN_NHC_UDP_CS_P_10:
                fail |= lowpan_fetch_skb(skb, &val, sizeof(val));
                uh.source = htons(val + LOWPAN_NHC_UDP_8BIT_PORT);
                fail |= lowpan_fetch_skb(skb, &uh.dest, sizeof(uh.dest));
                break;
        case LOWPAN_NHC_UDP_CS_P_11:
                fail |= lowpan_fetch_skb(skb, &val, sizeof(val));
                uh.source = htons(LOWPAN_NHC_UDP_4BIT_PORT + (val >> 4));
                uh.dest = htons(LOWPAN_NHC_UDP_4BIT_PORT + (val & 0x0f));
                break;
        default:
                BUG();
        }

        pr_debug("uncompressed UDP ports: src = %d, dst = %d\n",
                 ntohs(uh.source), ntohs(uh.dest));

        /* checksum */
        if (tmp & LOWPAN_NHC_UDP_CS_C) {
                pr_debug_ratelimited("checksum elided currently not supported\n");
                fail = true;
        } else {
                fail |= lowpan_fetch_skb(skb, &uh.check, sizeof(uh.check));
        }

        if (fail)
                return -EINVAL;

        /* UDP length needs to be inferred from the lower layers
         * here, we obtain the hint from the remaining size of the
         * frame
         */
        switch (lowpan_dev(skb->dev)->lltype) {
        case LOWPAN_LLTYPE_IEEE802154:
                if (lowpan_802154_cb(skb)->d_size)
                        uh.len = htons(lowpan_802154_cb(skb)->d_size -
                                       sizeof(struct ipv6hdr));
                else
                        uh.len = htons(skb->len + sizeof(struct udphdr));
                break;
        default:
                uh.len = htons(skb->len + sizeof(struct udphdr));
                break;
        }
        pr_debug("uncompressed UDP length: src = %d", ntohs(uh.len));

        /* replace the compressed UDP head by the uncompressed UDP
         * header
         */
        err = skb_cow(skb, needed);
        if (unlikely(err))
                return err;

        skb_push(skb, sizeof(struct udphdr));
        skb_copy_to_linear_data(skb, &uh, sizeof(struct udphdr));

        return 0;
}

static int udp_compress(struct sk_buff *skb, u8 **hc_ptr)
{
        const struct udphdr *uh = udp_hdr(skb);
        u8 tmp;

        if (((ntohs(uh->source) & LOWPAN_NHC_UDP_4BIT_MASK) ==
             LOWPAN_NHC_UDP_4BIT_PORT) &&
            ((ntohs(uh->dest) & LOWPAN_NHC_UDP_4BIT_MASK) ==
             LOWPAN_NHC_UDP_4BIT_PORT)) {
                pr_debug("UDP header: both ports compression to 4 bits\n");
                /* compression value */
                tmp = LOWPAN_NHC_UDP_CS_P_11;
                lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
                /* source and destination port */
                tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_4BIT_PORT +
                      ((ntohs(uh->source) - LOWPAN_NHC_UDP_4BIT_PORT) << 4);
                lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
        } else if ((ntohs(uh->dest) & LOWPAN_NHC_UDP_8BIT_MASK) ==
                        LOWPAN_NHC_UDP_8BIT_PORT) {
                pr_debug("UDP header: remove 8 bits of dest\n");
                /* compression value */
                tmp = LOWPAN_NHC_UDP_CS_P_01;
                lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
                /* source port */
                lowpan_push_hc_data(hc_ptr, &uh->source, sizeof(uh->source));
                /* destination port */
                tmp = ntohs(uh->dest) - LOWPAN_NHC_UDP_8BIT_PORT;
                lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
        } else if ((ntohs(uh->source) & LOWPAN_NHC_UDP_8BIT_MASK) ==
                        LOWPAN_NHC_UDP_8BIT_PORT) {
                pr_debug("UDP header: remove 8 bits of source\n");
                /* compression value */
                tmp = LOWPAN_NHC_UDP_CS_P_10;
                lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
                /* source port */
                tmp = ntohs(uh->source) - LOWPAN_NHC_UDP_8BIT_PORT;
                lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
                /* destination port */
                lowpan_push_hc_data(hc_ptr, &uh->dest, sizeof(uh->dest));
        } else {
                pr_debug("UDP header: can't compress\n");
                /* compression value */
                tmp = LOWPAN_NHC_UDP_CS_P_00;
                lowpan_push_hc_data(hc_ptr, &tmp, sizeof(tmp));
                /* source port */
                lowpan_push_hc_data(hc_ptr, &uh->source, sizeof(uh->source));
                /* destination port */
                lowpan_push_hc_data(hc_ptr, &uh->dest, sizeof(uh->dest));
        }

        /* checksum is always inline */
        lowpan_push_hc_data(hc_ptr, &uh->check, sizeof(uh->check));

        return 0;
}

LOWPAN_NHC(nhc_udp, "RFC6282 UDP", NEXTHDR_UDP, sizeof(struct udphdr),
           LOWPAN_NHC_UDP_ID, LOWPAN_NHC_UDP_MASK, udp_uncompress, udp_compress);

module_lowpan_nhc(nhc_udp);
MODULE_DESCRIPTION("6LoWPAN next header RFC6282 UDP compression");
MODULE_LICENSE("GPL");