You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

274 lines
8.0 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
  5. * Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include <wx/app.h>
  21. #include <wx/datetime.h>
  22. #include <wx/event.h>
  23. #include <wx/stdpaths.h>
  24. #include <advanced_config.h>
  25. #include <api/api_plugin_manager.h> // traceApi
  26. #include <api/api_server.h>
  27. #include <api/api_handler_common.h>
  28. #include <kiid.h>
  29. #include <kinng.h>
  30. #include <paths.h>
  31. #include <pgm_base.h>
  32. #include <settings/common_settings.h>
  33. #include <string_utils.h>
  34. #include <api/common/envelope.pb.h>
  35. using kiapi::common::ApiRequest, kiapi::common::ApiResponse, kiapi::common::ApiStatusCode;
  36. wxString KICAD_API_SERVER::s_logFileName = "api.log";
  37. wxDEFINE_EVENT( API_REQUEST_EVENT, wxCommandEvent );
  38. KICAD_API_SERVER::KICAD_API_SERVER() :
  39. wxEvtHandler(),
  40. m_token( KIID().AsStdString() ),
  41. m_readyToReply( false )
  42. {
  43. m_commonHandler = std::make_unique<API_HANDLER_COMMON>();
  44. RegisterHandler( m_commonHandler.get() );
  45. if( !Pgm().GetCommonSettings()->m_Api.enable_server )
  46. {
  47. wxLogTrace( traceApi, "Server: disabled by user preferences." );
  48. return;
  49. }
  50. Start();
  51. }
  52. KICAD_API_SERVER::~KICAD_API_SERVER()
  53. {
  54. }
  55. void KICAD_API_SERVER::Start()
  56. {
  57. if( Running() )
  58. return;
  59. wxFileName socket;
  60. #ifdef __WXMAC__
  61. socket.AssignDir( wxS( "/tmp" ) );
  62. #else
  63. socket.AssignDir( wxStandardPaths::Get().GetTempDir() );
  64. #endif
  65. socket.AppendDir( wxS( "kicad" ) );
  66. socket.SetFullName( wxS( "api.sock" ) );
  67. if( !PATHS::EnsurePathExists( socket.GetPath() ) )
  68. {
  69. wxLogTrace( traceApi, wxString::Format( "Server: socket path %s could not be created",
  70. socket.GetPath() ) );
  71. return;
  72. }
  73. if( socket.FileExists() )
  74. {
  75. socket.SetFullName( wxString::Format( wxS( "api-%ul.sock" ), ::wxGetProcessId() ) );
  76. if( socket.FileExists() )
  77. {
  78. wxLogTrace( traceApi, wxString::Format( "Server: PID socket path %s already exists!",
  79. socket.GetFullPath() ) );
  80. return;
  81. }
  82. }
  83. m_server = std::make_unique<KINNG_REQUEST_SERVER>(
  84. fmt::format( "ipc://{}", socket.GetFullPath().ToStdString() ) );
  85. m_server->SetCallback( [&]( std::string* aRequest ) { onApiRequest( aRequest ); } );
  86. m_logFilePath.AssignDir( PATHS::GetLogsPath() );
  87. m_logFilePath.SetName( s_logFileName );
  88. if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
  89. {
  90. PATHS::EnsurePathExists( PATHS::GetLogsPath() );
  91. log( fmt::format( "--- KiCad API server started at {} ---\n", SocketPath() ) );
  92. }
  93. wxLogTrace( traceApi, wxString::Format( "Server: listening at %s", SocketPath() ) );
  94. Bind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this );
  95. }
  96. void KICAD_API_SERVER::Stop()
  97. {
  98. if( !Running() )
  99. return;
  100. wxLogTrace( traceApi, "Stopping server" );
  101. Unbind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this );
  102. m_server->Stop();
  103. m_server.reset( nullptr );
  104. }
  105. bool KICAD_API_SERVER::Running() const
  106. {
  107. return m_server && m_server->Running();
  108. }
  109. void KICAD_API_SERVER::RegisterHandler( API_HANDLER* aHandler )
  110. {
  111. wxCHECK( aHandler, /* void */ );
  112. m_handlers.insert( aHandler );
  113. }
  114. void KICAD_API_SERVER::DeregisterHandler( API_HANDLER* aHandler )
  115. {
  116. m_handlers.erase( aHandler );
  117. }
  118. std::string KICAD_API_SERVER::SocketPath() const
  119. {
  120. return m_server ? m_server->SocketPath() : "";
  121. }
  122. void KICAD_API_SERVER::onApiRequest( std::string* aRequest )
  123. {
  124. if( !m_readyToReply )
  125. {
  126. ApiResponse notHandled;
  127. notHandled.mutable_status()->set_status( ApiStatusCode::AS_NOT_READY );
  128. notHandled.mutable_status()->set_error_message( "KiCad is not ready to reply" );
  129. m_server->Reply( notHandled.SerializeAsString() );
  130. log( "Got incoming request but was not yet ready to reply." );
  131. return;
  132. }
  133. wxCommandEvent* evt = new wxCommandEvent( API_REQUEST_EVENT );
  134. // We don't actually need write access to this string, but client data is non-const
  135. evt->SetClientData( static_cast<void*>( aRequest ) );
  136. // Takes ownership and frees the wxCommandEvent
  137. QueueEvent( evt );
  138. }
  139. void KICAD_API_SERVER::handleApiEvent( wxCommandEvent& aEvent )
  140. {
  141. std::string& requestString = *static_cast<std::string*>( aEvent.GetClientData() );
  142. ApiRequest request;
  143. if( !request.ParseFromString( requestString ) )
  144. {
  145. ApiResponse error;
  146. error.mutable_header()->set_kicad_token( m_token );
  147. error.mutable_status()->set_status( ApiStatusCode::AS_BAD_REQUEST );
  148. error.mutable_status()->set_error_message( "request could not be parsed" );
  149. m_server->Reply( error.SerializeAsString() );
  150. if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
  151. log( "Response (ERROR): " + error.Utf8DebugString() );
  152. }
  153. if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
  154. log( "Request: " + request.Utf8DebugString() );
  155. if( !request.header().kicad_token().empty() &&
  156. request.header().kicad_token().compare( m_token ) != 0 )
  157. {
  158. ApiResponse error;
  159. error.mutable_header()->set_kicad_token( m_token );
  160. error.mutable_status()->set_status( ApiStatusCode::AS_TOKEN_MISMATCH );
  161. error.mutable_status()->set_error_message(
  162. "the provided kicad_token did not match this KiCad instance's token" );
  163. m_server->Reply( error.SerializeAsString() );
  164. if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
  165. log( "Response (ERROR): " + error.Utf8DebugString() );
  166. }
  167. API_RESULT result;
  168. for( API_HANDLER* handler : m_handlers )
  169. {
  170. result = handler->Handle( request );
  171. if( result.has_value() )
  172. break;
  173. else if( result.error().status() != ApiStatusCode::AS_UNHANDLED )
  174. break;
  175. }
  176. // Note: at the point we call Reply(), we no longer own requestString.
  177. if( result.has_value() )
  178. {
  179. result->mutable_header()->set_kicad_token( m_token );
  180. m_server->Reply( result->SerializeAsString() );
  181. if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
  182. log( "Response: " + result->Utf8DebugString() );
  183. }
  184. else
  185. {
  186. ApiResponse error;
  187. error.mutable_status()->CopyFrom( result.error() );
  188. error.mutable_header()->set_kicad_token( m_token );
  189. if( result.error().status() == ApiStatusCode::AS_UNHANDLED )
  190. {
  191. std::string type = "<unparseable Any>";
  192. google::protobuf::Any::ParseAnyTypeUrl( request.message().type_url(), &type );
  193. std::string msg = fmt::format( "no handler available for request of type {}", type );
  194. error.mutable_status()->set_error_message( msg );
  195. }
  196. m_server->Reply( error.SerializeAsString() );
  197. if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
  198. log( "Response (ERROR): " + error.Utf8DebugString() );
  199. }
  200. }
  201. void KICAD_API_SERVER::log( const std::string& aOutput )
  202. {
  203. FILE* fp = wxFopen( m_logFilePath.GetFullPath(), wxT( "a" ) );
  204. if( !fp )
  205. return;
  206. wxString out;
  207. wxDateTime now = wxDateTime::Now();
  208. fprintf( fp, "%s", TO_UTF8( out.Format( wxS( "%s: %s" ),
  209. now.FormatISOCombined(), aOutput ) ) );
  210. fclose( fp );
  211. }