NOTE HOW ROCKET DOES IT
- circular dependency!
- test & verify erthing ursel
- strip dev-dependencies
- publish --no-verify!
- restore dev-dependencies
!!!

API notes: https://rust-lang-nursery.github.io/api-guidelines

never panic in code called from C, ie in the callbacks! undefined behaviour!

FFI
- https://stackoverflow.com/q/28927215
- https://kornel.ski/rust-sys-crate
- https://github.com/servo/rust-harfbuzz/tree/master/harfbuzz/src
- https://michael-f-bryan.github.io/rust-ffi-guide/overview.html
- https://svartalf.info/posts/2019-02-25-introducing-battery-crate/

note: "FFR" = "for future release" (seen in some libmilter/sendmail README)!

https://crates.io/crates/ctest

https://crates.io/crates/err-derive
https://docs.rs/failure/0.1.3/failure/
https://docs.rs/error-chain/0.12.0/error_chain/

https://blog.burntsushi.net/rust-error-handling/
https://users.rust-lang.org/t/the-state-of-error-handling-in-the-2018-edition/23263/5

https://metacpan.org/pod/distribution/Sendmail-PMilter/lib/Sendmail/PMilter.pm
https://metacpan.org/pod/Sendmail::Milter
https://metacpan.org/pod/Net::Milter
https://pythonhosted.org/pymilter/
https://pythonhosted.org/milter/
https://www.perlmonks.org/?node_id=693040

TODO for bindgen require install "llvm-dev" "libclang-dev" "clang"
^ so won't be needed w/o bindgen

fragments

// TODO Enum varients Ok/Errr also fully-qualified?


// struct MilterStatus(sys::sfsistat);
// impl MilterStatus {
//     fn new(status: sys::sfsistat) -> Self {
//         match status {
//             sys::MI_SUCCESS | sys::MI_FAILURE => MilterStatus(status),
//             _ => panic!("invalid status in milter library"),
//         }
//     }
// }
// impl From<MilterStatus> for Result<()> {
//     fn from(status: MilterStatus) -> Self {
//         match status.0 {
//             sys::MI_SUCCESS => Ok(()),
//             sys::MI_FAILURE => Err(Error::new(ErrorKind::MilterError)),
//             _ => unreachable!(),
//         }
//     }
// }



// TODO impossible to support unix sockaddr!
// * not constructible w/o transmute etc.
// * not enough info! need addrlen to cover path, unnamed, abstract socks!
// #[derive(Debug)]
// pub enum SocketAddr {
//     Inet(std::net::SocketAddr),
//     Unix(std::os::unix::net::SocketAddr),
// }

// /usr/include/x86_64-linux-gnu/bits/types.h:
// #define __U32_TYPE           unsigned int
// __STD_TYPE __U32_TYPE __socklen_t;
//
// /usr/include/netinet/in.h:
// typedef uint32_t in_addr_t;
// struct in_addr
//   {
//     in_addr_t s_addr;
//   };
// typedef uint16_t in_port_t;
// struct sockaddr_in
//   {
//     __SOCKADDR_COMMON (sin_);
//     in_port_t sin_port;			/* Port number.  */
//     struct in_addr sin_addr;		/* Internet address.  */
//     /* Pad to size of `struct sockaddr'.  */
//     unsigned char sin_zero[sizeof (struct sockaddr) -
// 			   __SOCKADDR_COMMON_SIZE -
// 			   sizeof (in_port_t) -
// 			   sizeof (struct in_addr)];
//   };
// struct sockaddr_in6
//   {
//     __SOCKADDR_COMMON (sin6_);
//     in_port_t sin6_port;	/* Transport layer port # */
//     uint32_t sin6_flowinfo;	/* IPv6 flow information */
//     struct in6_addr sin6_addr;	/* IPv6 address */
//     uint32_t sin6_scope_id;	/* IPv6 scope-id */
//   };
//
// /usr/include/x86_64-linux-gnu/sys/un.h:
// struct sockaddr_un
//   {
//     __SOCKADDR_COMMON (sun_);
//     char sun_path[108];		/* Path name.  */
//   };
//
// /usr/include/x86_64-linux-gnu/bits/sockaddr.h:
// /* Size of struct sockaddr_storage.  */
// #define _SS_SIZE 128
// typedef unsigned short int sa_family_t;
// #define	__SOCKADDR_COMMON(sa_prefix) \
//   sa_family_t sa_prefix##family
// #define __SOCKADDR_COMMON_SIZE	(sizeof (unsigned short int))
//
// /usr/include/x86_64-linux-gnu/bits/socket.h:
// #define PF_LOCAL	1	/* Local to host (pipes and file-domain).  */
// #define PF_UNIX		PF_LOCAL /* POSIX name for PF_LOCAL.  */
// #define PF_INET		2	/* IP protocol family.  */
// #define PF_INET6	10	/* IP version 6.  */
// #define AF_UNIX		PF_UNIX
// #define AF_INET		PF_INET
// #define AF_INET6	PF_INET6
// struct sockaddr
//   {
//     __SOCKADDR_COMMON (sa_);	/* Common data: address family and length.  */
//     char sa_data[14];		/* Address data.  */
//   };
// /* Structure large enough to hold any socket address (with the historical
//    exception of AF_UNIX).  */
// #define __ss_aligntype	unsigned long int
// #define _SS_PADSIZE (_SS_SIZE - __SOCKADDR_COMMON_SIZE - sizeof (__ss_aligntype))
// struct sockaddr_storage
//   {
//     __SOCKADDR_COMMON (ss_);	/* Address family, etc.  */
//     char __ss_padding[_SS_PADSIZE];
//     __ss_aligntype __ss_align;	/* Force desired alignment.  */
//   };
//
// https://github.com/rust-lang/rust/blob/master/src/libstd/net/mod.rs
// https://github.com/rust-lang/rust/blob/master/src/libstd/net/ip.rs
// https://tools.ietf.org/html/rfc2553#section-3.1   ipv6 struct layout!!
// https://tools.ietf.org/html/rfc1700
// https://stackoverflow.com/q/7184789  bitshift
// https://stackoverflow.com/q/42178179  sockaddr_storage
// https://stackoverflow.com/q/33319224  casting
// https://stackoverflow.com/q/44274977  byte order
// https://stackoverflow.com/q/20130415  sockaddr_storage cast


Macros:

NEGOTIATE
Actions: SET_REQUESTED_MACROS | CHANGE_SENDER | ADD_RECIPIENT | ADD_RECIPIENT_EXT | DELETE_RECIPIENT | ADD_HEADER | CHANGE_HEADER | CHANGE_BODY | QUARANTINE
Protocol options: NO_CONNECT | NO_HELO | NO_MAIL | NO_RCPT | NO_DATA | NO_HEADER | NO_EOH | NO_BODY | NO_UNKNOWN | SKIP | REJECTED_RCPT | NOREPLY_CONNECT | NOREPLY_HELO | NOREP
LY_MAIL | NOREPLY_RCPT | NOREPLY_DATA | NOREPLY_HEADER | NOREPLY_EOH | NOREPLY_BODY | NOREPLY_UNKNOWN | HEADER_LEADING_SPACE

CONNECT
hostname: localhost
socket_address: 127.0.0.1:0
j: mail.gluet.ch
{daemon_addr}: 127.0.0.1
{daemon_name}: mail.gluet.ch
v: Postfix 3.3.0

HELO
helo_host: localhost
j: mail.gluet.ch
{daemon_addr}: 127.0.0.1
{daemon_name}: mail.gluet.ch
v: Postfix 3.3.0

MAIL
smtp_args: ["ubuntu@gluet.ch"]
i: 7F0B681140
j: mail.gluet.ch
{daemon_addr}: 127.0.0.1
{daemon_name}: mail.gluet.ch
{mail_addr}: ubuntu@gluet.ch
v: Postfix 3.3.0

RCPT
smtp_args: ["dbuergin@gluet.ch"]
i: 7F0B681140
j: mail.gluet.ch
{daemon_addr}: 127.0.0.1
{daemon_name}: mail.gluet.ch
{mail_addr}: ubuntu@gluet.ch
{rcpt_addr}: dbuergin@gluet.ch
v: Postfix 3.3.0

DATA
i: 7F0B681140
j: mail.gluet.ch
{daemon_addr}: 127.0.0.1
{daemon_name}: mail.gluet.ch
{mail_addr}: ubuntu@gluet.ch
{rcpt_addr}: dbuergin@gluet.ch
v: Postfix 3.3.0

HEADER
header Message-Id: <20191020062515.7F0B681140@mail.gluet.ch>
got it
i: 7F0B681140
j: mail.gluet.ch
{daemon_addr}: 127.0.0.1
{daemon_name}: mail.gluet.ch
{mail_addr}: ubuntu@gluet.ch
{rcpt_addr}: dbuergin@gluet.ch
v: Postfix 3.3.0
