AsamK / signal-cli

signal-cli provides an unofficial commandline, JSON-RPC and dbus interface for the Signal messenger.
GNU General Public License v3.0
3.22k stars 306 forks source link

Messages limited to 2000 bytes, where is this limit defined? #1598

Closed Thireus closed 1 month ago

Thireus commented 1 month ago

It would appear that historically messages were limited to 2000 bytes. This is no longer the case as clients now accept up to 65536 bytes: For ref. https://github.com/signalapp/Signal-Android/commit/ccfcfa71df28c4da2bef0277eb255c739024aed0 & https://github.com/signalapp/Signal-Android/issues/5146

signal-cli-native appears to enforce this limit somehow, but I'm not able to find where that limit is defined. I also looked into https://github.com/signalapp/libsignal. Messages that are over 2,000 bytes get timed right at this limit.

Any idea? I can confirm that my clients can receive more than 2000 bytes of message when the message is sent using signald which uses https://github.com/Turasa/libsignal-service-java but also when using official native clients for iOS/Android.

There was mention of this issue here as well: https://github.com/AsamK/signal-cli/issues/6, but it's unclear how to turn messages into a text attachment. Is there a special kind of attachment to use that the clients would then render as if it was a message?

Thireus commented 1 month ago

Not sure how come I've missed it... Isn't it these line?

https://github.com/AsamK/signal-cli/blob/304c44064dc6ebab7735046b66b8069c840183e1/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java#L751

https://github.com/AsamK/signal-cli/blob/304c44064dc6ebab7735046b66b8069c840183e1/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java#L760

Can we raise it to meet the new 65536 bytes limit please @AsamK ?

Thireus commented 1 month ago

I understand this piece of code is supposed to create a special attachment with mime LONG_TEXT ("text/x-signal-plain"). But strangely that doesn't seem to work (my clients don't appear to receive it or process it). All I receive is the trimmed message.

https://github.com/AsamK/signal-cli/blob/304c44064dc6ebab7735046b66b8069c840183e1/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java#L757-L761

piechologist commented 1 month ago

I'd rather have a higher limit too.

2000 chars is not that much and it's hard to tell if the message will get truncated because the use of UTF-whats-so-ever.

Attachments are inconvenient in Signal Desktop as they must be downloaded first and then opened in a second step.

A high message limit would mitigate those issues.

Thireus commented 1 month ago

I managed to find a workaround by manually creating the "text/x-signal-plain" attachment and I can confirm it is working as expected (clients process it as text, even can apply style to it). The message can be empty as it will be replaced by the attachment text. This is exactly what the code here should be doing, so not sure why it does not work...

https://github.com/AsamK/signal-cli/blob/304c44064dc6ebab7735046b66b8069c840183e1/lib/src/main/java/org/asamk/signal/manager/internal/ManagerImpl.java#L757-L761

Here is a PoC with test.txt containing a large text:

import base64
import mimetypes

def get_base64_and_mimetype(file_path):
    # Add webp mime type manually if it's missing
    mimetypes.add_type('image/webp', '.webp')

    # Determine the mimetype based on the file extension
    mime_type, _ = mimetypes.guess_type(file_path)

    # Read the file in binary mode and encode it in base64
    with open(file_path, 'rb') as file:
        encoded_string = base64.b64encode(file.read()).decode('utf-8')

    return encoded_string, mime_type

base64_string, mime_type = get_base64_and_mimetype("test.txt")

import requests
import uuid

# Construct the JSON-RPC request using data URI cf. https://github.com/AsamK/signal-cli/blob/dbbc3fbd71faf18e73ad409a3a7b308ce56cd52d/man/signal-cli.1.adoc#L24
res = requests.post(
    "http://xxxxx:xxxx/api/v1/rpc",
    json={
        "jsonrpc": "2.0",
        "method": "send",
        "id": str(uuid.uuid4()),  # Generate a new UUID for the request
        "params": {
            "account": "+xxxx",
            "recipients": ["+xxxx"],
            "message": "This message will be replaced once the attachment gets downloaded",
            "textStyle": ["5:4:MONOSPACE", "10:4:SPOILER"],
            "attachments": [
                f"data:text/x-signal-plain;filename=test.txt;base64,{base64_string}"
            ]
        }
    }
)
AsamK commented 1 month ago

Signal-Android has the same logic to split messages larger than 2000 UTF-16 chars: MAX_PRIMARY_SIZE Splitting is implemented here: https://github.com/signalapp/Signal-Android/blob/main/app/src/main/java/org/thoughtcrime/securesms/util/MessageUtil.java#L28

There was a bug in the signal-cli implementation, that the text attachment was only sent when there were other attachments as well.