flow_compose(struct dp_packet *p, const struct flow *flow,
             const void *l7, size_t l7_len)
{
    /* Add code to this function (or its callees) for emitting new fields or
     * protocols.  (This isn't essential, so it can be skipped for initial
     * testing.) */
    BUILD_ASSERT_DECL(FLOW_WC_SEQ == 41);

    uint32_t pseudo_hdr_csum;
    size_t l4_len;

    /* eth_compose() sets l3 pointer and makes sure it is 32-bit aligned. */
    eth_compose(p, flow->dl_dst, flow->dl_src, ntohs(flow->dl_type), 0);
    if (flow->dl_type == htons(FLOW_DL_TYPE_NONE)) {
        struct eth_header *eth = dp_packet_eth(p);
        eth->eth_type = htons(dp_packet_size(p));
        return;
    }

    for (int encaps = FLOW_MAX_VLAN_HEADERS - 1; encaps >= 0; encaps--) {
        if (flow->vlans[encaps].tci & htons(VLAN_CFI)) {
            eth_push_vlan(p, flow->vlans[encaps].tpid,
                          flow->vlans[encaps].tci);
        }
    }

    if (flow->dl_type == htons(ETH_TYPE_IP)) {
        struct ip_header *ip;

        ip = dp_packet_put_zeros(p, sizeof *ip);
        ip->ip_ihl_ver = IP_IHL_VER(5, 4);
        ip->ip_tos = flow->nw_tos;
        ip->ip_ttl = flow->nw_ttl;
        ip->ip_proto = flow->nw_proto;
        put_16aligned_be32(&ip->ip_src, flow->nw_src);
        put_16aligned_be32(&ip->ip_dst, flow->nw_dst);

        if (flow->nw_frag & FLOW_NW_FRAG_ANY) {
            ip->ip_frag_off |= htons(IP_MORE_FRAGMENTS);
            if (flow->nw_frag & FLOW_NW_FRAG_LATER) {
                ip->ip_frag_off |= htons(100);
            }
        }

        dp_packet_set_l4(p, dp_packet_tail(p));

        l4_len = flow_compose_l4(p, flow, l7, l7_len);

        ip = dp_packet_l3(p);
        ip->ip_tot_len = htons(p->l4_ofs - p->l3_ofs + l4_len);
        /* Checksum has already been zeroed by put_zeros call. */
        ip->ip_csum = csum(ip, sizeof *ip);

        pseudo_hdr_csum = packet_csum_pseudoheader(ip);
        flow_compose_l4_csum(p, flow, pseudo_hdr_csum);
    } else if (flow->dl_type == htons(ETH_TYPE_IPV6)) {
        struct ovs_16aligned_ip6_hdr *nh;

        nh = dp_packet_put_zeros(p, sizeof *nh);
        put_16aligned_be32(&nh->ip6_flow, htonl(6 << 28) |
                           htonl(flow->nw_tos << 20) | flow->ipv6_label);
        nh->ip6_hlim = flow->nw_ttl;
        nh->ip6_nxt = flow->nw_proto;

        memcpy(&nh->ip6_src, &flow->ipv6_src, sizeof(nh->ip6_src));
        memcpy(&nh->ip6_dst, &flow->ipv6_dst, sizeof(nh->ip6_dst));

        dp_packet_set_l4(p, dp_packet_tail(p));

        l4_len = flow_compose_l4(p, flow, l7, l7_len);

        nh = dp_packet_l3(p);
        nh->ip6_plen = htons(l4_len);

        pseudo_hdr_csum = packet_csum_pseudoheader6(nh);
        flow_compose_l4_csum(p, flow, pseudo_hdr_csum);
    } else if (flow->dl_type == htons(ETH_TYPE_ARP) ||
               flow->dl_type == htons(ETH_TYPE_RARP)) {
        struct arp_eth_header *arp;

        arp = dp_packet_put_zeros(p, sizeof *arp);
        dp_packet_set_l3(p, arp);
        arp->ar_hrd = htons(1);
        arp->ar_pro = htons(ETH_TYPE_IP);
        arp->ar_hln = ETH_ADDR_LEN;
        arp->ar_pln = 4;
        arp->ar_op = htons(flow->nw_proto);

        if (flow->nw_proto == ARP_OP_REQUEST ||
            flow->nw_proto == ARP_OP_REPLY) {
            put_16aligned_be32(&arp->ar_spa, flow->nw_src);
            put_16aligned_be32(&arp->ar_tpa, flow->nw_dst);
            arp->ar_sha = flow->arp_sha;
            arp->ar_tha = flow->arp_tha;
        }
    }

    if (eth_type_mpls(flow->dl_type)) {
        int n;

        p->l2_5_ofs = p->l3_ofs;
        for (n = 1; n < FLOW_MAX_MPLS_LABELS; n++) {
            if (flow->mpls_lse[n - 1] & htonl(MPLS_BOS_MASK)) {
                break;
            }
        }
        while (n > 0) {
            push_mpls(p, flow->dl_type, flow->mpls_lse[--n]);
        }
    }
}