Another option is to not save the characters in buffer but instead check each one as they arrive to see if the character is the next in the tag sequence.
This has the advantage that you don't need a buffer to match the tag and you don't have to repeatedly search for the string in the buffer.
const char* TAG_STR="(__BOOK_UPLOAD_START__)";
int tag_pos = 0;
I think your problem lies in the usage of the String class. Every time, that you concat a String variable, a new buffer for the result is allocated in the background via dynamic memory allocation. That can result in heap fragmentation, which eats up your memory pretty fast (especially on the smaller ones, like the Uno).
Instead you should use a char array ...
You are mixing ASCII characters and integer values.
The serial data is a pure binary stream, a series of bytes. It is up to the programs on both sides to interpret, how these bytes are meant. When you type 1 into your bluetooth serial app, it already does this. It complies to the ASCII standard, which gives a meaning to every binary value between 0 and 127. ...
Probably the first time because you received the character '1' or '0'.
The second character is probably a '\0' or '\d', an end-of-string or end-of-line character.
You can easily check this by printing the value of each received character, like
You probably see the value for '0' (48), '1' (49), end of string '0' of ...
If there are no other constraints, send them as a text with delimiters and a terminating character.
e.g. something like "123;45;678\n"
On the arduino, either manually check for those delimiters and convert the digits to a number on the fly, or use strtok and atoi after reading the whole line.