Merge pull request #1226 from mattias-symphony/SDA-3155

SDA-3155 Implementation of auto update service
This commit is contained in:
mattias-symphony 2021-06-07 10:36:21 +02:00 committed by GitHub
commit 4bffbbfb17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 454 additions and 335 deletions

View File

@ -0,0 +1,103 @@
#define _CRT_SECURE_NO_WARNINGS
#define _CRT_NONSTDC_NO_WARNINGS
#include "service.h"
#include "ipc.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <windows.h>
#pragma comment(lib, "Shell32.lib")
#define SERVICE_NAME "symphony_sda_auto_update_service"
#define PIPE_NAME "symphony_sda_auto_update_ipc"
// Runs msiexec with the supplied filename
// TODO: logging/error handling
bool run_installer( char const* filename ) {
char command[ 512 ];
sprintf( command, "/i %s /q", filename );
SHELLEXECUTEINFO ShExecInfo = { 0 };
ShExecInfo.cbSize = sizeof( SHELLEXECUTEINFO );
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = "open";
ShExecInfo.lpFile = "msiexec";
ShExecInfo.lpParameters = command;
ShExecInfo.lpDirectory = NULL;
ShExecInfo.nShow = SW_SHOW;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx( &ShExecInfo );
WaitForSingleObject( ShExecInfo.hProcess, INFINITE );
CloseHandle( ShExecInfo.hProcess );
return true;
}
// Called by IPC server when a request comes in. Installs the package and returns a resul
// Also detects disconnects
void ipc_handler( char const* request, void* user_data, char* response, size_t capacity ) {
bool* is_connected = (bool*) user_data;
if( !request ) {
*is_connected = false;
service_cancel_sleep();
return;
}
if( run_installer( request ) ) {
strcpy( response, "OK" );
} else {
strcpy( response, "ERROR" );
}
}
// Service main function. Keeps an IPC server running - if it gets disconnected it starts it
// up again. Install requests are handled by ipc_handler above
void service_main( void ) {
while( service_is_running() ) {
bool is_connected = true;
ipc_server_t* server = ipc_server_start( PIPE_NAME, ipc_handler, &is_connected );
while( is_connected && service_is_running() ) {
service_sleep();
}
ipc_server_stop( server );
}
}
int main( int argc, char** argv ) {
// Debug helpers for install/uninstall
if( argc >= 2 && stricmp( argv[ 1 ], "install" ) == 0 ) {
if( service_install( SERVICE_NAME ) ) {
printf("Service installed successfully\n");
return EXIT_SUCCESS;
} else {
printf("Service failed to install\n");
return EXIT_FAILURE;
}
}
if( argc >= 2 && stricmp( argv[ 1 ], "uninstall" ) == 0 ) {
if( service_uninstall( SERVICE_NAME ) ) {
printf("Service uninstalled successfully\n");
return EXIT_SUCCESS;
} else {
printf("Service failed to uninstall\n");
return EXIT_FAILURE;
}
}
// Run the service - called by the Windows Services system
service_run( SERVICE_NAME, service_main );
return EXIT_SUCCESS;
}
#define SERVICE_IMPLEMENTATION
#include "service.h"
#define IPC_IMPLEMENTATION
#include "ipc.h"

View File

@ -1,332 +0,0 @@
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <aclapi.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>
#include "ipc.h"
#pragma comment(lib, "advapi32.lib")
#pragma comment(lib, "Shell32.lib")
#define PIPE_NAME "symphony_sda_auto_update_ipc"
#define SVCNAME TEXT("SDA Auto Update")
SERVICE_STATUS gSvcStatus;
SERVICE_STATUS_HANDLE gSvcStatusHandle;
HANDLE ghSvcStopEvent = NULL;
VOID SvcInstall(void);
VOID WINAPI SvcCtrlHandler( DWORD );
VOID WINAPI SvcMain( DWORD, LPTSTR * );
VOID ReportSvcStatus( DWORD, DWORD, DWORD );
VOID SvcInit( DWORD, LPTSTR * );
//
// Purpose:
// Entry point for the process
//
// Parameters:
// None
//
// Return value:
// None, defaults to 0 (zero)
//
int __cdecl _tmain(int argc, TCHAR *argv[])
{
// If command-line parameter is "install", install the service.
// Otherwise, the service is probably being started by the SCM.
if( lstrcmpi( argv[1], TEXT("install")) == 0 )
{
SvcInstall();
return 0;
}
// TO_DO: Add any additional services for the process to this table.
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{ (LPSTR)SVCNAME, (LPSERVICE_MAIN_FUNCTION) SvcMain },
{ NULL, NULL }
};
// This call returns when the service has stopped.
// The process should simply terminate when the call returns.
if (!StartServiceCtrlDispatcher( DispatchTable ))
{
//SvcReportEvent(TEXT("StartServiceCtrlDispatcher"));
}
return 0;
}
//
// Purpose:
// Installs a service in the SCM database
//
// Parameters:
// None
//
// Return value:
// None
//
VOID SvcInstall()
{
SC_HANDLE schSCManager;
SC_HANDLE schService;
TCHAR szPath[MAX_PATH];
if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
{
printf("Cannot install service (%d)\n", GetLastError());
return;
}
// Get a handle to the SCM database.
schSCManager = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS); // full access rights
if (NULL == schSCManager)
{
printf("OpenSCManager failed (%d)\n", GetLastError());
return;
}
// Create the service
schService = CreateService(
schSCManager, // SCM database
SVCNAME, // name of service
SVCNAME, // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_DEMAND_START, // start type
SERVICE_ERROR_NORMAL, // error control type
szPath, // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password
if (schService == NULL)
{
printf("CreateService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return;
}
else printf("Service installed successfully\n");
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
}
//
// Purpose:
// Entry point for the service
//
// Parameters:
// dwArgc - Number of arguments in the lpszArgv array
// lpszArgv - Array of strings. The first string is the name of
// the service and subsequent strings are passed by the process
// that called the StartService function to start the service.
//
// Return value:
// None.
//
VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv )
{
// Register the handler function for the service
gSvcStatusHandle = RegisterServiceCtrlHandler(
SVCNAME,
SvcCtrlHandler);
if( !gSvcStatusHandle )
{
// SvcReportEvent(TEXT("RegisterServiceCtrlHandler"));
return;
}
// These SERVICE_STATUS members remain as set here
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
gSvcStatus.dwServiceSpecificExitCode = 0;
// Report initial status to the SCM
ReportSvcStatus( SERVICE_START_PENDING, NO_ERROR, 3000 );
// Perform service-specific initialization and work.
SvcInit( dwArgc, lpszArgv );
}
// This routine is a simple function to print the client request to the console
// and populate the reply buffer with a default data string. This is where you
// would put the actual client request processing code that runs in the context
// of an instance thread. Keep in mind the main thread will continue to wait for
// and receive other client connections while the instance thread is working.
void run_installer( char const* request, void* user_data, char* response, size_t capacity ) {
printf( "Client Request String:\"%s\"\n", request );
char command[ 512 ];
sprintf( command, "/i %s /q", request );
SHELLEXECUTEINFO ShExecInfo = { 0 };
ShExecInfo.cbSize = sizeof( SHELLEXECUTEINFO );
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = "open";
ShExecInfo.lpFile = "msiexec";
ShExecInfo.lpParameters = command;
ShExecInfo.lpDirectory = NULL;
ShExecInfo.nShow = SW_SHOW;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx( &ShExecInfo );
WaitForSingleObject( ShExecInfo.hProcess, INFINITE );
CloseHandle( ShExecInfo.hProcess );
strcpy( response, "OK" );
}
//
// Purpose:
// The service code
//
// Parameters:
// dwArgc - Number of arguments in the lpszArgv array
// lpszArgv - Array of strings. The first string is the name of
// the service and subsequent strings are passed by the process
// that called the StartService function to start the service.
//
// Return value:
// None
//
VOID SvcInit( DWORD dwArgc, LPTSTR *lpszArgv)
{
ipc_server_t* server = ipc_server_start( PIPE_NAME, run_installer, NULL );
// TO_DO: Declare and set any required variables.
// Be sure to periodically call ReportSvcStatus() with
// SERVICE_START_PENDING. If initialization fails, call
// ReportSvcStatus with SERVICE_STOPPED.
// Create an event. The control handler function, SvcCtrlHandler,
// signals this event when it receives the stop control code.
ghSvcStopEvent = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name
if ( ghSvcStopEvent == NULL)
{
ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
return;
}
// Report running status when initialization is complete.
ReportSvcStatus( SERVICE_RUNNING, NO_ERROR, 0 );
// TO_DO: Perform work until service stops.
while(1)
{
// Check whether to stop the service.
WaitForSingleObject(ghSvcStopEvent, INFINITE);
ReportSvcStatus( SERVICE_STOPPED, NO_ERROR, 0 );
return;
}
}
//
// Purpose:
// Sets the current service status and reports it to the SCM.
//
// Parameters:
// dwCurrentState - The current state (see SERVICE_STATUS)
// dwWin32ExitCode - The system error code
// dwWaitHint - Estimated time for pending operation,
// in milliseconds
//
// Return value:
// None
//
VOID ReportSvcStatus( DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwWaitHint)
{
static DWORD dwCheckPoint = 1;
// Fill in the SERVICE_STATUS structure.
gSvcStatus.dwCurrentState = dwCurrentState;
gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
gSvcStatus.dwWaitHint = dwWaitHint;
if (dwCurrentState == SERVICE_START_PENDING)
gSvcStatus.dwControlsAccepted = 0;
else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
if ( (dwCurrentState == SERVICE_RUNNING) ||
(dwCurrentState == SERVICE_STOPPED) )
gSvcStatus.dwCheckPoint = 0;
else gSvcStatus.dwCheckPoint = dwCheckPoint++;
// Report the status of the service to the SCM.
SetServiceStatus( gSvcStatusHandle, &gSvcStatus );
}
//
// Purpose:
// Called by SCM whenever a control code is sent to the service
// using the ControlService function.
//
// Parameters:
// dwCtrl - control code
//
// Return value:
// None
//
VOID WINAPI SvcCtrlHandler( DWORD dwCtrl )
{
// Handle the requested control code.
switch(dwCtrl)
{
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
// Signal the service to stop.
SetEvent(ghSvcStopEvent);
ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
return;
case SERVICE_CONTROL_INTERROGATE:
break;
default:
break;
}
}
#define IPC_IMPLEMENTATION
#include "ipc.h"

View File

@ -134,10 +134,11 @@
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="auto_update_service.cpp" />
<ClCompile Include="auto_update_service.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="ipc.h" />
<ClInclude Include="service.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@ -1,6 +1,9 @@
#ifndef ipc_h
#define ipc_h
#include <stdbool.h>
#include <stddef.h>
#define IPC_MESSAGE_MAX_LENGTH 512
// client
@ -345,7 +348,7 @@ DWORD WINAPI ipc_client_thread( LPVOID param ) {
// TODO: consider if there are better ways to deal with the error. There might not be,
// but then the user-code calling client send/receive might need some robust retry code
if( !success || bytes_read == 0 ) {
if (GetLastError() == ERROR_BROKEN_PIPE) {
if( GetLastError() == ERROR_BROKEN_PIPE ) {
//IPC_LOG( "ipc_client_thread: client disconnected.\n" );
} else {
IPC_LOG( "ipc_client_thread ReadFile failed, LastError=%d.\n", GetLastError() );
@ -391,6 +394,8 @@ DWORD WINAPI ipc_client_thread( LPVOID param ) {
}
}
// Signal that a disconnect has happened
context->request_handler( NULL, context->user_data, NULL, 0 );
// Flush the pipe to allow the client to read the pipe's contents
// before disconnecting. Then disconnect the pipe, and close the

View File

@ -5,6 +5,6 @@
"scripts": {
"test": "cl tests.cpp /O2 /MTd /D_DEBUG /D_CRTDBG_MAP_ALLOC /nologo /link /SUBSYSTEM:CONSOLE && tests.exe",
"preinstall": "npm run test && npm run build",
"build": "cl auto_update_helper.cpp /O2 /MT /nologo /link /SUBSYSTEM:CONSOLE && cl auto_update_service.cpp /O2 /MT /nologo /link /SUBSYSTEM:CONSOLE"
"build": "cl auto_update_helper.cpp /O2 /MT /nologo /link /SUBSYSTEM:CONSOLE && cl auto_update_service.c /O2 /MT /nologo /link /SUBSYSTEM:CONSOLE"
}
}

342
auto_update/service.h Normal file
View File

@ -0,0 +1,342 @@
#ifndef service_h
#define service_h
#include <stdbool.h>
bool service_install( char const* service_name );
bool service_uninstall( char const* service_name );
typedef void (*service_main_func_t)( void );
void service_run( char const* service_name, service_main_func_t main_func );
bool service_is_running( void );
void service_sleep( void );
void service_cancel_sleep( void );
#endif /* service_h */
#ifdef SERVICE_IMPLEMENTATION
#undef SERVICE_IMPLEMENTATION
#include <windows.h>
#include <stdio.h>
// Temporary logging for debugging, will be replaced by something better
static struct {
FILE* fp;
CRITICAL_SECTION mutex;
} g_log = { NULL };
void logf( char const* format, ... ) {
if( !g_log.fp ) {
char path[ MAX_PATH ];
ExpandEnvironmentStringsA( "%LOCALAPPDATA%\\SdaAutoUpdate", path, MAX_PATH );
CreateDirectory( path, NULL );
strcat( path, "\\log.txt" );
g_log.fp = fopen( path, "w" );
InitializeCriticalSection( &g_log.mutex );
}
EnterCriticalSection( &g_log.mutex );
va_list args;
va_start( args, format );
vfprintf( g_log.fp, format, args );
vprintf( format, args );
va_end( args );
fflush( g_log.fp );
LeaveCriticalSection( &g_log.mutex );
}
// Get an errror description for the last error from Windows, for logging purposes
// TODO: thread safe return value
char const* error_message( void ) {
DWORD error = GetLastError();
if( error == 0 ) {
return "";
}
LPSTR buffer = NULL;
size_t size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPSTR)&buffer, 0, NULL);
static char message[ 1024 ] = "";
if( buffer ) {
strncpy( message, buffer, sizeof( message ) );
message[ sizeof( message ) - 1 ] = '\0';
LocalFree(buffer);
}
return message;
}
// Helper function to install the service, used for debugging (for production, service is set up by installer).
bool service_install( char const* service_name ) {
// Get the path to the running executable
TCHAR path[ MAX_PATH ];
if( !GetModuleFileName( NULL, path, MAX_PATH ) ) {
printf("Cannot install service: LastError = %d %s\n", GetLastError(), error_message() );
return false;
}
// Get a handle to the SCM database.
SC_HANDLE scm = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS ); // full access rights
if( !scm ) {
printf( "OpenSCManager failed: LastError = %d %s\n", GetLastError(), error_message() );
return false;
}
// Create the service
SC_HANDLE service = CreateService(
scm, // SCM database
service_name, // name of service
service_name, // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_OWN_PROCESS, // service type
SERVICE_DEMAND_START, // start type
SERVICE_ERROR_NORMAL, // error control type
path, // path to service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password
if( !service ) {
printf( "CreateService failed: LastError = %d %s\n", GetLastError(), error_message() );
CloseServiceHandle( scm );
return false;
}
CloseServiceHandle( service );
CloseServiceHandle( scm );
return true;
}
// Helper function to uninstall the service, used for debugging (for production, service is set up by installer).
bool service_uninstall( char const* service_name ) {
// Get a handle to the SCM database.
SC_HANDLE scm = OpenSCManager(
NULL, // local computer
NULL, // ServicesActive database
SC_MANAGER_ALL_ACCESS ); // full access rights
if( !scm ) {
printf( "OpenSCManager failed: LastError = %d %s\n", GetLastError(), error_message() );
return false;
}
// Get a handle to the service.
SC_HANDLE service = OpenService(
scm, // SCM database
service_name, // name of service
DELETE); // need delete access
if( !service ) {
printf( "OpenService failed: LastError = %d %s\n", GetLastError(), error_message() );
CloseServiceHandle( scm );
return false;
}
// Delete the service.
if( !DeleteService( service ) ) {
printf( "DeleteService failed: LastError = %d %s\n", GetLastError(), error_message() );
CloseServiceHandle( service );
CloseServiceHandle( scm );
return false;
}
CloseServiceHandle( service );
CloseServiceHandle( scm );
return true;
}
// Global state for the service
struct {
char const* service_name;
service_main_func_t main_func;
bool is_running;
HANDLE stop_event;
SERVICE_STATUS service_status;
SERVICE_STATUS_HANDLE status_handle;
HANDLE sleep_event;
} g_service = { FALSE };
// Thread proc for service. Just calls the user-provided main func
DWORD WINAPI service_main_thread( LPVOID param ) {
g_service.main_func();
return EXIT_SUCCESS;
}
// Helper function to report the status of the service. The service needs to
// notify the windows service manager of its status correctly, otherwise it
// will be considerered unresponsive and closed
VOID report_service_status( DWORD current_state, DWORD exit_code, DWORD wait_hint ) {
static DWORD check_point = 1;
g_service.service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
g_service.service_status.dwServiceSpecificExitCode = 0;
g_service.service_status.dwCurrentState = current_state;
g_service.service_status.dwWin32ExitCode = exit_code;
g_service.service_status.dwWaitHint = wait_hint;
if( current_state == SERVICE_START_PENDING ) {
g_service.service_status.dwControlsAccepted = 0;
} else {
g_service.service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
}
if( current_state == SERVICE_RUNNING || current_state == SERVICE_STOPPED ) {
g_service.service_status.dwCheckPoint = 0;
} else {
g_service.service_status.dwCheckPoint = check_point++;
}
// Report the status of the service to the SCM.
SetServiceStatus( g_service.status_handle, &g_service.service_status );
}
// Service control handler, used to signal the service to stop (if the user stops it
// in the Services control panel)
VOID WINAPI service_ctrl_handler( DWORD ctrl ) {
switch( ctrl ) {
case SERVICE_CONTROL_STOP: {
report_service_status( SERVICE_STOP_PENDING, NO_ERROR, 0 );
// Signal the service to stop.
SetEvent( g_service.stop_event );
report_service_status(g_service.service_status.dwCurrentState, NO_ERROR, 0);
return;
} break;
case SERVICE_CONTROL_INTERROGATE: {
report_service_status( g_service.service_status.dwCurrentState, NO_ERROR, 0 );
} break;
default:
break;
}
}
// TODO: better logging once we have a final solution for logging (another jira ticket)
// Main function of the service. Starts a thread to run the user-provided service function
VOID WINAPI service_proc( DWORD argc, LPSTR *argv ) {
logf( "Service proc\n" );
// Register the handler function for the service
g_service.status_handle = RegisterServiceCtrlHandler( g_service.service_name, service_ctrl_handler );
if( !g_service.status_handle ) {
logf( "Failed to register the service control handler\n" );
return;
}
// Report initial status to the SCM
report_service_status( SERVICE_START_PENDING, NO_ERROR, 3000 );
// Create the event used to signal the service to stop
g_service.stop_event = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name
if ( g_service.stop_event == NULL ) {
logf( "No event\n" );
report_service_status( SERVICE_STOPPED, NO_ERROR, 0 );
return;
}
// Start the thread which runs the user-provided main function
HANDLE thread = CreateThread(
NULL, // no security attribute
0, // default stack size
service_main_thread, // thread proc
NULL, // thread parameter
0, // not suspended
NULL ); // returns thread ID
// Report to the SCM that the service is now up and running
report_service_status( SERVICE_RUNNING, NO_ERROR, 0 );
logf( "Service started\n" );
// Wait until the service is stopped
WaitForSingleObject( g_service.stop_event, INFINITE );
// Flag the user-provided main func to exit
g_service.is_running = false;
SetEvent( g_service.sleep_event );
// Wait until user-provided main func has exited
WaitForSingleObject( thread, INFINITE );
// Report to the SCM that the service has stopped
report_service_status( SERVICE_STOPPED, NO_ERROR, 0 );
logf( "Exit service proc\n" );
}
// Run the service with the specified name, and invoke the main_func function
// The main_func provided should exit if service_is_running returns false
void service_run( char const* service_name, service_main_func_t main_func ) {
SetLastError( 0 );
logf( "Starting service\n" );
// Initialize global state
g_service.service_name = service_name;
g_service.main_func = main_func;
g_service.is_running = true;
g_service.sleep_event = CreateEvent(
NULL, // default security attributes
TRUE, // manual reset event
FALSE, // not signaled
NULL); // no name
// Launch service
SERVICE_TABLE_ENTRY dispatch_table[] = {
{ (LPSTR) service_name, (LPSERVICE_MAIN_FUNCTION) service_proc },
{ NULL, NULL }
};
if( !StartServiceCtrlDispatcher( dispatch_table ) ) {
logf( "Error: LastError = %d %s\n", GetLastError(), error_message() );
}
// Cleanup
CloseHandle( g_service.sleep_event );
logf( "Service stopped\n" );
}
// Returns true while service is running, false if it has been asked to stop
bool service_is_running() {
return g_service.is_running;
}
// Sleeps until service_cancel_sleep is called, either manually by the user or
// because the serice received a stop request
void service_sleep( void ) {
ResetEvent( g_service.sleep_event );
WaitForSingleObject( g_service.sleep_event, INFINITE );
}
// Can be used to cancel a pending service sleep
void service_cancel_sleep( void ) {
SetEvent( g_service.sleep_event );
}
#endif /* SERVICE_IMPLEMENTATION */