import sys
import multiprocessing

import windows.alpc
from windows.generated_def import LPC_CONNECTION_REQUEST, LPC_REQUEST
import windows.generated_def as gdef

import ctypes
import tempfile

PORT_NAME = r"\RPC Control\PythonForWindowsPORT_2"
PORT_CONTEXT = 0x11223344


def full_alpc_server():
    print("server pid = {0}".format(windows.current_process.pid))
    server = windows.alpc.AlpcServer(PORT_NAME)
    print("[SERV] PORT <{0}> CREATED".format(PORT_NAME))
    msg = server.recv()
    print("[SERV] == Message received ==")
    if msg.type & 0xfff == LPC_CONNECTION_REQUEST:
        print(" * ALPC connection request: <{0}>".format(msg.data))
        msg.data = "Connection message response"
        server.accept_connection(msg, port_context=PORT_CONTEXT)
    else:
        raise ValueError("Expected connection")

    while True:
        msg = server.recv()
        print("[SERV] == Message received ==")
        # print("       * Data: {0}".format(msg.data))
        # print("[SERV] RECV Message type = {0:#x}".format(msg.type))
        # print("[SERV] RECV Message Valid ATTRS = {0:#x}".format(msg.attributes.ValidAttributes))
        # print("[SERV] RECV Message ATTRS = {0:#x}".format(msg.attributes.AllocatedAttributes))
        if msg.type & 0xfff == LPC_REQUEST:
            print(" * ALPC request: <{0}>".format(msg.data))
            print(" * view_is_valid <{0}>".format(msg.view_is_valid))
            if msg.view_is_valid:
                print("   * message view attribute:")
                windows.utils.print_ctypes_struct(msg.view_attribute, "       - VIEW", hexa=True)
                view_data = windows.current_process.read_string(msg.view_attribute.ViewBase)
                print("   * Reading view content: <{0}>".format(view_data))
                # Needed in Win7 - TODO: why is there a different behavior ?
                msg.attributes.ValidAttributes -= gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE
            print(" * security_is_valid <{0}>".format(msg.security_is_valid))
            print(" * handle_is_valid <{0}>".format(msg.handle_is_valid))
            if msg.handle_is_valid:
                if msg.handle_attribute.Handle:
                    print("   * message handle attribute:")
                    windows.utils.print_ctypes_struct(msg.handle_attribute, "       - HANDLE", hexa=True)
                    if msg.handle_attribute.ObjectType == 1:
                        f = windows.utils.create_file_from_handle(msg.handle_attribute.Handle)
                        print("   - File: {0}".format(f))
                        print("   - content: <{0}>".format(f.read()))
                    else:
                        print("  - unknow object type == {0}".format(msg.handle_attribute.ObjectType))
                msg.attributes.ValidAttributes -= gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE

            print(" * context_is_valid <{0}>".format(msg.context_is_valid))
            if msg.context_is_valid:
                print("   * message context attribute:")
                windows.utils.print_ctypes_struct(msg.context_attribute, "     - CTX", hexa=True)

            if msg.attributes.ValidAttributes & gdef.ALPC_MESSAGE_TOKEN_ATTRIBUTE:
                print(" * message token attribute:")
                token_struct = msg.attributes.get_attribute(gdef.ALPC_MESSAGE_TOKEN_ATTRIBUTE)
                windows.utils.print_ctypes_struct(token_struct, "   - TOKEN", hexa=True)

            # We can reply by to way:
            #    - Send the same message with modified data
            #    - Recreate a Message and copy the MessageId
            msg.data = "REQUEST '{0}' DONE".format(msg.data)
            sys.stdout.flush()
            server.send(msg)
        else:
            print ValueError("Unexpected message type <{0}>".format(msg.type & 0xfff))


def send_message_with_handle(client):
    print ""
    print("[Client] == Sending a message with a handle ==")

    # Craft a file with some data
    f = tempfile.NamedTemporaryFile()
    f.write("Tempfile data <3")
    f.seek(0)

    # New message with a Handle
    msg = windows.alpc.AlpcMessage()
    msg.attributes.ValidAttributes |= gdef.ALPC_MESSAGE_HANDLE_ATTRIBUTE
    msg.handle_attribute.Flags = gdef.ALPC_HANDLEFLG_DUPLICATE_SAME_ACCESS
    msg.handle_attribute.Handle = windows.utils.get_handle_from_file(f)
    msg.handle_attribute.ObjectType = 0
    msg.handle_attribute.DesiredAccess = 0
    msg.data = "some message with a file"
    client.send_receive(msg)

def send_message_with_view(client):
    print ""
    print("[Client] == Sending a message with a view ==")

    # Create View
    section = client.create_port_section(0, 0, 0x4000)
    view = client.map_section(section[0], 0x4000)

    # New message with a View
    msg = windows.alpc.AlpcMessage(0x2000)
    msg.attributes.ValidAttributes |= gdef.ALPC_MESSAGE_VIEW_ATTRIBUTE
    msg.view_attribute.Flags = 0
    msg.view_attribute.ViewBase = view.ViewBase
    msg.view_attribute.SectionHandle = view.SectionHandle
    msg.view_attribute.ViewSize = 0x4000
    msg.data = "some message with a view"
    windows.current_process.write_memory(view.ViewBase, "The content of the view :)\x00")
    client.send_receive(msg)

def alpc_client():
    print("Client pid = {0}".format(windows.current_process.pid))
    client = windows.alpc.AlpcClient()

    # You can create a non-connected AlpcClient and send a custom
    # 'AlpcMessage' for complexe alpc port connection.
    connect_message = windows.alpc.AlpcMessage()
    connect_message.data = "Connection request client message"
    print("[CLIENT] == Connecting to port ==")
    connect_response = client.connect_to_port(PORT_NAME, connect_message)
    print("[CLIENT] Connected with response: <{0}>".format(connect_response.data))

    # AlpcClient send/recv/send_receive methods accept both string or
    # AlpcMessage for complexe message.
    print""
    print("[CLIENT] == Sending a message ==")
    msg = windows.alpc.AlpcMessage()
    msg.data = "Complex Message 1"
    print(" * Sending Message <{0}>".format(msg.data))
    response = client.send_receive(msg)
    print("[CLIENT] Server response: <{0}>".format(response.data))
    print("[CLIENT] RESP Message Valid ATTRS = {0}".format(response.valid_attributes))

    send_message_with_handle(client)
    send_message_with_view(client)
    sys.stdout.flush()


if __name__ == "__main__":
    proc = multiprocessing.Process(target=full_alpc_server, args=())
    proc.start()
    import time; time.sleep(0.5)
    alpc_client()
    import time; time.sleep(0.5)
    print("BYE")
    proc.terminate()