From: John Hodge Date: Sat, 20 Nov 2010 15:12:51 +0000 (+0800) Subject: Why didn't this update properly? X-Git-Url: https://git.ucc.asn.au/?a=commitdiff_plain;h=143d4f50570aaa85a401dc232df0942dd061b3ab;p=tpg%2Fopendispense2.git Why didn't this update properly? --- diff --git a/items.cfg b/items.cfg index aa381cd..b101b7e 100644 --- a/items.cfg +++ b/items.cfg @@ -1,13 +1,15 @@ +# Type ID Price Description + # Drinks -drink 0 70 Lemonade -drink 1 71 solo++ -drink 2 70 Solo -drink 3 70 Orange Foo -drink 4 255 V Black -drink 5 101 Coke Zero -drink 6 96 Coke +coke 0 70 Lemonade +coke 1 71 solo++ +coke 2 70 Solo +coke 3 70 Orange Foo +coke 4 255 V Black +coke 5 101 Coke Zero +coke 6 96 Coke # Pseudo items pseudo 0 128 clue # clue.flac -pseudo 0 10 laserprint # print 10 pages +pseudo 1 10 laserprint # print 10 pages diff --git a/proto.txt b/proto.txt index 0e2575b..e8d235c 100644 --- a/proto.txt +++ b/proto.txt @@ -40,7 +40,7 @@ c ENUM_ITEMS\n s 201 Items ...\n --- Get Item Information --- c ITEM_INFO \n -s 202 Item ""\n +s 202 Item \n --- Dispense an item --- c DISPENSE \n s 200 Dispense OK\n or 402 Poor You\n or 500 Dispense Error\n or 406 Bad Item\n @@ -48,10 +48,10 @@ s 200 Dispense OK\n or 402 Poor You\n or 500 Dispense Error\n or 406 Bad Item\n c GIVE \n s 200 Give OK\n or 402 Poor You\n or 404 Bad User\n --- Update balance --- -c ADD ""\n +c ADD \n s 200 Add OK\n or 403 Not Coke\n or 404 Bad User\n --- Set Balance --- -c SET ""\n +c SET \n s 200 Set OK\n or 403 Not allowed\n or 404 Bad User\n --- Get Users Balances --- c ENUM_USERS[ ]\n diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index fec2672..0000000 --- a/server/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.[od] -*~ -dispsrv diff --git a/server/src/Makefile b/server/src/Makefile deleted file mode 100644 index 256ba42..0000000 --- a/server/src/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# OpenDispense 2 -# - -OBJ := main.o server.o logging.o -OBJ += dispense.o cokebank.o itemdb.o -BIN := ../dispsrv - -LINKFLAGS := -g -CPPFLAGS := -CFLAGS := -Wall -g - -.PHONY: all clean - -all: $(BIN) - -clean: - $(RM) $(BIN) $(OBJ) - -$(BIN): $(OBJ) - $(CC) -o $(BIN) $(LINKFLAGS) $(OBJ) - diff --git a/server/src/cokebank.c b/server/src/cokebank.c deleted file mode 100644 index 00d7d4f..0000000 --- a/server/src/cokebank.c +++ /dev/null @@ -1,63 +0,0 @@ -/* - * OpenDispense 2 - * UCC (University [of WA] Computer Club) Electronic Accounting System - * - * cokebank.c - Coke-Bank management - * - * This file is licenced under the 3-clause BSD Licence. See the file COPYING - * for full details. - * - * TODO: Make this a Dynamic Library and load it at runtime - */ -#include -#include -#include "common.h" - -// === PROTOTYPES === -void Init_Cokebank(void); - int AlterBalance(int User, int Delta); - int GetBalance(int User); -char *GetUserName(int User); - int GetUserID(const char *Username); - -// === CODE === -/** - * \brief Load the cokebank database - */ -void Init_Cokebank(void) -{ - -} - -/** - * \brief Alters a user's balance by \a Delta - */ -int AlterBalance(int User, int Delta) -{ - return 0; -} - -/** - * \brief Get the balance of the passed user - */ -int GetBalance(int User) -{ - return 0; -} - -/** - * \brief Return the name the passed user - */ -char *GetUserName(int User) -{ - return NULL; -} - -/** - * \brief Get the User ID of the named user - */ -int GetUserID(const char *Username) -{ - return -1; -} - diff --git a/server/src/common.h b/server/src/common.h deleted file mode 100644 index a7e319d..0000000 --- a/server/src/common.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * OpenDispense2 - * - * This code is published under the terms of the Acess licence. - * See the file COPYING for details. - * - * common.h - Core Header - */ -#ifndef _COMMON_H_ -#define _COMMON_H_ - -// === CONSTANTS === -#define DEFAULT_CONFIG_FILE "/etc/opendispense/main.cfg" -#define DEFAULT_ITEM_FILE "/etc/opendispense/items.cfg" - -// === HELPER MACROS === -#define _EXPSTR(x) #x -#define EXPSTR(x) _EXPSTR(x) - -// === STRUCTURES === -typedef struct sItem tItem; -struct sItem -{ - char *Name; //!< Display Name - int Price; //!< Price - - short Type; //!< References an action - short ID; //!< Item ID -}; - -typedef struct sUser tUser; -struct sUser -{ - int ID; //!< User ID (LDAP ID) - int Balance; //!< Balance in cents - int Bytes; //!< Traffic Usage - char Name[]; //!< Username -}; - -typedef struct sHandler tHandler; -struct sHandler -{ - char *Name; - int (*CanDispense)(int User, int ID); - int (*DoDispense)(int User, int ID); -}; - -// === GLOBALS === -extern tItem *gaItems; -extern int giNumItems; -extern tHandler *gaHandlers; -extern int giDebugLevel; - -// === FUNCTIONS === -// --- Logging --- -extern void Log_Error(const char *Format, ...); -extern void Log_Info(const char *Format, ...); - -// --- Cokebank Functions --- -extern int AlterBalance(int User, int Ammount); -extern int GetBalance(int User); -extern char *GetUserName(int User); -extern int GetUserID(const char *Username); - -#endif diff --git a/server/src/dispense.c b/server/src/dispense.c deleted file mode 100644 index 5552277..0000000 --- a/server/src/dispense.c +++ /dev/null @@ -1,58 +0,0 @@ -/** - */ -#include "common.h" -#include - -// === CODE === -/** - * \brief Dispense an item for a user - * - * The core of the dispense system, I kinda like it :) - */ -int DispenseItem(int User, int Item) -{ - int ret; - tItem *item; - tHandler *handler; - char *username; - - // Sanity check please? - if(Item < 0 || Item >= giNumItems) - return -1; - - // Get item pointers - item = &gaItems[Item]; - handler = &gaHandlers[ item->Type ]; - - // Check if the dispense is possible - ret = handler->CanDispense( User, item->ID ); - if(!ret) return ret; - - // Subtract the balance - ret = AlterBalance( User, -item->Price ); - // What value should I use for this error? - // AlterBalance should return the final user balance - if(ret == 0) return 1; - - // Get username for debugging - username = GetUserName(User); - - // Actually do the dispense - ret = handler->DoDispense( User, item->ID ); - if(ret) { - Log_Error("Dispense failed after deducting cost (%s dispensing %s - %ic)", - username, item->Name, item->Price); - AlterBalance( User, item->Price ); - free( username ); - return 1; - } - - // And log that it happened - Log_Info("Dispensed %s (%i:%i) for %s [cost %i, balance %i cents]", - item->Name, item->Type, item->ID, - username, item->Price, GetBalance(User) - ); - - free( username ); - return 0; -} diff --git a/server/src/itemdb.c b/server/src/itemdb.c deleted file mode 100644 index 0ddcbf7..0000000 --- a/server/src/itemdb.c +++ /dev/null @@ -1,114 +0,0 @@ -/* - * OpenDispense 2 - * UCC (University [of WA] Computer Club) Electronic Accounting System - * - * itemdb.c - Dispense Item Databse - * - * This file is licenced under the 3-clause BSD Licence. See the file COPYING - * for full details. - */ -#include -#include -#include -#include -#include "common.h" -#include - -// === GLOBALS === - int giNumItems = 0; -tItem *gaItems = NULL; -tHandler *gaHandlers = NULL; -char *gsItemListFile = DEFAULT_ITEM_FILE; - -// === PROTOTYPES === -void Load_Itemlist(void); -char *trim(char *__str); - -// === CODE === -/** - * \brief Read the item list from disk - */ -void Load_Itemlist(void) -{ - FILE *fp = fopen(gsItemListFile, "r"); - char buffer[BUFSIZ]; - char *line; - int lineNum = 0; - int i; - regex_t regex; - regmatch_t matches[5]; - - i = regcomp(®ex, "^([a-zA-Z][a-zA-Z0-9]*)\\s+([0-9]+)\\s+([0-9]+)\\s+(.*)", REG_EXTENDED); - //i = regcomp(®ex, "\\(\\d+\\)", 0);//\\s+([0-9]+)\\s+([0-9]+)\\s+(.*)", 0); - if( i ) - { - size_t len = regerror(i, ®ex, NULL, 0); - char *errorStr = malloc(len); - regerror(i, ®ex, errorStr, len); - fprintf(stderr, "Rexex compilation failed - %s\n", errorStr); - free(errorStr); - exit(-1); - } - - // Error check - if(!fp) { - fprintf(stderr, "Unable to open item file '%s'\n", gsItemListFile); - perror("Unable to open item file"); - } - - while( fgets(buffer, BUFSIZ, fp) ) - { - char *tmp; - char *type, *desc; - int num, price; - - lineNum ++; - - // Remove comments - tmp = strchr(buffer, '#'); - if(tmp) *tmp = '\0'; - tmp = strchr(buffer, ';'); - if(tmp) *tmp = '\0'; - - // Trim whitespace - line = trim(buffer); - - if(strlen(line) == 0) continue; - - // Pass regex over line - if( (i = regexec(®ex, line, 5, matches, 0)) ) { - size_t len = regerror(i, ®ex, NULL, 0); - char *errorStr = malloc(len); - regerror(i, ®ex, errorStr, len); - fprintf(stderr, "Syntax error on line %i of item file '%s'\n%s", lineNum, gsItemListFile, errorStr); - free(errorStr); - exit(-1); - } - - // Read line data - type = line + matches[1].rm_so; line[ matches[1].rm_eo ] = '\0'; - num = atoi( line + matches[2].rm_so ); - price = atoi( line + matches[3].rm_so ); - desc = line + matches[4].rm_so; - - - printf("Item '%s' - %i cents, %s:%i\n", desc, price, type, num); - } -} - -char *trim(char *__str) -{ - char *ret; - int i; - - while( isspace(*__str) ) - __str++; - ret = __str; - - i = strlen(ret); - while( i-- && isspace(__str[i]) ) { - __str[i] = '\0'; - } - - return ret; -} diff --git a/server/src/logging.c b/server/src/logging.c deleted file mode 100644 index 51cc8d2..0000000 --- a/server/src/logging.c +++ /dev/null @@ -1,19 +0,0 @@ -/* - * OpenDispense2 - * - * logging.c - Debug/Logging Routines - */ -#include -#include -#include "common.h" - -// === CODE == -void Log_Error(const char *Format, ...) -{ - -} - -void Log_Info(const char *Format, ...) -{ -} - diff --git a/server/src/main.c b/server/src/main.c deleted file mode 100644 index daf209f..0000000 --- a/server/src/main.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * OpenDispense 2 - * UCC (University [of WA] Computer Club) Electronic Accounting System - * - * main.c - Initialisation Code - * - * This file is licenced under the 3-clause BSD Licence. See the file - * COPYING for full details. - */ -#include -#include -#include -#include "common.h" - -// === IMPORTS === -extern void Init_Cokebank(void); // cokebank.c -extern void Load_Itemlist(void); -extern void Server_Start(void); -extern int giServer_Port; -extern char *gsItemListFile; - -// === GLOBALS === - int giDebugLevel = 0; - -// === CODE === -int main(int argc, char *argv[]) -{ - int i; - - // Parse Arguments - for( i = 1; i < argc; i++ ) - { - char *arg = argv[i]; - if( arg[0] == '-' && arg[1] != '-') - { - switch(arg[1]) - { - case 'p': - giServer_Port = atoi(argv[++i]); - break; - case 'd': - giDebugLevel = atoi(argv[++i]); - break; - default: - // Usage Error? - break; - } - } - else if( arg[0] == '-' && arg[1] == '-' ) { - if( strcmp(arg, "--itemsfile") == 0 ) { - gsItemListFile = argv[++i]; - } - else { - // Usage error? - } - } - else { - // Usage Error? - } - } - - Init_Cokebank(); - - Load_Itemlist(); - - Server_Start(); - - return 0; -} - diff --git a/server/src/server.c b/server/src/server.c deleted file mode 100644 index 6bf0434..0000000 --- a/server/src/server.c +++ /dev/null @@ -1,419 +0,0 @@ -/* - * OpenDispense 2 - * UCC (University [of WA] Computer Club) Electronic Accounting System - * - * server.c - Client Server Code - * - * This file is licenced under the 3-clause BSD Licence. See the file - * COPYING for full details. - */ -#include -#include -#include "common.h" -#include -#include -#include -#include -#include - -#define MAX_CONNECTION_QUEUE 5 -#define INPUT_BUFFER_SIZE 256 - -#define HASH_TYPE SHA512 -#define HASH_LENGTH 64 - -#define MSG_STR_TOO_LONG "499 Command too long (limit "EXPSTR(INPUT_BUFFER_SIZE)")\n" - -// === TYPES === -typedef struct sClient -{ - int ID; // Client ID - - int bIsTrusted; // Is the connection from a trusted host/port - - char *Username; - char Salt[9]; - - int UID; - int bIsAuthed; -} tClient; - -// === PROTOTYPES === -void Server_Start(void); -void Server_HandleClient(int Socket, int bTrusted); -char *Server_ParseClientCommand(tClient *Client, char *CommandString); -// --- Commands --- -char *Server_Cmd_USER(tClient *Client, char *Args); -char *Server_Cmd_PASS(tClient *Client, char *Args); -char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args); -// --- Helpers --- -void HexBin(uint8_t *Dest, char *Src, int BufSize); - -// === GLOBALS === - int giServer_Port = 1020; - int giServer_NextClientID = 1; -// - Commands -struct sClientCommand { - char *Name; - char *(*Function)(tClient *Client, char *Arguments); -} gaServer_Commands[] = { - {"USER", Server_Cmd_USER}, - {"PASS", Server_Cmd_PASS}, - {"AUTOAUTH", Server_Cmd_AUTOAUTH} -}; -#define NUM_COMMANDS (sizeof(gaServer_Commands)/sizeof(gaServer_Commands[0])) - -// === CODE === -/** - * \brief Open listenting socket and serve connections - */ -void Server_Start(void) -{ - int server_socket, client_socket; - struct sockaddr_in server_addr, client_addr; - - // Create Server - server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); - if( server_socket < 0 ) { - fprintf(stderr, "ERROR: Unable to create server socket\n"); - return ; - } - - // Make listen address - memset(&server_addr, 0, sizeof(server_addr)); - server_addr.sin_family = AF_INET; // Internet Socket - server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Listen on all interfaces - server_addr.sin_port = htons(giServer_Port); // Port - - // Bind - if( bind(server_socket, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0 ) { - fprintf(stderr, "ERROR: Unable to bind to 0.0.0.0:%i\n", giServer_Port); - return ; - } - - // Listen - if( listen(server_socket, MAX_CONNECTION_QUEUE) < 0 ) { - fprintf(stderr, "ERROR: Unable to listen to socket\n"); - return ; - } - - printf("Listening on 0.0.0.0:%i\n", giServer_Port); - - for(;;) - { - uint len = sizeof(client_addr); - int bTrusted = 0; - - client_socket = accept(server_socket, (struct sockaddr *) &client_addr, &len); - if(client_socket < 0) { - fprintf(stderr, "ERROR: Unable to accept client connection\n"); - return ; - } - - if(giDebugLevel >= 2) { - char ipstr[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &client_addr.sin_addr, ipstr, INET_ADDRSTRLEN); - printf("Client connection from %s:%i\n", - ipstr, ntohs(client_addr.sin_port)); - } - - // Trusted Connections - if( ntohs(client_addr.sin_port) < 1024 ) - { - // TODO: Make this runtime configurable - switch( ntohl( client_addr.sin_addr.s_addr ) ) - { - case 0x7F000001: // 127.0.0.1 localhost - //case 0x825E0D00: // 130.95.13.0 - case 0x825E0D12: // 130.95.13.18 mussel - case 0x825E0D17: // 130.95.13.23 martello - bTrusted = 1; - break; - default: - break; - } - } - - // TODO: Multithread this? - Server_HandleClient(client_socket, bTrusted); - - close(client_socket); - } -} - -/** - * \brief Reads from a client socket and parses the command strings - * \param Socket Client socket number/handle - * \param bTrusted Is the client trusted? - */ -void Server_HandleClient(int Socket, int bTrusted) -{ - char inbuf[INPUT_BUFFER_SIZE]; - char *buf = inbuf; - int remspace = INPUT_BUFFER_SIZE-1; - int bytes = -1; - tClient clientInfo = {0}; - - // Initialise Client info - clientInfo.ID = giServer_NextClientID ++; - clientInfo.bIsTrusted = bTrusted; - - // Read from client - /* - * Notes: - * - The `buf` and `remspace` variables allow a line to span several - * calls to recv(), if a line is not completed in one recv() call - * it is saved to the beginning of `inbuf` and `buf` is updated to - * the end of it. - */ - while( (bytes = recv(Socket, buf, remspace, 0)) > 0 ) - { - char *eol, *start; - buf[bytes] = '\0'; // Allow us to use stdlib string functions on it - - // Split by lines - start = inbuf; - while( (eol = strchr(start, '\n')) ) - { - char *ret; - *eol = '\0'; - ret = Server_ParseClientCommand(&clientInfo, start); - // `ret` is a string on the heap - send(Socket, ret, strlen(ret), 0); - free(ret); - start = eol + 1; - } - - // Check if there was an incomplete line - if( *start != '\0' ) { - int tailBytes = bytes - (start-buf); - // Roll back in buffer - memcpy(inbuf, start, tailBytes); - remspace -= tailBytes; - if(remspace == 0) { - send(Socket, MSG_STR_TOO_LONG, sizeof(MSG_STR_TOO_LONG), 0); - buf = inbuf; - remspace = INPUT_BUFFER_SIZE - 1; - } - } - else { - buf = inbuf; - remspace = INPUT_BUFFER_SIZE - 1; - } - } - - // Check for errors - if( bytes < 0 ) { - fprintf(stderr, "ERROR: Unable to recieve from client on socket %i\n", Socket); - return ; - } - - if(giDebugLevel >= 2) { - printf("Client %i: Disconnected\n", clientInfo.ID); - } -} - -/** - * \brief Parses a client command and calls the required helper function - * \param Client Pointer to client state structure - * \param CommandString Command from client (single line of the command) - * \return Heap String to return to the client - */ -char *Server_ParseClientCommand(tClient *Client, char *CommandString) -{ - char *space, *args; - int i; - - // Split at first space - space = strchr(CommandString, ' '); - if(space == NULL) { - args = NULL; - } - else { - *space = '\0'; - args = space + 1; - } - - // Find command - for( i = 0; i < NUM_COMMANDS; i++ ) - { - if(strcmp(CommandString, gaServer_Commands[i].Name) == 0) - return gaServer_Commands[i].Function(Client, args); - } - - return strdup("400 Unknown Command\n"); -} - -// --- -// Commands -// --- -/** - * \brief Set client username - * - * Usage: USER - */ -char *Server_Cmd_USER(tClient *Client, char *Args) -{ - char *ret; - - // Debug! - if( giDebugLevel ) - printf("Client %i authenticating as '%s'\n", Client->ID, Args); - - // Save username - if(Client->Username) - free(Client->Username); - Client->Username = strdup(Args); - - #if USE_SALT - // Create a salt (that changes if the username is changed) - // Yes, I know, I'm a little paranoid, but who isn't? - Client->Salt[0] = 0x21 + (rand()&0x3F); - Client->Salt[1] = 0x21 + (rand()&0x3F); - Client->Salt[2] = 0x21 + (rand()&0x3F); - Client->Salt[3] = 0x21 + (rand()&0x3F); - Client->Salt[4] = 0x21 + (rand()&0x3F); - Client->Salt[5] = 0x21 + (rand()&0x3F); - Client->Salt[6] = 0x21 + (rand()&0x3F); - Client->Salt[7] = 0x21 + (rand()&0x3F); - - // "100 Salt xxxxXXXX\n" - ret = strdup("100 SALT xxxxXXXX\n"); - sprintf(ret, "100 SALT %s\n", Client->Salt); - #else - ret = strdup("100 User Set\n"); - #endif - return ret; -} - -/** - * \brief Authenticate as a user - * - * Usage: PASS - */ -char *Server_Cmd_PASS(tClient *Client, char *Args) -{ - uint8_t clienthash[HASH_LENGTH] = {0}; - - // Read user's hash - HexBin(clienthash, Args, HASH_LENGTH); - - if( giDebugLevel ) { - int i; - printf("Client %i: Password hash ", Client->ID); - for(i=0;i - */ -char *Server_Cmd_AUTOAUTH(tClient *Client, char *Args) -{ - char *spos = strchr(Args, ' '); - if(spos) *spos = '\0'; // Remove characters after the ' ' - - // Check if trusted - if( !Client->bIsTrusted ) { - if(giDebugLevel) - printf("Client %i: Untrusted client attempting to AUTOAUTH\n", Client->ID); - return strdup("401 Untrusted\n"); - } - - // Get UID - Client->UID = GetUserID( Args ); - if( Client->UID < 0 ) { - if(giDebugLevel) - printf("Client %i: Unknown user '%s'\n", Client->ID, Args); - return strdup("401 Auth Failure\n"); - } - - if(giDebugLevel) - printf("Client %i: Authenticated as '%s' (%i)\n", Client->ID, Args, Client->UID); - - return strdup("200 Auth OK\n"); -} - -// --- INTERNAL HELPERS --- -// TODO: Move to another file -void HexBin(uint8_t *Dest, char *Src, int BufSize) -{ - int i; - for( i = 0; i < BufSize; i ++ ) - { - uint8_t val = 0; - - if('0' <= *Src && *Src <= '9') - val |= (*Src-'0') << 4; - else if('A' <= *Src && *Src <= 'F') - val |= (*Src-'A'+10) << 4; - else if('a' <= *Src && *Src <= 'f') - val |= (*Src-'a'+10) << 4; - else - break; - Src ++; - - if('0' <= *Src && *Src <= '9') - val |= (*Src-'0'); - else if('A' <= *Src && *Src <= 'F') - val |= (*Src-'A'+10); - else if('a' <= *Src && *Src <= 'f') - val |= (*Src-'a'+10); - else - break; - Src ++; - - Dest[i] = val; - } - for( ; i < BufSize; i++ ) - Dest[i] = 0; -} - -/** - * \brief Decode a Base64 value - */ -int UnBase64(uint8_t *Dest, char *Src, int BufSize) -{ - uint32_t val; - int i, j; - char *start_src = Src; - - for( i = 0; i+2 < BufSize; i += 3 ) - { - val = 0; - for( j = 0; j < 4; j++, Src ++ ) { - if('A' <= *Src && *Src <= 'Z') - val |= (*Src - 'A') << ((3-j)*6); - else if('a' <= *Src && *Src <= 'z') - val |= (*Src - 'a' + 26) << ((3-j)*6); - else if('0' <= *Src && *Src <= '9') - val |= (*Src - '0' + 52) << ((3-j)*6); - else if(*Src == '+') - val |= 62 << ((3-j)*6); - else if(*Src == '/') - val |= 63 << ((3-j)*6); - else if(!*Src) - break; - else if(*Src != '=') - j --; // Ignore invalid characters - } - Dest[i ] = (val >> 16) & 0xFF; - Dest[i+1] = (val >> 8) & 0xFF; - Dest[i+2] = val & 0xFF; - if(j != 4) break; - } - - // Finish things off - if(i < BufSize) - Dest[i] = (val >> 16) & 0xFF; - if(i+1 < BufSize) - Dest[i+1] = (val >> 8) & 0xFF; - - return Src - start_src; -}