/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2023 Jon Evans * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include #include #include #include #include #include // traceApi #include #include #include #include #include #include #include #include #include using kiapi::common::ApiRequest, kiapi::common::ApiResponse, kiapi::common::ApiStatusCode; wxString KICAD_API_SERVER::s_logFileName = "api.log"; wxDEFINE_EVENT( API_REQUEST_EVENT, wxCommandEvent ); KICAD_API_SERVER::KICAD_API_SERVER() : wxEvtHandler(), m_token( KIID().AsStdString() ), m_readyToReply( false ) { m_commonHandler = std::make_unique(); RegisterHandler( m_commonHandler.get() ); if( !Pgm().GetCommonSettings()->m_Api.enable_server ) { wxLogTrace( traceApi, "Server: disabled by user preferences." ); return; } Start(); } KICAD_API_SERVER::~KICAD_API_SERVER() { } void KICAD_API_SERVER::Start() { if( Running() ) return; wxFileName socket; #ifdef __WXMAC__ socket.AssignDir( wxS( "/tmp" ) ); #else socket.AssignDir( wxStandardPaths::Get().GetTempDir() ); #endif socket.AppendDir( wxS( "kicad" ) ); socket.SetFullName( wxS( "api.sock" ) ); if( !PATHS::EnsurePathExists( socket.GetPath() ) ) { wxLogTrace( traceApi, wxString::Format( "Server: socket path %s could not be created", socket.GetPath() ) ); return; } if( socket.FileExists() ) { socket.SetFullName( wxString::Format( wxS( "api-%ul.sock" ), ::wxGetProcessId() ) ); if( socket.FileExists() ) { wxLogTrace( traceApi, wxString::Format( "Server: PID socket path %s already exists!", socket.GetFullPath() ) ); return; } } m_server = std::make_unique( fmt::format( "ipc://{}", socket.GetFullPath().ToStdString() ) ); m_server->SetCallback( [&]( std::string* aRequest ) { onApiRequest( aRequest ); } ); m_logFilePath.AssignDir( PATHS::GetLogsPath() ); m_logFilePath.SetName( s_logFileName ); if( ADVANCED_CFG::GetCfg().m_EnableAPILogging ) { PATHS::EnsurePathExists( PATHS::GetLogsPath() ); log( fmt::format( "--- KiCad API server started at {} ---\n", SocketPath() ) ); } wxLogTrace( traceApi, wxString::Format( "Server: listening at %s", SocketPath() ) ); Bind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this ); } void KICAD_API_SERVER::Stop() { if( !Running() ) return; wxLogTrace( traceApi, "Stopping server" ); Unbind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this ); m_server->Stop(); m_server.reset( nullptr ); } bool KICAD_API_SERVER::Running() const { return m_server && m_server->Running(); } void KICAD_API_SERVER::RegisterHandler( API_HANDLER* aHandler ) { wxCHECK( aHandler, /* void */ ); m_handlers.insert( aHandler ); } void KICAD_API_SERVER::DeregisterHandler( API_HANDLER* aHandler ) { m_handlers.erase( aHandler ); } std::string KICAD_API_SERVER::SocketPath() const { return m_server ? m_server->SocketPath() : ""; } void KICAD_API_SERVER::onApiRequest( std::string* aRequest ) { if( !m_readyToReply ) { ApiResponse notHandled; notHandled.mutable_status()->set_status( ApiStatusCode::AS_NOT_READY ); notHandled.mutable_status()->set_error_message( "KiCad is not ready to reply" ); m_server->Reply( notHandled.SerializeAsString() ); log( "Got incoming request but was not yet ready to reply." ); return; } wxCommandEvent* evt = new wxCommandEvent( API_REQUEST_EVENT ); // We don't actually need write access to this string, but client data is non-const evt->SetClientData( static_cast( aRequest ) ); // Takes ownership and frees the wxCommandEvent QueueEvent( evt ); } void KICAD_API_SERVER::handleApiEvent( wxCommandEvent& aEvent ) { std::string& requestString = *static_cast( aEvent.GetClientData() ); ApiRequest request; if( !request.ParseFromString( requestString ) ) { ApiResponse error; error.mutable_header()->set_kicad_token( m_token ); error.mutable_status()->set_status( ApiStatusCode::AS_BAD_REQUEST ); error.mutable_status()->set_error_message( "request could not be parsed" ); m_server->Reply( error.SerializeAsString() ); if( ADVANCED_CFG::GetCfg().m_EnableAPILogging ) log( "Response (ERROR): " + error.Utf8DebugString() ); } if( ADVANCED_CFG::GetCfg().m_EnableAPILogging ) log( "Request: " + request.Utf8DebugString() ); if( !request.header().kicad_token().empty() && request.header().kicad_token().compare( m_token ) != 0 ) { ApiResponse error; error.mutable_header()->set_kicad_token( m_token ); error.mutable_status()->set_status( ApiStatusCode::AS_TOKEN_MISMATCH ); error.mutable_status()->set_error_message( "the provided kicad_token did not match this KiCad instance's token" ); m_server->Reply( error.SerializeAsString() ); if( ADVANCED_CFG::GetCfg().m_EnableAPILogging ) log( "Response (ERROR): " + error.Utf8DebugString() ); } API_RESULT result; for( API_HANDLER* handler : m_handlers ) { result = handler->Handle( request ); if( result.has_value() ) break; else if( result.error().status() != ApiStatusCode::AS_UNHANDLED ) break; } // Note: at the point we call Reply(), we no longer own requestString. if( result.has_value() ) { result->mutable_header()->set_kicad_token( m_token ); m_server->Reply( result->SerializeAsString() ); if( ADVANCED_CFG::GetCfg().m_EnableAPILogging ) log( "Response: " + result->Utf8DebugString() ); } else { ApiResponse error; error.mutable_status()->CopyFrom( result.error() ); error.mutable_header()->set_kicad_token( m_token ); if( result.error().status() == ApiStatusCode::AS_UNHANDLED ) { std::string type = ""; google::protobuf::Any::ParseAnyTypeUrl( request.message().type_url(), &type ); std::string msg = fmt::format( "no handler available for request of type {}", type ); error.mutable_status()->set_error_message( msg ); } m_server->Reply( error.SerializeAsString() ); if( ADVANCED_CFG::GetCfg().m_EnableAPILogging ) log( "Response (ERROR): " + error.Utf8DebugString() ); } } void KICAD_API_SERVER::log( const std::string& aOutput ) { FILE* fp = wxFopen( m_logFilePath.GetFullPath(), wxT( "a" ) ); if( !fp ) return; wxString out; wxDateTime now = wxDateTime::Now(); fprintf( fp, "%s", TO_UTF8( out.Format( wxS( "%s: %s" ), now.FormatISOCombined(), aOutput ) ) ); fclose( fp ); }