I built a bit on Richard's work..

On Sat, Mar 3, 2018 at 2:12 AM, Richard Hainsworth
<rnhainswo...@gmail.com> wrote:
> In addition, I have not figured out how to access CStruct variables yet.

They are mostly like normal classes, but unpacking native C bits isn't always
straightforward.

In this particular case, ToddAndMargo, you've picked a particularly
complicated example.

> sub getaddrinfo( <args see below>) returns int32 is native(Str) { * };

You can pass in Str (type object) as you've done here, or just omit
and it will work too.

> So now we have
> sub getaddrinfo( Str $node, Str $service, AddrInfo $hints, AddrInfo $res is
> rw) returns int32 is native(Str) { * };

I've had trouble with CStruct types and "is rw", so I usually just make those
"Pointer is rw" to get the pointer back, then use nativecast() to make it into
the CStruct type I want.

> struct sockaddr {
>     unsigned short sa_family;   // address family, AF_xxx
>     char           sa_data[14]; // 14 bytes of protocol address
> };

This is another complexity.  There are actually multiple address families,
and each has its own sockaddr struct.

> Whence a Perl6 class:
> class SockAddr {
>     has uint16 $.sa_family;
>     has CArray[int8] $.sa_data;
> }

This is incorrect because "CArray[int8]" will make a pointer to
the buffer, but the C struct really wants to put the 14 bytes directly
here.  But that is only for INET addresses, for INET6 (which google.com
also has), you need even more bytes here.

Thankfully, we can ignore most of that since for this example, we're
only reading stuff, not actually trying to create the structure.

Building from Richard's work, here's my take:

#!/usr/bin/env perl6
use v6;
use NativeCall;

constant \INET_ADDRSTRLEN = 16;
constant \INET6_ADDRSTRLEN = 46;

enum AddrInfo-Family (
    AF_UNSPEC                   => 0;
    AF_INET                     => 2;
    AF_INET6                    => 10;
);

enum AddrInfo-Socktype (
    SOCK_STREAM                 => 1;
    SOCK_DGRAM                  => 2;
    SOCK_RAW                    => 3;
    SOCK_RDM                    => 4;
    SOCK_SEQPACKET              => 5;
    SOCK_DCCP                   => 6;
    SOCK_PACKET                 => 10;
);

enum AddrInfo-Flags (
    AI_PASSIVE                  => 0x0001;
    AI_CANONNAME                => 0x0002;
    AI_NUMERICHOST              => 0x0004;
    AI_V4MAPPED                 => 0x0008;
    AI_ALL                      => 0x0010;
    AI_ADDRCONFIG               => 0x0020;
    AI_IDN                      => 0x0040;
    AI_CANONIDN                 => 0x0080;
    AI_IDN_ALLOW_UNASSIGNED     => 0x0100;
    AI_IDN_USE_STD3_ASCII_RULES => 0x0200;
    AI_NUMERICSERV              => 0x0400;
);

sub inet_ntop(int32, Pointer, Blob, int32 --> Str)
    is native() {}

class SockAddr is repr('CStruct') {
    has uint16 $.sa_family;
}

class SockAddr-in is repr('CStruct') {
    has int16 $.sin_family;
    has uint16 $.sin_port;
    has uint32 $.sin_addr;

    method address {
        my $buf = buf8.allocate(INET_ADDRSTRLEN);
        inet_ntop(AF_INET, Pointer.new(nativecast(Pointer,self)+4),
            $buf, INET_ADDRSTRLEN)
    }
}

class SockAddr-in6 is repr('CStruct') {
    has uint16 $.sin6_family;
    has uint16 $.sin6_port;
    has uint32 $.sin6_flowinfo;
    has uint64 $.sin6_addr0;
    has uint64 $.sin6_addr1;
    has uint32 $.sin6_scope_id;

    method address {
        my $buf = buf8.allocate(INET6_ADDRSTRLEN);
        inet_ntop(AF_INET6, Pointer.new(nativecast(Pointer,self)+8),
            $buf, INET6_ADDRSTRLEN)
    }
}

class Addrinfo is repr('CStruct') {
    has int32 $.ai_flags;
    has int32 $.ai_family;
    has int32 $.ai_socktype;
    has int32 $.ai_protocol;
    has uint32 $.ai_addrlen;
    has SockAddr $.ai_addr is rw;
    has Str $.ai_cannonname is rw;
    has Addrinfo $.ai_next is rw;

    method flags {
        do for AddrInfo-Flags.enums { .key if $!ai_flags +| .value }
    }

    method family {
        AddrInfo-Family($!ai_family)
    }

    method socktype {
        AddrInfo-Socktype($!ai_socktype)
    }

    method address {
        given $.family {
            when AF_INET {
                nativecast(SockAddr-in, $!ai_addr).address
            }
            when AF_INET6 {
                nativecast(SockAddr-in6, $!ai_addr).address
            }
        }
    }
}

sub getaddrinfo(Str $node, Str $service, Addrinfo $hints,
                Pointer $res is rw --> int32)
    is native() {};

sub freeaddrinfo(Pointer)
    is native() {}

sub MAIN() {
    my Addrinfo $hint .= new(:ai_flags(AI_CANONNAME));
    my Pointer $res .= new;
    my $rv = getaddrinfo("google.com", Str, $hint, $res);
    say "return val: $rv";
    if ( ! $rv ) {
        my $addr = nativecast(Addrinfo, $res);
        while $addr
        {
            with $addr {
                say "Name: ", $_ with .ai_cannonname;
                say .family, ' ', .socktype;
                say .address;
                $addr = .ai_next;
            }
        }
    }
    freeaddrinfo($res);
}


I added some enumerated types with the C constants needed, and put in some
convenience methods to unpack into them.

I used a generic Pointer to get the structure back from C, then
nativecast() it to
Addrinfo to use it.

I put in a while loop to walk through the linked list.

Then I check the family, handling only two cases, AF_INET and AF_INET6
(you'll have to add other families if you need them), and cast to the
appropriate class for INET vs. INET6.  (struct sockaddr_in and struct
sockaddr_in6)
use a little pointer math to get a pointer to the address of each and pass to
inet_ntop() to convert the address to a friendly printable form.

Here's what it prints:

 return val: 0
Name: google.com
AF_INET SOCK_STREAM
216.58.219.206
AF_INET SOCK_DGRAM
216.58.219.206
AF_INET SOCK_RAW
216.58.219.206
AF_INET6 SOCK_STREAM
2607:f8b0:4006:800::200e
AF_INET6 SOCK_DGRAM
2607:f8b0:4006:800::200e
AF_INET6 SOCK_RAW
2607:f8b0:4006:800::200e

Reply via email to