I tried to implement an automation tool, and got problem when intercepting IME (Windows 10's default Microsoft IME) input Unicode strings (such as Japanese/Chinese).
I've written a 64-bit dll for injection to other processes/windows. The dll is as follows,
#include <windows.h>
#include <fstream>
#include <locale>
// for output only
static wchar_t* className(HWND hwnd) {
static wchar_t className[128];
::GetClassNameW(hwnd, className, 128);
return className;
}
extern "C" __declspec(dllexport) LRESULT CALLBACK ImeCallback(int code, WPARAM wParam, LPARAM lParam) {
std::wofstream out("test.log", std::ios::app);
out.imbue(std::locale("zh_CN.UTF-8"));
if (code >= 0)
{
PCWPSTRUCT msg = (PCWPSTRUCT)lParam;
if (msg->message == WM_IME_COMPOSITION) {
out << "composition: " << className(msg->hwnd) << ":";
if (msg->lParam & GCS_COMPATTR) {
out << ":GCS_COMPATTR";
}
if (msg->lParam & GCS_COMPCLAUSE) {
out << ":GCS_COMPCLAUSE";
}
if (msg->lParam & GCS_COMPREADSTR) {
out << ":GCS_COMPREADSTR";
}
if (msg->lParam & GCS_COMPREADATTR) {
out << ":GCS_COMPREADATTR";
}
if (msg->lParam & GCS_COMPREADCLAUSE) {
out << ":GCS_COMPREADCLAUSE";
}
if (msg->lParam & GCS_COMPSTR) {
out << ":GCS_COMPSTR";
}
if (msg->lParam & GCS_CURSORPOS) {
out << ":GCS_CURSORPOS";
}
if (msg->lParam & GCS_DELTASTART) {
out << ":GCS_DELTASTART";
}
if (msg->lParam & GCS_RESULTCLAUSE) {
out << ":GCS_RESULTCLAUSE";
}
if (msg->lParam & GCS_RESULTREADCLAUSE) {
out << ":GCS_RESULTREADCLAUSE";
}
if (msg->lParam & GCS_RESULTREADSTR) {
out << ":GCS_RESULTREADSTR";
}
if (msg->lParam & GCS_RESULTSTR) {
out << ":GCS_RESULTSTR";
}
out << std::endl;
if (msg->lParam & GCS_RESULTSTR) {
wchar_t data[128] = { 0 };
HIMC context = ImmGetContext(msg->hwnd);
ImmGetCompositionStringW(context, GCS_RESULTSTR, data, 255);
ImmReleaseContext(msg->hwnd, context);
out << " result data: " << data << std::endl;
}
else if (msg->lParam & GCS_COMPSTR) {
wchar_t data[128] = { 0 };
HIMC context = ImmGetContext(msg->hwnd);
ImmGetCompositionStringW(context, GCS_COMPSTR, data, 255);
ImmReleaseContext(msg->hwnd, context);
out << " composition data: " << data << std::endl;
}
}
else if (msg->message == WM_IME_STARTCOMPOSITION) {
out << "start composition" << std::endl;
}
else if (msg->message == WM_IME_ENDCOMPOSITION)
{
out << "end composition" << std::endl;
}
else if (msg->message == WM_IME_SETCONTEXT) {
out << "set context : " << className(msg->hwnd) << ":" << (wParam ? "TRUE" : "FALSE") << std::endl;
switch (msg->lParam) {
case ISC_SHOWUICOMPOSITIONWINDOW:
out << " ISC_SHOWUICOMPOSITIONWINDOW" << std::endl;
break;
case ISC_SHOWUIGUIDELINE:
out << " ISC_SHOWUIGUIDELINE" << std::endl;
break;
case ISC_SHOWUIALLCANDIDATEWINDOW:
out << " ISC_SHOWUIALLCANDIDATEWINDOW" << std::endl;
break;
case ISC_SHOWUIALL:
out << " ISC_SHOWUIALL" << std::endl;
break;
case ISC_SHOWUICANDIDATEWINDOW:
out << " ISC_SHOWUICANDIDATEWINDOW" << std::endl;
break;
case ISC_SHOWUICANDIDATEWINDOW << 1:
out << " ISC_SHOWUICANDIDATEWINDOW << 1" << std::endl;
break;
case ISC_SHOWUICANDIDATEWINDOW << 2:
out << " ISC_SHOWUICANDIDATEWINDOW << 2" << std::endl;
break;
case ISC_SHOWUICANDIDATEWINDOW << 3:
out << " ISC_SHOWUICANDIDATEWINDOW << 3" << std::endl;
break;
default:
out << " default" << std::endl;
}
}
else if (msg->message == WM_IME_NOTIFY) {
HIMC context;
wchar_t data[128] = { 0 };
out << "notify : " << className(msg->hwnd) << std::endl;
switch (msg->wParam) {
case IMN_CHANGECANDIDATE:
out << " IMN_CHANGECANDIDATE" << std::endl;
break;
case IMN_CLOSECANDIDATE:
out << " IMN_CLOSECANDIDATE" << std::endl;
break;
case IMN_CLOSESTATUSWINDOW:
out << " IMN_CLOSESTATUSWINDOW" << std::endl;
break;
case IMN_GUIDELINE:
out << " IMN_GUIDELINE" << std::endl;
break;
case IMN_OPENCANDIDATE:
out << " IMN_OPENCANDIDATE" << std::endl;
break;
case IMN_OPENSTATUSWINDOW:
out << " IMN_OPENSTATUSWINDOW" << std::endl;
break;
case IMN_SETCANDIDATEPOS:
out << " IMN_SETCANDIDATEPOS" << std::endl;
break;
case IMN_SETCOMPOSITIONFONT:
out << " IMN_SETCOMPOSITIONFONT" << std::endl;
break;
case IMN_SETCOMPOSITIONWINDOW:
out << " IMN_SETCOMPOSITIONWINDOW" << std::endl;
break;
case IMN_SETCONVERSIONMODE:
out << " IMN_SETCONVERSIONMODE" << std::endl;
break;
case IMN_SETOPENSTATUS:
out << " IMN_SETOPENSTATUS" << std::endl;
break;
case IMN_SETSENTENCEMODE:
out << " IMN_SETSENTENCEMODE" << std::endl;
break;
case IMN_SETSTATUSWINDOWPOS:
out << " IMN_SETSTATUSWINDOWPOS" << std::endl;
break;
default:
out << " default: " << msg->wParam << ":" << msg->lParam << std::endl;
context = ImmGetContext(msg->hwnd);
auto result = ImmGetCompositionStringW(context, GCS_RESULTSTR, data, 256);
ImmReleaseContext(msg->hwnd, context);
out << " data: " << data << std::endl;
}
}
else if (msg->message == WM_IME_CHAR) {
out << "char:" << msg->wParam << std::endl;
}
else if (msg->message == WM_IME_CONTROL) {
out << "control" << std::endl;
}
else if (msg->message == WM_IME_SELECT) {
out << "select : " << className(msg->hwnd) << " : " << (msg->wParam ? "TRUE" : "FALSE") << " : " << msg->lParam << std::endl;
}
else if (msg->message == WM_IME_KEYDOWN) {
out << "key down" << std::endl;
}
else if (msg->message == WM_IME_KEYUP) {
out << "key up" << std::endl;
}
else if (msg->message == WM_IME_REQUEST) {
out << "request" << std::endl;
}
}
out.close();
return ::CallNextHookEx(0, code, wParam, lParam);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
I then wrote a 64-bit .NET application to inject this dll.
public delegate long HookProc(long code, long wParam, long lParam);
[DllImport("kernel32.dll", EntryPoint = "LoadLibraryW", SetLastError = true)]
public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPWStr)] string lpFileName);
[DllImport("kernel32.dll", EntryPoint = "FreeLibrary", SetLastError = true)]
public static extern IntPtr FreeLibrary([In] IntPtr hModule);
[DllImport("kernel32.dll", BestFitMapping = false, CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true, ThrowOnUnmappableChar = true)]
public static extern IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName);
[DllImport("user32.dll")]
public static extern int SetWindowsHookEx(int hookType, HookProc hookFn, IntPtr hMod, int threadId);
[DllImport("user32.dll")]
public static extern int UnhookWindowsHookEx(int hookId);
public int hookId;
private void button1_Click(object sender, EventArgs e)
{
IntPtr hMod = LoadLibrary("ImeHook.dll");
IntPtr callback = GetProcAddress(hMod, "ImeCallback");
// 4 represents WH_CALLWNDPROC
// Try to inject into all other 64-bits applications
hookId = SetWindowsHookEx(4, (HookProc)Marshal.GetDelegateForFunctionPointer(callback, typeof(HookProc)), hMod, 0);
}
private void button2_Click(object sender, EventArgs e)
{
UnhookWindowsHookEx(hookId);
}
The program can indeed inject the DLL into other processes because I can see messages from various windows in the logging file.
I can even intercept the IME Unicode strings in the WM_IME_COMPOSITION message (GCS_RESULTSTR), but only for some applications such as 64-bit forms written by .NET.
For some other applications, such as Firefox and Microsoft Edge, I can only see some WM_IME_NOTIFY messages, and there are no any WM_IME_COMPOSITION/WM_IME_STARTCOMPOSITION/WM_IME_ENDCOMPOSITION messages received for these windows. Therefore I cannot get the final Unicode input strings for these windows.
Did I do something wrong? Is it even possible to get such information for all processes or windows (such as 64-bit Edge/Chrome/Firefox) in Windows 10?
I also tried the method you described in this question but failed to capture the windows IME output Unicode string(Chinese)
I found something from the Microsoft docs site, after windows XP, windows introduce the Text Services Framework, and the TSF manager functions as a mediator between an application and one or more text services, maybe there is no message like WM_IME_COMPOSITION or WM_CHAR sent by the IME for Firefox/Chrome/World in latest OS(windows 10 or 11)
but when I use Notepad, still can get the Unicode string of Chinese by hook WH_CALLWNDPROC
Im attempting to create an assembler in c which just reads the instructions from an input file and then translates them to their machine/hex code. So far so good until i hit the jump instruction, while i have it so that it translates the hex properly to in the case of jump 24 to c0 00 00 18 im unsure of how to check those 00's properly without hard coding it in. How can i do this? Should i be using shifts?
PR1.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char *ltrim(char *s) {
while (*s == ' ' || *s == '\t') s++;
return s;
}
char getRegister(char *text) {
if (*text == 'r' || *text=='R') text++;
return atoi(text);
}
char getHex(char *text){
int number = (int)strtol(text, NULL, 16);
char reservednum[5];
if(number <= 0xFFFF){
sprintf(&reservednum[0], "%04x", number);
}
return atoi(reservednum);
}
int assembleLine(char *text, unsigned char* bytes) {
text = ltrim(text);
char *keyWord = strtok(text," ");
if (strcmp("add",keyWord) == 0) {
bytes[0] = 0x10;
bytes[0] |= getRegister(strtok(NULL," "));
bytes[1] = getRegister(strtok(NULL," ")) << 4 | getRegister(strtok(NULL," "));
return 2;
}
else if(strcmp("subtract", keyWord) == 0){
bytes[0] = 0x50;
bytes[0] |= getRegister(strtok(NULL, " "));
bytes[1] = getRegister(strtok(NULL, " ")) << 4 | getRegister(strtok(NULL, " "));
return 2;
}
else if(strcmp("and", keyWord) == 0){
bytes[0] = 0x20;
bytes[0] |= getRegister(strtok(NULL, " "));
bytes[1] = getRegister(strtok(NULL, " ")) << 4 | getRegister(strtok(NULL, " "));
return 2;
}
else if(strcmp("divide", keyWord) == 0){
bytes[0] = 0x30;
bytes[0] |= getRegister(strtok(NULL, " "));
bytes[1] = getRegister(strtok(NULL, " ")) << 4 | getRegister(strtok(NULL, " "));
return 2;
}
else if(strcmp("multiply", keyWord) == 0){
bytes[0] = 0x40;
bytes[0] |= getRegister(strtok(NULL, " "));
bytes[1] = getRegister(strtok(NULL, " ")) << 4 | getRegister(strtok(NULL, " "));
return 2;
}
else if(strcmp("or", keyWord) == 0){
bytes[0] = 0x60;
bytes[0] |= getRegister(strtok(NULL, " "));
bytes[1] = getRegister(strtok(NULL, " ")) << 4 | getRegister(strtok(NULL, " "));
return 2;
}
else if(strcmp("halt", keyWord) == 0){
bytes[0] = 0x00;
bytes[1] = 0x00;
return 2;
}
else if(strcmp("return", keyWord) == 0){
bytes[0] = 0x70;
bytes[1] = 0x00;
return 2;
}
else if(strcmp("addimmediate", keyWord) == 0){
bytes[0] = 0x90;
bytes[0] |= getRegister(strtok(NULL, " "));
bytes[1] = getHex(strtok(NULL, " "));
return 2;
}
else if(strcmp("interrupt", keyWord) == 0){
bytes[0] = 0x80;
bytes[0] |= getHex(strtok(NULL, " "));
bytes[1] = 0x00;
return 2;
}
else if(strcmp("push", keyWord) == 0){
bytes[0] = 0x70;
bytes[0] |= getRegister(strtok(NULL, " "));
bytes[1] = 0x40;
return 2;
}
else if(strcmp("pop", keyWord) == 0){
bytes[0] = 0x70;
bytes[0] |= getRegister(strtok(NULL, " "));
bytes[1] = 0x80;
return 2;
}
else if(strcmp("load", keyWord) == 0){
bytes[0] = 0xE0;
bytes[0] |= getRegister(strtok(NULL, " "));
bytes[1] = getRegister(strtok(NULL, " ")) << 4 | getHex(strtok(NULL, " "));
return 2;
}
else if(strcmp("store", keyWord) == 0){
bytes[0] = 0xF0;
bytes[0] |= getRegister(strtok(NULL, " "));
bytes[1] = getRegister(strtok(NULL, " ")) << 4 | getHex(strtok(NULL, " "));
return 2;
}
else if(strcmp("jump", keyWord) == 0){
bytes[0] = 0xC0;
bytes[1] = 0x00;
bytes[2] = 0x00;
bytes[3] = getHex(strtok(NULL, " "));
return 4;
}
}
int main(int argc, char **argv) {
FILE *src = fopen(argv[1],"r");
FILE *dst = fopen(argv[2],"w");
while (!feof(src)) {
unsigned char bytes[4];
char line[1000];
if (NULL != fgets(line, 1000, src)) {
printf ("read: %s\n",line);
int byteCount = assembleLine(line,bytes);
fwrite(bytes,byteCount,1,dst);
}
}
fclose(src);
fclose(dst);
return 0;
}
I have a bank thread, and 4 ATM machine .txt files.(atm_0_input_file.txt - atm_4_input_file.txt).
Every file is a thread, and a bank is a thread. when the files are read the program ends. the bank outputs the current state of the accounts (id,amount,pw).
Problem:
The sync isn't done well, I'm expecting an output like this:
And I'm currently getting (if using all the 4 files) just one time bank's output. Btw, if I use only one file or two files, I get 2 outputs of the bank. I'm trying to achieve atleast 3-4 outputs of the bank, I'm using usleep(100000) for every file and sleep(3) for the bank.
Also, I'm using system calls for reading the files, reading every byte and saving to a string, then parsing it byte by byte.
Well, here's the code. I'm running CentOS on a Virtual Machine so I can't debug those 750 lines of code.
I hope you can help me, I'm working on it more than 24 hours without a break and feeling helpless..
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#define BUF_SIZE 8192
sem_t sem_log;
sem_t sem_rc;
sem_t sem_db;
int rc;
int n, numOfFiles;
char* logName = "log.txt";
int logDescriptor;
typedef struct Account{
int id;
int password;
int balance;
struct Account* next;
struct Account* prev;
sem_t sem_rc;
sem_t sem_db;
int rc;
}*acc;
acc head ,SaveHead;
typedef struct fileATM{
char fileName[50];
int fileNum;
}*fileAtm;
void parseMessage(char *msg, char tempId[20],char tempPw[20],char tempAmount[20],char temp2Id[20])
{
int i = 0;
int j = 0, k = 0, p = 0, h = 0;
int count = 0;
while(msg[i] != '\0')
{
if(msg[i] == ' '){
count++;
}
i++;
switch(count){
case 1: {tempId[j] = msg[i]; j++; break;}
case 2: {tempPw[k] = msg[i]; k++; break;}
case 3: {tempAmount[p] = msg[i]; p++; break;}
case 4: {temp2Id[h] = msg[i]; h++; break;}
}
//i++;
}
strcat(tempId, "\0");
strcat(tempPw, "\0");
strcat(tempAmount, "\0");
strcat(temp2Id, "\0");
}
//*finish: Update log.txt and lock it
void WriteLog(char* msg){
sem_wait(&sem_log);
strcat(msg,"\n");
write(logDescriptor,msg,strlen(msg));
sem_post(&sem_log);
}
void beforeReadAcc(acc temp)
{
sem_wait(&temp->sem_rc);
temp->rc++; //inc # of readers
if(temp->rc==1)//sem_wait(&temp->sem_db);
sem_post(&temp->sem_rc);
}
void beforeRead()
{
sem_wait(&sem_rc);
rc++; //inc # of readers
if(rc==1)sem_wait(&sem_db);
sem_post(&sem_rc);
}
void afterRead()
{
sem_wait(&sem_rc);
rc--; //dec # of readers
if(rc==0)sem_post(&sem_db); /*last reader*/
sem_post(&sem_rc);
}
void afterReadAcc(acc temp)
{
sem_wait(&temp->sem_rc);
temp->rc--; //dec # of readers
if(temp->rc==0)sem_post(&temp->sem_db); /*last reader*/
sem_post(&temp->sem_rc);
}
int doesAccExists(int id)
{
//BLOCK DB
//char* msgLog;
acc temp = (acc)malloc(sizeof(struct Account));
temp = head;
beforeRead();
if(temp != NULL){
while(temp != NULL)
{
sem_wait(&(temp->sem_db));
if(temp->id == id){
//RELEASE DB TWICE
sem_post(&(temp->sem_db));
afterRead();
return 1;
}
sem_post(&(temp->sem_db));
temp = temp->next;
}
}
afterRead();
return 0;
}
void TransferAccount(fileAtm fileName, char* msg)
{
printf("\n Transfering to account... %s\n", msg);
char tempId[20];
char tempPw[20];
char tempAmount[20];
char temp2Id[20];
int length = strlen(msg);
char msgLog[400];
int found = 0;
strcat(msg,"\0");
//Parse the message from file into 3 strings
parseMessage(msg, tempId, tempPw, temp2Id, tempAmount);
acc tempDest = (acc)malloc(sizeof(struct Account));
acc temp2 = (acc)malloc(sizeof(struct Account));
temp2=SaveHead;
tempDest = temp2;
printf(" %d", doesAccExists(atoi(tempId)));
printf("%d %d",atoi(tempId),atoi(temp2Id) );
if((doesAccExists(atoi(tempId)) == 1) && (found=doesAccExists(atoi(temp2Id)) == 1)){
while(temp2 != NULL)
{
sem_wait(&(temp2->sem_db));
if(temp2->id == atoi(tempId))
{
if(temp2->password == atoi(tempPw))
{
if(temp2->balance > atoi(tempAmount)){
temp2->balance -= atoi(tempAmount);
//Find destination id
sem_post(&(temp2->sem_db));
while(tempDest != NULL)
{
sem_wait(&(tempDest->sem_db));
if(tempDest->id == atoi(temp2Id)){
tempDest->balance += atoi(tempAmount);
found = 1;
sem_post(&(tempDest->sem_db));
break;
}
sem_post(&(tempDest->sem_db));
tempDest = tempDest->next;
}
beforeReadAcc(temp2);
sprintf(msgLog, "<ATM ID: %d>: Transfer <%d> from account <%d> to account <%d> new balance is <%d> new target account balance is <%d>", fileName->fileNum, atoi(tempAmount),temp2->id, tempDest->id, temp2->balance,tempDest->balance);
afterReadAcc(temp2);
WriteLog(msgLog);
return;
}
}
else
{
sprintf(msgLog, "Error <ATM ID: %d>: Your transaction failed - password for id <%d> is incorrect", fileName->fileNum, atoi(tempId));
WriteLog(msgLog);
sem_post(&(temp2->sem_db));
return;
}
}
sem_post(&(temp2->sem_db));
temp2 = temp2->next;
}//end while
}
else
{
if (found == 0)
{ //Did not found destination account
sprintf(msgLog, "<ATM ID: %d>: Your transaction failed - Account id <%d> doesn't exist", fileName->fileNum,atoi(temp2Id));
WriteLog(msgLog); return;
}
sprintf(msgLog, "<ATM ID: %d>: Your transaction failed - Account id <%d> doesn't exist", fileName->fileNum,atoi(tempId));
WriteLog(msgLog);
return;
}
}
void CloseAccount(fileAtm fileName, char* msg)
{
printf("\n Closing account.. %s\n", msg);
char tempId[20];
char tempPw[20];
char tempAmount[20];
char temp2Id[20];
int length = strlen(msg);
char msgLog[400];
strcat(msg,"\0");
int temp2Bal;
//Parse the message from file into 3 strings
parseMessage(msg, tempId, tempPw, tempAmount, temp2Id);
acc temp2 = (acc)malloc(sizeof(struct Account));
acc temp3 = (acc)malloc(sizeof(struct Account));
sem_init(&(temp3->sem_db),0,1);
temp2=SaveHead;
printf(" %d", doesAccExists(atoi(tempId)));
if(doesAccExists(atoi(tempId)) == 1){
while(temp2 != NULL)
{
sem_wait(&(temp2->sem_db));
if(temp2->id == atoi(tempId))
{
if(temp2->password == atoi(tempPw))
{
beforeRead();
//If it's the only item'
//Lock DB
//sem_wait(&sem_db);
if(temp2->prev == NULL && temp2->next == NULL) {
//Lock and Release temp2,temp3
beforeReadAcc(temp2);
sem_wait(&(temp3->sem_db));
temp3->balance = temp2->balance;
sem_post(&(temp3->sem_db));
sem_post(&(temp2->sem_db));
afterReadAcc(temp2);
free(temp2);
//Lock and release SaveHead,Head
SaveHead=head=NULL;
}
//If it's head and there're more items on da list
else if(temp2->prev == NULL && temp2->next != NULL)
{
beforeReadAcc(temp2);
//sem_wait(&(SaveHead->sem_db));
SaveHead = temp2->next;
//sem_post(&(SaveHead->sem_db));
sem_wait(&(temp2->next->sem_db));
temp2->next->prev = NULL;
sem_post(&(temp2->next->sem_db));
sem_wait(&(temp3->sem_db));
temp3->balance = temp2->balance;
sem_post(&(temp3->sem_db));
sem_post(&(temp2->sem_db));
afterReadAcc(temp2);
free(temp2);
}
//Delete from middle in the list
else if(temp2->prev != NULL && temp2->next != NULL)
{
beforeReadAcc(temp2);
sem_wait(&(temp2->prev->sem_db));
temp2->prev->next = temp2->next;
sem_post(&(temp2->prev->sem_db));
sem_wait(&(temp2->next->sem_db));
temp2->next->prev = temp2->prev;
sem_post(&(temp2->next->sem_db));
sem_wait(&(temp3->sem_db));
temp3->balance = temp2->balance;
sem_post(&(temp3->sem_db));
sem_post(&(temp2->sem_db));
afterReadAcc(temp2);
free(temp2);
}
//Delete from the end
else if(temp2->prev != NULL && temp2->next == NULL)
{
beforeReadAcc(temp2);
sem_wait(&(temp2->prev->sem_db));
temp2->prev->next = NULL;
sem_post(&(temp2->prev->sem_db));
sem_wait(&(temp3->sem_db));
temp3->balance = temp2->balance;
sem_post(&(temp3->sem_db));
sem_post(&(temp2->sem_db));
afterReadAcc(temp2);
free(temp2);
}
//Release DB
//sem_post(&sem_db);
afterRead();
sprintf(msgLog, "<ATM ID: %d>: Account <%d> is now closed. Balance was <%d>", fileName->fileNum, atoi(tempId), temp3->balance);
WriteLog(msgLog); return;
}
else
{
sprintf(msgLog, "Error <ATM ID: %d>: Your transaction failed - password for id <%d> is incorrect", fileName->fileNum, atoi(tempId));
WriteLog(msgLog); return;
}
}
sem_post(&(temp2->sem_db));
temp2 = temp2->next;
}
}
else
{
sprintf(msgLog, "<ATM ID: %d>: Your transaction failed - Account id <%d> doesn't exist", fileName->fileNum,atoi(tempId));
WriteLog(msgLog);
return;
}
}
void depositAccount(fileAtm fileName, char* msg)
{
printf("\n Depositing to account.. %s\n", msg);
char tempId[20];
char tempPw[20];
char tempAmount[20];
char temp2Id[20];
int length = strlen(msg);
char msgLog[400];
strcat(msg,"\0");
int SaveId;
//Parse the message from file into 3 strings
parseMessage(msg, tempId, tempPw, tempAmount, temp2Id);
acc temp2 = (acc)malloc(sizeof(struct Account));
temp2=SaveHead;
printf(" %d", doesAccExists(atoi(tempId)));
if(doesAccExists(atoi(tempId)) == 1){
sem_wait(&(temp2->sem_db));
while(temp2 != NULL)
{
if(temp2->id == atoi(tempId))
{
if(temp2->password == atoi(tempPw))
{
//Block User's DB'
temp2->balance += atoi(tempAmount);
//Release User's DB'
sem_post(&(temp2->sem_db));
beforeReadAcc(temp2);
sprintf(msgLog, "<ATM ID: %d>: Account <%d> new balance is <%d> after <%d>$ was deposited", fileName->fileNum, temp2->id, temp2->balance, atoi(tempAmount));
afterReadAcc(temp2);
WriteLog(msgLog);
return;
}
else
{
sprintf(msgLog, "Error <ATM ID: %d>: Your transaction failed - password for id <%d> is incorrect", fileName->fileNum, temp2->id);
WriteLog(msgLog); return;
}
}
sem_post(&(temp2->sem_db));
temp2 = temp2->next;
}//end while
}
else
{
sprintf(msgLog, "<ATM ID: %d>: Your transaction failed - Account id <%d> doesn't exist", fileName->fileNum,atoi(tempId));
WriteLog(msgLog);
return;
}
}
void Withdrawl(fileAtm fileName, char* msg)
{
printf("\n Withdrawl from account.. %s\n", msg);
char tempId[20];
char tempPw[20];
char tempAmount[20];
char temp2Id[20];
int length = strlen(msg);
char msgLog[400];
strcat(msg,"\0");
//Parse the message from file into 3 strings
parseMessage(msg, tempId, tempPw, tempAmount, temp2Id);
acc temp2 = (acc)malloc(sizeof(struct Account));
temp2=SaveHead;
printf(" %d", doesAccExists(atoi(tempId)));
if(doesAccExists(atoi(tempId)) == 1){
sem_wait(&(temp2->sem_db));
while(temp2 != NULL)
{
if(temp2->id == atoi(tempId)){
if(temp2->password == atoi(tempPw)){
//Block User's DB'
if(temp2->balance > atoi(tempAmount))
{
temp2->balance -= atoi(tempAmount);
//Release User's DB'
sem_post(&(temp2->sem_db));
//Lock acc
beforeReadAcc(temp2);
sprintf(msgLog, "<ATM ID: %d>: Account <%d> new balance is <%d> after <%d>$ was Withdrawl", fileName->fileNum, temp2->id, temp2->balance, atoi(tempAmount));
//Release acc
afterReadAcc(temp2);
WriteLog(msgLog);
return;
}
else
{
sprintf(msgLog, "Error <ATM ID: %d>: Your transaction failed account id <%d> balance is lower than <%d> ", fileName->fileNum, temp2->id, atoi(tempAmount));
WriteLog(msgLog); return;
}
}
else
{
sprintf(msgLog, "Error <ATM ID: %d>: Your transaction failed - password for id <%d> is incorrect", fileName->fileNum, atoi(tempId));
WriteLog(msgLog); return;
}
}
sem_post(&(temp2->sem_db));
temp2 = temp2->next;
}
}
else
{
sprintf(msgLog, "<ATM ID: %d>: Your transaction failed - Account id <%d> doesn't exist", fileName->fileNum,atoi(tempId));
WriteLog(msgLog);
return;
}
}
void Balance(fileAtm fileName, char* msg){
printf("\n Balance from account.. %s\n", msg);
char tempId[20];
char tempPw[20];
char tempAmount[20];
char temp2Id[20];
int length = strlen(msg);
char msgLog[400];
strcat(msg,"\0");
//Parse the message from file into 3 strings
parseMessage(msg, tempId, tempPw, tempAmount, temp2Id);
acc temp2 = (acc)malloc(sizeof(struct Account));
temp2=SaveHead;
printf(" %d", doesAccExists(atoi(tempId)));
if(doesAccExists(atoi(tempId)) == 1){
while(temp2 != NULL)
{
if(temp2->id == atoi(tempId))
{
if(temp2->password == atoi(tempPw))
{
//lock user
beforeReadAcc(temp2);
sprintf(msgLog, "<ATM ID: %d>: Account <%d> new balance is <%d> ", fileName->fileNum, temp2->id, temp2->balance);
//release user
afterReadAcc(temp2);
WriteLog(msgLog); return;
}
else
{
sprintf(msgLog, "Error <ATM ID: %d>: Your transaction failed - password for account id <%d> is incorrect", fileName->fileNum, temp2->id);
WriteLog(msgLog); return;
}
}
temp2 = temp2->next;
}
}
else
{
sprintf(msgLog, "<ATM ID: %d>: Your transaction failed - Account id <%d> doesn't exist", fileName->fileNum,temp2->id);
WriteLog(msgLog);
return;
}
}
void openNewAccount(fileAtm fileName, char* msg){
printf("\n Opening account.. %s", msg);
int i = 0;
char tempId[20];
char tempPw[20];
char tempAmount[20];
char temp2Id[20];
acc temp = (acc)malloc(sizeof(struct Account));
int length = strlen(msg);
char msgLog[400];
strcat(msg,"\0");
//Parse the message from file into 3 strings
parseMessage(msg, tempId, tempPw, tempAmount, temp2Id);
//Translate id,pw,amount from string to int
temp->id = atoi(tempId);
temp->password = atoi(tempPw);
temp->balance = atoi(tempAmount);
acc temp2 = (acc)malloc(sizeof(struct Account));
acc tempTest = (acc)malloc(sizeof(struct Account));
temp2 = head;
tempTest = head;
//Search if id exists
beforeRead();
while(temp2 != NULL)
{
sem_wait(&temp2->sem_db);
if(temp2->id == temp->id){
sem_post(&temp2->sem_db);
sprintf(msgLog, "Error <ATM ID: %d>: Your transaction failed - account with the same id exists", fileName->fileNum);
WriteLog(msgLog);
return;
}
else{
sem_post(&temp2->sem_db);
temp2 = temp2->next;
}
}
afterRead();
//Start opening account - lock db
sem_wait(&sem_db);
//Inititalize list, append its head
if(head == NULL){
//List is empty
head = (acc) malloc(sizeof(struct Account));
sem_init(&(head->sem_db), 0, 1);
sem_init(&(head->sem_rc), 0, 1);
//Lock user's DB'
sem_wait(&(head->sem_db));
head->id = temp->id;
head->password = temp->password;
head->balance = temp->balance;
head->prev = NULL;
head->next = NULL;
head->rc = 0;
SaveHead = head;
//Release user's DB'
sem_post(&(head->sem_db));
printf("\n**Account: id %d, pw %d, amount %d\n", head->id, head->password, head->balance);
//Update log
sem_wait(&head->sem_db);
sprintf(msgLog, "<ATM ID: %d> New account id is <%d> with password <%d> and initial balance <%d>,", fileName->fileNum, head->id, head->password, head->balance);
sem_post(&head->sem_db);
}
else
{
//List is not empty
//Lock user's DB'
temp->rc = 0;
sem_init(&(temp->sem_db), 0, 1);
sem_init(&(temp->sem_rc), 0, 1);
temp->prev=NULL;
temp->next=NULL;
//sem_post(&(temp->sem_db));
//Find tail
while(head->next != NULL)
{
sem_wait(&(head->sem_db));
tempTest = head;
head=head->next;
sem_post(&(tempTest->sem_db));
}
sem_wait(&(temp->sem_db));
temp->prev=head;
sem_post(&(temp->sem_db));
sem_wait(&(head->sem_db));
head->next=temp;
sem_post(&(head->sem_db));
head=SaveHead;
sem_wait(&temp->sem_db);
printf("\n***Account: id %d, pw %d, amount %d\n", temp->id, temp->password, temp->balance);
sem_post(&temp->sem_db);
//Update log
sem_wait(&temp->sem_db);
sprintf(msgLog, "<ATM ID: %d> New account id is <%d> with password <%d> and initial balance <%d>,", fileName->fileNum, temp->id, temp->password, temp-> balance);
sem_post(&temp->sem_db);
}
WriteLog(msgLog);
//Account created - Release DB
sem_post(&sem_db);
}
//*Opens file for every ATM
void* openFile(void* args){
//test:
//To add later: while(true) { sleep(100); do next file's line }
//Open file
fileAtm getFile = (fileAtm) args;
printf("\n %s ", getFile->fileName);
int ret_in, in1,file;
char buffer1[BUF_SIZE];
char myLetter;
int count = 0;
int i = 0, j = 0,k;
char msg[30][30];
file = open(getFile->fileName,O_RDONLY,0644);
//Test:
// printf("\n %s ", getFile->fileName);
//msg = "test";
if((ret_in = read (file, &buffer1, BUF_SIZE)) > 0)
{
for(i=0,k=0; i<ret_in; i++,k++)
{
if(buffer1[i]=='\n')
{j++; count++; k=0;i++;}
msg[j][k] = buffer1[i];
}
}
printf("# of lines: %d %c", count, msg[1][0]);
//Here we call the relevant function of the msg
for(j=0; j<=count;j++)
{
switch (msg[j][0]){
case 'O': {openNewAccount(getFile,&(msg[j][0]));
// printf("\ntest :%c ",msg[j][0]);
break;}
case 'D': {printf("\ntest :%c ",msg[j][0]);depositAccount(getFile, &(msg[j][0])); break; }
case 'W': {printf("\ntest :%c ",msg[j][0]);Withdrawl(getFile, &(msg[j][0])); break; }
case 'B': {printf("\ntest :%c ",msg[j][0]);Balance(getFile, &(msg[j][0])); break;}
case 'Q': {printf("\ntest :%c ",msg[j][0]);CloseAccount(getFile, &(msg[j][0])); break;}
case 'T': {printf("\ntest :%c ",msg[j][0]);TransferAccount(getFile, &(msg[j][0])); break; }
}
}
usleep(100000);
numOfFiles++;
close(file);
}
void* bankLoop(void* nothing)
{
int totalAmount = 0;
acc temp = (acc)malloc(sizeof(struct Account));
acc temp2 = (acc)malloc(sizeof(struct Account));
while(1)
{
sleep(1);
temp = head;
beforeRead();
printf("\n*******************************\n");
printf("\nBank status: \n");
while(temp != NULL)
{
beforeReadAcc(temp);
printf("\nAccount: %d, Account password: %d ,Balance: %d$",temp->id, temp->password, temp->balance);
totalAmount+= temp->balance;
temp2 = temp;
temp = temp->next;
afterReadAcc(temp2);
}
//Print and Reset bank's balance to zero
printf("\nBank's balance: %d\n", totalAmount);
if(totalAmount != 0) totalAmount = 0;
printf("\n*********************************\n");
afterRead();
if(numOfFiles == n)
break;
}
}
int main(void)
{
int i,a;
pthread_t bank;
char fileName[50];
head = NULL;
numOfFiles = 0;
//Init semaphores
sem_init(&sem_log, 0, 1);
sem_init(&sem_rc,0,1);
sem_init(&sem_db,0,1);
printf("Please enter the number of ATMs you want: \n");
scanf("%d", &n);
//Open log file in background
logDescriptor = open(logName,O_WRONLY|O_CREAT,0644);
//Create bank thread
pthread_create( &bank, NULL, bankLoop, NULL);
//Initialization FILES threads
fileAtm files = (fileAtm)malloc(n*sizeof(struct fileATM));
//Files initialization
for(i = 0; i < n; i++){
sprintf(fileName, "ATM_%d_input_file.txt", i);
strcpy((files[i]).fileName,fileName);
(files[i]).fileNum = i;
}
//Testing:
for(i=0; i<n;i++)
printf("\n%s %d", (files[i]).fileName, (files[i]).fileNum);
//Threads (ATM) initialization
pthread_t* atmThreads = (pthread_t*)malloc(n*sizeof(pthread_t));
printf("test\n"); //TEST: check msg
//Create ATMs threads
for(i = 0; i < n; i++){
pthread_create ( &atmThreads[i] , NULL , openFile , (void*)&files[i]);
}
//Join bank thread
pthread_join(bank,NULL);
//Join ATM threads
for(i=0; i < n; i++)
pthread_join(atmThreads[i],NULL);
//scanf("%d",&a);
}
sorry for those 750 lines of code, I hope someone will have the time and be courage enough to check it out.
ATM_0_input_file.txt
O 1111 1111 123
O 4444 4444 598
T 4444 4444 2222 61
W 1111 1234 50
D 4444 4444 50
D 2222 0300 50
D 2222 0000 500
Q 1111 1111
Q 4444 4444
W 2234 2345 50
O 1212 4444 433
O 1432 4444 23
O 1165 4444 2
O 0986 4444
Q 1212 4444
Q 1432 4444
Q 1165 4444
Q 0986 4444
ATM_1_input_file.txt
O 1234 1234 60
D 1234 1234 50
W 1234 1234 20
D 3333 1234 20
T 1234 0000 2244 20
W 1234 1234 900
ATM_2_input_file.txt
D 1234 0000 60
O 3456 1234 20
D 3333 1234 20
T 1234 0000 2244 20
W 1234 0000 900
D 3456 1234 999
D 1234 0000 9
T 3456 1234 2244 203
Q 3333 1234
ATM_3_input_file.txt
O 7777 7777 1766
O 4444 4444 598
T 4444 4444 7777 5
T 7777 7777 4444 500
D 1111 1234 50
O 2222 1234 50
Q 1111 1111
Q 4444 4444
W 2234 2345 50
Edit: Fixed the warning. added the .txt files
I know it's tagged c, but since you spammed it in Lounge<C++>, let me humor you with C++:
Live On Coliru
#include <algorithm>
#include <functional>
#include <atomic>
#include <fstream>
#include <iostream>
#include <list>
#include <mutex>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
// not thread-aware:
struct Bank {
struct Account {
int id;
int password;
int balance;
Account(int id, int password = 0, int balance = 0)
: id(id), password(password), balance(balance) { }
bool operator==(Account const& other) const { return id == other.id; }
};
void remove(int id) {
auto& acc = get_unverified(id);
accounts.remove(acc);
}
void add(Account const& acc) {
if (std::count(accounts.begin(), accounts.end(), acc.id))
throw std::runtime_error("account with the same id exists"); // TODO include id?
accounts.push_back(acc);
}
Account& get_unverified(int id) {
auto it = std::find(accounts.begin(), accounts.end(), id);
if (it != accounts.end())
return *it;
throw std::runtime_error("Account " + std::to_string(id) + " doesn't exist");
}
Account& get(int id, int password) {
auto& acc = get_unverified(id);
if (acc.password != password)
throw std::runtime_error("Password for id <" + std::to_string(id) + "> is incorrect");
return acc;
}
void status() const {
std::cout << "*******************************\n";
std::cout << "Bank status: \n";
int totalAmount = 0;
for (auto const &acc : accounts) {
std::cout << "Account: " << acc.id << ", Account password: " << acc.password << ", Balance: " << acc.balance
<< "$\n";
totalAmount += acc.balance;
}
// Print and Reset bank's balance to zero
std::cout << "Bank's balance: " << totalAmount << "\n";
std::cout << "*******************************\n";
}
private:
std::list<Account> accounts;
};
// something to make access guarded:
template <typename T>
struct Locking {
template <typename Op>
void transactional(Op&& op) {
std::lock_guard<std::mutex> lk(_mx);
std::forward<Op>(op)(_value);
}
private:
std::mutex _mx;
T _value;
};
static void bankLoop(Locking<Bank>& safe, std::atomic_bool& keepRunning) {
while (keepRunning) {
std::this_thread::sleep_for(std::chrono::seconds(1));
safe.transactional(std::mem_fn(&Bank::status));
}
}
struct Log {
Log() : ofs("log.txt") { } // TODO fix mode/fail on existing?
void Write(std::string const &msg) {
std::lock_guard<std::mutex> lk(_mx);
ofs << msg << "\n";
}
private:
std::mutex _mx;
std::ofstream ofs;
} logDescriptor;
struct Atm {
Locking<Bank>& safe;
int const fileNum;
void process(std::string const& fileName) {
std::ifstream file(fileName);
std::string line;
while (std::getline(file, line)) {
// totally made up this feature:
if (line.empty()) {
std::this_thread::sleep_for(std::chrono::milliseconds(200));
continue;
}
switch (line[0]) {
case 'O': openNewAccount(line); break;
case 'D': depositAccount(line); break;
case 'W': Withdrawl(line); break;
case 'B': Balance(line); break;
case 'Q': CloseAccount(line); break;
case 'T': TransferAccount(line); break;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
file.close();
}
private:
void TransferAccount(std::string const& msg) {
std::cout << "Transfering to account... " << msg << "\n";
safe.transactional([=](Bank& bank) {
std::ostringstream msgLog;
try {
auto const cmd = parseCommand(msg);
auto& source = bank.get(cmd.id, cmd.pw);
auto& target = bank.get_unverified(cmd.id2);
source.balance -= cmd.amount;
target.balance += cmd.amount;
msgLog
<< "<ATM ID: " << fileNum << ">: "
<< "Transfer <" << cmd.amount << "> "
<< "from account <" << cmd.id << "> "
<< "to account <" << cmd.id2 << "> "
<< "new balance is <" << source.balance << "> "
<< "new target account balance is <" << target.balance << ">";
} catch(std::exception const& e) {
msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed - " << e.what();
}
logDescriptor.Write(msgLog.str());
});
}
void CloseAccount(std::string const& msg) {
std::cout << "Closing account... " << msg << "\n";
safe.transactional([=](Bank& bank) {
std::ostringstream msgLog;
try {
auto const cmd = parseCommand(msg);
auto& acc = bank.get(cmd.id, cmd.pw);
auto const balance = acc.balance;
bank.remove(acc.id);
msgLog
<< "<ATM ID: " << fileNum << ">: "
<< "Account <" << cmd.id << "> is now closed. "
<< "Balance was <" << balance << ">";
} catch(std::exception const& e) {
msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed - " << e.what();
}
logDescriptor.Write(msgLog.str());
});
}
void depositAccount(std::string const& msg) {
std::cout << "Depositing to account.. " << msg << "\n";
safe.transactional([=](Bank& bank) {
std::ostringstream msgLog;
try {
auto const cmd = parseCommand(msg);
auto& acc = bank.get(cmd.id, cmd.pw);
acc.balance += cmd.amount;
msgLog
<< "<ATM ID: " << fileNum << ">: "
<< "Account <" << cmd.id << "> "
<< "new balance is <" << acc.balance << "> "
<< "after <" << cmd.amount << "> was deposited";
} catch(std::exception const& e) {
msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed - " << e.what();
}
logDescriptor.Write(msgLog.str());
});
}
void Withdrawl(std::string const& msg) {
std::cout << "Withdrawl from account.. " << msg << "\n";
safe.transactional([=](Bank& bank) {
std::ostringstream msgLog;
try {
auto const cmd = parseCommand(msg);
auto& acc = bank.get(cmd.id, cmd.pw);
acc.balance -= cmd.amount;
msgLog
<< "<ATM ID: " << fileNum << ">: "
<< "Account <" << cmd.id << "> "
<< "new balance is <" << acc.balance << "> "
<< "after <" << cmd.amount << "> was Withdrawl";
} catch(std::exception const& e) {
msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed - " << e.what();
}
logDescriptor.Write(msgLog.str());
});
}
void Balance(std::string const& msg) {
std::cout << "Balance from account.. " << msg << "\n";
safe.transactional([=](Bank& bank) {
std::ostringstream msgLog;
try {
auto const cmd = parseCommand(msg);
auto& acc = bank.get(cmd.id, cmd.pw);
msgLog
<< "<ATM ID: " << fileNum << ">: "
<< "Account <" << cmd.id << "> "
<< "new balance is <" << acc.balance << ">";
} catch(std::exception const& e) {
msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed - " << e.what();
}
logDescriptor.Write(msgLog.str());
});
}
void openNewAccount(std::string const& msg) {
std::cout << "Opening account... " << msg << "\n";
safe.transactional([=](Bank& bank) {
std::ostringstream msgLog;
try {
auto const cmd = parseCommand(msg);
Bank::Account const acc(cmd.id, cmd.pw, cmd.amount);
bank.add(acc);
msgLog
<< "<ATM ID: " << fileNum << ">: "
<< "New account id is <" << acc.id << "> with passoword <" << acc.password << "> and initial balance <" << acc.balance << ">,"; // FIXME trailing comma
} catch(std::exception const& e) {
msgLog << "Error <ATM ID: " << fileNum << ">: Your transaction failed - " << e.what();
}
logDescriptor.Write(msgLog.str());
});
}
private:
struct Cmd { int id=-1, pw=-1, amount=0, id2=-1; };
static Cmd parseCommand(std::string const& msg) {
Cmd result;
char discard; // absorbs command character
std::istringstream iss(msg);
if (!(iss >> discard >> result.id >> result.pw))
throw std::runtime_error("the command message is invalid");
iss >> result.amount >> result.id2;
return result;
}
};
int main() {
std::cout << "Please enter the number of ATMs you want: ";
int n = 0;
if (!(std::cin >> n))
throw std::runtime_error("Input failed");
// Create bank thread
Locking<Bank> bank;
std::atomic_bool keepRunning{true};
std::thread bankThread(&bankLoop, std::ref(bank), std::ref(keepRunning));
std::list<std::thread> atmThreads;
for (int i = 0; i < n; i++) {
atmThreads.emplace_back([&bank, i] {
Atm atm { bank, i };
atm.process("ATM_" + std::to_string(i) + "_input_file.txt");
});
}
// Join ATM threads
for (auto &atm : atmThreads)
atm.join();
// Join bank thread
keepRunning = false;
bankThread.join();
}
Let's test it with 1 ATM file
O 123 8888 10
O 123 8888 10
W 123 8888 5
B 123 8888
B 123 7777
O 234 9999 20
D 234 9999 50
B 234 9999
W 123 8888 15
T 234 9999 30 123
Q 234 9999
Q 234 9999
B 123 what
It prints
Please enter the number of ATMs you want: 1
Opening account... O 123 8888 10
Opening account... O 123 8888 10
Withdrawl from account.. W 123 8888 5
Balance from account.. B 123 8888
Balance from account.. B 123 7777
Opening account... O 234 9999 20
Depositing to account.. D 234 9999 50
Balance from account.. B 234 9999
Withdrawl from account.. W 123 8888 15
Transfering to account... T 234 9999 30 123
Closing account... Q 234 9999
Closing account... Q 234 9999
Balance from account.. B 123 what
*******************************
Bank status:
Account: 123, Account password: 8888, Balance: 20$
Bank's balance: 20
*******************************
And log.txt ends up like:
<ATM ID: 0>: New account id is <123> with passoword <8888> and initial balance <10>,
Error <ATM ID: 0>: Your transaction failed - account with the same id exists
<ATM ID: 0>: Account <123> new balance is <5> after <5> was Withdrawl
<ATM ID: 0>: Account <123> new balance is <5>
Error <ATM ID: 0>: Your transaction failed - Password for id <123> is incorrect
<ATM ID: 0>: New account id is <234> with passoword <9999> and initial balance <20>,
<ATM ID: 0>: Account <234> new balance is <70> after <50> was deposited
<ATM ID: 0>: Account <234> new balance is <70>
<ATM ID: 0>: Account <123> new balance is <-10> after <15> was Withdrawl
<ATM ID: 0>: Transfer <30> from account <234> to account <123> new balance is <40> new target account balance is <20>
<ATM ID: 0>: Account <234> is now closed. Balance was <40>
Error <ATM ID: 0>: Your transaction failed - Account 234 doesn't exist
Error <ATM ID: 0>: Your transaction failed - the command message is invalid