mirror of
https://github.com/SantaSpeen/winConnect.git
synced 2026-10-27 14:40:24 +00:00
[!] Ready
This commit is contained in:
1 parent
cbd0285bf3
commit
29626a7063
5 files changed
+313
No files matched your search
@@ -0,0 +1,195 @@
|
|||||||
|
import json
|
||||||
|
import pickle
|
||||||
|
import struct
|
||||||
|
import threading
|
||||||
|
import zlib
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import ormsgpack
|
||||||
|
import win32file
|
||||||
|
|
||||||
|
from winConnect.utils import SimpleConvertor
|
||||||
|
|
||||||
|
|
||||||
|
class WinConnectErrors(Enum):
|
||||||
|
NO_ERROR = 0
|
||||||
|
|
||||||
|
INIT_FIRST = 10
|
||||||
|
|
||||||
|
UNKNOWN_DATA_TYPE = 30
|
||||||
|
UNKNOWN_COMMAND = 31
|
||||||
|
UNKNOWN_ACTION = 32
|
||||||
|
|
||||||
|
BAD_DATA = 50
|
||||||
|
BAD_VERSION = 51
|
||||||
|
|
||||||
|
# header: len(data) in struct.pack via header_format
|
||||||
|
# data: action:data
|
||||||
|
# headerDATA
|
||||||
|
|
||||||
|
class WinConnectBase:
|
||||||
|
init_encoding = 'utf-8'
|
||||||
|
init_header_format = ">L" # Format for reading header (big-endian, unsigned long)
|
||||||
|
init_header_size = 32 # bits; Size of header
|
||||||
|
|
||||||
|
default_encoding = 'utf-8'
|
||||||
|
|
||||||
|
read_max_buffer = SimpleConvertor.to_gb(4) # Max size of buffer for message
|
||||||
|
|
||||||
|
def __init__(self, pipe_name: str):
|
||||||
|
self.run = True
|
||||||
|
self._version = 1
|
||||||
|
self._pipe_name = r'\\.\pipe\{}'.format(pipe_name)
|
||||||
|
self._pipe = None
|
||||||
|
self._opened = False
|
||||||
|
|
||||||
|
self.header_format = self.init_header_format
|
||||||
|
self.header_size = self.init_header_size // 8 # bytes
|
||||||
|
|
||||||
|
self._client_connected = False
|
||||||
|
self._inited = False
|
||||||
|
self._session_encoding = self.init_encoding
|
||||||
|
|
||||||
|
self._parts_buffer = None # Buffer for parts of message (If message is too big)
|
||||||
|
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
|
||||||
|
def set_header_settings(self, format, size):
|
||||||
|
self.header_format = format
|
||||||
|
self.header_size = size // 8
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pipe_name(self):
|
||||||
|
return self._pipe_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def encoding(self):
|
||||||
|
return self._session_encoding
|
||||||
|
|
||||||
|
def _open_pipe(self): ...
|
||||||
|
|
||||||
|
def __pack_data(self, action, data) -> (bytes, bytes):
|
||||||
|
data_type = "msg"
|
||||||
|
data = ormsgpack.packb(data, option=ormsgpack.OPT_NAIVE_UTC)
|
||||||
|
compressed_data = zlib.compress(data)
|
||||||
|
return data_type.encode(self._session_encoding) + b":" + action + b":" + compressed_data
|
||||||
|
|
||||||
|
def __unpack_data(self, data: bytes) -> (str, Any):
|
||||||
|
data_type, action_data = self.__parse_message(data)
|
||||||
|
if data_type != b"msg":
|
||||||
|
raise ValueError('Is client using correct lib? Unknown data type')
|
||||||
|
action, data = self.__parse_message(action_data)
|
||||||
|
decompressed_data = zlib.decompress(data)
|
||||||
|
deserialized_data = ormsgpack.unpackb(decompressed_data)
|
||||||
|
return action, deserialized_data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __parse_message(message: bytes):
|
||||||
|
return message.split(b":", 1)
|
||||||
|
|
||||||
|
def _read_message(self) -> (str, Any):
|
||||||
|
with self._lock:
|
||||||
|
_, header = win32file.ReadFile(self._pipe, self.header_size)
|
||||||
|
if not header:
|
||||||
|
return b""
|
||||||
|
if len(header) != self.header_size and self._inited:
|
||||||
|
raise ValueError('Header is too small')
|
||||||
|
message_size = struct.unpack(self.header_format, header)[0]
|
||||||
|
_, data = win32file.ReadFile(self._pipe, message_size)
|
||||||
|
return self.__unpack_data(data)
|
||||||
|
|
||||||
|
def _send_message(self, action: str, data: Any):
|
||||||
|
with self._lock:
|
||||||
|
data = self.__pack_data(action.encode(self.encoding), data)
|
||||||
|
message_size = len(data)
|
||||||
|
if message_size > self.read_max_buffer:
|
||||||
|
raise ValueError('Message is too big')
|
||||||
|
# Если размер сообщения больше размера read_header_size, то ошибка
|
||||||
|
if message_size > 2 ** (8 * self.header_size):
|
||||||
|
raise ValueError('Message is too big')
|
||||||
|
header = struct.pack(self.header_format, message_size)
|
||||||
|
print("Sending message:", header, data)
|
||||||
|
win32file.WriteFile(self._pipe, header)
|
||||||
|
win32file.WriteFile(self._pipe, data)
|
||||||
|
|
||||||
|
def _send_error(self, error: WinConnectErrors, error_message: str = None):
|
||||||
|
e = {"error": True, "code": error.value, "message": error.name, "description": error_message}
|
||||||
|
self._send_message("error", e)
|
||||||
|
|
||||||
|
def _parse_action(self, action, data: bytes):
|
||||||
|
match action:
|
||||||
|
case b"command":
|
||||||
|
return self._parse_command(data)
|
||||||
|
case b"data":
|
||||||
|
return data
|
||||||
|
case b"error":
|
||||||
|
print(data)
|
||||||
|
case _:
|
||||||
|
return self._send_error(WinConnectErrors.UNKNOWN_ACTION, f"Unknown action '{action}'")
|
||||||
|
|
||||||
|
def _parse_command(self, data: bytes):
|
||||||
|
command, data = self.__parse_message(data)
|
||||||
|
match command:
|
||||||
|
case b'get_session_settings':
|
||||||
|
settings = {
|
||||||
|
'version': self._version,
|
||||||
|
'encoding': self.default_encoding,
|
||||||
|
'header_size': self.header_size,
|
||||||
|
'header_format': self.header_format,
|
||||||
|
'max_buffer': self.read_max_buffer
|
||||||
|
}
|
||||||
|
session_settings = f"set_session_settings:{json.dumps(settings)}".encode(self.init_encoding)
|
||||||
|
self._send_message("command", session_settings)
|
||||||
|
return True
|
||||||
|
case b'set_session_settings':
|
||||||
|
try:
|
||||||
|
settings = json.loads(data.decode(self.init_encoding))
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
self._send_error(WinConnectErrors.BAD_DATA, f"JSONDecodeError: {e}")
|
||||||
|
return self.close()
|
||||||
|
if settings.get('version') != self._version:
|
||||||
|
self._send_error(WinConnectErrors.BAD_VERSION, f"Version mismatch")
|
||||||
|
return self.close()
|
||||||
|
self._session_encoding = settings.get('encoding', self.default_encoding)
|
||||||
|
self.header_size = settings.get('header_size', self.header_size)
|
||||||
|
self.header_format = settings.get('header_format', self.header_format)
|
||||||
|
self.read_max_buffer = settings.get('max_buffer', self.read_max_buffer)
|
||||||
|
self._send_message("command", b"ready:")
|
||||||
|
return True
|
||||||
|
case b"ready":
|
||||||
|
return True
|
||||||
|
case _:
|
||||||
|
return self._send_error(WinConnectErrors.UNKNOWN_COMMAND, f"Command {command!r} is unknown")
|
||||||
|
|
||||||
|
def _init_session(self):
|
||||||
|
action, data = self._read_message()
|
||||||
|
if action != b"command":
|
||||||
|
return self._send_error(WinConnectErrors.BAD_DATA, "Unknown data type")
|
||||||
|
if not self._parse_command(data):
|
||||||
|
return self._send_error(WinConnectErrors.INIT_FIRST, "Server need to init session first")
|
||||||
|
self._inited = True
|
||||||
|
|
||||||
|
def send_data(self, data):
|
||||||
|
self._send_message("data", data)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
if self._opened:
|
||||||
|
win32file.CloseHandle(self._pipe)
|
||||||
|
self._opened = False
|
||||||
|
self._client_connected = False
|
||||||
|
self._inited = False
|
||||||
|
self._pipe = None
|
||||||
|
|
||||||
|
def read_pipe(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def listen(self):
|
||||||
|
while self.run:
|
||||||
|
yield self.read_pipe()
|
||||||
|
self.stop()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.run = False
|
||||||
|
with self._lock:
|
||||||
|
self.close()
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import win32file
|
||||||
|
|
||||||
|
from winConnect.WinConnectBase import WinConnectBase
|
||||||
|
|
||||||
|
|
||||||
|
class WinConnectClient(WinConnectBase):
|
||||||
|
# see: https://mhammond.github.io/pywin32/win32pipe__CreateNamedPipe_meth.html
|
||||||
|
pipe_desiredAccess = win32file.GENERIC_READ | win32file.GENERIC_WRITE # Access mode (read/write)
|
||||||
|
pipe_shareMode = 0 # Share mode (None)
|
||||||
|
pipe_sa = None # Security attributes
|
||||||
|
pipe_CreationDisposition = win32file.OPEN_EXISTING # Open mode (open existing)
|
||||||
|
pipe_flagsAndAttributes = 0 # Flags and attributes
|
||||||
|
pipe_hTemplateFile = None # Template file
|
||||||
|
|
||||||
|
def __init__(self, pipe_name: str):
|
||||||
|
super().__init__(pipe_name)
|
||||||
|
|
||||||
|
def _open_pipe(self):
|
||||||
|
self._pipe = win32file.CreateFile(
|
||||||
|
self._pipe_name,
|
||||||
|
self.pipe_desiredAccess,
|
||||||
|
self.pipe_shareMode,
|
||||||
|
self.pipe_sa,
|
||||||
|
self.pipe_CreationDisposition,
|
||||||
|
self.pipe_flagsAndAttributes,
|
||||||
|
self.pipe_hTemplateFile
|
||||||
|
)
|
||||||
|
self._opened = True
|
||||||
|
self._client_connected = True
|
||||||
|
|
||||||
|
def _init(self):
|
||||||
|
self._send_message("command", b"get_session_settings:")
|
||||||
|
self._init_session()
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self._open_pipe()
|
||||||
|
|
||||||
|
def init_session(self):
|
||||||
|
self._init()
|
||||||
|
|
||||||
|
def read_pipe(self):
|
||||||
|
if not self._client_connected:
|
||||||
|
self.connect()
|
||||||
|
if not self._inited:
|
||||||
|
self.init_session()
|
||||||
|
return self._parse_action(*self._read_message())
|
||||||
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import win32pipe
|
||||||
|
|
||||||
|
from winConnect.WinConnectBase import WinConnectBase, WinConnectErrors
|
||||||
|
from winConnect.utils import SimpleConvertor
|
||||||
|
|
||||||
|
|
||||||
|
class WinConnectDaemon(WinConnectBase):
|
||||||
|
# see: https://mhammond.github.io/pywin32/win32pipe__CreateNamedPipe_meth.html
|
||||||
|
pipe_openMode = win32pipe.PIPE_ACCESS_DUPLEX # Open mode (read/write)
|
||||||
|
pipe_pipeMode = win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_WAIT # Pipe mode (message type, message read mode, blocking mode)
|
||||||
|
pipe_nMaxInstances = 2 # Max number of instances
|
||||||
|
pipe_nOutBufferSize = SimpleConvertor.to_kb(64) # Max size of output buffer
|
||||||
|
pipe_nInBufferSize = SimpleConvertor.to_kb(64) # Max size of input buffer
|
||||||
|
pipe_nDefaultTimeOut = 0 # ~ ms
|
||||||
|
pipe_sa = None # Security attributes
|
||||||
|
|
||||||
|
def __init__(self, pipe_name: str):
|
||||||
|
super().__init__(pipe_name)
|
||||||
|
|
||||||
|
def _open_pipe(self):
|
||||||
|
self._pipe = win32pipe.CreateNamedPipe(
|
||||||
|
self._pipe_name,
|
||||||
|
self.pipe_openMode,
|
||||||
|
self.pipe_pipeMode,
|
||||||
|
self.pipe_nMaxInstances,
|
||||||
|
self.pipe_nOutBufferSize,
|
||||||
|
self.pipe_nInBufferSize,
|
||||||
|
self.pipe_nDefaultTimeOut,
|
||||||
|
self.pipe_sa
|
||||||
|
)
|
||||||
|
self._opened = True
|
||||||
|
|
||||||
|
def wait_client(self):
|
||||||
|
if not self._opened:
|
||||||
|
self._open_pipe()
|
||||||
|
win32pipe.ConnectNamedPipe(self._pipe, None)
|
||||||
|
self._client_connected = True
|
||||||
|
|
||||||
|
def read_pipe(self):
|
||||||
|
if not self._client_connected:
|
||||||
|
self.wait_client()
|
||||||
|
if not self._inited:
|
||||||
|
self._init_session()
|
||||||
|
return self._parse_action(*self._read_message())
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
__title__ = 'winConnect'
|
||||||
|
__description__ = 'Communication Client->Daemon via NamedPipe.'
|
||||||
|
__url__ = 'https://github.com/SantaSpeen/winConnect'
|
||||||
|
__version__ = '1.0.0'
|
||||||
|
__build__ = 16
|
||||||
|
__author__ = 'SantaSpeen'
|
||||||
|
__author_email__ = 'admin@anidev.ru'
|
||||||
|
__license__ = "MIT"
|
||||||
|
__copyright__ = 'Copyright 2025 © SantaSpeen'
|
||||||
|
|
||||||
|
from .WinConnectDaemon import WinConnectDaemon
|
||||||
|
from .WinConnectClient import WinConnectClient
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
class SimpleConvertor:
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_kb(cls, value: int) -> int:
|
||||||
|
return value * 1024
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_mb(cls, value: int) -> int:
|
||||||
|
return cls.to_kb(value) * 1024
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_gb(cls, value: int) -> int:
|
||||||
|
return cls.to_mb(value) * 1024
|
||||||
Reference in new issue
Block a user