The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * MRT.xs
 * $Id$
 *
 * Copyright (C) 2013 MaxiM Basunov <maxim.basunov@gmail.com>
 * All rights reserved.
 *
 * This program is free software; you may redistribute it and/or
 * modify it under the same terms as Perl itself.
 */

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
// #include "poll.h"
#ifdef I_UNISTD
#  include <unistd.h>
#endif
#if defined(I_FCNTL) || defined(HAS_FCNTL)
#  include <fcntl.h>
#endif

#include "ppport.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include "mrttypes.h"

// Windows XP workaround for inet_ntop
// TODO: Windows Vista/7 can use InetNtop
// TODO: http://vinsworldcom.blogspot.com/2012/08/ipv6-in-perl-on-windows_20.html
#ifdef WIN32
    #include "inet_ntop.c"
#endif

// String buffer size
#define SBUFF 200

// Global variables
SV* USE_RFC4760;

// Function to decode single MRT message and compose HV contents
void mrt_decode(HV* const rt, Off_t const msgpos, MRT_MESSAGE* const mh)
{
    char sbuff[SBUFF] = {}; // Char buffer for SNPRINTF
    char* pPos = (char*)&mh->message; // Current read position
    int AF = 0; // Address family from message
    uint32_t iSeq; // Sequence number from MRT
    struct sockaddr_in6 sa6;
    memset(&sa6, 0, sizeof(sa6));
    char cIpAddress[INET6_ADDRSTRLEN]; // Buffer for INET_PTON
    int iRemainingLen = mh->length; // Self-check for buffer remaining length

    // Some temporary variables
    AV* avTmpAv;
    uint8_t iTmpU8;
    uint16_t iTmpU16;
    uint32_t iTmpU32;

#   ifdef _DEBUG_
    printf("mrt_decode_single(): Input type: %d subtype %d data len: %d\n", mh->type, mh->subtype, mh->length);
#   endif
    switch (mh->type) {
        case MT_TABLE_DUMP_V2:
            switch (mh->subtype) {
                case MST_TD2_PEER_INDEX_TABLE:
//PEER_INDEX_TABLE
//4 Collector BGP ID - ID of this collector
//2 view name length (can be =0)
//V View Name (optional)
//2 Peer count
//V Peers (index start with 0)
//
//PEER:
//1 PEer type 000000AI. A=2 - AS 32 bit; IP=1 - IPv6
//4 Peer BGP ID
//IP Peer IP address
//AS Peer AS number
                    // Decode Collector BGP ID
                    memset(&sa6, 0, sizeof(sa6));
                    mrt_copy_next(&pPos, &sa6, 4, &iRemainingLen);
                    inet_ntop(AF_INET, &sa6, cIpAddress, INET6_ADDRSTRLEN);
                    hv_stores(rt, "collector_bgp_id", newSVpv(cIpAddress, 0));

                    // Decode View name
                    mrt_copy_next(&pPos, &iTmpU16, 2, &iRemainingLen);
                    iTmpU16 = ntohs(iTmpU16);
                    if (iTmpU16 > 0) {
                        SV* svViewName = newSVpv(pPos, iTmpU16);
                        SvUTF8_on(svViewName);
                        hv_stores(rt, "view_name", svViewName);
                        pPos += iTmpU16;
                        iRemainingLen -= iTmpU16;
                    } else {
                        hv_stores(rt, "view_name", &PL_sv_undef);
                    }

                    // Decode amount of peers
                    uint16_t iPeerCount;
                    mrt_copy_next(&pPos, &iPeerCount, 2, &iRemainingLen);
                    iPeerCount = htons(iPeerCount);

                    // Store peers container
                    avTmpAv = newAV();
                    hv_stores(rt, "peers", newRV_noinc((SV *)avTmpAv));

                    // Decode peers
                    while (iPeerCount > 0)
                    {
                        iPeerCount--;
                        // Append new onePeer container
                        HV* hvOnePeer = newHV();
                        av_push(avTmpAv, newRV_noinc((SV *)hvOnePeer));

                        // Decode Peer's Type
                        PEER_TYPE ptPT;
                        mrt_copy_next(&pPos, &ptPT, 1, &iRemainingLen);

                        // Decode Peer's BGP ID
                        memset(&sa6, 0, sizeof(sa6));
                        mrt_copy_next(&pPos, &sa6, 4, &iRemainingLen);
                        inet_ntop(AF_INET, &sa6, cIpAddress, INET6_ADDRSTRLEN);
                        hv_stores(hvOnePeer, "bgp_id", newSVpv(cIpAddress, 0));

                        // Decode Peer's IP address
                        memset(&sa6, 0, sizeof(sa6));
                        mrt_copy_next(&pPos, &sa6, (!ptPT.ipv6 ? 4 : 16), &iRemainingLen);
                        inet_ntop((!ptPT.ipv6 ? AF_INET : AF_INET6), &sa6, cIpAddress, INET6_ADDRSTRLEN);
                        hv_stores(hvOnePeer, "peer_ip", newSVpv(cIpAddress, 0));

                        // Decode Peer's AS number
                        if (!ptPT.as32)
                        {
                            mrt_copy_next(&pPos, &iTmpU16, 2, &iRemainingLen);
                            iTmpU16 = ntohs(iTmpU16);
                            hv_stores(hvOnePeer, "as", newSVuv(iTmpU16));
                        } else {
                            mrt_copy_next(&pPos, &iTmpU32, 4, &iRemainingLen);
                            iTmpU32 = ntohl(iTmpU32);
                            hv_stores(hvOnePeer, "as", newSVuv(iTmpU32));
                        }
                    } // while (iPeerCount > 0)
                    break; // MST_TD2_PEER_INDEX_TABLE
                // Try to decode MULTICAST/ANYCAST
                //case MST_TD2_RIB_IPV6_MULTICAST:
                case MST_TD2_RIB_IPV6_UNICAST:
                    AF = AF_INET6;
                //case MST_TD2_RIB_IPV4_MULTICAST:
                case MST_TD2_RIB_IPV4_UNICAST:
                    if (AF == 0) AF = AF_INET; // Address Family also set for IPV6 messages

                    // Decode Sequence
                    mrt_copy_next(&pPos, &iSeq, 4, &iRemainingLen);
                    iSeq = ntohl(iSeq);
                    hv_stores(rt, "sequence", newSVuv(iSeq));

                    // Decode Prefix Bits
                    uint8_t iPrefixBits;
                    mrt_copy_next(&pPos, &iPrefixBits, 1, &iRemainingLen);
                    hv_stores(rt, "bits", newSVuv(iPrefixBits));

                    // Decode Prefix
                    memset(&sa6, 0, sizeof(sa6));
                    if (iPrefixBits > 0)
                        mrt_copy_next(&pPos, &sa6, (int)ceil((double)iPrefixBits/8), &iRemainingLen);
                    inet_ntop(AF, &sa6, cIpAddress, INET6_ADDRSTRLEN);
                    hv_stores(rt, "prefix", newSVpv(cIpAddress, 0));

                    // Decode count of entries
                    uint16_t iEntries;
                    mrt_copy_next(&pPos, &iEntries, 2, &iRemainingLen);
                    iEntries = ntohs(iEntries);
#                   ifdef _DEBUG_
                    printf("mrt_decode_single(): Decoded prefix %s/%d\n", cIpAddress, iPrefixBits);
                    printf("mrt_decode_single(): Decode have %d entries\n", iEntries);
#                   endif

                    // Prepare entres
                    AV* avEntries = newAV();
                    hv_stores(rt, "entries", newRV_noinc((SV *)avEntries));
                    // Loop each entry

                    while (iEntries > 0)
                    {
                        iEntries--;
                        AV* avNextHop = NULL; // NEXT_HOP container
                        AV* avAsPath  = NULL; // AS_PATH container

#                       ifdef _DEBUG_
                        printf("mrt_decode_single(): %d entries remaining\n", iEntries);
#                       endif
                        // Prepare Entry HashRef

                        HV* hvEntry = newHV();
                        av_push(avEntries, newRV_noinc((SV *)hvEntry));

                        // Decode one entry
                        uint16_t iPeer;
                        mrt_copy_next(&pPos, &iPeer, 2, &iRemainingLen);
                        iPeer = ntohs(iPeer);
                        hv_stores(hvEntry, "peer_index", newSVuv(iPeer));

                        // Decode Originated Time
                        int32_t orig_time;
                        mrt_copy_next(&pPos, &orig_time, 4, &iRemainingLen);
                        orig_time = ntohl(orig_time);
                        hv_stores(hvEntry, "originated_time", newSViv(orig_time));

                        // Store length of BGP attributes
                        uint16_t iBgpAttributesLen;
                        int iBgpAttributesRemainLen;
                        mrt_copy_next(&pPos, &iBgpAttributesLen, 2, &iRemainingLen);
                        iBgpAttributesRemainLen = iBgpAttributesLen = ntohs(iBgpAttributesLen);

                        // Store pointer to BGP attributes
                        char* pBgpAttributes = pPos;
                        if (iRemainingLen < iBgpAttributesLen)
                            croak("Attempt to read %d bytes while buffer contain only %d", iBgpAttributesLen, iRemainingLen);
                        pPos = pPos + iBgpAttributesLen; // Skip pPos to next entry
                        iRemainingLen -= iBgpAttributesLen; // Decrease main remaining length

                        // Scan each BGP attribute
                        while (iBgpAttributesRemainLen > 0) // pPos points to next entry
                        {
                            // Parse each attribute
                            uint8_t attribute_flags;
                            mrt_copy_next(&pBgpAttributes, &attribute_flags, 1, &iBgpAttributesRemainLen);
                            uint8_t attribute_code;
                            mrt_copy_next(&pBgpAttributes, &attribute_code, 1, &iBgpAttributesRemainLen);
                            // Check for Extended Length and read length
                            uint16_t iAttributeLen = 0;
                            int iAttributeRemainLen;
                            if (attribute_flags & 0x10) {
                                mrt_copy_next(&pBgpAttributes, &iAttributeLen, 2, &iBgpAttributesRemainLen);
                                iAttributeLen = ntohs(iAttributeLen);
                            } else {
                                uint8_t att_len_8;
                                mrt_copy_next(&pBgpAttributes, &att_len_8, 1, &iBgpAttributesRemainLen);
                                iAttributeLen = att_len_8;
                            }
                            iAttributeRemainLen = iAttributeLen;
#                           ifdef _DEBUG_
                            printf("mrt_decode_single(): Decoding attribute code %d (len %d)\n", attribute_code, iAttributeLen);
#                           endif

                            // Decode attributes
                            switch (attribute_code)
                            {
                                // 1	ORIGIN	[RFC4271]
                                case 1:
                                    mrt_copy_next(&pBgpAttributes, &iTmpU8, 1, &iBgpAttributesRemainLen);
                                    hv_stores(hvEntry, "ORIGIN", newSVuv(iTmpU8));
                                    break;
                                // 2	AS_PATH	[RFC4271]
                                case 2:
                                    // Check for absent NEXT_HOP array and create it
                                    if (avAsPath == NULL)
                                    {
                                        avAsPath = newAV();
                                        hv_stores(hvEntry, "AS_PATH", newRV_noinc((SV *)avAsPath));
                                    }
                                    while (iAttributeRemainLen > 0)
                                    {
                                        // Read next AS_PATH subtype
                                        iAttributeRemainLen -= 2;
                                        uint8_t iPathType;
                                        uint8_t iPathCount;
                                        mrt_copy_next(&pBgpAttributes, &iPathType, 1, &iBgpAttributesRemainLen);
                                        mrt_copy_next(&pBgpAttributes, &iPathCount, 1, &iBgpAttributesRemainLen);

                                        uint32_t iAsPathEntry;
                                        // Decode AS_SET & AS_SEQUENCE
                                        AV* avTmpAv2 = NULL;
                                        if (iPathType == 1) // Compose subarray in case of AS_SET
                                        {
                                            avTmpAv2 = newAV();
                                            av_push(avAsPath, newRV_noinc((SV *)avTmpAv2));
                                        }
                                        while (iPathCount > 0) {
                                            iPathCount--;
                                            iAttributeRemainLen -= 4; // NOTE: RIPE RIS hold 4-byte ASn in AS_PATH
                                            mrt_copy_next(&pBgpAttributes, &iAsPathEntry, 4, &iBgpAttributesRemainLen);
                                            iAsPathEntry = ntohl(iAsPathEntry);
                                            av_push(((iPathType == 1)? avTmpAv2 : avAsPath), newSVuv(iAsPathEntry));
                                        }
                                    } // end while (iAttributeRemainLen > 0)
                                    break; // 2	AS_PATH	[RFC4271]
                                // 3	NEXT_HOP	[RFC4271]
                                case 3:
                                    // Check for absent NEXT_HOP array and create it
                                    if (avNextHop == NULL)
                                    {
                                        avNextHop = newAV();
                                        hv_stores(hvEntry, "NEXT_HOP", newRV_noinc((SV *)avNextHop));
                                    }

                                    mrt_copy_next(&pBgpAttributes, &iTmpU32, 4, &iBgpAttributesRemainLen);
                                    inet_ntop(AF_INET, &iTmpU32, cIpAddress, INET6_ADDRSTRLEN);
                                    av_push(avNextHop, newSVpv(cIpAddress, 0));
                                    break;// 3	NEXT_HOP	[RFC4271]
                                // 4	MULTI_EXIT_DISC	[RFC4271]
                                case 4:
                                    mrt_copy_next(&pBgpAttributes, &iTmpU32, 4, &iBgpAttributesRemainLen);
                                    iTmpU32 = ntohl(iTmpU32);
                                    hv_stores(hvEntry, "MULTI_EXIT_DISC", newSVuv(iTmpU32));
                                    break;// 4	MULTI_EXIT_DISC	[RFC4271]
                                // 5	LOCAL_PREF	[RFC4271]
                                case 5:
                                    mrt_copy_next(&pBgpAttributes, &iTmpU32, 4, &iBgpAttributesRemainLen);
                                    iTmpU32 = ntohl(iTmpU32);
                                    hv_stores(hvEntry, "LOCAL_PREF", newSVuv(iTmpU32));
                                    break;// 5	LOCAL_PREF	[RFC4271]
                                // 6	ATOMIC_AGGREGATE	[RFC4271]
                                case 6:
                                    hv_stores(hvEntry, "ATOMIC_AGGREGATE", &PL_sv_undef);
                                    break;// 6	ATOMIC_AGGREGATE	[RFC4271]
                                // 7	AGGREGATOR	[RFC4271]
                                case 7:
                                    mrt_copy_next(&pBgpAttributes, &iTmpU32, 4, &iBgpAttributesRemainLen);
                                    iTmpU32 = ntohl(iTmpU32);
                                    hv_stores(hvEntry, "AGGREGATOR_AS", newSVuv(iTmpU32));
                                    mrt_copy_next(&pBgpAttributes, &iTmpU32, 4, &iBgpAttributesRemainLen);
                                    inet_ntop(AF_INET, &iTmpU32, cIpAddress, INET6_ADDRSTRLEN);
                                    hv_stores(hvEntry, "AGGREGATOR_BGPID", newSVpv(cIpAddress, 0));
                                    break;// 7	AGGREGATOR	[RFC4271]
                                // 8	COMMUNITY	[RFC1997]
                                case 8:
                                    avTmpAv = (AV *)sv_2mortal((SV *)newAV());
                                    hv_stores(hvEntry, "COMMUNITY", newRV_inc((SV *)avTmpAv));

                                    while (iAttributeRemainLen > 0)
                                    {
                                        // Read and decode community
                                        iAttributeRemainLen -= 4;
                                        uint16_t c1, c2;
                                        mrt_copy_next(&pBgpAttributes, &c1, 2, &iBgpAttributesRemainLen);
                                        c1 = ntohs(c1);
                                        mrt_copy_next(&pBgpAttributes, &c2, 2, &iBgpAttributesRemainLen);
                                        c2 = ntohs(c2);
                                        snprintf(sbuff, SBUFF, "%d:%d", c1, c2);
                                        av_push(avTmpAv, newSVpv(sbuff, 0));
                                    } // end while (iAttributeRemainLen > 0)
                                    break;
                                // 14	MP_REACH_NLRI   http://tools.ietf.org/html/rfc6396#section-4.3.4
                                case 14:
                                    // Check for absent NEXT_HOP array and create it
                                    if (avNextHop == NULL)
                                    {
                                        avNextHop = newAV();
                                        hv_stores(hvEntry, "NEXT_HOP", newRV_noinc((SV *)avNextHop));
                                    }
                                    if (SvOK(USE_RFC4760) && SvIV(USE_RFC4760) == 1)
                                    {
                                        // read AFI
                                        uint16_t AFI;
                                        mrt_copy_next(&pBgpAttributes, &iTmpU16, 2, &iBgpAttributesRemainLen);
                                        AFI = ntohs(iTmpU16);
                                        // read/skip SAFI
                                        mrt_copy_next(&pBgpAttributes, &iTmpU8, 1, &iBgpAttributesRemainLen);
                                        // read LEN
                                        mrt_copy_next(&pBgpAttributes, &iTmpU8, 1, &iBgpAttributesRemainLen);
                                        iAttributeRemainLen -= 4;
                                        // validate LEN & decode IP
                                        bool bSkip = false;
                                        if (AFI == 1 && iTmpU8 == 4)
                                        {
                                            // Read and decode IPv4
                                            memset(&sa6, 0, sizeof(sa6));
                                            mrt_copy_next(&pBgpAttributes, &sa6, iTmpU8, &iBgpAttributesRemainLen);
                                            iAttributeRemainLen -= iTmpU8;
                                            inet_ntop(AF_INET, &sa6, cIpAddress, INET6_ADDRSTRLEN);
                                            av_push(avNextHop, newSVpv(cIpAddress, 0));
                                        } else if (AFI == 2 && (iTmpU8 == 16 || iTmpU8 == 32)) // AFI=IPv6
                                        {
                                            // Read and decode IPv6
                                            memset(&sa6, 0, sizeof(sa6));
                                            mrt_copy_next(&pBgpAttributes, &sa6, 16, &iBgpAttributesRemainLen);
                                            iAttributeRemainLen -= 16;
                                            inet_ntop(AF_INET6, &sa6, cIpAddress, INET6_ADDRSTRLEN);
                                            av_push(avNextHop, newSVpv(cIpAddress, 0));
                                            if (iTmpU8 == 32) // Case for combined Global and Link Local IPv6
                                            {
                                                memset(&sa6, 0, sizeof(sa6));
                                                mrt_copy_next(&pBgpAttributes, &sa6, 16, &iBgpAttributesRemainLen);
                                                iAttributeRemainLen -= 16;
                                                inet_ntop(AF_INET6, &sa6, cIpAddress, INET6_ADDRSTRLEN);
                                                av_push(avNextHop, newSVpv(cIpAddress, 0));
                                            }
                                        } else {
                                            snprintf(sbuff, SBUFF, "Unsupported/invalid AFI %d len %d", AFI, iTmpU8);
                                            hv_stores(hvEntry, "MP_REACH_NLRI", newSVpv(sbuff, 0));
                                            // Skip to end of attribute
                                            bSkip = true;
                                        }
                                        if (!bSkip) {
                                            // Process MP_REACH_NLRI
                                            mrt_copy_next(&pBgpAttributes, &iTmpU8, 1, &iBgpAttributesRemainLen); // Skip reserved byte
                                            iAttributeRemainLen--;

                                            avTmpAv = (AV *)sv_2mortal((SV *)newAV());
                                            hv_stores(hvEntry, "MP_REACH_NLRI", newRV_inc((SV *)avTmpAv));

                                            while (iAttributeRemainLen > 0)
                                            {
                                                // Read and decode NLRI
                                                // Decode Prefix Bits
                                                uint8_t iBits;
                                                mrt_copy_next(&pBgpAttributes, &iBits, 1, &iBgpAttributesRemainLen);
                                                iAttributeRemainLen--;
                                                hv_stores(rt, "bits", newSVuv(iPrefixBits));

                                                // Decode Prefix
                                                memset(&sa6, 0, sizeof(sa6));
                                                if (iPrefixBits > 0) {
                                                    mrt_copy_next(&pBgpAttributes, &sa6, (int)ceil((double)iBits/8), &iBgpAttributesRemainLen);
                                                    iAttributeRemainLen -= (int)ceil((double)iBits/8);
                                                }
                                                inet_ntop((AFI == 1 ? AF_INET : AF_INET6), &sa6, cIpAddress, INET6_ADDRSTRLEN);

                                                snprintf(sbuff, SBUFF, "%s/%d", cIpAddress, iBits);
                                                av_push(avTmpAv, newSVpv(sbuff, 0));
                                            } // end while (iAttributeRemainLen > 0)
                                        }
                                        pBgpAttributes += iAttributeRemainLen;
                                        iBgpAttributesRemainLen -= iAttributeRemainLen;
                                    } else if (SvOK(USE_RFC4760) && SvIV(USE_RFC4760) == -1) {
                                        pBgpAttributes += iAttributeLen;
                                        iBgpAttributesRemainLen -= iAttributeLen;
                                    } else {
                                        // Use RFC 6396
                                        iTmpU8 = (AF == AF_INET ? 4 : 16); // Store size of IP address for AF
                                        // Read size of MRT MP_REACH_NLRI
                                        mrt_copy_next(&pBgpAttributes, &iAttributeRemainLen, 1, &iBgpAttributesRemainLen);
#                                       ifdef _DEBUG_
                                        printf("mrt_decode_single(): Attribute 14 have %u bytes for AF. Remain %d bytes\n", iTmpU8, iAttributeRemainLen);
#                                       endif
                                        while (iAttributeRemainLen > 0)
                                        {
                                            iAttributeRemainLen -= iTmpU8;
                                            memset(&sa6, 0, sizeof(sa6));
                                            mrt_copy_next(&pBgpAttributes, &sa6, iTmpU8, &iBgpAttributesRemainLen);
                                            inet_ntop(AF, &sa6, cIpAddress, INET6_ADDRSTRLEN);
                                            av_push(avNextHop, newSVpv(cIpAddress, 0));
                                        }
                                    } // end else if RFC4760
                                    break;// 14	MP_REACH_NLRI	[RFC4760]
                                default:
                                    hv_store(hvEntry, sbuff, strlen(sbuff), &PL_sv_undef, 0);
                                    pBgpAttributes += iAttributeLen;
                                    iBgpAttributesRemainLen -= iAttributeLen;
                            } // switch (attribute_code)
                        } // iBgpAttributesRemainLen > 0
                    } // while (iEntries > 0)

                    break; // subtype = IPv4/6 UNICAST/MULTICAST
                default:
                    snprintf(sbuff, SBUFF, "Unsupported MRT type %d subtype %d in message at %lli", mh->type, mh->subtype, (intmax_t)msgpos);
                    hv_stores(rt, "error", newSVpv(sbuff, 0));
            } // switch subtype
            break; // MT_TABLE_DUMP_V2
        default:
            snprintf(sbuff, SBUFF, "Unsupported MRT type %d in message at %lli", mh->type, (intmax_t)msgpos);
            hv_stores(rt, "error", newSVpv(sbuff, 0));
    } // switch message type
    return;
}

MODULE = Net::MRT		PACKAGE = Net::MRT

void
mrt_read_next(f)
PerlIO * f;
    PROTOTYPE: *
    PPCODE:
        # Definitions
        Off_t msgpos = PerlIO_tell(f); // Store message position & check for proper handle
        int sz;
        MRT_MESSAGE mh;
        char sbuff[SBUFF] = {};
        HV* rt;

        if (msgpos == -1)
            croak("Invalid filehandle passed to mrt_read_next");
        sz = PerlIO_read(f, &mh, 12);
        if (sz == 0)
        {
            # No data to read
            ST(0) = &PL_sv_undef;
            XSRETURN(1);
        } else {
            # Network to host for MH
            mh.timestamp = ntohl(mh.timestamp);
            mh.type      = ntohs(mh.type);
            mh.subtype   = ntohs(mh.subtype);
            mh.length    = ntohl(mh.length);

            # Create resulting HASHREF
            rt = newHV();
            hv_stores(rt, "timestamp",  newSVuv(mh.timestamp));
            hv_stores(rt, "type",       newSVuv(mh.type));
            hv_stores(rt, "subtype",    newSVuv(mh.subtype));

            # Decode header
            # Check for length to be less than buffer
            if (mh.length > BUFFER_SIZE)
            {
                snprintf(sbuff, SBUFF, "Message length too big at %lli", (intmax_t)msgpos);
                hv_stores(rt, "error", newSVpv(sbuff, 0));
                PerlIO_seek(f, mh.length, SEEK_CUR);
            } else {
                # Try to read message
                if (mh.length > 0)
                    sz = PerlIO_read(f, &mh.message, mh.length);
                if ((mh.length > 0) && (sz != mh.length))
                    croak("Unable to read %d bytes in message at pos %lli", mh.length, (intmax_t)msgpos);

                # Try to decode
                mrt_decode(rt, msgpos, &mh);
            }
            ST(0) = sv_2mortal((SV*)newRV_noinc((SV*)rt));
            XSRETURN(1);
        }

void
mrt_get_next(f)
PerlIO * f;
    PROTOTYPE: *
    PPCODE:
        # Definitions
        Off_t msgpos = PerlIO_tell(f); // Store message position & check for proper handle
        int sz;
        MRT_MESSAGE mh;
        char sbuff[SBUFF] = {};
        HV* rt;

        if (msgpos == -1)
            croak("Invalid filehandle passed to mrt_read_next");
        sz = PerlIO_read(f, &mh, 12);
        if (sz == 0)
        {
            # No data to read
            ST(0) = &PL_sv_undef;
            XSRETURN(1);
        } else {
            # Network to host for MH
            mh.timestamp = ntohl(mh.timestamp);
            mh.type      = ntohs(mh.type);
            mh.subtype   = ntohs(mh.subtype);
            mh.length    = ntohl(mh.length);

            # Decode header
            # Check for length to be less than buffer
            ST(0) = newSVuv(mh.type);
            ST(1) = newSVuv(mh.subtype);
            if (mh.length > BUFFER_SIZE)
            {
                snprintf(sbuff, SBUFF, "Message length too big at %lli", (intmax_t)msgpos);
                PerlIO_seek(f, mh.length, SEEK_CUR);
                ST(2) = newSVuv(-1);
                ST(3) = newSVpv(sbuff, strlen(sbuff));
            } else {
                # Try to read message
                if (mh.length > 0)
                    sz = PerlIO_read(f, &mh.message, mh.length);
                if ((mh.length > 0) && (sz != mh.length))
                    croak("Unable to read %d bytes in message at pos %lli", mh.length, (intmax_t)msgpos);

                ST(2) = newSVuv(mh.length);
                ST(3) = newSVpv(mh.message, mh.length);
            }
            XSRETURN(4);
        }

HV*
mrt_decode_single(type, subtype, message)
uint16_t type;
uint16_t subtype;
SV*      message;
    CODE:
        MRT_MESSAGE mh;
        char* msg;

        // Prepare returning variable(s)
        RETVAL = newHV();

        // Prepare intermediate variables
        mh.timestamp    = 0;
        mh.type         = type;
        mh.subtype      = subtype;
        msg = (char*)SvPV(message, mh.length);
        memcpy(&mh.message, msg, mh.length);

        // Perform checks
        if (mh.length == 0)
            croak("I don't know how to decode a message without contents");
        if (mh.length > BUFFER_SIZE)
            croak("Unable to process message larger than %d bytes", BUFFER_SIZE);

        mrt_decode(RETVAL, 0, &mh);
        sv_2mortal((SV*)newRV_noinc((SV*)RETVAL));
    OUTPUT:
        RETVAL

# void mrt_decode(HV* const rt, Off_t const msgpos, MRT_MESSAGE* const mh)

BOOT:
    USE_RFC4760 = get_sv("Net::MRT::USE_RFC4760", GV_ADDMULTI);
    //sv_setiv(USE_RFC4760, 1); // Change default behavior