from construct import Adapter, Aligned, Array, BitsInteger, BitStruct, Bytes, Computed, CString, Default, Enum, \
    FlagsEnum, GreedyRange, Hex, Int8ul, Int16sl, Int16ub, Int16ul, Int32sl, Int32ub, Int32ul, Int64sl, Int64ul, \
    LazyArray, Octet, PaddedString, Padding, Seek, Struct, Switch, Tell, Union, this

from rpcclient.structs.consts import AF_INET, AF_INET6, AF_UNIX
from rpcclient.structs.generic import UNIX_PATH_MAX, gid_t, in_addr, long, mode_t, short, st_flags, time_t, u_char, \
    u_int32_t, u_short, uid_t, uint8_t, uint32_t, uint64_t

MAXPATHLEN = 1024
_SYS_NAMELEN = 256

utsname = Struct(
    'sysname' / PaddedString(_SYS_NAMELEN, 'utf8'),
    'nodename' / PaddedString(_SYS_NAMELEN, 'utf8'),
    'release' / PaddedString(_SYS_NAMELEN, 'utf8'),
    'version' / PaddedString(_SYS_NAMELEN, 'utf8'),
    'machine' / PaddedString(_SYS_NAMELEN, 'utf8'),
)

pid_t = Int32ul
exitcode_t = Int32sl
ino_t = Int32ul
dev_t = Int32ul
off_t = Int64ul
nlink_t = Int16ul
blkcnt_t = Int64ul
blksize_t = Int32ul
ino64_t = Int64ul
fsid_t = Array(2, Int32sl)
mach_port_t = Int64ul
io_name_t = PaddedString(1024, 'utf8')
io_object_t = Int64ul
vm_prot_t = Int32ul
vm_inherit_t = Int32ul
boolean_t = Int32ul
memory_object_offset_t = Int64ul
vm_behavior_t = Int32ul
mach_vm_address_t = Int64ul
mach_vm_size_t = Int64ul
integer_t = Int32sl

timespec = Struct(
    'tv_sec' / long,
    'tv_nsec' / long,
)
dirent32 = Struct(
    'd_ino' / ino_t,
    'd_offset' / Int16ul,
    'd_reclen' / Int8ul,
    'd_namelen' / Int8ul,
    'd_name' / PaddedString(this.d_namelen, 'utf8'),
)
dirent64 = Struct(
    'd_ino' / ino64_t,
    'd_offset' / Int64ul,
    'd_reclen' / Int16ul,
    'd_namelen' / Int16ul,
    'd_type' / Int8ul,
    'd_name' / PaddedString(this.d_namelen, 'utf8'),
)
stat32 = Struct(
    'st_dev' / dev_t,  # device inode resides on
    'st_ino' / ino_t,  # inode's number
    'st_mode' / mode_t,  # inode protection mode
    'st_nlink' / nlink_t,  # number of hard links to the file
    'st_uid' / uid_t,  # user-id of owner
    'st_gid' / gid_t,  # group-id of owner
    'st_rdev' / dev_t,  # device type, for special file inode

    'st_atimespec' / timespec,  # time of last access
    'st_atime' / Computed(this.st_atimespec.tv_sec + (this.st_atimespec.tv_nsec / 10 ** 9)),
    'st_mtimespec' / timespec,  # time of last data modification
    'st_mtime' / Computed(this.st_mtimespec.tv_sec + (this.st_mtimespec.tv_nsec / 10 ** 9)),
    'st_ctimespec' / timespec,  # time of last file status change
    'st_ctime' / Computed(this.st_ctimespec.tv_sec + (this.st_ctimespec.tv_nsec / 10 ** 9)),
    'st_size' / off_t,  # file size, in bytes
    'st_blocks' / blkcnt_t,  # blocks allocated for file
    'st_blksize' / blksize_t,  # optimal blocksize for I/O
    'st_flags' / Int32ul,  # user defined flags for file
    'st_gen' / Int32ul,
    'st_lspare' / Int32sl,
    'st_qspare' / Array(2, Int64sl),
    Padding(24),  # Compiled size is 144
)
stat64 = Struct(
    'st_dev' / dev_t,  # device inode resides on
    'st_mode' / mode_t,  # inode protection mode
    'st_nlink' / nlink_t,  # number of hard links to the file
    'st_ino' / ino64_t,  # inode's number
    'st_uid' / uid_t,  # user-id of owner
    'st_gid' / gid_t,  # group-id of owner
    'st_rdev' / dev_t,  # device type, for special file inode
    Padding(4),

    'st_atimespec' / timespec,  # time of last access
    'st_atime' / Computed(this.st_atimespec.tv_sec + (this.st_atimespec.tv_nsec / 10 ** 9)),
    'st_mtimespec' / timespec,  # time of last data modification
    'st_mtime' / Computed(this.st_mtimespec.tv_sec + (this.st_mtimespec.tv_nsec / 10 ** 9)),
    'st_ctimespec' / timespec,  # time of last file status change
    'st_ctime' / Computed(this.st_ctimespec.tv_sec + (this.st_ctimespec.tv_nsec / 10 ** 9)),
    'st_birthtimespec' / timespec,  # time of file creation(birth)
    'st_btime' / Computed(this.st_birthtimespec.tv_sec + (this.st_birthtimespec.tv_nsec / 10 ** 9)),

    'st_size' / off_t,
    'st_blocks' / blkcnt_t,  # blocks allocated for file
    'st_blksize' / blksize_t,  # optimal blocksize for I/O
    'st_flags' / st_flags,  # blocks allocated for file
    'st_gen' / Int32ul,  # user defined flags for file
    # seems like this value doesn't really exist
    # 'st_lspare' / Int32ul,
    'st_qspare' / Array(2, Int64sl),
)

MFSNAMELEN = 15  # length of fs type name, not inc. nul
MNAMELEN = 90  # length of buffer for returned name

# when _DARWIN_FEATURE_64_BIT_INODE is NOT defined
statfs64 = Struct(
    'f_otype' / Int16sl,  # type of file system (reserved: zero)
    'f_oflags' / Int16sl,  # copy of mount flags (reserved: zero)
    Padding(4),
    'f_bsize' / Int64ul,  # fundamental file system block size
    'f_iosize' / Int64ul,  # optimal transfer block size
    'f_blocks' / Int64ul,  # total data blocks in file system
    'f_bfree' / Int64ul,  # free blocks in fs
    'f_bavail' / Int64ul,  # free blocks avail to non-superuser
    'f_files' / Int64ul,  # total file nodes in file system
    'f_ffree' / Int64ul,  # free file nodes in fs
    'f_fsid' / fsid_t,  # file system id
    'f_owner' / uid_t,  # user that mounted the file system
    Padding(4),
    'f_reserved1' / Int16sl,  # reserved for future use
    'f_type' / Int16sl,  # type of file system (reserved)
    Padding(4),
    'f_flags' / Int64ul,  # copy of mount flags
    'f_reserved2' / Int64ul,  # reserved for future use
    'f_fstypename' / PaddedString(MFSNAMELEN, 'utf8'),  # fs type name
    'f_mntonname' / PaddedString(MNAMELEN, 'utf8'),  # directory on which mounted
    'f_mntfromname' / PaddedString(MNAMELEN, 'utf8'),  # mounted file system
    'f_reserved3' / Int8ul,  # reserved for future use
    'f_reserved4' / Int64ul,  # reserved for future use
)

MFSTYPENAMELEN = 16  # length of fs type name including null
MNAMELEN = MAXPATHLEN

# when _DARWIN_FEATURE_64_BIT_INODE is defined
statfs32 = Struct(
    'f_bsize' / Int32ul,  # fundamental file system block size
    'f_iosize' / Int32sl,  # optimal transfer block size
    'f_blocks' / Int64ul,  # total data blocks in file system
    'f_bfree' / Int64ul,  # free blocks in fs
    'f_bavail' / Int64ul,  # free blocks avail to non-superuser
    'f_files' / Int64ul,  # total file nodes in file system
    'f_ffree' / Int64ul,  # free file nodes in fs
    'f_fsid' / fsid_t,  # file system id
    'f_owner' / uid_t,  # user that mounted the filesystem
    'f_type' / Int32ul,  # type of filesystem
    'f_flags' / Int32ul,  # copy of mount exported flags
    'f_fssubtype' / Int32ul,  # fs sub-type (flavor)
    'f_fstypename' / PaddedString(MFSTYPENAMELEN, 'utf8'),  # fs type name
    'f_mntonname' / PaddedString(MAXPATHLEN, 'utf8'),  # directory on which mounted
    'f_mntfromname' / PaddedString(MAXPATHLEN, 'utf8'),  # mounted filesystem
    'f_reserved' / Int32ul[8],  # For future use
)

PROC_ALL_PIDS = 1

# Flavors for proc_pidinfo()
PROC_PIDLISTFDS = 1
PROC_PIDTASKALLINFO = 2
PROC_PIDTBSDINFO = 3
PROC_PIDTASKINFO = 4
PROC_PIDTHREADINFO = 5
PROC_PIDLISTTHREADS = 6
PROC_PIDREGIONINFO = 7
PROC_PIDREGIONPATHINFO = 8
PROC_PIDVNODEPATHINFO = 9
PROC_PIDTHREADPATHINFO = 10
PROC_PIDPATHINFO = 11
PROC_PIDWORKQUEUEINFO = 12
PROC_PIDT_SHORTBSDINFO = 13
PROC_PIDLISTFILEPORTS = 14
PROC_PIDTHREADID64INFO = 15
PROC_PID_RUSAGE = 16
PROC_PIDUNIQIDENTIFIERINFO = 17
PROC_PIDT_BSDINFOWITHUNIQID = 18
PROC_PIDARCHINFO = 19
PROC_PIDCOALITIONINFO = 20
PROC_PIDNOTEEXIT = 21
PROC_PIDREGIONPATHINFO2 = 22
PROC_PIDREGIONPATHINFO3 = 23

# Flavors for proc_pidfdinfo
PROC_PIDFDVNODEINFO = 1
PROC_PIDFDVNODEPATHINFO = 2
PROC_PIDFDSOCKETINFO = 3
PROC_PIDFDPSEMINFO = 4
PROC_PIDFDPSHMINFO = 5
PROC_PIDFDPIPEINFO = 6
PROC_PIDFDKQUEUEINFO = 7
PROC_PIDFDATALKINFO = 8

# Flavors for proc_pidfileportinfo
PROC_PIDFILEPORTVNODEPATHINFO = 2  # out: vnode_fdinfowithpath
PROC_PIDFILEPORTPSHMINFO = 5  # out: pshm_fdinfo
PROC_PIDFILEPORTPIPEINFO = 6  # out: pipe_fdinfo
# used for proc_setcontrol
PROC_SELFSET_PCONTROL = 1
PROC_SELFSET_THREADNAME = 2
PROC_SELFSET_VMRSRCOWNER = 3
PROC_SELFSET_DELAYIDLESLEEP = 4
# used for proc_dirtycontrol
PROC_DIRTYCONTROL_TRACK = 1
PROC_DIRTYCONTROL_SET = 2
PROC_DIRTYCONTROL_GET = 3
PROC_DIRTYCONTROL_CLEAR = 4
# proc_track_dirty() flags
PROC_DIRTY_TRACK = 0x1
PROC_DIRTY_ALLOW_IDLE_EXIT = 0x2
PROC_DIRTY_DEFER = 0x4
PROC_DIRTY_LAUNCH_IN_PROGRESS = 0x8
# proc_get_dirty() flags
PROC_DIRTY_TRACKED = 0x1
PROC_DIRTY_ALLOWS_IDLE_EXIT = 0x2
PROC_DIRTY_IS_DIRTY = 0x4
PROC_DIRTY_LAUNCH_IS_IN_PROGRESS = 0x8

# Flavors for proc_pidoriginatorinfo
PROC_PIDORIGINATOR_UUID = 0x1
PROC_PIDORIGINATOR_BGSTATE = 0x2
# __proc_info() call numbers
PROC_INFO_CALL_LISTPIDS = 0x1
PROC_INFO_CALL_PIDINFO = 0x2
PROC_INFO_CALL_PIDFDINFO = 0x3
PROC_INFO_CALL_KERNMSGBUF = 0x4
PROC_INFO_CALL_SETCONTROL = 0x5
PROC_INFO_CALL_PIDFILEPORTINFO = 0x6
PROC_INFO_CALL_TERMINATE = 0x7
PROC_INFO_CALL_DIRTYCONTROL = 0x8
PROC_INFO_CALL_PIDRUSAGE = 0x9
PROC_INFO_CALL_PIDORIGINATORINFO = 0xa

# defns of process file desc type
PROX_FDTYPE_ATALK = 0
PROX_FDTYPE_VNODE = 1
PROX_FDTYPE_SOCKET = 2
PROX_FDTYPE_PSHM = 3
PROX_FDTYPE_PSEM = 4
PROX_FDTYPE_KQUEUE = 5
PROX_FDTYPE_PIPE = 6
PROX_FDTYPE_FSEVENTS = 7

pbi_flags_t = FlagsEnum(Int32ul,
                        # pbi_flags values
                        PROC_FLAG_SYSTEM=1,  # System process
                        PROC_FLAG_TRACED=2,  # process currently being traced, possibly by gdb
                        PROC_FLAG_INEXIT=4,  # process is working its way in exit()
                        PROC_FLAG_PPWAIT=8,
                        PROC_FLAG_LP64=0x10,  # 64bit process
                        PROC_FLAG_SLEADER=0x20,  # The process is the session leader
                        PROC_FLAG_CTTY=0x40,  # process has a control tty
                        PROC_FLAG_CONTROLT=0x80,  # Has a controlling terminal
                        PROC_FLAG_THCWD=0x100,  # process has a thread with cwd
                        # process control bits for resource starvation
                        PROC_FLAG_PC_THROTTLE=0x200,
                        # In resource starvation situations, this process is to be throttled
                        PROC_FLAG_PC_SUSP=0x400,  # In resource starvation situations, this process is to be suspended
                        PROC_FLAG_PC_KILL=0x600,  # In resource starvation situations, this process is to be terminated
                        PROC_FLAG_PC_MASK=0x600,
                        # process action bits for resource starvation
                        PROC_FLAG_PA_THROTTLE=0x800,  # The process is currently throttled due to resource starvation
                        PROC_FLAG_PA_SUSP=0x1000,  # The process is currently suspended due to resource starvation
                        PROC_FLAG_PSUGID=0x2000,  # process has set privileges since last exec
                        PROC_FLAG_EXEC=0x4000,  # process has called exec
                        PROC_FLAG_DARWINBG=0x8000,  # process in darwin background
                        PROC_FLAG_EXT_DARWINBG=0x10000,  # process in darwin background - external enforcement
                        PROC_FLAG_IOS_APPLEDAEMON=0x20000,  # Process is apple daemon
                        PROC_FLAG_DELAYIDLESLEEP=0x40000,  # Process is marked to delay idle sleep on disk IO
                        PROC_FLAG_IOS_IMPPROMOTION=0x80000,  # Process is daemon which receives importane donation
                        PROC_FLAG_ADAPTIVE=0x100000,  # Process is adaptive
                        PROC_FLAG_ADAPTIVE_IMPORTANT=0x200000,  # Process is adaptive, and is currently important
                        PROC_FLAG_IMPORTANCE_DONOR=0x400000,  # Process is marked as an importance donor
                        PROC_FLAG_SUPPRESSED=0x800000,  # Process is suppressed
                        PROC_FLAG_APPLICATION=0x1000000,  # Process is an application
                        PROC_FLAG_IOS_APPLICATION=0x1000000,  # Process is an application
                        )

MAXCOMLEN = 16

proc_bsdinfo = Struct(
    'pbi_flags' / pbi_flags_t,  # 64bit emulated etc
    'pbi_status' / Int32ul,
    'pbi_xstatus' / Int32ul,
    'pbi_pid' / Int32ul,
    'pbi_ppid' / Int32ul,
    'pbi_uid' / uid_t,
    'pbi_gid' / gid_t,
    'pbi_ruid' / uid_t,
    'pbi_rgid' / gid_t,
    'pbi_svuid' / uid_t,
    'pbi_svgid' / gid_t,
    'rfu_1' / Int32ul,  # reserved
    '_pbi_comm' / Bytes(MAXCOMLEN),
    'pbi_comm' / Computed(lambda x: x._pbi_comm.split(b'\x00', 1)[0].decode()),
    '_pbi_name' / Bytes(2 * MAXCOMLEN),  # empty if no name is registered
    'pbi_name' / Computed(lambda x: x._pbi_name.split(b'\x00', 1)[0].decode()),
    'pbi_nfiles' / Int32ul,
    'pbi_pgid' / Int32ul,
    'pbi_pjobc' / Int32ul,
    'e_tdev' / Int32ul,  # controlling tty dev
    'e_tpgid' / Int32ul,  # tty process group id
    'pbi_nice' / Int32sl,
    'pbi_start_tvsec' / Int64ul,
    'pbi_start_tvusec' / Int64ul,
)

proc_taskinfo = Struct(
    'pti_virtual_size' / Int64ul,  # virtual memory size (bytes)
    'pti_resident_size' / Int64ul,  # resident memory size (bytes)
    'pti_total_user' / Int64ul,  # total time
    'pti_total_system' / Int64ul,
    'pti_threads_user' / Int64ul,  # existing threads only
    'pti_threads_system' / Int64ul,
    'pti_policy' / Int32sl,  # default policy for new threads
    'pti_faults' / Int32sl,  # number of page faults
    'pti_pageins' / Int32sl,  # number of actual pageins
    'pti_cow_faults' / Int32sl,  # number of copy-on-write faults
    'pti_messages_sent' / Int32sl,  # number of messages sent
    'pti_messages_received' / Int32sl,  # number of messages received
    'pti_syscalls_mach' / Int32sl,  # number of mach system calls
    'pti_syscalls_unix' / Int32sl,  # number of unix system calls
    'pti_csw' / Int32sl,  # number of context switches
    'pti_threadnum' / Int32sl,  # number of threads in the task
    'pti_numrunning' / Int32sl,  # number of running threads
    'pti_priority' / Int32sl,  # task priority
)

proc_taskallinfo = Struct(
    'pbsd' / proc_bsdinfo,
    'ptinfo' / proc_taskinfo,
)

proc_regioninfo = Struct(
    'pri_protection' / Int32ul,
    'pri_max_protection' / Int32ul,
    'pri_inheritance' / Int32ul,
    'pri_flags' / Int32ul,  # shared, external pager, is submap
    'pri_offset' / Int64ul,
    'pri_behavior' / Int32ul,
    'pri_user_wired_count' / Int32ul,
    'pri_user_tag' / Int32ul,
    'pri_pages_resident' / Int32ul,
    'pri_pages_shared_now_private' / Int32ul,
    'pri_pages_swapped_out' / Int32ul,
    'pri_pages_dirtied' / Int32ul,
    'pri_ref_count' / Int32ul,
    'pri_shadow_depth' / Int32ul,
    'pri_share_mode' / Int32ul,
    'pri_private_pages_resident' / Int32ul,
    'pri_shared_pages_resident' / Int32ul,
    'pri_obj_id' / Int32ul,
    'pri_depth' / Int32ul,
    'pri_address' / Int64ul,
    'pri_size' / Int64ul,
)

proc_fdinfo = Struct(
    'proc_fd' / Int32sl,
    'proc_fdtype' / Int32ul,
)

proc_fileinfo = Struct(
    'fi_openflags' / Int32ul,
    'fi_status' / Int32ul,
    'fi_offset' / off_t,
    'fi_guardflags' / Int32ul,
)

# A copy of stat64 with static sized fields.
vinfo_stat = stat64

vnode_info = Struct(
    'vi_stat' / vinfo_stat,
    'vi_type' / Int32sl,
    'vi_pad' / Int32sl,
    'vi_fsid' / fsid_t,
)

vnode_info_path = Struct(
    'vip_vi' / vnode_info,
    '_vip_path' / Bytes(MAXPATHLEN),
    'vip_path' / Computed(lambda x: x._vip_path.split(b'\x00', 1)[0].decode()),
)

vnode_fdinfowithpath = Struct(
    'pfi' / proc_fileinfo,
    'pvip' / vnode_info_path,
)

sockbuf_info = Struct(
    'sbi_cc' / uint32_t,
    'sbi_hiwat' / uint32_t,  # SO_RCVBUF, SO_SNDBUF
    'sbi_mbcnt' / uint32_t,
    'sbi_mbmax' / uint32_t,
    'sbi_lowat' / uint32_t,
    'sbi_flags' / short,
    'sbi_timeo' / short,
)

# TCP Sockets

TSI_T_REXMT = 0  # retransmit
TSI_T_PERSIST = 1  # retransmit persistence
TSI_T_KEEP = 2  # keep alive
TSI_T_2MSL = 3  # 2*msl quiet time timer
TSI_T_NTIMERS = 4


class IpAddressAdapter(Adapter):
    def _decode(self, obj, context, path):
        return ".".join(map(str, obj))

    def _encode(self, obj, context, path):
        return list(map(int, obj.split(".")))


in4in6_addr = Struct(
    'i46a_pad32' / u_int32_t[3],
    'i46a_addr4' / IpAddressAdapter(in_addr),
)

in6_addr = Struct(
    'i46a_pad32' / u_int32_t[3],
    'i46a_addr4' / IpAddressAdapter(in_addr),
)

in_sockinfo = Struct(
    'insi_fport' / Int16ub,
    Padding(2),
    'insi_lport' / Int16ub,
    Padding(2),
    'insi_gencnt' / uint64_t,
    'insi_flags' / uint32_t,
    'insi_flow' / uint32_t,
    'insi_vflag' / uint8_t,
    'insi_ip_ttl' / uint8_t,
    Padding(2),
    'rfu_1' / uint32_t,

    # protocol dependent part
    'insi_faddr' / Union(None,
                         'ina_46' / in4in6_addr,
                         'ina_6' / in6_addr),

    'insi_laddr' / Union(None,
                         'ina_46' / in4in6_addr,
                         'ina_6' / in6_addr),

    'insi_v4' / Struct(
        'in4_tos' / u_char,
    ),

    'insi_v6' / Struct(
        'in6_hlim' / uint8_t,
        'in6_cksum' / Int32sl,
        'in6_ifindex' / u_short,
        'in6_hops' / short,
    ),
)

tcp_sockinfo = Struct(
    'tcpsi_ini' / in_sockinfo,
    'in_sockinfo' / Int32sl,
    'tcpsi_timer' / Int32sl[TSI_T_NTIMERS],
    'tcpsi_mss' / Int32sl,
    'tcpsi_flags' / uint32_t,
    'rfu_1' / uint32_t,  # reserved
    'tcpsi_tp' / uint64_t,  # opaque handle of TCP protocol control block
)

SOCK_MAXADDRLEN = 255

# Unix Domain Sockets

# we can't use sockaddr_un since the sun_path may contain utf8 invalid characters
sockaddr_un_raw = Struct(
    'sun_family' / Default(Int16sl, AF_UNIX),
    '_sun_path' / Bytes(UNIX_PATH_MAX),
    'sun_path' / Computed(lambda x: x._sun_path.split(b'\x00', 1)[0].decode())
)

un_sockinfo = Struct(
    'unsi_conn_so' / uint64_t,
    'unsi_conn_pcb' / uint64_t,
    'unsi_addr' / Union(None,
                        'ua_sun' / sockaddr_un_raw,
                        'ua_dummy' / Bytes(SOCK_MAXADDRLEN)),
    'unsi_caddr' / Union(None,
                         'ua_sun' / sockaddr_un_raw,
                         'ua_dummy' / Bytes(SOCK_MAXADDRLEN)),
)

IF_NAMESIZE = 16

# PF_NDRV Sockets

ndrv_info = Struct(
    'ndrvsi_if_family' / uint32_t,
    'ndrvsi_if_unit' / uint32_t,
    'ndrvsi_if_name' / Bytes(IF_NAMESIZE),
)

# Kernel Event Sockets

kern_event_info = Struct(
    'kesi_vendor_code_filter' / uint32_t,
    'kesi_class_filter' / uint32_t,
    'kesi_subclass_filter' / uint32_t,
)

# Kernel Control Sockets

MAX_KCTL_NAME = 96

kern_ctl_info = Struct(
    'kcsi_id' / uint32_t,
    'kcsi_reg_unit' / uint32_t,
    'kcsi_flags' / uint32_t,
    'kcsi_recvbufsize' / uint32_t,
    'kcsi_sendbufsize' / uint32_t,
    'kcsi_unit' / uint32_t,
    'kcsi_name' / Bytes(MAX_KCTL_NAME),
)

so_kind_t = Enum(Int32ul,
                 SOCKINFO_GENERIC=0,
                 SOCKINFO_IN=1,
                 SOCKINFO_TCP=2,
                 SOCKINFO_UN=3,
                 SOCKINFO_NDRV=4,
                 SOCKINFO_KERN_EVENT=5,
                 SOCKINFO_KERN_CTL=6
                 )

so_family_t = Enum(Int32ul,
                   AF_INET=AF_INET,
                   AF_INET6=AF_INET6,
                   )

socket_info = Struct(
    'soi_stat' / vinfo_stat,
    'soi_so' / uint64_t,  # opaque handle of socket
    'soi_pcb' / uint64_t,  # opaque handle of protocol control block
    'soi_type' / Int32sl,
    'soi_protocol' / Int32sl,
    'soi_family' / so_family_t,
    'soi_options' / short,
    'soi_linger' / short,
    'soi_state' / short,
    'soi_qlen' / short,
    'soi_incqlen' / short,
    'soi_qlimit' / short,
    'soi_timeo' / short,
    'soi_error' / u_short,
    'soi_oobmark' / uint32_t,
    'soi_rcv' / sockbuf_info,
    'soi_snd' / sockbuf_info,
    'soi_kind' / so_kind_t,
    'rfu_1' / uint32_t,  # reserved

    'soi_proto' / Switch(this.soi_kind, {
        so_kind_t.SOCKINFO_IN: Struct('pri_in' / in_sockinfo),
        so_kind_t.SOCKINFO_TCP: Struct('pri_tcp' / tcp_sockinfo),
        so_kind_t.SOCKINFO_UN: Struct('pri_un' / un_sockinfo),
        so_kind_t.SOCKINFO_NDRV: Struct('pri_ndrv' / ndrv_info),
        so_kind_t.SOCKINFO_KERN_EVENT: Struct('pri_kern_event' / kern_event_info),
        so_kind_t.SOCKINFO_KERN_CTL: Struct('pri_kern_ctl' / kern_ctl_info),
    }),
)

socket_fdinfo = Struct(
    'pfi' / proc_fileinfo,
    'psi' / socket_info,
)

pipe_info = Struct(
    'pipe_stat' / vinfo_stat,
    'pipe_handle' / uint64_t,
    'pipe_peerhandle' / uint64_t,
    'pipe_status' / Int32sl,
    'rfu_1' / Int32sl  # reserved
)

pipe_fdinfo = Struct(
    'pfi' / proc_fileinfo,
    'pipeinfo' / pipe_info,
)

vm_region_basic_info = Struct(
    'protection' / vm_prot_t,
    'max_protection' / vm_prot_t,
    'inheritance' / vm_inherit_t,
    'shared' / boolean_t,
    'reserved' / boolean_t,
    Padding(4),
    'offset' / memory_object_offset_t,
    'behavior' / vm_behavior_t,
    'user_wired_count' / Int16ul,
)

vm_region_basic_info_64 = Struct(
    'protection' / vm_prot_t,
    'max_protection' / vm_prot_t,
    'inheritance' / vm_inherit_t,
    'shared' / boolean_t,
    'reserved' / boolean_t,
    Padding(4),
    'offset' / memory_object_offset_t,
    'behavior' / vm_behavior_t,
    'user_wired_count' / Int16ul,
)

VM_REGION_BASIC_INFO_COUNT_64 = vm_region_basic_info_64.sizeof() // 4
vm_region_basic_info_data_t = vm_region_basic_info
natural_t = Int32ul

task_dyld_info = Struct(
    'all_image_info_addr' / mach_vm_address_t,
    'all_image_info_size' / mach_vm_size_t,
    'all_image_info_format' / integer_t,
)

uuid_t = Struct(
    'time_low' / Int64ul,
    'time_mid' / Int32ul,
    'time_hi_and_version' / Int32ul,
    'clock_seq_hi_and_reserved' / Int8ul,
    'clock_seq_low' / Int8ul,
    'node' / Array(6, Int8ul)
)

dyld_image_info_t = Struct(
    'imageLoadAddress' / Int64ul,
    'imageFilePath' / Int64ul,
    'imageFileModDate' / Int64ul
)

all_image_infos_t = Struct(
    'version' / Int32ul,
    'infoArrayCount' / Int32ul,
    'infoArray' / Int64ul,
)

task_dyld_info_data_t = task_dyld_info
TASK_DYLD_INFO_COUNT = task_dyld_info_data_t.sizeof() / natural_t.sizeof()

STRUCT_ARM_THREAD_STATE64 = Struct(
    'x' / Array(29, uint64_t),
    'fp' / Default(uint64_t, 0),
    'lr' / Default(uint64_t, 0),
    'sp' / Default(uint64_t, 0),
    'pc' / Default(uint64_t, 0),
    'cpsr' / Default(uint32_t, 0),
    Padding(4),
)

arm_thread_state64_t = STRUCT_ARM_THREAD_STATE64
ARM_THREAD_STATE64_COUNT = arm_thread_state64_t.sizeof() // uint32_t.sizeof()

STRUCT_X86_THREAD_STATE32 = Struct(
    'eax' / Int32sl,
    'ebx' / Int32sl,
    'ecx' / Int32sl,
    'edx' / Int32sl,
    'edi' / Int32sl,
    'esi' / Int32sl,
    'ebp' / Int32sl,
    'esp' / Int32sl,
    'ss' / Int32sl,
    'eflags' / Int32sl,
    'eip' / Int32sl,
    'cs' / Int32sl,
    'ds' / Int32sl,
    'es' / Int32sl,
    'fs' / Int32sl,
    'gs' / Int32sl,
)

x86_thread_state32_t = STRUCT_X86_THREAD_STATE32

STRUCT_X86_THREAD_STATE64 = Struct(
    'rax' / uint64_t,
    'rbx' / uint64_t,
    'rcx' / uint64_t,
    'rdx' / uint64_t,
    'rdi' / uint64_t,
    'rsi' / uint64_t,
    'rbp' / uint64_t,
    'rsp' / uint64_t,
    'r8' / uint64_t,
    'r9' / uint64_t,
    'r10' / uint64_t,
    'r11' / uint64_t,
    'r12' / uint64_t,
    'r13' / uint64_t,
    'r14' / uint64_t,
    'r15' / uint64_t,
    'rip' / uint64_t,
    'rflags' / uint64_t,
    'cs' / uint64_t,
    'fs' / uint64_t,
    'gs' / uint64_t,
)

x86_thread_state64_t = STRUCT_X86_THREAD_STATE64

suseconds_t = uint32_t

timeval = Struct(
    'tv_sec' / time_t,
    Padding(4),
    'tv_usec' / suseconds_t,
    Padding(4),
)

procargs2_t = Struct(
    'argc' / Int32ul,
    'executable' / Aligned(4, CString('utf8')),
    'argv' / Array(this.argc, CString('utf8')),
    '_environ_apple' / GreedyRange(CString('utf8')),
    'environ_apple' / Computed(lambda ctx: [s for s in ctx._environ_apple if s]),
)

# See: https://opensource.apple.com/source/xnu/xnu-7195.81.3/EXTERNAL_HEADERS/mach-o/loader.h
LC_REQ_DYLD = 0x80000000

LOAD_COMMAND_TYPE = Enum(Int32ul,
                         LC_SEGMENT=0x1,
                         LC_SYMTAB=0x2,
                         LC_SYMSEG=0x3,
                         LC_THREAD=0x4,
                         LC_UNIXTHREAD=0x5,
                         LC_LOADFVMLIB=0x6,
                         LC_IDFVMLIB=0x7,
                         LC_IDENT=0x8,
                         LC_FVMFILE=0x9,
                         LC_PREPAGE=0xa,
                         LC_DYSYMTAB=0xb,
                         LC_LOAD_DYLIB=0xc,
                         LC_ID_DYLIB=0xd,
                         LC_LOAD_DYLINKER=0xe,
                         LC_ID_DYLINKER=0xf,
                         LC_PREBOUND_DYLIB=0x10,
                         LC_ROUTINES=0x11,
                         LC_SUB_FRAMEWORK=0x12,
                         LC_SUB_UMBRELLA=0x13,
                         LC_SUB_CLIENT=0x14,
                         LC_SUB_LIBRARY=0x15,
                         LC_TWOLEVEL_HINTS=0x16,
                         LC_PREBIND_CKSUM=0x17,
                         LC_LOAD_WEAK_DYLIB=(0x18 | LC_REQ_DYLD),
                         LC_SEGMENT_64=0x19,
                         LC_ROUTINES_64=0x1a,
                         LC_UUID=0x1b,
                         LC_RPATH=(0x1c | LC_REQ_DYLD),
                         LC_CODE_SIGNATURE=0x1d,
                         LC_SEGMENT_SPLIT_INFO=0x1e,
                         LC_REEXPORT_DYLIB=0x1f | LC_REQ_DYLD,
                         LC_LAZY_LOAD_DYLIB=0x20,
                         LC_ENCRYPTION_INFO=0x21,
                         LC_DYLD_INFO=0x22,
                         LC_DYLD_INFO_ONLY=(0x22 | LC_REQ_DYLD),
                         LC_LOAD_UPWARD_DYLIB=(0x23 | LC_REQ_DYLD),
                         LC_VERSION_MIN_MACOSX=0x24,
                         LC_VERSION_MIN_IPHONEOS=0x25,
                         LC_FUNCTION_STARTS=0x26,
                         LC_DYLD_ENVIRONMENT=0x27,
                         LC_MAIN=(0x28 | LC_REQ_DYLD),
                         LC_DATA_IN_CODE=0x29,
                         LC_SOURCE_VERSION=0x2A,
                         LC_DYLIB_CODE_SIGN_DRS=0x2B,
                         LC_ENCRYPTION_INFO_64=0x2C,
                         LC_LINKER_OPTION=0x2D,
                         LC_LINKER_OPTIMIZATION_HINT=0x2E,
                         LC_VERSION_MIN_TVOS=0x2F,
                         LC_VERSION_MIN_WATCHOS=0x30,
                         LC_NOTE=0x31,
                         LC_BUILD_VERSION=0x32,
                         LC_DYLD_EXPORTS_TRIE=(0x33 | LC_REQ_DYLD),
                         LC_DYLD_CHAINED_FIXUPS=(0x34 | LC_REQ_DYLD),
                         LC_FILESET_ENTRY=(0x35 | LC_REQ_DYLD)
                         )

# Load commands:
# reference - https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h

FAT_MAGIC = 0xcafebabe
FAT_CIGAM = 0xbebafeca

cpu_type_t = Int32ul
cpu_subtype_t = Int32ul

version_t = BitStruct(
    'major' / BitsInteger(16),
    'minor' / Octet,
    'bug' / Octet
)

segment_command_t = Struct(
    'segname' / PaddedString(16, 'utf8'),
    'vmaddr' / Int64ul,
    'vmsize' / Int64ul,
    'fileoff' / Int64ul,
    'filesize' / Int64ul,
    'maxprot' / Int32ul,
    'initprot' / Int32ul,
    'nsects' / Int32ul,
    'flags' / Int32ul,
)

uuid_command_t = Struct(
    'uuid' / Array(16, Int8ul)
)

build_tool_version = Struct(
    'tool' / Int32ul,
    'version' / Int32ul
)

build_version_command_t = Struct(
    'platform' / Int32ul,
    'minos' / version_t,
    'sdk' / version_t,
    'ntools' / Int32ul,
)

encryption_info_command = Struct(
    'cryptoff' / Int32ul,  # file offset of encrypted range
    'cryptsize' / Int32ul,  # file size of encrypted range
    'cryptid' / Int32ul,  # which encryption system, 0 means not-encrypted yet
)

encryption_info_command_64 = Struct(
    'cryptoff' / Int32ul,  # file offset of encrypted range
    'cryptsize' / Int32ul,  # file size of encrypted range
    'cryptid_offset' / Tell,
    'cryptid' / Int32ul,  # which encryption system, 0 means not-encrypted yet
    '_pad' / Int32ul,
)

load_command_t = Struct(
    '_start' / Tell,
    'cmd' / LOAD_COMMAND_TYPE,
    'cmdsize' / Int32ul,
    '_data_offset' / Tell,
    'data' / Switch(this.cmd, {
        LOAD_COMMAND_TYPE.LC_BUILD_VERSION: build_version_command_t,
        LOAD_COMMAND_TYPE.LC_UUID: uuid_command_t,
        LOAD_COMMAND_TYPE.LC_SEGMENT_64: segment_command_t,
        LOAD_COMMAND_TYPE.LC_ENCRYPTION_INFO_64: encryption_info_command_64,
        LOAD_COMMAND_TYPE.LC_ENCRYPTION_INFO: encryption_info_command,
    }, Bytes(this.cmdsize - (this._data_offset - this._start))),
    Seek(this._start + this.cmdsize),
)

mach_header_t = Struct(
    'load_address' / Tell,
    'magic' / Hex(Int32ul),
    'cputype' / Hex(cpu_type_t),
    'cpusubtype' / Hex(cpu_subtype_t),
    'filetype' / Hex(Int32ul),
    'ncmds' / Hex(Int32ul),
    'sizeofcmds' / Hex(Int32ul),
    'flags' / Hex(Int32ul),
    'reserved' / Hex(Int32ul),
    'load_commands' / LazyArray(this.ncmds, load_command_t),
)

fat_arch = Struct(
    'cputype' / Hex(Int32ub),
    'cpusubtype' / Hex(Int32ub),
    'offset' / Hex(Int32ub),
    'size' / Hex(Int32ub),
    'align' / Hex(Int32ub),
)

fat_header = Struct(
    'magic' / Hex(Int32ul),
    'nfat_arch' / Hex(Int32ub),
    'archs' / Array(this.nfat_arch, fat_arch)
)
