/*
 * Copyright (c) 1990,1991 Regents of The University of Michigan.
 * All Rights Reserved. See COPYRIGHT.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/mbuf.h>
#include <sys/socket.h>
#include <errno.h>
#include <atalk/logger.h>

#include <net/if.h>
#include <net/route.h>

#include <netinet/in.h>
#undef s_net
#include <netinet/if_ether.h>

#include "at.h"
#include "at_var.h"
#include "ddp.h"
#include "ddp_var.h"

unsigned short	at_cksum();
int	ddp_cksum = 1;

ddp_output(ddp, m)
struct ddpcb	*ddp;
struct mbuf		*m;
{
#ifndef BSD4_4
    struct mbuf		*m0;
    int			len;
#endif /* ! BSD4_4 */
    struct ifnet	*ifp;
    struct at_ifaddr	*aa = NULL;
    struct ddpehdr	*deh;
    unsigned short		net;
#ifdef BSD4_4
    M_PREPEND(m, sizeof(struct ddpehdr), M_WAIT);
#else /* BSD4_4 */

    for (len = 0, m0 = m; m; m = m->m_next) {
        len += m->m_len;
    }

    MGET(m, M_WAIT, MT_HEADER);

    if (m == 0) {
        m_freem(m0);
        return ENOBUFS;
    }

    m->m_next = m0;
#endif /* BSD4_4 */
#ifndef BSD4_4
# define align(a)	(((a)+3)&0xfc)
    m->m_off = MMINOFF + align(SZ_ELAPHDR);
    m->m_len = sizeof(struct ddpehdr);
#endif /* ! BSD4_4 */
    deh = mtod(m, struct ddpehdr *);
    deh->deh_pad = 0;
    deh->deh_hops = 0;
#ifdef BSD4_4
    deh->deh_len = m->m_pkthdr.len;
#else /* BSD4_4 */
    deh->deh_len = len + sizeof(struct ddpehdr);
#endif /* BSD4_4 */
    deh->deh_dnet = ddp->ddp_fsat.sat_addr.s_net;
    deh->deh_dnode = ddp->ddp_fsat.sat_addr.s_node;
    deh->deh_dport = ddp->ddp_fsat.sat_port;
    deh->deh_snet = ddp->ddp_lsat.sat_addr.s_net;
    deh->deh_snode = ddp->ddp_lsat.sat_addr.s_node;
    deh->deh_sport = ddp->ddp_lsat.sat_port;

    /*
     * The checksum calculation is done after all of the other bytes have
     * been filled in.
     */
    if (ddp_cksum) {
        deh->deh_sum = at_cksum(m, sizeof(int));
    } else {
        deh->deh_sum = 0;
    }

    deh->deh_bytes = htonl(deh->deh_bytes);
    return ddp_route(m, &ddp->ddp_route);
}

unsigned short
at_cksum(m, skip)
struct mbuf	*m;
int		skip;
{
    unsigned char	*data, *end;
    uint32_t	cksum = 0;

    for (; m; m = m->m_next) {
        for (data = mtod(m, unsigned char *), end = data + m->m_len; data < end;
                data++) {
            if (skip) {
                skip--;
                continue;
            }

            cksum = (cksum + *data) << 1;

            if (cksum & 0x00010000) {
                cksum++;
            }

            cksum &= 0x0000ffff;
        }
    }

    if (cksum == 0) {
        cksum = 0x0000ffff;
    }

    return (unsigned short)cksum;
}

ddp_route(m, ro)
struct mbuf		*m;
struct route	*ro;
{
    struct sockaddr_at	gate;
    struct elaphdr	*elh;
    struct mbuf		*m0;
    struct at_ifaddr	*aa = NULL;
    struct ifnet	*ifp;
    int			mlen;
    unsigned short		net;

    if (ro->ro_rt && (ifp = ro->ro_rt->rt_ifp)) {
#ifdef BSD4_4
        net = satosat(ro->ro_rt->rt_gateway)->sat_addr.s_net;
#else /* BSD4_4 */
        net = satosat(&ro->ro_rt->rt_gateway)->sat_addr.s_net;
#endif /* BSD4_4 */

        for (aa = at_ifaddr; aa; aa = aa->aa_next) {
            if (aa->aa_ifp == ifp &&
                    ntohs(net) >= ntohs(aa->aa_firstnet) &&
                    ntohs(net) <= ntohs(aa->aa_lastnet)) {
                break;
            }
        }
    }

    if (aa == NULL) {
        m_freem(m);
        return EINVAL;
    }

    /*
     * There are several places in the kernel where data is added to
     * an mbuf without ensuring that the mbuf pointer is aligned.
     * This is bad for transition routing, since phase 1 and phase 2
     * packets end up poorly aligned due to the three byte elap header.
     */
    if (aa->aa_flags & AFA_PHASE2) {
        for (mlen = 0, m0 = m; m0; m0 = m0->m_next) {
            mlen += m0->m_len;
        }

        if ((m = m_pullup(m, MIN(MLEN, mlen))) == 0) {
            return ENOBUFS;
        }
    } else {
# ifdef notdef
#ifdef BSD4_4
        M_PREPEND(m, SZ_ELAPHDR, M_DONTWAIT);

        if (m == NULL) {
            return ENOBUFS;
        }

#else /* BSD4_4 */
        m->m_off -= SZ_ELAPHDR;
        m->m_len += SZ_ELAPHDR;
#endif /* BSD4_4 */
# endif /* notdef */
        MGET(m0, M_WAIT, MT_HEADER);

        if (m0 == 0) {
            m_freem(m);
            return ENOBUFS;
        }

        m0->m_next = m;
        m0->m_off = MMINOFF + align(sizeof(struct ether_header));
        m0->m_len = SZ_ELAPHDR;
        m = m0;
        elh = mtod(m, struct elaphdr *);
        elh->el_snode = satosat(&aa->aa_addr)->sat_addr.s_node;
        elh->el_type = ELAP_DDPEXTEND;

        if (ntohs(satosat(&ro->ro_dst)->sat_addr.s_net) >=
                ntohs(aa->aa_firstnet) &&
                ntohs(satosat(&ro->ro_dst)->sat_addr.s_net) <=
                ntohs(aa->aa_lastnet)) {
            elh->el_dnode = satosat(&ro->ro_dst)->sat_addr.s_node;
        } else {
#ifdef BSD4_4
            elh->el_dnode = satosat(ro->ro_rt->rt_gateway)->sat_addr.s_node;
#else /* BSD4_4 */
            elh->el_dnode = satosat(&ro->ro_rt->rt_gateway)->sat_addr.s_node;
#endif /* BSD4_4 */
        }
    }

    if (ntohs(satosat(&ro->ro_dst)->sat_addr.s_net) >=
            ntohs(aa->aa_firstnet) &&
            ntohs(satosat(&ro->ro_dst)->sat_addr.s_net) <=
            ntohs(aa->aa_lastnet)) {
        gate = *satosat(&ro->ro_dst);
    } else {
#ifdef BSD4_4
        gate = *satosat(ro->ro_rt->rt_gateway);
#else /* BSD4_4 */
        gate = *satosat(&ro->ro_rt->rt_gateway);
#endif /* BSD4_4 */
    }

    ro->ro_rt->rt_use++;
    return (*ifp->if_output)(ifp, m, &gate);
}
