Index: uspace/srv/net/inetsrv/pdu.c
===================================================================
--- uspace/srv/net/inetsrv/pdu.c	(revision 1d24ad312b4ddcdf3dc7c30ee0effc5293fd669c)
+++ uspace/srv/net/inetsrv/pdu.c	(revision c84146d3591766f01374dd9dbf6a71f9db5f7055)
@@ -88,5 +88,5 @@
 }
 
-/** Encode Internet PDU.
+/** Encode IPv4 PDU.
  *
  * Encode internet packet into PDU (serialized form). Will encode a
@@ -96,25 +96,17 @@
  * be set in the header, otherwise the offset will equal @a packet->size.
  *
- * @param packet	Packet to encode
- * @param offs		Offset into packet payload (in bytes)
- * @param mtu		MTU (Maximum Transmission Unit) in bytes
- * @param rdata		Place to store pointer to allocated data buffer
- * @param rsize		Place to store size of allocated data buffer
- * @param roffs		Place to store offset of remaning data
- */
-int inet_pdu_encode(inet_packet_t *packet, size_t offs, size_t mtu,
-    void **rdata, size_t *rsize, size_t *roffs)
-{
-	addr32_t src_v4;
-	addr128_t src_v6;
-	uint16_t src_af = inet_addr_get(&packet->src, &src_v4, &src_v6);
-	
-	addr32_t dest_v4;
-	addr128_t dest_v6;
-	uint16_t dest_af = inet_addr_get(&packet->dest, &dest_v4, &dest_v6);
-	
-	if (src_af != dest_af)
-		return EINVAL;
-	
+ * @param packet Packet to encode
+ * @param src    Source address
+ * @param dest   Destination address
+ * @param offs   Offset into packet payload (in bytes)
+ * @param mtu    MTU (Maximum Transmission Unit) in bytes
+ * @param rdata  Place to store pointer to allocated data buffer
+ * @param rsize  Place to store size of allocated data buffer
+ * @param roffs  Place to store offset of remaning data
+ *
+ */
+int inet_pdu_encode(inet_packet_t *packet, addr32_t src, addr32_t dest,
+   size_t offs, size_t mtu, void **rdata, size_t *rsize, size_t *roffs)
+{
 	/* Upper bound for fragment offset field */
 	size_t fragoff_limit = 1 << (FF_FRAGOFF_h - FF_FRAGOFF_l);
@@ -124,16 +116,5 @@
 		return ELIMIT;
 	
-	size_t hdr_size;
-	
-	switch (src_af) {
-	case AF_INET:
-		hdr_size = sizeof(ip_header_t);
-		break;
-	case AF_INET6:
-		hdr_size = sizeof(ip6_header_t);
-		break;
-	default:
-		assert(false);
-	}
+	size_t hdr_size = sizeof(ip_header_t);
 	
 	size_t data_offs = ROUND_UP(hdr_size, 4);
@@ -177,47 +158,125 @@
 	
 	/* Encode header fields */
-	ip_header_t *hdr;
-	ip6_header_t *hdr6;
-	
-	switch (src_af) {
-	case AF_INET:
-		hdr = (ip_header_t *) data;
-		
-		hdr->ver_ihl =
-		    (4 << VI_VERSION_l) | (hdr_size / sizeof(uint32_t));
-		hdr->tos = packet->tos;
-		hdr->tot_len = host2uint16_t_be(size);
-		hdr->id = host2uint16_t_be(ident);
-		hdr->flags_foff = host2uint16_t_be(flags_foff);
-		hdr->ttl = packet->ttl;
-		hdr->proto = packet->proto;
-		hdr->chksum = 0;
-		hdr->src_addr = host2uint32_t_be(src_v4);
-		hdr->dest_addr = host2uint32_t_be(dest_v4);
-		
-		/* Compute checksum */
-		uint16_t chksum = inet_checksum_calc(INET_CHECKSUM_INIT,
-		    (void *) hdr, hdr_size);
-		hdr->chksum = host2uint16_t_be(chksum);
-		
-		break;
-	case AF_INET6:
-		// TODO FIXME: fragmentation
-		
-		hdr6 = (ip6_header_t *) data;
-		
-		hdr6->ver_tc = (6 << (VI_VERSION_l));
-		memset(hdr6->tc_fl, 0, 3);
-		hdr6->payload_len = host2uint16_t_be(packet->size);
-		hdr6->next = packet->proto;
-		hdr6->hop_limit = packet->ttl;
-		
-		host2addr128_t_be(src_v6, hdr6->src_addr);
-		host2addr128_t_be(dest_v6, hdr6->dest_addr);
-		
-		break;
-	default:
-		assert(false);
-	}
+	ip_header_t *hdr = (ip_header_t *) data;
+	
+	hdr->ver_ihl =
+	    (4 << VI_VERSION_l) | (hdr_size / sizeof(uint32_t));
+	hdr->tos = packet->tos;
+	hdr->tot_len = host2uint16_t_be(size);
+	hdr->id = host2uint16_t_be(ident);
+	hdr->flags_foff = host2uint16_t_be(flags_foff);
+	hdr->ttl = packet->ttl;
+	hdr->proto = packet->proto;
+	hdr->chksum = 0;
+	hdr->src_addr = host2uint32_t_be(src);
+	hdr->dest_addr = host2uint32_t_be(dest);
+	
+	/* Compute checksum */
+	uint16_t chksum = inet_checksum_calc(INET_CHECKSUM_INIT,
+	    (void *) hdr, hdr_size);
+	hdr->chksum = host2uint16_t_be(chksum);
+	
+	/* Copy payload */
+	memcpy((uint8_t *) data + data_offs, packet->data + offs, xfer_size);
+	
+	*rdata = data;
+	*rsize = size;
+	*roffs = rem_offs;
+	
+	return EOK;
+}
+
+/** Encode IPv6 PDU.
+ *
+ * Encode internet packet into PDU (serialized form). Will encode a
+ * fragment of the payload starting at offset @a offs. The resulting
+ * PDU will have at most @a mtu bytes. @a *roffs will be set to the offset
+ * of remaining payload. If some data is remaining, the MF flag will
+ * be set in the header, otherwise the offset will equal @a packet->size.
+ *
+ * @param packet Packet to encode
+ * @param src    Source address
+ * @param dest   Destination address
+ * @param offs   Offset into packet payload (in bytes)
+ * @param mtu    MTU (Maximum Transmission Unit) in bytes
+ * @param rdata  Place to store pointer to allocated data buffer
+ * @param rsize  Place to store size of allocated data buffer
+ * @param roffs Place to store offset of remaning data
+ *
+ */
+int inet_pdu_encode6(inet_packet_t *packet, addr128_t src, addr128_t dest,
+    size_t offs, size_t mtu, void **rdata, size_t *rsize, size_t *roffs)
+{
+	/* Upper bound for fragment offset field */
+	size_t fragoff_limit = 1 << (FF_FRAGOFF_h - FF_FRAGOFF_l);
+	
+	/* Verify that total size of datagram is within reasonable bounds */
+	if (offs + packet->size > FRAG_OFFS_UNIT * fragoff_limit)
+		return ELIMIT;
+	
+	size_t hdr_size = sizeof(ip6_header_t);
+	
+	size_t data_offs = ROUND_UP(hdr_size, 4);
+	
+	assert(offs % FRAG_OFFS_UNIT == 0);
+	assert(offs / FRAG_OFFS_UNIT < fragoff_limit);
+	
+#if 0
+	// FIXME TODO fragmentation
+	
+	/* Value for the fragment offset field */
+	uint16_t foff = offs / FRAG_OFFS_UNIT;
+#endif
+	
+	if (hdr_size >= mtu)
+		return EINVAL;
+	
+	/* Amount of space in the PDU available for payload */
+	size_t spc_avail = mtu - hdr_size;
+	spc_avail -= (spc_avail % FRAG_OFFS_UNIT);
+	
+	/* Amount of data (payload) to transfer */
+	size_t xfer_size = min(packet->size - offs, spc_avail);
+	
+	/* Total PDU size */
+	size_t size = hdr_size + xfer_size;
+	
+	/* Offset of remaining payload */
+	size_t rem_offs = offs + xfer_size;
+	
+#if 0
+	// FIXME TODO fragmentation
+	
+	/* Flags */
+	uint16_t flags_foff =
+	    (packet->df ? BIT_V(uint16_t, FF_FLAG_DF) : 0) +
+	    (rem_offs < packet->size ? BIT_V(uint16_t, FF_FLAG_MF) : 0) +
+	    (foff << FF_FRAGOFF_l);
+#endif
+	
+	void *data = calloc(size, 1);
+	if (data == NULL)
+		return ENOMEM;
+	
+#if 0
+	// FIXME TODO fragmentation
+	
+	/* Allocate identifier */
+	fibril_mutex_lock(&ip_ident_lock);
+	uint16_t ident = ++ip_ident;
+	fibril_mutex_unlock(&ip_ident_lock);
+#endif
+	
+	/* Encode header fields */
+	ip6_header_t *hdr6 = (ip6_header_t *) data;
+	
+	hdr6->ver_tc = (6 << (VI_VERSION_l));
+	memset(hdr6->tc_fl, 0, 3);
+	hdr6->payload_len = host2uint16_t_be(packet->size);
+	hdr6->next = packet->proto;
+	hdr6->hop_limit = packet->ttl;
+	
+	host2addr128_t_be(src, hdr6->src_addr);
+	host2addr128_t_be(dest, hdr6->dest_addr);
 	
 	/* Copy payload */
@@ -257,5 +316,5 @@
 	if (tot_len > size) {
 		log_msg(LOG_DEFAULT, LVL_DEBUG, "Total Length = %zu > PDU size = %zu",
-			tot_len, size);
+		    tot_len, size);
 		return EINVAL;
 	}
@@ -296,6 +355,153 @@
 int inet_pdu_decode6(void *data, size_t size, inet_packet_t *packet)
 {
-	// FIXME TODO
-	return ENOTSUP;
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_pdu_decode6()");
+	
+	if (size < sizeof(ip6_header_t)) {
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "PDU too short (%zu)", size);
+		return EINVAL;
+	}
+	
+	ip6_header_t *hdr6 = (ip6_header_t *) data;
+	
+	uint8_t version = BIT_RANGE_EXTRACT(uint8_t, VI_VERSION_h,
+	    VI_VERSION_l, hdr6->ver_tc);
+	if (version != 6) {
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Version (%d) != 6", version);
+		return EINVAL;
+	}
+	
+	size_t payload_len = uint16_t_be2host(hdr6->payload_len);
+	if (payload_len + sizeof(ip6_header_t) > size) {
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Total Length = %zu > PDU size = %zu",
+		    payload_len + sizeof(ip6_header_t), size);
+		return EINVAL;
+	}
+	
+#if 0
+	// FIXME TODO fragmentation
+	
+	uint16_t ident = uint16_t_be2host(hdr->id);
+	uint16_t flags_foff = uint16_t_be2host(hdr->flags_foff);
+	uint16_t foff = BIT_RANGE_EXTRACT(uint16_t, FF_FRAGOFF_h, FF_FRAGOFF_l,
+	    flags_foff);
+#endif
+	
+	/* XXX Checksum */
+	
+	addr128_t src;
+	addr128_t dest;
+	
+	addr128_t_be2host(hdr6->src_addr, src);
+	inet_addr_set6(src, &packet->src);
+	
+	addr128_t_be2host(hdr6->dest_addr, dest);
+	inet_addr_set6(dest, &packet->dest);
+	
+	packet->tos = 0;
+	packet->proto = hdr6->next;
+	packet->ttl = hdr6->hop_limit;
+	
+#if 0
+	// FIXME TODO fragmentation
+	
+	packet->ident = ident;
+	packet->df = (flags_foff & BIT_V(uint16_t, FF_FLAG_DF)) != 0;
+	packet->mf = (flags_foff & BIT_V(uint16_t, FF_FLAG_MF)) != 0;
+	packet->offs = foff * FRAG_OFFS_UNIT;
+	
+	/* XXX IP options */
+	size_t data_offs = sizeof(uint32_t) *
+	    BIT_RANGE_EXTRACT(uint8_t, VI_IHL_h, VI_IHL_l, hdr->ver_ihl);
+#endif
+	
+	packet->ident = 0;
+	packet->df = 0;
+	packet->mf = 0;
+	packet->offs = 0;
+	
+	packet->size = payload_len;
+	packet->data = calloc(packet->size, 1);
+	if (packet->data == NULL) {
+		log_msg(LOG_DEFAULT, LVL_WARN, "Out of memory.");
+		return ENOMEM;
+	}
+	
+	memcpy(packet->data, (uint8_t *) data + sizeof(ip6_header_t), packet->size);
+	
+	return EOK;
+}
+
+int ndp_pdu_encode(ndp_packet_t *ndp, inet_dgram_t *dgram)
+{
+	inet_addr_set6(ndp->sender_proto_addr, &dgram->src);
+	inet_addr_set6(ndp->target_proto_addr, &dgram->dest);
+	dgram->tos = 0;
+	dgram->size = sizeof(icmpv6_message_t) + sizeof(ndp_message_t);
+	
+	dgram->data = calloc(1, dgram->size);
+	if (dgram->data == NULL)
+		return ENOMEM;
+	
+	icmpv6_message_t *icmpv6 = (icmpv6_message_t *) dgram->data;
+	
+	icmpv6->type = ndp->opcode;
+	icmpv6->code = 0;
+	memset(icmpv6->un.ndp.reserved, 0, 3);
+	
+	ndp_message_t *message = (ndp_message_t *) (icmpv6 + 1);
+	
+	if (ndp->opcode == ICMPV6_NEIGHBOUR_SOLICITATION) {
+		host2addr128_t_be(ndp->solicited_ip, message->target_address);
+		message->option = 1;
+		icmpv6->un.ndp.flags = 0;
+	} else {
+		host2addr128_t_be(ndp->sender_proto_addr, message->target_address);
+		message->option = 2;
+		icmpv6->un.ndp.flags = NDP_FLAG_OVERRIDE | NDP_FLAG_SOLICITED;
+	}
+	
+	message->length = 1;
+	addr48(ndp->sender_hw_addr, message->mac);
+	
+	icmpv6_pseudo_header phdr;
+	
+	host2addr128_t_be(ndp->sender_proto_addr, phdr.src_addr);
+	host2addr128_t_be(ndp->target_proto_addr, phdr.dest_addr);
+	phdr.length = host2uint32_t_be(dgram->size);
+	memset(phdr.zeroes, 0, 3);
+	phdr.next = IP_PROTO_ICMPV6;
+	
+	uint16_t cs_phdr =
+	    inet_checksum_calc(INET_CHECKSUM_INIT, &phdr,
+	    sizeof(icmpv6_pseudo_header));
+	
+	uint16_t cs_all = inet_checksum_calc(cs_phdr, dgram->data,
+	    dgram->size);
+	
+	icmpv6->checksum = host2uint16_t_be(cs_all);
+	
+	return EOK;
+}
+
+int ndp_pdu_decode(inet_dgram_t *dgram, ndp_packet_t *ndp)
+{
+	uint16_t src_af = inet_addr_get(&dgram->src, NULL,
+	    &ndp->sender_proto_addr);
+	if (src_af != AF_INET6)
+		return EINVAL;
+	
+	if (dgram->size < sizeof(icmpv6_message_t) + sizeof(ndp_message_t))
+		return EINVAL;
+	
+	icmpv6_message_t *icmpv6 = (icmpv6_message_t *) dgram->data;
+	
+	ndp->opcode = icmpv6->type;
+	
+	ndp_message_t *message = (ndp_message_t *) (icmpv6 + 1);
+	
+	addr128_t_be2host(message->target_address, ndp->target_proto_addr);
+	addr48(message->mac, ndp->sender_hw_addr);
+	
+	return EOK;
 }
 
