| // SPDX-License-Identifier: GPL-2.0-only | 
 | /** | 
 |  * Authors: | 
 |  * (C) 2020 Alexander Aring <alex.aring@gmail.com> | 
 |  */ | 
 |  | 
 | #include <net/ipv6.h> | 
 | #include <net/rpl.h> | 
 |  | 
 | #define IPV6_PFXTAIL_LEN(x) (sizeof(struct in6_addr) - (x)) | 
 | #define IPV6_RPL_BEST_ADDR_COMPRESSION 15 | 
 |  | 
 | static void ipv6_rpl_addr_decompress(struct in6_addr *dst, | 
 | 				     const struct in6_addr *daddr, | 
 | 				     const void *post, unsigned char pfx) | 
 | { | 
 | 	memcpy(dst, daddr, pfx); | 
 | 	memcpy(&dst->s6_addr[pfx], post, IPV6_PFXTAIL_LEN(pfx)); | 
 | } | 
 |  | 
 | static void ipv6_rpl_addr_compress(void *dst, const struct in6_addr *addr, | 
 | 				   unsigned char pfx) | 
 | { | 
 | 	memcpy(dst, &addr->s6_addr[pfx], IPV6_PFXTAIL_LEN(pfx)); | 
 | } | 
 |  | 
 | static void *ipv6_rpl_segdata_pos(const struct ipv6_rpl_sr_hdr *hdr, int i) | 
 | { | 
 | 	return (void *)&hdr->rpl_segdata[i * IPV6_PFXTAIL_LEN(hdr->cmpri)]; | 
 | } | 
 |  | 
 | size_t ipv6_rpl_srh_size(unsigned char n, unsigned char cmpri, | 
 | 			 unsigned char cmpre) | 
 | { | 
 | 	return (n * IPV6_PFXTAIL_LEN(cmpri)) + IPV6_PFXTAIL_LEN(cmpre); | 
 | } | 
 |  | 
 | void ipv6_rpl_srh_decompress(struct ipv6_rpl_sr_hdr *outhdr, | 
 | 			     const struct ipv6_rpl_sr_hdr *inhdr, | 
 | 			     const struct in6_addr *daddr, unsigned char n) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	outhdr->nexthdr = inhdr->nexthdr; | 
 | 	outhdr->hdrlen = (((n + 1) * sizeof(struct in6_addr)) >> 3); | 
 | 	outhdr->pad = 0; | 
 | 	outhdr->type = inhdr->type; | 
 | 	outhdr->segments_left = inhdr->segments_left; | 
 | 	outhdr->cmpri = 0; | 
 | 	outhdr->cmpre = 0; | 
 |  | 
 | 	for (i = 0; i < n; i++) | 
 | 		ipv6_rpl_addr_decompress(&outhdr->rpl_segaddr[i], daddr, | 
 | 					 ipv6_rpl_segdata_pos(inhdr, i), | 
 | 					 inhdr->cmpri); | 
 |  | 
 | 	ipv6_rpl_addr_decompress(&outhdr->rpl_segaddr[n], daddr, | 
 | 				 ipv6_rpl_segdata_pos(inhdr, n), | 
 | 				 inhdr->cmpre); | 
 | } | 
 |  | 
 | static unsigned char ipv6_rpl_srh_calc_cmpri(const struct ipv6_rpl_sr_hdr *inhdr, | 
 | 					     const struct in6_addr *daddr, | 
 | 					     unsigned char n) | 
 | { | 
 | 	unsigned char plen; | 
 | 	int i; | 
 |  | 
 | 	for (plen = 0; plen < sizeof(*daddr); plen++) { | 
 | 		for (i = 0; i < n; i++) { | 
 | 			if (daddr->s6_addr[plen] != | 
 | 			    inhdr->rpl_segaddr[i].s6_addr[plen]) | 
 | 				return plen; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return IPV6_RPL_BEST_ADDR_COMPRESSION; | 
 | } | 
 |  | 
 | static unsigned char ipv6_rpl_srh_calc_cmpre(const struct in6_addr *daddr, | 
 | 					     const struct in6_addr *last_segment) | 
 | { | 
 | 	unsigned int plen; | 
 |  | 
 | 	for (plen = 0; plen < sizeof(*daddr); plen++) { | 
 | 		if (daddr->s6_addr[plen] != last_segment->s6_addr[plen]) | 
 | 			return plen; | 
 | 	} | 
 |  | 
 | 	return IPV6_RPL_BEST_ADDR_COMPRESSION; | 
 | } | 
 |  | 
 | void ipv6_rpl_srh_compress(struct ipv6_rpl_sr_hdr *outhdr, | 
 | 			   const struct ipv6_rpl_sr_hdr *inhdr, | 
 | 			   const struct in6_addr *daddr, unsigned char n) | 
 | { | 
 | 	unsigned char cmpri, cmpre; | 
 | 	size_t seglen; | 
 | 	int i; | 
 |  | 
 | 	cmpri = ipv6_rpl_srh_calc_cmpri(inhdr, daddr, n); | 
 | 	cmpre = ipv6_rpl_srh_calc_cmpre(daddr, &inhdr->rpl_segaddr[n]); | 
 |  | 
 | 	outhdr->nexthdr = inhdr->nexthdr; | 
 | 	seglen = (n * IPV6_PFXTAIL_LEN(cmpri)) + IPV6_PFXTAIL_LEN(cmpre); | 
 | 	outhdr->hdrlen = seglen >> 3; | 
 | 	if (seglen & 0x7) { | 
 | 		outhdr->hdrlen++; | 
 | 		outhdr->pad = 8 - (seglen & 0x7); | 
 | 	} else { | 
 | 		outhdr->pad = 0; | 
 | 	} | 
 | 	outhdr->type = inhdr->type; | 
 | 	outhdr->segments_left = inhdr->segments_left; | 
 | 	outhdr->cmpri = cmpri; | 
 | 	outhdr->cmpre = cmpre; | 
 |  | 
 | 	for (i = 0; i < n; i++) | 
 | 		ipv6_rpl_addr_compress(ipv6_rpl_segdata_pos(outhdr, i), | 
 | 				       &inhdr->rpl_segaddr[i], cmpri); | 
 |  | 
 | 	ipv6_rpl_addr_compress(ipv6_rpl_segdata_pos(outhdr, n), | 
 | 			       &inhdr->rpl_segaddr[n], cmpre); | 
 | } |