Changeset dd0c8a0 in mainline for uspace/srv/net/inetsrv/pdu.c


Ignore:
Timestamp:
2013-09-29T06:56:33Z (12 years ago)
Author:
Beniamino Galvani <b.galvani@…>
Branches:
lfn, master, serial, ticket/834-toolchain-update, topic/msim-upgrade, topic/simplify-dev-export
Children:
a9bd960d
Parents:
3deb0155 (diff), 13be2583 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
Message:

Merge mainline changes.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • uspace/srv/net/inetsrv/pdu.c

    r3deb0155 rdd0c8a0  
    4444#include <mem.h>
    4545#include <stdlib.h>
    46 
     46#include <net/socket_codes.h>
    4747#include "inetsrv.h"
    4848#include "inet_std.h"
    4949#include "pdu.h"
    50 
    51 static FIBRIL_MUTEX_INITIALIZE(ip_ident_lock);
    52 static uint16_t ip_ident = 0;
    5350
    5451/** One's complement addition.
     
    8885}
    8986
    90 /** Encode Internet PDU.
     87/** Encode IPv4 PDU.
    9188 *
    9289 * Encode internet packet into PDU (serialized form). Will encode a
     
    9693 * be set in the header, otherwise the offset will equal @a packet->size.
    9794 *
    98  * @param packet        Packet to encode
    99  * @param offs          Offset into packet payload (in bytes)
    100  * @param mtu           MTU (Maximum Transmission Unit) in bytes
    101  * @param rdata         Place to store pointer to allocated data buffer
    102  * @param rsize         Place to store size of allocated data buffer
    103  * @param roffs         Place to store offset of remaning data
    104  */
    105 int inet_pdu_encode(inet_packet_t *packet, size_t offs, size_t mtu,
    106     void **rdata, size_t *rsize, size_t *roffs)
    107 {
    108         void *data;
    109         size_t size;
    110         ip_header_t *hdr;
    111         size_t hdr_size;
    112         size_t data_offs;
    113         uint16_t chksum;
    114         uint16_t ident;
    115         uint16_t flags_foff;
    116         uint16_t foff;
    117         size_t fragoff_limit;
    118         size_t xfer_size;
    119         size_t spc_avail;
    120         size_t rem_offs;
    121 
     95 * @param packet Packet to encode
     96 * @param src    Source address
     97 * @param dest   Destination address
     98 * @param offs   Offset into packet payload (in bytes)
     99 * @param mtu    MTU (Maximum Transmission Unit) in bytes
     100 * @param rdata  Place to store pointer to allocated data buffer
     101 * @param rsize  Place to store size of allocated data buffer
     102 * @param roffs  Place to store offset of remaning data
     103 *
     104 */
     105int inet_pdu_encode(inet_packet_t *packet, addr32_t src, addr32_t dest,
     106    size_t offs, size_t mtu, void **rdata, size_t *rsize, size_t *roffs)
     107{
    122108        /* Upper bound for fragment offset field */
    123         fragoff_limit = 1 << (FF_FRAGOFF_h - FF_FRAGOFF_l);
    124 
     109        size_t fragoff_limit = 1 << (FF_FRAGOFF_h - FF_FRAGOFF_l);
     110       
    125111        /* Verify that total size of datagram is within reasonable bounds */
    126112        if (offs + packet->size > FRAG_OFFS_UNIT * fragoff_limit)
    127113                return ELIMIT;
    128 
    129         hdr_size = sizeof(ip_header_t);
    130         data_offs = ROUND_UP(hdr_size, 4);
    131 
     114       
     115        size_t hdr_size = sizeof(ip_header_t);
     116        if (hdr_size >= mtu)
     117                return EINVAL;
     118       
     119        assert(hdr_size % 4 == 0);
    132120        assert(offs % FRAG_OFFS_UNIT == 0);
    133121        assert(offs / FRAG_OFFS_UNIT < fragoff_limit);
    134 
     122       
    135123        /* Value for the fragment offset field */
    136         foff = offs / FRAG_OFFS_UNIT;
    137 
    138         if (hdr_size >= mtu)
    139                 return EINVAL;
    140 
     124        uint16_t foff = offs / FRAG_OFFS_UNIT;
     125       
    141126        /* Amount of space in the PDU available for payload */
    142         spc_avail = mtu - hdr_size;
     127        size_t spc_avail = mtu - hdr_size;
    143128        spc_avail -= (spc_avail % FRAG_OFFS_UNIT);
    144 
     129       
    145130        /* Amount of data (payload) to transfer */
    146         xfer_size = min(packet->size - offs, spc_avail);
    147 
     131        size_t xfer_size = min(packet->size - offs, spc_avail);
     132       
    148133        /* Total PDU size */
    149         size = hdr_size + xfer_size;
    150 
     134        size_t size = hdr_size + xfer_size;
     135       
    151136        /* Offset of remaining payload */
    152         rem_offs = offs + xfer_size;
    153 
     137        size_t rem_offs = offs + xfer_size;
     138       
    154139        /* Flags */
    155         flags_foff =
     140        uint16_t flags_foff =
    156141            (packet->df ? BIT_V(uint16_t, FF_FLAG_DF) : 0) +
    157142            (rem_offs < packet->size ? BIT_V(uint16_t, FF_FLAG_MF) : 0) +
    158143            (foff << FF_FRAGOFF_l);
    159 
    160         data = calloc(size, 1);
     144       
     145        void *data = calloc(size, 1);
    161146        if (data == NULL)
    162147                return ENOMEM;
    163 
    164         /* Allocate identifier */
    165         fibril_mutex_lock(&ip_ident_lock);
    166         ident = ++ip_ident;
    167         fibril_mutex_unlock(&ip_ident_lock);
    168 
     148       
    169149        /* Encode header fields */
    170         hdr = (ip_header_t *)data;
    171         hdr->ver_ihl = (4 << VI_VERSION_l) | (hdr_size / sizeof(uint32_t));
     150        ip_header_t *hdr = (ip_header_t *) data;
     151       
     152        hdr->ver_ihl =
     153            (4 << VI_VERSION_l) | (hdr_size / sizeof(uint32_t));
    172154        hdr->tos = packet->tos;
    173155        hdr->tot_len = host2uint16_t_be(size);
    174         hdr->id = host2uint16_t_be(ident);
     156        hdr->id = host2uint16_t_be(packet->ident);
    175157        hdr->flags_foff = host2uint16_t_be(flags_foff);
    176158        hdr->ttl = packet->ttl;
    177159        hdr->proto = packet->proto;
    178160        hdr->chksum = 0;
    179         hdr->src_addr = host2uint32_t_be(packet->src.ipv4);
    180         hdr->dest_addr = host2uint32_t_be(packet->dest.ipv4);
    181 
     161        hdr->src_addr = host2uint32_t_be(src);
     162        hdr->dest_addr = host2uint32_t_be(dest);
     163       
    182164        /* Compute checksum */
    183         chksum = inet_checksum_calc(INET_CHECKSUM_INIT, (void *)hdr, hdr_size);
     165        uint16_t chksum = inet_checksum_calc(INET_CHECKSUM_INIT,
     166            (void *) hdr, hdr_size);
    184167        hdr->chksum = host2uint16_t_be(chksum);
    185 
     168       
    186169        /* Copy payload */
    187         memcpy((uint8_t *)data + data_offs, packet->data + offs, xfer_size);
    188 
     170        memcpy((uint8_t *) data + hdr_size, packet->data + offs, xfer_size);
     171       
    189172        *rdata = data;
    190173        *rsize = size;
    191174        *roffs = rem_offs;
    192 
     175       
    193176        return EOK;
    194177}
    195178
     179/** Encode IPv6 PDU.
     180 *
     181 * Encode internet packet into PDU (serialized form). Will encode a
     182 * fragment of the payload starting at offset @a offs. The resulting
     183 * PDU will have at most @a mtu bytes. @a *roffs will be set to the offset
     184 * of remaining payload. If some data is remaining, the MF flag will
     185 * be set in the header, otherwise the offset will equal @a packet->size.
     186 *
     187 * @param packet Packet to encode
     188 * @param src    Source address
     189 * @param dest   Destination address
     190 * @param offs   Offset into packet payload (in bytes)
     191 * @param mtu    MTU (Maximum Transmission Unit) in bytes
     192 * @param rdata  Place to store pointer to allocated data buffer
     193 * @param rsize  Place to store size of allocated data buffer
     194 * @param roffs  Place to store offset of remaning data
     195 *
     196 */
     197int inet_pdu_encode6(inet_packet_t *packet, addr128_t src, addr128_t dest,
     198    size_t offs, size_t mtu, void **rdata, size_t *rsize, size_t *roffs)
     199{
     200        /* IPv6 mandates a minimal MTU of 1280 bytes */
     201        if (mtu < 1280)
     202                return ELIMIT;
     203       
     204        /* Upper bound for fragment offset field */
     205        size_t fragoff_limit = 1 << (OF_FRAGOFF_h - OF_FRAGOFF_l);
     206       
     207        /* Verify that total size of datagram is within reasonable bounds */
     208        if (offs + packet->size > FRAG_OFFS_UNIT * fragoff_limit)
     209                return ELIMIT;
     210       
     211        /* Determine whether we need the Fragment extension header */
     212        bool fragment;
     213        if (offs == 0)
     214                fragment = (packet->size + sizeof(ip6_header_t) > mtu);
     215        else
     216                fragment = true;
     217       
     218        size_t hdr_size;
     219        if (fragment)
     220                hdr_size = sizeof(ip6_header_t) + sizeof(ip6_header_fragment_t);
     221        else
     222                hdr_size = sizeof(ip6_header_t);
     223       
     224        if (hdr_size >= mtu)
     225                return EINVAL;
     226       
     227        assert(sizeof(ip6_header_t) % 8 == 0);
     228        assert(hdr_size % 8 == 0);
     229        assert(offs % FRAG_OFFS_UNIT == 0);
     230        assert(offs / FRAG_OFFS_UNIT < fragoff_limit);
     231       
     232        /* Value for the fragment offset field */
     233        uint16_t foff = offs / FRAG_OFFS_UNIT;
     234       
     235        /* Amount of space in the PDU available for payload */
     236        size_t spc_avail = mtu - hdr_size;
     237        spc_avail -= (spc_avail % FRAG_OFFS_UNIT);
     238       
     239        /* Amount of data (payload) to transfer */
     240        size_t xfer_size = min(packet->size - offs, spc_avail);
     241       
     242        /* Total PDU size */
     243        size_t size = hdr_size + xfer_size;
     244       
     245        /* Offset of remaining payload */
     246        size_t rem_offs = offs + xfer_size;
     247       
     248        /* Flags */
     249        uint16_t offsmf =
     250            (rem_offs < packet->size ? BIT_V(uint16_t, OF_FLAG_M) : 0) +
     251            (foff << OF_FRAGOFF_l);
     252       
     253        void *data = calloc(size, 1);
     254        if (data == NULL)
     255                return ENOMEM;
     256       
     257        /* Encode header fields */
     258        ip6_header_t *hdr6 = (ip6_header_t *) data;
     259       
     260        hdr6->ver_tc = (6 << (VI_VERSION_l));
     261        memset(hdr6->tc_fl, 0, 3);
     262        hdr6->hop_limit = packet->ttl;
     263       
     264        host2addr128_t_be(src, hdr6->src_addr);
     265        host2addr128_t_be(dest, hdr6->dest_addr);
     266       
     267        /* Optionally encode Fragment extension header fields */
     268        if (fragment) {
     269                assert(offsmf != 0);
     270               
     271                hdr6->payload_len = host2uint16_t_be(packet->size +
     272                    sizeof(ip6_header_fragment_t));
     273                hdr6->next = IP6_NEXT_FRAGMENT;
     274               
     275                ip6_header_fragment_t *hdr6f = (ip6_header_fragment_t *)
     276                    (hdr6 + 1);
     277               
     278                hdr6f->next = packet->proto;
     279                hdr6f->reserved = 0;
     280                hdr6f->offsmf = host2uint16_t_be(offsmf);
     281                hdr6f->id = host2uint32_t_be(packet->ident);
     282        } else {
     283                assert(offsmf == 0);
     284               
     285                hdr6->payload_len = host2uint16_t_be(packet->size);
     286                hdr6->next = packet->proto;
     287        }
     288       
     289        /* Copy payload */
     290        memcpy((uint8_t *) data + hdr_size, packet->data + offs, xfer_size);
     291       
     292        *rdata = data;
     293        *rsize = size;
     294        *roffs = rem_offs;
     295       
     296        return EOK;
     297}
     298
     299/** Decode IPv4 datagram
     300 *
     301 * @param data   Serialized IPv4 datagram
     302 * @param size   Length of serialized IPv4 datagram
     303 * @param packet IP datagram structure to be filled
     304 *
     305 * @return EOK on success
     306 * @return EINVAL if the datagram is invalid or damaged
     307 * @return ENOMEM if not enough memory
     308 *
     309 */
    196310int inet_pdu_decode(void *data, size_t size, inet_packet_t *packet)
    197311{
    198         ip_header_t *hdr;
    199         size_t tot_len;
    200         size_t data_offs;
    201         uint8_t version;
    202         uint16_t ident;
    203         uint16_t flags_foff;
    204         uint16_t foff;
    205 
    206312        log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_pdu_decode()");
    207 
     313       
    208314        if (size < sizeof(ip_header_t)) {
    209315                log_msg(LOG_DEFAULT, LVL_DEBUG, "PDU too short (%zu)", size);
    210316                return EINVAL;
    211317        }
    212 
    213         hdr = (ip_header_t *)data;
    214 
    215         version = BIT_RANGE_EXTRACT(uint8_t, VI_VERSION_h, VI_VERSION_l,
    216             hdr->ver_ihl);
     318       
     319        ip_header_t *hdr = (ip_header_t *) data;
     320       
     321        uint8_t version = BIT_RANGE_EXTRACT(uint8_t, VI_VERSION_h,
     322            VI_VERSION_l, hdr->ver_ihl);
    217323        if (version != 4) {
    218324                log_msg(LOG_DEFAULT, LVL_DEBUG, "Version (%d) != 4", version);
    219325                return EINVAL;
    220326        }
    221 
    222         tot_len = uint16_t_be2host(hdr->tot_len);
     327       
     328        size_t tot_len = uint16_t_be2host(hdr->tot_len);
    223329        if (tot_len < sizeof(ip_header_t)) {
    224330                log_msg(LOG_DEFAULT, LVL_DEBUG, "Total Length too small (%zu)", tot_len);
    225331                return EINVAL;
    226332        }
    227 
     333       
    228334        if (tot_len > size) {
    229335                log_msg(LOG_DEFAULT, LVL_DEBUG, "Total Length = %zu > PDU size = %zu",
    230                         tot_len, size);
    231                 return EINVAL;
    232         }
    233 
    234         ident = uint16_t_be2host(hdr->id);
    235         flags_foff = uint16_t_be2host(hdr->flags_foff);
    236         foff = BIT_RANGE_EXTRACT(uint16_t, FF_FRAGOFF_h, FF_FRAGOFF_l,
     336                    tot_len, size);
     337                return EINVAL;
     338        }
     339       
     340        uint16_t ident = uint16_t_be2host(hdr->id);
     341        uint16_t flags_foff = uint16_t_be2host(hdr->flags_foff);
     342        uint16_t foff = BIT_RANGE_EXTRACT(uint16_t, FF_FRAGOFF_h, FF_FRAGOFF_l,
    237343            flags_foff);
    238344        /* XXX Checksum */
    239 
    240         packet->src.ipv4 = uint32_t_be2host(hdr->src_addr);
    241         packet->dest.ipv4 = uint32_t_be2host(hdr->dest_addr);
     345       
     346        inet_addr_set(uint32_t_be2host(hdr->src_addr), &packet->src);
     347        inet_addr_set(uint32_t_be2host(hdr->dest_addr), &packet->dest);
    242348        packet->tos = hdr->tos;
    243349        packet->proto = hdr->proto;
    244350        packet->ttl = hdr->ttl;
    245351        packet->ident = ident;
    246 
     352       
    247353        packet->df = (flags_foff & BIT_V(uint16_t, FF_FLAG_DF)) != 0;
    248354        packet->mf = (flags_foff & BIT_V(uint16_t, FF_FLAG_MF)) != 0;
    249355        packet->offs = foff * FRAG_OFFS_UNIT;
    250 
     356       
    251357        /* XXX IP options */
    252         data_offs = sizeof(uint32_t) * BIT_RANGE_EXTRACT(uint8_t, VI_IHL_h,
    253             VI_IHL_l, hdr->ver_ihl);
    254 
     358        size_t data_offs = sizeof(uint32_t) *
     359            BIT_RANGE_EXTRACT(uint8_t, VI_IHL_h, VI_IHL_l, hdr->ver_ihl);
     360       
    255361        packet->size = tot_len - data_offs;
    256362        packet->data = calloc(packet->size, 1);
     
    259365                return ENOMEM;
    260366        }
    261 
    262         memcpy(packet->data, (uint8_t *)data + data_offs, packet->size);
    263 
     367       
     368        memcpy(packet->data, (uint8_t *) data + data_offs, packet->size);
     369       
    264370        return EOK;
    265371}
    266372
     373/** Decode IPv6 datagram
     374 *
     375 * @param data   Serialized IPv6 datagram
     376 * @param size   Length of serialized IPv6 datagram
     377 * @param packet IP datagram structure to be filled
     378 *
     379 * @return EOK on success
     380 * @return EINVAL if the datagram is invalid or damaged
     381 * @return ENOMEM if not enough memory
     382 *
     383 */
     384int inet_pdu_decode6(void *data, size_t size, inet_packet_t *packet)
     385{
     386        log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_pdu_decode6()");
     387       
     388        if (size < sizeof(ip6_header_t)) {
     389                log_msg(LOG_DEFAULT, LVL_DEBUG, "PDU too short (%zu)", size);
     390                return EINVAL;
     391        }
     392       
     393        ip6_header_t *hdr6 = (ip6_header_t *) data;
     394       
     395        uint8_t version = BIT_RANGE_EXTRACT(uint8_t, VI_VERSION_h,
     396            VI_VERSION_l, hdr6->ver_tc);
     397        if (version != 6) {
     398                log_msg(LOG_DEFAULT, LVL_DEBUG, "Version (%d) != 6", version);
     399                return EINVAL;
     400        }
     401       
     402        size_t payload_len = uint16_t_be2host(hdr6->payload_len);
     403        if (payload_len + sizeof(ip6_header_t) > size) {
     404                log_msg(LOG_DEFAULT, LVL_DEBUG, "Payload Length = %zu > PDU size = %zu",
     405                    payload_len + sizeof(ip6_header_t), size);
     406                return EINVAL;
     407        }
     408       
     409        uint32_t ident;
     410        uint16_t offsmf;
     411        uint16_t foff;
     412        uint16_t next;
     413        size_t data_offs = sizeof(ip6_header_t);
     414       
     415        /* Fragment extension header */
     416        if (hdr6->next == IP6_NEXT_FRAGMENT) {
     417                ip6_header_fragment_t *hdr6f = (ip6_header_fragment_t *)
     418                    (hdr6 + 1);
     419               
     420                ident = uint32_t_be2host(hdr6f->id);
     421                offsmf = uint16_t_be2host(hdr6f->offsmf);
     422                foff = BIT_RANGE_EXTRACT(uint16_t, OF_FRAGOFF_h, OF_FRAGOFF_l,
     423                    offsmf);
     424                next = hdr6f->next;
     425                data_offs += sizeof(ip6_header_fragment_t);
     426                payload_len -= sizeof(ip6_header_fragment_t);
     427        } else {
     428                ident = 0;
     429                offsmf = 0;
     430                foff = 0;
     431                next = hdr6->next;
     432        }
     433       
     434        addr128_t src;
     435        addr128_t dest;
     436       
     437        addr128_t_be2host(hdr6->src_addr, src);
     438        inet_addr_set6(src, &packet->src);
     439       
     440        addr128_t_be2host(hdr6->dest_addr, dest);
     441        inet_addr_set6(dest, &packet->dest);
     442       
     443        packet->tos = 0;
     444        packet->proto = next;
     445        packet->ttl = hdr6->hop_limit;
     446        packet->ident = ident;
     447       
     448        packet->df = 1;
     449        packet->mf = (offsmf & BIT_V(uint16_t, OF_FLAG_M)) != 0;
     450        packet->offs = foff * FRAG_OFFS_UNIT;
     451       
     452        packet->size = payload_len;
     453        packet->data = calloc(packet->size, 1);
     454        if (packet->data == NULL) {
     455                log_msg(LOG_DEFAULT, LVL_WARN, "Out of memory.");
     456                return ENOMEM;
     457        }
     458       
     459        memcpy(packet->data, (uint8_t *) data + data_offs, packet->size);
     460       
     461        return EOK;
     462}
     463
     464/** Encode NDP packet
     465 *
     466 * @param ndp   NDP packet structure to be serialized
     467 * @param dgram IPv6 datagram structure to be filled
     468 *
     469 * @return EOK on success
     470 *
     471 */
     472int ndp_pdu_encode(ndp_packet_t *ndp, inet_dgram_t *dgram)
     473{
     474        inet_addr_set6(ndp->sender_proto_addr, &dgram->src);
     475        inet_addr_set6(ndp->target_proto_addr, &dgram->dest);
     476        dgram->tos = 0;
     477        dgram->size = sizeof(icmpv6_message_t) + sizeof(ndp_message_t);
     478       
     479        dgram->data = calloc(1, dgram->size);
     480        if (dgram->data == NULL)
     481                return ENOMEM;
     482       
     483        icmpv6_message_t *icmpv6 = (icmpv6_message_t *) dgram->data;
     484       
     485        icmpv6->type = ndp->opcode;
     486        icmpv6->code = 0;
     487        memset(icmpv6->un.ndp.reserved, 0, 3);
     488       
     489        ndp_message_t *message = (ndp_message_t *) (icmpv6 + 1);
     490       
     491        if (ndp->opcode == ICMPV6_NEIGHBOUR_SOLICITATION) {
     492                host2addr128_t_be(ndp->solicited_ip, message->target_address);
     493                message->option = 1;
     494                icmpv6->un.ndp.flags = 0;
     495        } else {
     496                host2addr128_t_be(ndp->sender_proto_addr, message->target_address);
     497                message->option = 2;
     498                icmpv6->un.ndp.flags = NDP_FLAG_OVERRIDE | NDP_FLAG_SOLICITED;
     499        }
     500       
     501        message->length = 1;
     502        addr48(ndp->sender_hw_addr, message->mac);
     503       
     504        icmpv6_phdr_t phdr;
     505       
     506        host2addr128_t_be(ndp->sender_proto_addr, phdr.src_addr);
     507        host2addr128_t_be(ndp->target_proto_addr, phdr.dest_addr);
     508        phdr.length = host2uint32_t_be(dgram->size);
     509        memset(phdr.zeroes, 0, 3);
     510        phdr.next = IP_PROTO_ICMPV6;
     511       
     512        uint16_t cs_phdr =
     513            inet_checksum_calc(INET_CHECKSUM_INIT, &phdr,
     514            sizeof(icmpv6_phdr_t));
     515       
     516        uint16_t cs_all = inet_checksum_calc(cs_phdr, dgram->data,
     517            dgram->size);
     518       
     519        icmpv6->checksum = host2uint16_t_be(cs_all);
     520       
     521        return EOK;
     522}
     523
     524/** Decode NDP packet
     525 *
     526 * @param dgram Incoming IPv6 datagram encapsulating NDP packet
     527 * @param ndp   NDP packet structure to be filled
     528 *
     529 * @return EOK on success
     530 * @return EINVAL if the Datagram is invalid
     531 *
     532 */
     533int ndp_pdu_decode(inet_dgram_t *dgram, ndp_packet_t *ndp)
     534{
     535        uint16_t src_af = inet_addr_get(&dgram->src, NULL,
     536            &ndp->sender_proto_addr);
     537        if (src_af != AF_INET6)
     538                return EINVAL;
     539       
     540        if (dgram->size < sizeof(icmpv6_message_t) + sizeof(ndp_message_t))
     541                return EINVAL;
     542       
     543        icmpv6_message_t *icmpv6 = (icmpv6_message_t *) dgram->data;
     544       
     545        ndp->opcode = icmpv6->type;
     546       
     547        ndp_message_t *message = (ndp_message_t *) (icmpv6 + 1);
     548       
     549        addr128_t_be2host(message->target_address, ndp->target_proto_addr);
     550        addr48(message->mac, ndp->sender_hw_addr);
     551       
     552        return EOK;
     553}
     554
    267555/** @}
    268556 */
Note: See TracChangeset for help on using the changeset viewer.