/*** Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: AuIPCPipe.NT.cpp Date: 2022-4-15 Author: Reece ***/ #include #include "IPC.hpp" #include "AuIPCHandle.hpp" #include #include #include #include #include "AuIPCPipe.NT.hpp" namespace Aurora::IO::IPC { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Pipes ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// struct IPCPipeImpl; struct IPCHasConnectionEvent : Loop::LSHandle { IPCHasConnectionEvent(AuSPtr parent); bool IsSignaled() override; Loop::ELoopSource GetType() override; bool OnTrigger(AuUInt handle) override; private: AuWPtr parent_; }; IPCHasConnectionEvent::IPCHasConnectionEvent(AuSPtr parent) : parent_(parent), LSHandle((AuUInt)parent->GetConnectHandle()) { } bool IPCHasConnectionEvent::IsSignaled() { return OnTrigger(0); } Loop::ELoopSource IPCHasConnectionEvent::GetType() { return Loop::ELoopSource::eSourceIPCHasClient; } bool IPCHasConnectionEvent::OnTrigger(AuUInt handle) { auto parent = this->parent_.lock(); if (!parent) { SysPushErrorMem("IPC pipe is dead"); return false; } parent->TryConnect(); if (WaitForSingleObject(parent->overlapped.hEvent, 0) != WAIT_OBJECT_0) { return false; } if (parent->clientHandle_ != INVALID_HANDLE_VALUE) { return true; } DWORD avail {}; auto h = parent->GetPipeHandle(); if (!PeekNamedPipe(h, NULL, NULL, NULL, &avail, NULL)) { return false; } return true; } IPCPipeImpl::IPCPipeImpl(HANDLE clientHandle, HANDLE serverHandle, const IPCHandle &handle) : serverHandle_(serverHandle), clientHandle_(clientHandle), ipcHandle_(handle) { if (serverHandle != INVALID_HANDLE_VALUE) { this->hasClient_ = Loop::NewLSEvent(false, false, true); } this->fsHandle_ = AuMakeShared(); this->fsStream_ = AuMakeShared(); this->fsHandle_->Init(this->GetPipeHandle(), this->GetPipeHandle()); this->fsStream_->Init(this->fsHandle_); TryConnect(); } void IPCPipeImpl::TryConnect() { if (this->serverHandle_ == INVALID_HANDLE_VALUE) { return; } this->overlapped.hEvent = GetConnectHandle(); if (AuExchange(bFirstTime, false) || (WaitForSingleObject(this->overlapped.hEvent, 0) == WAIT_OBJECT_0)) { ResetEvent(this->overlapped.hEvent); if (ConnectNamedPipe(this->serverHandle_, &this->overlapped)) { this->bFirstTime = true; TryConnect(); } else { auto lastError = GetLastError(); if (lastError == ERROR_IO_PENDING) { // No-op } else if (lastError == ERROR_PIPE_CONNECTED) { SetEvent(this->overlapped.hEvent); } else if (lastError == ERROR_NO_DATA) { DisconnectNamedPipe(this->serverHandle_); this->bFirstTime = true; TryConnect(); } else { SysPushErrorIO("{}", lastError); } } } } IPCPipeImpl::~IPCPipeImpl() { } AuSPtr IPCPipeImpl::AsReadChannelIsOpen() { if (this->serverHandle_ == INVALID_HANDLE_VALUE) { return {}; } if (!this->lshasConnection_) { this->lshasConnection_ = AuMakeShared(AuSharedFromThis()); } return this->lshasConnection_; } AuSPtr IPCPipeImpl::AsReadChannelHasData() { // TODO (Hack): we should at least make a shared timer return AuUnsafeRaiiToShared(this); } AuSPtr IPCPipeImpl::NewAsyncTransaction() { auto transaction = AuStaticCast(this->fsStream_->NewTransaction()); if (transaction) { transaction->pNtIpcPipeImpl = AuSharedFromThis(); } return transaction; } bool IPCPipeImpl::Read(const Memory::MemoryViewStreamWrite &write, bool nonblocking) { DWORD size = write.length; TryConnect(); auto h = this->GetPipeHandle(); if (h == INVALID_HANDLE_VALUE) { SysPushErrorUninitialized(); return false; } if (nonblocking || !write.ptr) { DWORD avail {}; if (!PeekNamedPipe(h, NULL, NULL, NULL, &avail, NULL)) { return false; } if (!avail) { return true; } size = AuMin(size, avail); } if (!write.ptr) { write.outVariable = size; return true; } OVERLAPPED a {}; a.hEvent = CreateEventA(NULL, true, 0, NULL); if (!::ReadFile(h, write.ptr, size, NULL, &a) && ::GetLastError() != ERROR_IO_PENDING) { ::CloseHandle(a.hEvent); return false; } ::WaitForSingleObject(a.hEvent, 0); if (!::GetOverlappedResult(h, &a, &size, true)) { ::CloseHandle(a.hEvent); return false; } ::CloseHandle(a.hEvent); write.outVariable = size; return true; } bool IPCPipeImpl::Write(const Memory::MemoryViewStreamRead &read) { auto h = this->GetPipeHandle(); if (h == INVALID_HANDLE_VALUE) { SysPushErrorUninitialized(); return false; } TryConnect(); DWORD temp; OVERLAPPED a {}; a.hEvent = CreateEventA(NULL, true, 0, NULL); if (!::WriteFile(h, read.ptr, read.length, NULL, &a) && ::GetLastError() != ERROR_IO_PENDING) { SysPushErrorIO("{}", GetLastError()); ::CloseHandle(a.hEvent); return false; } ::WaitForSingleObject(a.hEvent, 0); if (!::GetOverlappedResult(h, &a, &temp, true)) { ::CloseHandle(a.hEvent); return false; } ::CloseHandle(a.hEvent); read.outVariable = temp; return true; } HANDLE IPCPipeImpl::GetPipeHandle() { return this->clientHandle_ == INVALID_HANDLE_VALUE ? this->serverHandle_ : this->clientHandle_; } HANDLE IPCPipeImpl::GetConnectHandle() { return (HANDLE)AuStaticCast(this->hasClient_)->GetHandle(); } void IPCPipeImpl::OnEndOfReadStream() { // TODO: fire inverse LS DisconnectNamedPipe(this->serverHandle_); this->bFirstTime = true; this->TryConnect(); } bool IPCPipeImpl::IsSignaled() { DWORD avail {}; TryConnect(); if (!PeekNamedPipe(this->GetPipeHandle(), NULL, NULL, NULL, &avail, NULL)) { return false; } return avail; } bool IPCPipeImpl::WaitOn(AuUInt32 timeout) { return LSHandle::WaitOn(timeout); } Loop::ELoopSource IPCPipeImpl::GetType() { return Loop::ELoopSource::eSourceIPCReadPipe; } AuString IPCPipeImpl::ExportToString() { TryConnect(); return this->clientHandle_ == INVALID_HANDLE_VALUE ? this->ipcHandle_.ToString() : AuString {}; } AUKN_SYM AuSPtr NewPipe() { IPC::IPCHandle handle; IPC::IPCToken token; token.NewId(); handle.PushId(EIPCHandleType::eIPCPipe, token); auto path = token.ToNTPath(); auto name = "\\\\.\\pipe\\" + token.ToNTPath(); auto maxLength = 16 * AuHwInfo::GetPageSize() ? AuHwInfo::GetPageSize() : 4096; auto pipeServer = CreateNamedPipeA(name.c_str(), PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_OVERLAPPED, PIPE_WAIT, 1, maxLength, maxLength, NMPWAIT_WAIT_FOREVER, nullptr); if ((!pipeServer) || (pipeServer == INVALID_HANDLE_VALUE)) { SysPushErrorIO("{}", GetLastError()); return {}; } auto object = AuMakeShared(INVALID_HANDLE_VALUE, pipeServer, handle); if (!object) { SysPushErrorMem(); AuWin32CloseHandle(pipeServer); return {}; } return object; } AUKN_SYM AuSPtr ImportPipe(const AuString &handleString) { IPCHandle handle; HANDLE pipe; if (!handle.FromString(handleString)) { SysPushErrorParseError(); return {}; } auto token = handle.GetToken(EIPCHandleType::eIPCPipe, 0); if (!token) { SysPushErrorParseError(); return {}; } auto name = "\\\\.\\pipe\\" + token->token.ToNTPath(); pipe = CreateFileA(name.c_str(), GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL); if ((!pipe) || (pipe == INVALID_HANDLE_VALUE)) { if (GetLastError() == ERROR_PIPE_BUSY) { SysPushErrorIO("Pipe is used -> a client has already connected or the nt server is not ready"); return {}; } SysPushErrorIO("{}", GetLastError()); return {}; } auto object = AuMakeShared(pipe, INVALID_HANDLE_VALUE, handle); if (!object) { SysPushErrorMem(); AuWin32CloseHandle(pipe); return {}; } return object; } } // > The pipe created by UWP process with name \\.\pipe\Local\PipeName is converted to \\.\pipe\Sessions\\AppContainerNamedObjects\\PipeName. // > I can use this to communicate between UWP as server and Win32 as client. // https://jike.in/qa/?qa=103904/ // ...good to know