[receiver] basic MMS support in webhooks#258
Conversation
WalkthroughAdds MMS receive support: manifest permissions and a WAP‑push BroadcastReceiver; implements MMS PDU parsing, new InboxMessage.Mms model, subscription extraction helper, webhook event/payload for MMS, ReceiverService emission of MMS events, UI permission request, and parser unit tests. Changes
Sequence Diagram(s)sequenceDiagram
participant Telephony as Android Telephony
participant MmsRx as MmsReceiver
participant Parser as MMSParser
participant Logs as LogsService
participant RcvSvc as ReceiverService
participant Hooks as WebHooksService
participant Endpoint as Remote Webhook
Telephony->>MmsRx: WAP_PUSH_RECEIVED (application/vnd.wap.mms-message)
MmsRx->>MmsRx: validate intent, extract extras/pdu
MmsRx->>Parser: parseMNotificationInd(pdu)
Parser-->>MmsRx: MNotificationInd
MmsRx->>Logs: insert(metadata: uri, extras, header hex, pdu hex, subscription)
MmsRx->>RcvSvc: process(InboxMessage.Mms)
RcvSvc->>RcvSvc: compute simNumber from subscriptionId (once)
RcvSvc->>Hooks: emit(WebHookEvent.MmsReceived, MmsReceivedPayload)
Hooks-->>Endpoint: POST mms:received payload
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (4)
app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt (1)
11-13: Document units and value expectations forattachmentCountandsize.Minor: Clarify via KDoc whether
sizeis in bytes and whether negative values are ever expected (shouldn’t be). Helps downstream consumers.Apply this diff to add brief docs:
class MmsReceivedPayload( messageId: String, phoneNumber: String, simNumber: Int?, val transactionId: String, val subject: String?, - val attachmentCount: Int, - val size: Long, + // Count of MMS parts (attachments + smil), non-negative + val attachmentCount: Int, + // Total MMS size in bytes, non-negative + val size: Long, val receivedAt: Date ) : MessageEventPayload(messageId, phoneNumber, simNumber)app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt (3)
102-112: Transaction ID extraction is a placeholder; real parsing is needed for reliability.Assuming “first 8 bytes” is fragile. Properly parse the WAP PDU (M-Notification.ind) to read the Transaction-Id field.
I can wire a minimal PDU parser to extract Transaction-Id and Subject from M-Notification.ind without hidden APIs. Do you want a utility added under
modules/receiver/mms/for this?
68-81: Consider guarding sensitive debug logs.Hex-dumping the PDU can leak PII (addresses, subject). Keep it under a debug flag or redact after capture.
Apply this diff if BuildConfig is available:
+import me.capcom.smsgateway.BuildConfig @@ - logsService.insert( + if (BuildConfig.DEBUG) logsService.insert( LogEntry.Priority.DEBUG, MODULE_NAME, "MMS received from $subscriptionId", mapOf( "data" to intent.dataString, "extras" to bundle.keySet().joinToString(", ") { it }, "uri" to intent.extras?.getString("uri"), "pduHex" to pdu.joinToString("") { "%02x".format(it) }, ) ) - Log.d(TAG, "MMS received from $subscriptionId") + Log.d(TAG, "MMS received from $subscriptionId")
82-96: Next step: wire the MMS message into ReceiverService when ready.The processing call is intentionally commented. Once PDU parsing is in place, construct InboxMessage.Mms and call
receiverSvc.process(...)asynchronously (per goAsync).When you’re ready, I can help implement the minimal parsing to populate
transactionId,subject(if present),size, and a sensibleaddressanddate.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
app/src/main/AndroidManifest.xml(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MessageEventPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/SmsEventPayload.kt(1 hunks)
🔇 Additional comments (8)
app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt (1)
24-25: Add MMS webhook event — LGTMEnum constant and @SerializedName value are consistent with existing SMS events and fit the established naming scheme.
app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MessageEventPayload.kt (1)
1-7: Base payload abstraction — LGTMSimple and clear base for message events; trailing comma is fine in modern Kotlin.
app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt (2)
86-95: MMS payload emission — LGTMUsing
transactionIdasmessageIdand including MMS-specific fields is appropriate and consistent with the new webhook event. Once SIM handling fix is applied, this path looks solid.
62-68: No need to guard against –1 in getSimSlotIndex
The helper’s signature returnsInt?and yieldsnullwhenever the subscription isn’t found (or on missing permissions), not-1. The original chainval simNumber = message.subscriptionId ?.let { SubscriptionsHelper.getSimSlotIndex(context, it) } ?.let { it + 1 }already produces
nullfor any “not found” case and only adds 1 to a valid index. No change required.Likely an incorrect or invalid review comment.
app/src/main/AndroidManifest.xml (1)
57-65: Receiver permissioning is good; confirm need for very high priorityRequiring
android.permission.BROADCAST_WAP_PUSHproperly restricts senders to the system. Theandroid:priority="999"is very high; on newer Android versions, ordered broadcast priority may have limited effect. Confirm that this priority is intentional and necessary.If this was copied from older examples, consider whether default priority suffices. No code change required if you’ve validated device behavior.
app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt (1)
5-14: Payload shape looks good and consistent with existing SMS payloads.Extends MessageEventPayload cleanly and adds MMS-specific fields. This will serialize well alongside existing SMS events.
app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/SmsEventPayload.kt (2)
5-9: Good refactor to centralize shared fields in MessageEventPayload.This reduces duplication and aligns SMS/MMS payloads behind a common base. No functional issues spotted.
5-9: Verify JSON compatibility of superclass fields after the refactor.Moving
messageId,phoneNumber, andsimNumberto the superclass should still serialize them (Gson serializes superclass fields), but this is a behavioral change worth validating against webhook consumers.Consider running an integration test or a quick JSON round-trip to ensure field presence and names are unchanged in emitted webhooks. If needed, I can provide a small harness to serialize a couple of payload instances and compare with snapshots.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt (3)
49-56: Be OEM-robust when fetching PDU: check both intent extras and bundleSome devices put the WAP PDU under the intent extra “pdu” instead of “data”. Prefer intent.getByteArrayExtra first, then fall back to the bundle.
Apply this diff:
- val pdu = bundle.getByteArray("data") ?: bundle.getByteArray("pdu") + val pdu = intent.getByteArrayExtra("data") + ?: intent.getByteArrayExtra("pdu") + ?: bundle.getByteArray("data") + ?: bundle.getByteArray("pdu")
69-79: Build break: MODULE_NAME is undefined
logsService.insertuses MODULE_NAME, but it’s not declared. Define it in the companion object.Apply this diff to define the constant:
companion object { private const val TAG = "MmsReceiver" + private const val MODULE_NAME = "receiver:mms" }
165-178: Use SubscriptionManager constants and map slot index to subscriptionIdReturn a true subscriptionId. Use SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, and when only a slot index is present, convert via SubscriptionsHelper.
Apply this diff:
- private fun extractSubscriptionId(context: Context, intent: Intent): Int? { - return when { - intent.extras?.containsKey("android.telephony.extra.SUBSCRIPTION_INDEX") == true -> intent.extras?.getInt( - "android.telephony.extra.SUBSCRIPTION_INDEX" - ) - - intent.extras?.containsKey("subscription") == true -> intent.extras?.getInt("subscription") - intent.extras?.containsKey("android.telephony.extra.SLOT_INDEX") == true -> intent.extras?.getInt( - "android.telephony.extra.SLOT_INDEX" - )?.let { SubscriptionsHelper.getSubscriptionId(context, it) } - - else -> null - } - } + private fun extractSubscriptionId(context: Context, intent: Intent): Int? { + val subId = when { + intent.hasExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX) -> + intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, -1) + // Some OEMs/legacy + intent.hasExtra("subscription") -> + intent.getIntExtra("subscription", -1) + intent.hasExtra(SubscriptionManager.EXTRA_SLOT_INDEX) -> { + val slot = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, -1) + if (slot >= 0) SubscriptionsHelper.getSubscriptionId(context, slot) ?: -1 else -1 + } + else -> -1 + } + return subId.takeIf { it >= 0 } + }
🧹 Nitpick comments (4)
app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt (4)
73-79: Reduce PII risk and log size: limit PDU hex output and label clearlyDumping full PDU hex can leak content and bloat logs. Log a short prefix and the length instead.
Apply this diff:
- "pdu" to pdu.joinToString("") { "%02x".format(it) }, + "pduHex" to pdu.copyOfRange(0, minOf(64, pdu.size)).joinToString("") { "%02x".format(it) }, + "pduLength" to pdu.size,
81-81: Clarify log wording: “on subscriptionId” rather than “from”“from $subscriptionId” can be misread as the sender; this value is the subscription ID.
Apply this diff:
- Log.d(TAG, "MMS received from $subscriptionId") + Log.d(TAG, "MMS received on subscriptionId=$subscriptionId")
3-15: Import platform constants for SubscriptionManagerUse SubscriptionManager constants instead of hard-coded strings.
Apply this diff:
import android.provider.Telephony import android.util.Log +import android.telephony.SubscriptionManager
115-147: Sender extraction is best-effort; consider PDU-based ‘From’ header when availableIntent extras/URI often don’t carry the MSISDN; MMS ‘From’ comes from the PDU. If you plan to surface sender, parse it from the PDU.
I can provide a lightweight parser for common PDU headers (X-Mms-Message-Type, X-Mms-Transaction-Id, From, Subject) if you want to extend debugging fidelity.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
app/src/main/AndroidManifest.xml(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/src/main/AndroidManifest.xml
🔇 Additional comments (2)
app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt (1)
33-49: MMS identity via transactionId only — good changeEquality/hashCode based solely on transactionId is aligned with stable MMS identity and decouples from base fields. This addresses prior feedback and avoids spurious inequality caused by date/subscription changes.
app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt (1)
103-113: Transaction ID extraction is placeholderCurrent approach slices first 8 bytes, which may not align with WAP PDU header structure. Keep for debugging, but plan a real PDU parser if you rely on it downstream.
Would you like me to sketch a minimal M-Notification.ind/M-Retrieve.conf parser to robustly extract Transaction-ID and From headers?
| override fun onReceive(context: Context, intent: Intent) { | ||
| if (intent.action != Telephony.Sms.Intents.WAP_PUSH_RECEIVED_ACTION) { | ||
| return | ||
| } | ||
|
|
||
| val contentType = intent.type | ||
| if (contentType != "application/vnd.wap.mms-message") { | ||
| return | ||
| } | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid I/O on broadcast main thread: use goAsync()
logsService.insert likely performs I/O. Offload processing from onReceive’s main thread to avoid ANRs.
Apply this diff to wrap processing with goAsync and a background thread:
- try {
+ val pendingResult = goAsync()
+ Thread {
+ try {
@@
- } catch (e: Exception) {
- Log.e(TAG, "Error processing MMS", e)
- }
+ } catch (e: Exception) {
+ Log.e(TAG, "Error processing MMS", e)
+ } finally {
+ pendingResult.finish()
+ }
+ }.start()Also applies to: 30-31, 98-101
There was a problem hiding this comment.
Actionable comments posted: 0
🔭 Outside diff range comments (1)
app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt (1)
333-341: Include RECEIVE_WAP_PUSH in runtime permission requestsThe manifest already declares RECEIVE_WAP_PUSH and your MmsReceiver is correctly registered for WAP_PUSH_RECEIVED, but HomeFragment’s
permissionsRequiredlist doesn’t request it at runtime. Add it alongside your other telephony permissions.• File: app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt (around lines 333–341)
Apply this diff:Manifest.permission.READ_SMS, Manifest.permission.RECEIVE_SMS, Manifest.permission.SEND_SMS, + Manifest.permission.RECEIVE_WAP_PUSH, Manifest.permission.RECEIVE_MMS,
🧹 Nitpick comments (1)
app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt (1)
369-386: Avoid starting the service when critical SMS/MMS permissions are denied.For MMS debugging, starting after a partially denied grant set can silently fail. Consider blocking start and resetting the toggle if essential permissions aren’t granted (at minimum RECEIVE_MMS and RECEIVE_WAP_PUSH).
Example adjustment:
) { result -> - if (result.values.all { it }) { - // Permission is granted. Continue the action or workflow in your - // app. - Log.d(javaClass.name, "Permissions granted") - } else { - Toast.makeText( - requireContext(), - "Not all permissions granted, some features may not work", - Toast.LENGTH_SHORT - ) - .show() - } - - start() + val essential = setOf( + Manifest.permission.RECEIVE_MMS, + Manifest.permission.RECEIVE_WAP_PUSH + ) + val essentialGranted = essential.all { result[it] == true } + + if (!essentialGranted) { + Toast.makeText( + requireContext(), + "MMS reception requires RECEIVE_MMS and RECEIVE_WAP_PUSH permissions.", + Toast.LENGTH_LONG + ).show() + binding.buttonStart.isChecked = false + return@registerForActivityResult + } + Log.d(javaClass.name, "Permissions granted") + start() }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
app/src/main/AndroidManifest.xml(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt
- app/src/main/AndroidManifest.xml
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt (2)
28-30: Fix hashCode for Data: use content-based hash to match equals.equals uses Arrays.equals on the ByteArray, but hashCode uses object identity. This breaks the equals/hashCode contract in sets/maps.
Apply this diff:
- override fun hashCode(): Int { - return 31 * super.hashCode() + data.hashCode() - } + override fun hashCode(): Int { + // Use content hash to mirror Arrays.equals used in equals + return 31 * super.hashCode() + (data?.contentHashCode() ?: 0) + }
59-64: Potential NPE in base hashCode when subscriptionId is null.subscriptionId is nullable but hashCode() is called without a null-safe operator. This can crash when subscriptionId is absent (common for single-SIM).
Apply this diff:
- result = 31 * result + subscriptionId.hashCode() + result = 31 * result + (subscriptionId?.hashCode() ?: 0)
♻️ Duplicate comments (2)
app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt (1)
33-48: Equality solely on transactionId is a good call for MMS identity.This decouples MMS identity from mutable/base fields (date, subscriptionId), aligning with typical MMS dedup keyed by transactionId.
app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt (1)
33-81: Offload processing from the broadcast main thread with goAsync().logsService.insert and receiverSvc.process likely do I/O. Doing this in onReceive risks ANRs. Use goAsync and finish() after background work completes.
Apply this diff:
- try { - val bundle: Bundle? = intent.extras - if (bundle == null) { - Log.w(TAG, "No extras found in MMS intent") - return - } - - // Extract MMS metadata from the intent - val pdu = bundle.getByteArray("data") ?: bundle.getByteArray("pdu") - - if (pdu == null) { - Log.w(TAG, "No PDU data found in MMS intent") - return - } - - val mmsNotification = MMSParser.parseMNotificationInd(pdu) - - logsService.insert( - LogEntry.Priority.DEBUG, - MODULE_NAME, - "MMS received from ${mmsNotification.from}", - mapOf( - "data" to intent.dataString, - "extras" to bundle.keySet().joinToString(", ") { it }, - "uri" to intent.extras?.getString("uri"), - "header" to intent.extras?.getByteArray("header") - ?.joinToString("") { "%02x".format(it) }, - "pdu" to pdu.joinToString("") { "%02x".format(it) }, - ) - ) - - Log.d(TAG, "MMS received from ${mmsNotification.from}") - - val mmsMessage = InboxMessage.Mms( - transactionId = mmsNotification.transactionId, - subject = mmsNotification.subject, - size = mmsNotification.messageSize, - address = mmsNotification.from.split('/').first(), - date = Date(), - subscriptionId = SubscriptionsHelper.extractSubscriptionId(context, intent), - ) - - // Process the message using the existing ReceiverService - receiverSvc.process(context, mmsMessage) - - } catch (e: Exception) { - Log.e(TAG, "Error processing MMS", e) - } + val pendingResult = goAsync() + Thread { + try { + val bundle: Bundle? = intent.extras + if (bundle == null) { + Log.w(TAG, "No extras found in MMS intent") + return@Thread + } + + // Extract MMS metadata from the intent + val pdu = intent.getByteArrayExtra("data") ?: intent.getByteArrayExtra("pdu") + if (pdu == null) { + Log.w(TAG, "No PDU data found in MMS intent") + return@Thread + } + + val mmsNotification = MMSParser.parseMNotificationInd(pdu) + + logsService.insert( + LogEntry.Priority.DEBUG, + MODULE_NAME, + "MMS received from ${mmsNotification.from}", + mapOf( + "data" to intent.dataString, + "extras" to bundle.keySet().joinToString(", ") { it }, + "uri" to intent.extras?.getString("uri"), + "header" to intent.extras?.getByteArray("header") + ?.joinToString("") { "%02x".format(it) }, + "pdu" to pdu.joinToString("") { "%02x".format(it) }, + ) + ) + + Log.d(TAG, "MMS received from ${mmsNotification.from}") + + val mmsMessage = InboxMessage.Mms( + transactionId = mmsNotification.transactionId, + subject = mmsNotification.subject, + size = mmsNotification.messageSize, + address = mmsNotification.from.split('/').first(), + date = Date(), + subscriptionId = SubscriptionsHelper.extractSubscriptionId(context, intent), + ) + + // Process the message using the existing ReceiverService + receiverSvc.process(context, mmsMessage) + } catch (e: Exception) { + Log.e(TAG, "Error processing MMS", e) + } finally { + pendingResult.finish() + } + }.start()
🧹 Nitpick comments (5)
app/src/main/java/me/capcom/smsgateway/helpers/SubscriptionsHelper.kt (1)
73-86: Prefer platform constants and normalize sentinel values in subscription-id extraction.Use SubscriptionManager’s EXTRA_* constants and getIntExtra/hasExtra to avoid string typos and default-0 pitfalls. Also, return null for -1 sentinels and convert slotIndex to subscriptionId only when non-negative.
Apply this diff:
@@ - fun extractSubscriptionId(context: Context, intent: Intent): Int? { - return when { - intent.extras?.containsKey("android.telephony.extra.SUBSCRIPTION_INDEX") == true -> intent.extras?.getInt( - "android.telephony.extra.SUBSCRIPTION_INDEX" - ) - - intent.extras?.containsKey("subscription") == true -> intent.extras?.getInt("subscription") - intent.extras?.containsKey("android.telephony.extra.SLOT_INDEX") == true -> intent.extras?.getInt( - "android.telephony.extra.SLOT_INDEX" - )?.let { SubscriptionsHelper.getSubscriptionId(context, it) } - - else -> null - } - } + fun extractSubscriptionId(context: Context, intent: Intent): Int? { + val subId = when { + intent.hasExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX) -> + intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, -1) + intent.hasExtra("subscription") -> // legacy/oem key + intent.getIntExtra("subscription", -1) + intent.hasExtra(SubscriptionManager.EXTRA_SLOT_INDEX) -> { + val slot = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, -1) + if (slot >= 0) getSubscriptionId(context, slot) ?: -1 else -1 + } + else -> -1 + } + return subId.takeIf { it >= 0 } + }app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt (2)
114-119: Bounds-check and clarify parsing of the FROM value.Guard against negative/oversized lengths and use the read length safely. Replace the unused
typewith a named variable.Apply this diff:
- private fun parseFrom(buffer: ByteBuffer): String { - val length = parseShortInteger(buffer) - val type = parseShortInteger(buffer) - - return String((0 until length - 1).map { buffer.get() }.toByteArray(), Charsets.UTF_8) - } + private fun parseFrom(buffer: ByteBuffer): String { + val length = parseShortInteger(buffer) + if (length <= 0 || buffer.remaining() < length) return "" + val addressType = parseShortInteger(buffer) // 128 == address-present + val toRead = (length - 1).coerceAtMost(buffer.remaining()) + val bytes = ByteArray(toRead) + buffer.get(bytes) + return String(bytes, Charsets.UTF_8) + }
143-150: Handle encoded-string values more safely.The current lookahead for a zero byte isn’t a reliable length indicator. Try a small length-prefixed read, otherwise fall back to text-string.
Apply this diff:
- private fun parseEncodedString(buffer: ByteBuffer): String { - return if (buffer.remaining() > 0 && buffer.get(buffer.position()).toInt() == 0) { - buffer.get() // Skip length indicator for now - parseTextString(buffer) - } else { - parseTextString(buffer) - } - } + private fun parseEncodedString(buffer: ByteBuffer): String { + if (!buffer.hasRemaining()) return "" + val mark = buffer.position() + val lenOrToken = buffer.get().toInt() and 0xFF + return if (lenOrToken in 1..64 && buffer.remaining() >= lenOrToken) { + val bytes = ByteArray(lenOrToken) + buffer.get(bytes) + String(bytes, Charsets.UTF_8).trimEnd('\u0000') + } else { + buffer.position(mark) + parseTextString(buffer) + } + }app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt (2)
41-42: Access PDU via intent extras directly for broader OEM compatibility.Some OEMs provide WAP PDU under intent extras rather than the Bundle you retrieve. You already fall back to "pdu"; prefer intent.getByteArrayExtra to cover both.
- val pdu = bundle.getByteArray("data") ?: bundle.getByteArray("pdu") + val pdu = intent.getByteArrayExtra("data") ?: intent.getByteArrayExtra("pdu")
67-75: Normalize “from” to handle insert-address-token and address types.Some carriers send
Fromasinsert-address-tokenor URIs (e.g.,+15551234/TYPE=PLMN). Consider a helper to strip/TYPE=...and fallback to “unknown” when the token is present.Would you like me to add a small normalizer like:
private fun normalizeFrom(from: String?): String = from?.substringBefore('/')?.takeUnless { it.equals("insert-address-token", ignoreCase = true) } ?: "unknown"and use it in the InboxMessage.Mms constructor?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
app/src/main/java/me/capcom/smsgateway/helpers/SubscriptionsHelper.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MessagesReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt(1 hunks)
🔇 Additional comments (4)
app/src/main/java/me/capcom/smsgateway/helpers/SubscriptionsHelper.kt (1)
6-6: Import looks correct and scoped.Importing Intent here is appropriate for centralizing subscription extraction.
app/src/main/java/me/capcom/smsgateway/modules/receiver/MessagesReceiver.kt (1)
33-34: Centralizing subscription-id extraction improves consistency.Switching to SubscriptionsHelper.extractSubscriptionId keeps SMS/MMS flows aligned and simplifies maintenance.
Also applies to: 40-41
app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt (1)
125-131: parseLongInteger doesn’t compile and computes an incorrect value.Using a Long in
until, thenreducewith an Int accumulator, and readinglength + 1bytes is incorrect. Read exactlylengthbytes into a Long accumulator.Apply this diff:
- private fun parseLongInteger(buffer: ByteBuffer): Long { - val length = parseShortInteger(buffer).toLong() - - return (0 until length + 1).reduce { acc, _ -> - (acc shl 8) + (buffer.get().toLong() and 0xFF) - } - } + private fun parseLongInteger(buffer: ByteBuffer): Long { + val length = parseShortInteger(buffer) // number of octets to read + var result = 0L + repeat(length.coerceAtMost(buffer.remaining())) { + result = (result shl 8) or (buffer.get().toLong() and 0xFF) + } + return result + }Likely an incorrect or invalid review comment.
app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt (1)
22-31: Action and content-type guards look correct.You properly gate on WAP_PUSH_RECEIVED_ACTION and the MMS content type.
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (2)
app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt (2)
69-105: Be resilient to unknown headers and stop once required fields are parsed.Throwing on unknown header types is brittle across carriers and can abort otherwise valid notifications. Also, once we have the required fields for our flow, we can stop parsing to reduce failure surface.
Apply this diff to make parsing tolerant and short-circuit once key headers are captured:
- // Parse headers until buffer is exhausted + // Parse headers until buffer is exhausted; stop once required fields are captured while (buffer.hasRemaining()) { val headerType = buffer.get().toInt() and 0xFF val value = when (headerType) { HEADER_MESSAGE_TYPE -> parseShortInteger(buffer) HEADER_TRANSACTION_ID -> parseTextString(buffer) HEADER_MMS_VERSION -> parseShortInteger(buffer) HEADER_FROM -> parseFrom(buffer) HEADER_SUBJECT -> parseEncodedString(buffer) HEADER_DELIVERY_REPORT -> parseShortInteger(buffer) HEADER_STORED -> parseShortInteger(buffer) HEADER_MESSAGE_CLASS -> parseShortInteger(buffer) HEADER_PRIORITY -> parseShortInteger(buffer) HEADER_MESSAGE_SIZE -> parseLongInteger(buffer) HEADER_EXPIRY -> parseExpiry(buffer) HEADER_REPLY_CHARGING -> parseShortInteger(buffer) HEADER_REPLY_CHARGING_DEADLINE -> parseShortInteger(buffer) HEADER_REPLY_CHARGING_SIZE -> parseLongInteger(buffer) HEADER_REPLY_CHARGING_ID -> parseTextString(buffer) HEADER_DISTRIBUTION_INDICATOR -> parseShortInteger(buffer) // HEADER_ELEMENT_DESCRIPTOR -> parseShortInteger(buffer) HEADER_RECOMMENDED_RETRIEVAL_MODE -> parseShortInteger(buffer) HEADER_RECOMMENDED_RETRIEVAL_MODE_TEXT -> parseEncodedString(buffer) HEADER_APPLIC_ID -> parseTextString(buffer) HEADER_REPLY_APPLIC_ID -> parseTextString(buffer) HEADER_AUX_APPLIC_INFO -> parseTextString(buffer) HEADER_CONTENT_CLASS -> parseContentClass(buffer) HEADER_DRM_CONTENT -> parseShortInteger(buffer) HEADER_REPLACE_ID -> parseTextString(buffer) HEADER_CONTENT_LOCATION -> parseTextString(buffer) HEADER_MESSAGE_ID -> parseTextString(buffer) HEADER_DATE -> parseLongInteger(buffer) - else -> throw IllegalArgumentException("Unknown header type: $headerType") + else -> { + // Be tolerant: skip unknown header values and continue + skipUnknownValue(buffer) + continue + } } headers[headerType] = value + // Short-circuit: we have everything we need for our workflow + if (headers.keys.containsAll(setOf(HEADER_MESSAGE_TYPE, HEADER_TRANSACTION_ID, HEADER_FROM, HEADER_MESSAGE_SIZE))) { + break + } }Add this helper outside the changed range:
private fun skipUnknownValue(buffer: ByteBuffer) { if (!buffer.hasRemaining()) return val mark = buffer.position() val b = buffer.get().toInt() and 0xFF // Heuristic 1: if it looks like a short length (1..64), skip that many bytes if (b in 1..64 && buffer.remaining() >= b) { buffer.position(buffer.position() + b) return } // Heuristic 2: reset and consume until NUL (text-string) or end buffer.position(mark) while (buffer.hasRemaining()) { if (buffer.get() == 0.toByte()) break } }
112-119: Validate required headers and avoid unsafe casts that can crash.Direct casts will throw when carriers omit or vary fields. Guard required ones and provide sane defaults for optional.
Apply this diff to safely construct MNotificationInd:
- return MNotificationInd( - transactionId = headers[HEADER_TRANSACTION_ID] as String, - date = (headers[HEADER_DATE] as? Long)?.let { Date(it * 1000L) }, - from = headers[HEADER_FROM] as String, - subject = headers[HEADER_SUBJECT] as String?, - messageSize = headers[HEADER_MESSAGE_SIZE] as Long, - contentClass = headers[HEADER_CONTENT_CLASS] as ContentClass? - ) + val transactionId = headers[HEADER_TRANSACTION_ID] as? String + ?: throw IllegalArgumentException("Missing transaction-id") + val messageSize = when (val ms = headers[HEADER_MESSAGE_SIZE]) { + is Long -> ms + is Int -> ms.toLong() + is Number -> ms.toLong() + else -> throw IllegalArgumentException("Missing message-size") + } + val fromRaw = headers[HEADER_FROM] as? String + val from = if (fromRaw.isNullOrBlank()) "unknown" else fromRaw + val date = (headers[HEADER_DATE] as? Long)?.let { Date(it * 1000L) } + return MNotificationInd( + transactionId = transactionId, + date = date, + from = from, + subject = headers[HEADER_SUBJECT] as? String, + messageSize = messageSize, + contentClass = headers[HEADER_CONTENT_CLASS] as? ContentClass + )
🧹 Nitpick comments (4)
app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt (1)
70-77: Document 1-based SIM numbering across webhook payloadsVerified that throughout the codebase,
simNumberis treated as a 1-based value:
- The local HTTP API enforces
simNumber >= 1(app/src/main/java/me/capcom/smsgateway/modules/localserver/routes/MessagesRoutes.kt lines 192–197).- The outgoing‐message model’s KDoc specifies “Sim number (from 1 to 3)” (app/src/main/java/me/capcom/smsgateway/modules/messages/data/SendParams.kt lines 9–11).
- Internally, MessagesService.selectSimNumber subtracts 1 from the 1-based input to get a zero-based slot index (app/src/main/java/me/capcom/smsgateway/modules/messages/MessagesService.kt lines 290–293).
This confirms that using
SubscriptionsHelper.getSimSlotIndex(...) + 1in ReceiverService.kt to produce a 1-based SIM number matches the established contract. No breaking change will occur, but to prevent future confusion:• Add a KDoc comment at the
val simNumber = …calculation in ReceiverService.kt (around lines 62–64) stating “// SIM slot index + 1 (1-based slot number)”.
• Update the webhook payload schema (SmsEventPayload and/or swagger.json) to include a description such as “SIM slot number (1-based)”.app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt (2)
138-145: Clamp long-integer width to 8 bytes to avoid silent overflow.Long-integer values beyond 8 octets will overflow a 64-bit accumulator; clamp and skip extras defensively.
Apply this diff:
- private fun parseLongInteger(buffer: ByteBuffer): Long { - val length = parseShortInteger(buffer) // number of octets to read - var result = 0L - repeat(length.coerceAtMost(buffer.remaining())) { - result = (result shl 8) or (buffer.get().toLong() and 0xFF) - } - return result - } + private fun parseLongInteger(buffer: ByteBuffer): Long { + val length = parseShortInteger(buffer) // number of octets to read + var result = 0L + val toRead = minOf(length, 8, buffer.remaining()) + repeat(toRead) { + result = (result shl 8) or (buffer.get().toLong() and 0xFF) + } + // If length > toRead, skip the remaining octets + val leftover = length - toRead + if (leftover > 0 && buffer.remaining() >= leftover) { + buffer.position(buffer.position() + leftover) + } + return result + }
107-111: Optional: consider tolerant validation for message type when short-circuiting earlier.If you adopt early-break parsing, ensure X-Mms-Message-Type is included in the early-break set (as in the proposed diff) so the require() check cannot fail due to ordering differences across carriers.
app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt (1)
5-7: Strengthen the test name and coverage while you’re here.
- Rename simpleTest to parseFrom_parsesAddressPresentToken to reflect intent.
- Add assertions for transactionId and messageSize to catch regressions in header parsing.
If you’d like, I can push an expanded test that also exercises:
- insert-address-token (FROM=129) defaulting to "unknown"
- tolerance to unknown headers
- date parsing from HEADER_DATE
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt(1 hunks)app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt
🔇 Additional comments (4)
app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt (2)
13-13: Import for MMS payload wired correctly.Import matches the new payload type and is used below. No issues.
86-95: Standardize or documentmessageIdformat across SMS and MMS eventsOur recent grep didn’t reveal any in-repo tests or consumers explicitly parsing
messageIdformat, but external integrations or future tests might assume the current formats. To avoid subtle bugs in correlation, please audit any webhook consumers, integration tests, or docs that referencemessageId.• In
ReceiverService.kt, SMS‐received events usemessage.hashCode().toUInt().toString(16)(hex‐encoded hash)
while MMS‐received events usemessage.transactionId(raw transaction ID).
• In
EventsReceiver.kt, outgoing SMS events (SmsSent, etc.) useevent.id, which may be a different ID format (e.g. DB‐generated UUID or numeric ID).Two paths forward:
Document the divergence
Clearly state in the API docs andswagger.jsonthat:
- SMS‐received
messageIdis a hex‐encoded hash- MMS‐received
messageIdis the MMSC transaction ID- SMS‐sent/delivery/failure events use the domain event’s
idUnify under one scheme
• Introduce anEventIdFactory.make(message: InboxMessage): String(or overload for sent events) that produces a stable, deterministic, namespaced ID, e.g.:
sms:<hexHash>mms:<transactionId>sent:<domainId>
• Apply this factory in bothReceiverServiceandEventsReceiver
• Update database entities and DAO methods to use the newmessageIdconsistentlyNext steps:
- Audit external webhook clients and any integration/system tests for assumptions about
messageId.- Decide on documentation vs. unification strategy.
- If standardizing, I can draft the
EventIdFactoryhelper and show the diff for updating both receiver modules.app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt (2)
5-14: Payload shape looks solid and consistent with existing patterns.Extends
MessageEventPayload, captures MMS-specific fields, and keeps nullability where appropriate. Good addition.
11-14: Nit: clarifysizesemantics and documentreceivedAtformat
- In
MmsReceivedPayload.kt, rename→val size: Longto make clear this is a byte count.val messageSizeBytes: Long- For
add a short KDoc noting that it’s serialized in ISO-8601 (perval receivedAt: DateGsonBuilder.configure()inextensions/GsonBuilder.kt), e.g.:/** Timestamp when the message was received, serialized as an ISO-8601 string. */ val receivedAt: DateNo change to the
Datetype is needed since all payloads already usejava.util.Date+ ISO-8601 formatting.
74f09de to
cf9d0fe
Compare
🤖 Pull request artifacts
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt (2)
3-3: Use JUnit assertions instead of Kotlin’s assert (disabled unless -ea).Add the JUnit Assert import:
import org.junit.Test +import org.junit.Assert.assertEquals
163-165: Replace Kotlin runtime assert with assertEquals for reliable failure signaling.- assert(notification.from == "+19162255887/TYPE=PLMN") + assertEquals("+19162255887/TYPE=PLMN", notification.from)
🧹 Nitpick comments (3)
app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt (3)
6-7: Nit: make the test name descriptive.simpleTest doesn’t convey intent. Consider something like parseMNotificationInd_parsesFrom().
- fun simpleTest() { + fun parseMNotificationInd_parsesFrom() {
5-5: Test discovery: prefer default (public) visibility for JUnit4.Kotlin’s internal compiles to public on the JVM, but some tooling/runners can be picky. If you’ve seen any flakiness, drop internal.
-internal class MMSParserTest { +class MMSParserTest {If your CI already runs this test reliably, feel free to ignore.
8-161: Optional: improve payload readability and maintainability.The gigantic inline PDU literal is hard to audit. Consider one of:
- Keep nums inline but wrap via bytesOf as above (already proposed).
- Move the payload to a test fixture file and load it.
- Store as a compact hex string and decode in-test.
Example hex approach (drop-in replacement for the helper):
private fun hexToBytes(hex: String) = hex .replace(Regex("\\s"), "") .chunked(2) .map { it.toInt(16).toByte() } .toByteArray() // usage val data = hexToBytes(""" 8C 82 98 69 64 31 5F 31 35 36 33 39 34 38 38 34 30 00 8D 92 8B 35 38 63 66 63 36 64 61 2D ... 31 """.trimIndent())
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (13)
app/src/main/AndroidManifest.xml(2 hunks)app/src/main/java/me/capcom/smsgateway/helpers/SubscriptionsHelper.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MessagesReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MessageEventPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/SmsEventPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt(1 hunks)app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/MessagesReceiver.kt
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MessageEventPayload.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt
- app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/SmsEventPayload.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt
- app/src/main/AndroidManifest.xml
🔇 Additional comments (2)
app/src/main/java/me/capcom/smsgateway/helpers/SubscriptionsHelper.kt (2)
6-6: LGTM: import is required for the new helper signature
Intentimport is necessary for the added API; all good.
73-87: SubscriptionsHelper.extractSubscriptionId: verified key coverage and null handling• I searched all *.kt files for other extras keys—literal
"slot","phone","simId"—and only found the existing cases: SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,"subscription", and EXTRA_SLOT_INDEX. No additional vendor-specific extras were in use.
• Call sites in MessagesReceiver.kt (lines 28–40) and MmsReceiver.kt (lines 72–80) pass the returned Int? directly into the InboxMessage constructors, whose subscriptionId property is declaredInt?. Nulls flow through safely and there are no unchecked dereferences.
• No occurrences of an INVALID_SUBSCRIPTION_ID constant were found—null is the canonical “no subscription” value.No further changes needed here.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
README.md (2)
92-94: Clarify MMS scope (WAP push vs full content).Given the PR adds a WAP‑push receiver and parses M‑Notification.ind PDUs, say explicitly that MMS support is for incoming MMS notifications (WAP push), not downloading MMS parts/content. This sets correct expectations.
Apply this minimal wording tweak:
- - 🔔 **Real-time incoming message notifications:** Receive instant notifications for incoming SMS and MMS messages via webhook. + - 🔔 **Real-time incoming message notifications:** Receive instant webhooks for incoming SMS and MMS WAP‑push notifications (M‑Notification.ind).
238-239: Name the supported webhook events explicitly.Since MMS webhooks were added, list the concrete event keys to avoid guesswork by integrators.
- Webhooks can be utilized to get notifications of messaging events, such as incoming SMS and MMS messages. + Webhooks can be utilized to get notifications of messaging events, such as incoming SMS and MMS messages (e.g., `sms:received`, `mms:received`).If helpful, I can draft a second, compact example showing registration and a sample
mms:receivedpayload.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
README.md(4 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md
[style] ~156-~156: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...: This permissions are optional. If you want to receive webhooks on incoming MMS messag...
(REP_WANT_TO_VB)
[style] ~156-~156: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... webhooks on incoming MMS messages, you need to grant these permissions. ### Installat...
(REP_NEED_TO_VB)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build release / build-apk
- GitHub Check: Build insecure / build-apk
🔇 Additional comments (1)
README.md (1)
258-258: Webhooks payload ID naming is consistentA sweep of all webhook payload classes and the README example confirms every payload field uses
messageId—there are no instances of a bareidfield in SMS/MMS/status payloads. We can close this concern.• README.md (line 258):
"messageId"
•MessageEventPayload,SmsEventPayload,MmsReceivedPayload, etc.: all definemessageIdin Kotlin data classes
• Noidkeys found in any webhook payload definitions
| - **READ_PHONE_STATE**: This permission is optional. If you want to select the SIM card, you can grant this permission. | ||
| - **READ_SMS**: This permission is optional. If you want to read previous SMS messages, you need to grant this permission. | ||
| - **RECEIVE_SMS**: This permission is optional. If you want to receive webhooks on incoming SMS, you need to grant this permission. | ||
| - **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: This permissions are optional. If you want to receive webhooks on incoming MMS messages, you need to grant these permissions. |
There was a problem hiding this comment.
Fix grammar and reduce repetition in permissions line.
Plural agreement and phrasing need correction in user-facing docs.
- - **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: This permissions are optional. If you want to receive webhooks on incoming MMS messages, you need to grant these permissions.
+ - **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: These permissions are optional. Grant them to receive webhooks for incoming MMS notifications.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: This permissions are optional. If you want to receive webhooks on incoming MMS messages, you need to grant these permissions. | |
| - **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: These permissions are optional. Grant them to receive webhooks for incoming MMS notifications. |
🧰 Tools
🪛 LanguageTool
[style] ~156-~156: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...: This permissions are optional. If you want to receive webhooks on incoming MMS messag...
(REP_WANT_TO_VB)
[style] ~156-~156: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... webhooks on incoming MMS messages, you need to grant these permissions. ### Installat...
(REP_NEED_TO_VB)
🤖 Prompt for AI Agents
In README.md around line 156, the permissions sentence has grammar and
repetition issues; replace "This permissions are optional. If you want to
receive webhooks on incoming MMS messages, you need to grant these permissions."
with a corrected, concise phrasing such as: state that RECEIVE_MMS and
RECEIVE_WAP_PUSH are optional permissions and, if you want webhooks for incoming
MMS, you must grant them — ensure singular/plural agreement and remove redundant
wording so it reads clearly for users.
| "message": "Received SMS text", | ||
| "phoneNumber": "+19162255887", | ||
| "simNumber": 1, | ||
| "receivedAt": "2024-06-07T11:41:31.000+07:00" |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Document SIM numbering semantics (0‑ vs 1‑based).
The example shows "simNumber": 1. Please state whether this is slot index (0‑based per SubscriptionManager) or user‑facing SIM number (1‑based). This avoids off‑by‑one misunderstandings for multi‑SIM devices.
To confirm what the code emits:
If it’s 0‑based, either change the example to 0 or add a note like “simNumber: 0 for SIM slot 1.” If it’s 1‑based, mention “1 = SIM slot 1”. I can update the README once you confirm.
🏁 Script executed:
#!/usr/bin/env bash
# Look for simNumber derivation and types in payloads/services.
rg -nC3 -e '\bsimNumber\b' -e 'SubscriptionManager' -e 'slotIndex' --type kotlinLength of output: 35767
Clarify simNumber as 1-based in README
The simNumber field in our webhook payloads and request bodies is user-facing and 1-based (1 = SIM slot 1). Internally, we convert this to a 0-based slot index (by doing simNumber – 1 in MessagesService.selectSimNumber). To avoid confusion, please update README.md around line 262 to document this. For example:
// Before sending:
// simNumber is 1-based: 1 = SIM slot 1, 2 = SIM slot 2, etc.
{
"simNumber": 1,
"receivedAt": "2024-06-07T11:41:31.000+07:00"
}🤖 Prompt for AI Agents
In README.md around line 262, the documentation doesn't state that simNumber is
user-facing and 1-based; update the README to explicitly note that simNumber is
1-based (1 = SIM slot 1, 2 = SIM slot 2, etc.) and show the example before
sending (as in the comment) and mention that the service converts it internally
to a 0-based index via simNumber - 1 (e.g., a short note after the JSON
example).
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (5)
README.md (2)
259-263: Document simNumber semantics (1‑based) to avoid off‑by‑one confusion.The example shows "simNumber": 1. Please add a short note clarifying whether this is 1‑based (user‑facing) or 0‑based (slot index). Prior guidance indicates it’s 1‑based.
Proposed note to insert right below the JSON example:
+ Note: simNumber is user‑facing and 1‑based: 1 = SIM slot 1, 2 = SIM slot 2, etc. The app converts this to a 0‑based slot index internally.
157-157: Fix grammar and reduce repetition in permissions line.Plural agreement and phrasing need correction in user-facing docs.
Apply:
- - **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: This permissions are optional. If you want to receive webhooks on incoming MMS messages, you need to grant these permissions. + - **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: These permissions are optional. Grant them to receive webhooks for incoming MMS notifications.app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt (2)
33-84: Avoid I/O and parsing on broadcast main thread: wrap with goAsync().logsService.insert() and PDU parsing are I/O/CPU heavy. Doing this on the main broadcast thread risks ANRs and truncated processing. Use goAsync() and finish().
Apply:
- try { - val bundle: Bundle? = intent.extras - if (bundle == null) { - Log.w(TAG, "No extras found in MMS intent") - return - } - - // Extract MMS metadata from the intent - val pdu = bundle.getByteArray("data") ?: bundle.getByteArray("pdu") - - if (pdu == null) { - Log.w(TAG, "No PDU data found in MMS intent") - return - } - - logsService.insert( - LogEntry.Priority.DEBUG, - MODULE_NAME, - "MMS received", - mapOf( - "data" to intent.dataString, - "extras" to bundle.keySet().joinToString(", ") { it }, - "uri" to intent.extras?.getString("uri"), - "header" to intent.extras?.getByteArray("header") - ?.joinToString("") { "%02x".format(it) }, - "pdu" to pdu.joinToString("") { "%02x".format(it) }, - ) - ) - - val mmsNotification = MMSParser.parseMNotificationInd(pdu) - Log.d(TAG, "MMS received from ${mmsNotification.from}") - - val mmsMessage = InboxMessage.Mms( - messageId = mmsNotification.messageId, - transactionId = mmsNotification.transactionId, - subject = mmsNotification.subject, - size = mmsNotification.messageSize, - contentClass = mmsNotification.contentClass?.name, - address = mmsNotification.from.substringBefore('/'), - date = mmsNotification.date ?: Date(), - subscriptionId = SubscriptionsHelper.extractSubscriptionId(context, intent), - ) - - // Process the message using the existing ReceiverService - receiverSvc.process(context, mmsMessage) - - } catch (e: Exception) { - Log.e(TAG, "Error processing MMS", e) - } + val pendingResult = goAsync() + Thread { + try { + val bundle: Bundle? = intent.extras + if (bundle == null) { + Log.w(TAG, "No extras found in MMS intent") + return@Thread + } + + // Extract MMS metadata from the intent + val pdu = bundle.getByteArray("data") ?: bundle.getByteArray("pdu") + if (pdu == null) { + Log.w(TAG, "No PDU data found in MMS intent") + return@Thread + } + + logsService.insert( + LogEntry.Priority.DEBUG, + MODULE_NAME, + "MMS received", + mapOf( + "data" to intent.dataString, + "extras" to bundle.keySet().joinToString(", ") { it }, + "uri" to intent.extras?.getString("uri"), + "header" to intent.extras?.getByteArray("header")?.joinToString("") { "%02x".format(it) }, + "pdu" to pdu.joinToString("") { "%02x".format(it) }, + ) + ) + + val mmsNotification = MMSParser.parseMNotificationInd(pdu) + Log.d(TAG, "MMS received from ${mmsNotification.from}") + + val mmsMessage = InboxMessage.Mms( + messageId = mmsNotification.messageId, + transactionId = mmsNotification.transactionId, + subject = mmsNotification.subject, + size = mmsNotification.messageSize, + contentClass = mmsNotification.contentClass?.name, + address = mmsNotification.from.substringBefore('/'), + date = mmsNotification.date ?: Date(), + subscriptionId = SubscriptionsHelper.extractSubscriptionId(context, intent), + ) + + receiverSvc.process(context, mmsMessage) + } catch (e: Exception) { + Log.e(TAG, "Error processing MMS", e) + } finally { + pendingResult.finish() + } + }.start()
86-88: Compile error: MODULE_NAME is undefined.logsService.insert() references MODULE_NAME which isn’t declared in this file.
Apply:
companion object { private const val TAG = "MmsReceiver" + private const val MODULE_NAME = "receiver:mms" }app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt (1)
124-134: Fix FROM parsing per WSP: use value‑length, handle insert‑address‑token, and avoid dropping an extra byte.Current logic uses parseShortInteger for the From-value length and truncates one byte; this corrupts the stream and address.
Apply:
- private fun parseFrom(buffer: ByteBuffer): String { - val length = parseShortInteger(buffer) - if (length <= 0 || buffer.remaining() < length) return "" - val addressType = parseShortInteger(buffer) // 128 == address-present - require(addressType == 128) { "Unexpected address type: $addressType" } - - val toRead = (length - 1).coerceAtMost(buffer.remaining()) - val bytes = ByteArray(toRead) - buffer.get(bytes) - return String(bytes.copyOfRange(0, toRead - 1), Charsets.UTF_8) - } + private fun parseFrom(buffer: ByteBuffer): String { + val length = parseValueLength(buffer) // WSP From-value length + if (length <= 0) return "" + if (buffer.remaining() < length) { + // Truncated value — skip what remains and bail out + val toEnd = buffer.remaining() + buffer.position(buffer.position() + toEnd) + return "" + } + val addressType = parseShortInteger(buffer) // 128 = address-present, 129 = insert-address-token + if (addressType == 129) return "unknown" + require(addressType == 128) { "Unexpected address type: $addressType" } + + val toRead = (length - 1).coerceAtMost(buffer.remaining()) + val bytes = ByteArray(toRead) + buffer.get(bytes) + // Trim trailing NUL if present + val end = bytes.indexOf(0) + val take = if (end >= 0) end else bytes.size + return String(bytes, 0, take, Charsets.UTF_8) + }
🧹 Nitpick comments (4)
README.md (2)
92-95: Nice additions; one tiny wording polish for readability."using the same webhook system used..." repeats "used". Consider this minor rephrase.
Apply:
- - 📖 **Read received messages:** Access [previously received messages](./features/reading-messages.md) using the same webhook system used for real-time notifications. + - 📖 **Read received messages:** Access [previously received messages](./features/reading-messages.md) using the same webhook system as real-time notifications.
239-239: Tighten the sentence and add an MMS example payload for completeness.The line reads fine but can be more direct. Also, since MMS webhooks are a headline feature in this PR, include a minimal mms:received example alongside sms:received.
Apply:
- Webhooks can be utilized to get notifications of messaging events, such as incoming SMS and MMS messages. + Use webhooks to receive notifications for messaging events (incoming SMS and MMS).Suggested MMS example to add right after the SMS example block:
{ "event": "mms:received", "payload": { "messageId": "msg_6da5b0...", "transactionId": "T123456789", "phoneNumber": "+19162255887", "simNumber": 1, "subject": "Hello from MMS", "size": 34567, "contentClass": "IMAGE_RICH", "receivedAt": "2024-06-07T11:41:31.000+07:00" } }app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt (1)
48-61: Guard detailed PDU logging or make it opt‑in to avoid PII-heavy logs.Hex-logging the full WAP PDU can expose sender/recipient identifiers in logs. Consider gating behind a debug flag or redacting sensitive fields.
If you have a debug build flag, wrap logsService.insert(...) with it, or add a user setting (e.g., “Verbose radio logs”) to control this.
app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt (1)
159-172: Encoded string handling is minimal; consider respecting charset if present.The current implementation largely ignores the optional charset/token aspect of Encoded-String-Value. This is acceptable for a baseline, but carriers sometimes use non-UTF-8.
If you need broader compatibility, extend parseEncodedString to parse and honor the charset when lenOrToken < 32 (value-length with well-known-charset), falling back to UTF-8 when absent.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
README.md(4 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt
🧰 Additional context used
🪛 LanguageTool
README.md
[grammar] ~94-~94: There might be a mistake here.
Context: ...system used for real-time notifications. 🔒 Security and Privacy: - 🔐 **End-to-e...
(QB_NEW_EN)
[style] ~157-~157: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...: This permissions are optional. If you want to receive webhooks on incoming MMS messag...
(REP_WANT_TO_VB)
[style] ~157-~157: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... webhooks on incoming MMS messages, you need to grant these permissions. ### Installat...
(REP_NEED_TO_VB)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build release / build-apk
- GitHub Check: Build insecure / build-apk
🔇 Additional comments (1)
app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt (1)
22-31: Good early guards for action and MIME type.The WAP_PUSH_RECEIVED_ACTION and MMS content-type checks are correct and help avoid unnecessary work.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
README.md (2)
157-157: Fix grammar and reduce repetition in permissions line.Plural agreement and phrasing in user-facing docs.
- **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: This permissions are optional. If you want to receive webhooks on incoming MMS messages, you need to grant these permissions. + **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: These permissions are optional. Grant them to receive webhooks for incoming MMS notifications.
255-266: Document messageId semantics and clarify SIM numbering (1‑based).Add a concise note after the example to avoid ambiguity for clients parsing payloads. The SIM note mirrors prior guidance.
{ "event": "sms:received", "payload": { "messageId": "msg_12345abcde", "message": "Received SMS text", "phoneNumber": "+19162255887", "simNumber": 1, "receivedAt": "2024-06-07T11:41:31.000+07:00" } } ``` + +Note: +- simNumber is user‑facing and 1‑based (1 = SIM slot 1, 2 = SIM slot 2, etc.). +- messageId is an opaque identifier for the message; treat it as a stable string identifier in your system.Follow‑up:
- Please confirm whether messageId is unique per device or globally so we can add that detail here.
🧹 Nitpick comments (2)
README.md (2)
92-95: Tighten phrasing and maintain parallelism in new feature bullets.Small wording tweaks improve clarity and fix the grammar flagged by the tool.
-- 🔔 **Real-time incoming message notifications:** Receive instant notifications for incoming SMS and MMS messages via webhook. -- 📱 **Multimedia message support:** Handle MMS messages through dedicated webhooks. -- 📖 **Read received messages:** Access [previously received messages](https://docs.sms-gate.app/features/reading-messages/) using the same webhook system as real-time notifications. +- 🔔 **Real-time incoming message notifications:** Receive instant SMS and MMS notifications via webhooks. +- 📱 **Multimedia message support:** Handle MMS via dedicated webhooks. +- 📖 **Read received messages:** Access [previously received messages](https://docs.sms-gate.app/features/reading-messages/) via the same webhooks used for real-time notifications.
239-241: Name the event types explicitly.Call out the concrete event keys so users know what to register for.
-Use webhooks to receive notifications for messaging events (e.g., incoming SMS and MMS). +Use webhooks to receive notifications for messaging events (e.g., incoming SMS and MMS) via event types such as "sms:received" and "mms:received".If helpful, I can add a short MMS registration example alongside the SMS one.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
README.md(4 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md
[grammar] ~94-~94: There might be a mistake here.
Context: ...bhook system as real-time notifications. 🔒 Security and Privacy: - 🔐 **End-to-e...
(QB_NEW_EN)
[style] ~157-~157: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...: This permissions are optional. If you want to receive webhooks on incoming MMS messag...
(REP_WANT_TO_VB)
[style] ~157-~157: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... webhooks on incoming MMS messages, you need to grant these permissions. ### Installat...
(REP_NEED_TO_VB)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build release / build-apk
- GitHub Check: Build insecure / build-apk
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
README.md (2)
156-156: Fix grammar and reduce repetition in permissions line.This is a user-facing string; tighten wording and correct plural agreement.
- **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: This permissions are optional. If you want to receive webhooks on incoming MMS messages, you need to grant these permissions. + **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: These permissions are optional. Grant them to receive webhooks for incoming MMS notifications.
261-262: Document simNumber semantics (1‑based) next to the example.The example shows
"simNumber": 1, but the README doesn’t state whether this is 0‑ or 1‑based. Prior discussion suggests it’s user‑facing and 1‑based (1 = SIM slot 1). Please add a one‑line note to avoid off‑by‑one confusion.Suggested note to insert immediately below the JSON example:
- simNumber is user‑facing and 1‑based: 1 = SIM slot 1, 2 = SIM slot 2, etc. (Internally converted to a 0‑based slot index.)
🧹 Nitpick comments (3)
README.md (3)
92-94: Clarify SMS/MMS phrasing; verify “Read received messages” claim.
- Line 92 looks good but consider naming the concrete events for discoverability.
- Line 93 could mislead: you don’t typically “read previous messages via webhooks” (webhooks are push). If documentation really provides a way to retrieve previously received items through the same webhook pipeline, please confirm; otherwise, rephrase to reflect how to access historical messages (e.g., server logs or an API).
Proposed edit:
- 🔔 **Real-time incoming message notifications:** Receive instant SMS and MMS notifications via webhooks. - 📖 **Read received messages:** Access [previously received messages](https://docs.sms-gate.app/features/reading-messages/) via the same webhooks used for real-time notifications. + 🔔 **Real-time incoming message notifications:** Receive instant notifications via webhooks (events such as `sms:received` and `mms:received`). + 📖 **Read received messages:** See [Reading Messages](https://docs.sms-gate.app/features/reading-messages/) for how to access previously received messages (e.g., from your webhook receiver’s storage or the provided API).
238-238: Name the webhook events explicitly.Spell out the event names to help readers wire the correct value into the registration payload.
-Use webhooks to receive notifications for messaging events (e.g., incoming SMS and MMS). +Use webhooks to receive notifications for messaging events such as incoming SMS (`sms:received`) and MMS (`mms:received`).Add an auxiliary MMS registration example (no diff needed):
curl -X POST -u <username>:<password> \ -H "Content-Type: application/json" \ -d '{ "id": "unique-id-mms", "url": "https://webhook.site/<your-uuid>", "event": "mms:received" }' \ http://<device_local_ip>:8080/webhooks
258-258: Good addition: messageId in webhook payload.Including a stable messageId improves idempotency and deduplication on the receiver side. Consider adding a short note after the JSON block specifying its uniqueness and lifetime (e.g., unique per device; persists in logs for N days), if applicable.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
README.md(4 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md
[grammar] ~93-~93: There might be a mistake here.
Context: ...bhooks used for real-time notifications. 🔒 Security and Privacy: - 🔐 **End-to-e...
(QB_NEW_EN)
[style] ~156-~156: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...: This permissions are optional. If you want to receive webhooks on incoming MMS messag...
(REP_WANT_TO_VB)
[style] ~156-~156: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... webhooks on incoming MMS messages, you need to grant these permissions. ### Installat...
(REP_NEED_TO_VB)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build release / build-apk
- GitHub Check: Build insecure / build-apk
b90cb30 to
1f07ad0
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (7)
app/src/main/java/me/capcom/smsgateway/helpers/SubscriptionsHelper.kt (1)
73-87: Avoid ClassCastException and INVALID_SUBSCRIPTION_ID leakage in extractSubscriptionId.
Using Bundle.getInt(...) will crash if OEMs put Longs in extras, and returning -1 as a valid subId cascades bad state. Parse extras via safe casts and filter invalid values; resolve slotIndex only after conversion.Apply this diff:
- @SuppressLint("InlinedApi") - fun extractSubscriptionId(context: Context, intent: Intent): Int? { - return when { - intent.extras?.containsKey(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX) == true -> intent.extras?.getInt( - SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX - ) - - intent.extras?.containsKey("subscription") == true -> intent.extras?.getInt("subscription") - intent.extras?.containsKey(SubscriptionManager.EXTRA_SLOT_INDEX) == true -> intent.extras?.getInt( - SubscriptionManager.EXTRA_SLOT_INDEX - )?.let { getSubscriptionId(context, it) } - - else -> null - } - } + @SuppressLint("InlinedApi") + fun extractSubscriptionId(context: Context, intent: Intent): Int? { + val extras = intent.extras ?: return null + + fun anyToInt(v: Any?): Int? = when (v) { + is Int -> v + is Long -> if (v in Int.MIN_VALUE..Int.MAX_VALUE) v.toInt() else null + is Number -> v.toInt() + else -> null + } + + anyToInt(extras.get(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX))?.let { subId -> + if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) return subId + } + anyToInt(extras.get(LEGACY_EXTRA_SUBSCRIPTION))?.let { subId -> + if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) return subId + } + anyToInt(extras.get(SubscriptionManager.EXTRA_SLOT_INDEX))?.let { slotIndex -> + return getSubscriptionId(context, slotIndex) + } + return null + }Add this helper constant (outside the range above):
private const val LEGACY_EXTRA_SUBSCRIPTION = "subscription"I can add unit tests covering Int/Long extras and INVALID_SUBSCRIPTION_ID handling; say the word and I’ll push a patch.
README.md (2)
258-263: Document simNumber semantics (1-based) in the example.
To avoid off‑by‑one confusion on multi‑SIM devices, clarify that simNumber is user‑facing and 1‑based.Apply this diff to annotate the example:
"phoneNumber": "+19162255887", - "simNumber": 1, + // simNumber is 1-based: 1 = SIM slot 1, 2 = SIM slot 2, ... + "simNumber": 1, "receivedAt": "2024-06-07T11:41:31.000+07:00"
156-156: Fix grammar and tighten phrasing for MMS permissions.
User-facing docs; please correct plurality and reduce repetition.Apply this diff:
- - **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: This permissions are optional. If you want to receive webhooks on incoming MMS messages, you need to grant these permissions. + - **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: These permissions are optional. Grant them to receive webhooks for incoming MMS notifications.app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt (1)
62-68: Guard against invalid slot index to avoid emitting simNumber=0.
getSimSlotIndex can return -1; adding 1 yields an invalid 0 for consumers. Guard before +1.Apply this diff:
- val simNumber = message.subscriptionId?.let { - SubscriptionsHelper.getSimSlotIndex( - context, - it - ) - }?.let { it + 1 } + val simNumber = message.subscriptionId + ?.let { SubscriptionsHelper.getSimSlotIndex(context, it) } + ?.takeIf { it >= 0 } + ?.plus(1)Optionally, extract into a small helper to keep this consistent across call sites.
app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt (3)
70-106: Don’t abort on unknown headers; skip them and optionally stop once required fields are parsedThrowing on vendor/unknown headers can drop notifications from some carriers. Also, you can exit the loop once MESSAGE_TYPE, TRANSACTION_ID, and MESSAGE_SIZE are parsed.
Apply:
- // Parse headers until buffer is exhausted + // Parse headers until buffer is exhausted; be liberal in what we accept while (buffer.hasRemaining()) { val headerType = buffer.get().toInt() and 0xFF val value = when (headerType) { HEADER_MESSAGE_TYPE -> parseShortInteger(buffer) HEADER_TRANSACTION_ID -> parseTextString(buffer) HEADER_MMS_VERSION -> parseShortInteger(buffer) HEADER_FROM -> parseFrom(buffer) HEADER_SUBJECT -> parseEncodedString(buffer) HEADER_DELIVERY_REPORT -> parseShortInteger(buffer) HEADER_STORED -> parseShortInteger(buffer) HEADER_MESSAGE_CLASS -> parseShortInteger(buffer) HEADER_PRIORITY -> parseShortInteger(buffer) HEADER_MESSAGE_SIZE -> parseLongInteger(buffer) HEADER_EXPIRY -> parseExpiry(buffer) HEADER_REPLY_CHARGING -> parseShortInteger(buffer) HEADER_REPLY_CHARGING_DEADLINE -> parseShortInteger(buffer) HEADER_REPLY_CHARGING_SIZE -> parseLongInteger(buffer) HEADER_REPLY_CHARGING_ID -> parseTextString(buffer) HEADER_DISTRIBUTION_INDICATOR -> parseShortInteger(buffer) // HEADER_ELEMENT_DESCRIPTOR -> parseShortInteger(buffer) HEADER_RECOMMENDED_RETRIEVAL_MODE -> parseShortInteger(buffer) HEADER_RECOMMENDED_RETRIEVAL_MODE_TEXT -> parseEncodedString(buffer) HEADER_APPLIC_ID -> parseTextString(buffer) HEADER_REPLY_APPLIC_ID -> parseTextString(buffer) HEADER_AUX_APPLIC_INFO -> parseTextString(buffer) HEADER_CONTENT_CLASS -> parseContentClass(buffer) HEADER_DRM_CONTENT -> parseShortInteger(buffer) HEADER_REPLACE_ID -> parseTextString(buffer) HEADER_CONTENT_LOCATION -> parseTextString(buffer) HEADER_MESSAGE_ID -> parseTextString(buffer) HEADER_DATE -> parseLongInteger(buffer) - - else -> throw IllegalArgumentException("Unknown header type: $headerType") + else -> { + skipUnknownValue(buffer) + continue + } } headers[headerType] = value + // Optional: bail out once we have the required fields for our workflow + if (headers.keys.containsAll(setOf(HEADER_MESSAGE_TYPE, HEADER_TRANSACTION_ID, HEADER_MESSAGE_SIZE))) { + break + } }Add this helper (outside this range):
// Helper to advance past an unknown value without desynchronizing the stream. private fun skipUnknownValue(buffer: ByteBuffer) { if (!buffer.hasRemaining()) return val mark = buffer.position() val b = buffer.get().toInt() and 0xFF when { b in 1..64 && buffer.remaining() >= b -> buffer.position(buffer.position() + b) b == LENGTH_QUOTE -> { val len = parseUnsignedInt(buffer) val toSkip = minOf(len, buffer.remaining()) buffer.position(buffer.position() + toSkip) } else -> { // Fallback: consume until NUL while (buffer.hasRemaining()) { if (buffer.get() == 0.toByte()) break } } } }
113-121: Validate required fields and use safe casts; fall back messageId to transactionIdDirect casts will crash on missing/variant headers. Enforce required fields and keep the rest resilient.
Apply:
- return MNotificationInd( - messageId = headers[HEADER_MESSAGE_ID] as String, - transactionId = headers[HEADER_TRANSACTION_ID] as String, - date = (headers[HEADER_DATE] as? Long)?.let { Date(it * 1000L) }, - from = headers[HEADER_FROM] as String, - subject = headers[HEADER_SUBJECT] as String?, - messageSize = headers[HEADER_MESSAGE_SIZE] as Long, - contentClass = headers[HEADER_CONTENT_CLASS] as ContentClass? - ) + val transactionId = headers[HEADER_TRANSACTION_ID] as? String + ?: throw IllegalArgumentException("Missing transaction-id") + val messageSize = headers[HEADER_MESSAGE_SIZE] as? Long + ?: throw IllegalArgumentException("Missing message-size") + val from = (headers[HEADER_FROM] as? String) ?: "unknown" + val messageId = (headers[HEADER_MESSAGE_ID] as? String) ?: transactionId + val date = (headers[HEADER_DATE] as? Long)?.let { Date(it * 1000L) } + return MNotificationInd( + messageId = messageId, + transactionId = transactionId, + date = date, + from = from, + subject = headers[HEADER_SUBJECT] as String?, + messageSize = messageSize, + contentClass = headers[HEADER_CONTENT_CLASS] as ContentClass? + )
124-134: Fix From parsing: use value-length, handle insert-address-token, and avoid buffer desyncSpec requires a value-length then an address-type. Current code reads a short-integer length, drops an extra byte, and doesn’t handle insert-address-token (129).
Apply:
- private fun parseFrom(buffer: ByteBuffer): String { - val length = parseShortInteger(buffer) - if (length <= 0 || buffer.remaining() < length) return "" - val addressType = parseShortInteger(buffer) // 128 == address-present - require(addressType == 128) { "Unexpected address type: $addressType" } - - val toRead = (length - 1).coerceAtMost(buffer.remaining()) - val bytes = ByteArray(toRead) - buffer.get(bytes) - return String(bytes.copyOfRange(0, toRead - 1), Charsets.UTF_8) - } + private fun parseFrom(buffer: ByteBuffer): String { + val length = parseValueLength(buffer) + if (length <= 0) return "" + if (buffer.remaining() < length) { + // Truncated value: consume what remains and bail out + buffer.position(buffer.limit()) + return "" + } + val addressType = parseShortInteger(buffer) // 128=address-present, 129=insert-address-token + if (addressType == 129) return "unknown" + require(addressType == 128) { "Unexpected address type: $addressType" } + + val toRead = (length - 1).coerceAtMost(buffer.remaining()) + val bytes = ByteArray(toRead) + buffer.get(bytes) + // Trim trailing NUL if present + val end = bytes.indexOf(0) + val take = if (end >= 0) end else bytes.size + return String(bytes, 0, take, Charsets.UTF_8) + }
🧹 Nitpick comments (5)
README.md (1)
238-238: Add an MMS payload example for completeness.
Since MMS is supported, include a compact mms:received example next to sms:received to reduce guesswork.Proposed snippet to add after the SMS example:
{ "event": "mms:received", "payload": { "messageId": "mms_12345abcde", "transactionId": "T12345", "subject": "Photos", "size": 45678, "contentClass": "PERSONAL", "phoneNumber": "+19162255887", "simNumber": 1, "receivedAt": "2024-06-07T11:41:31.000+07:00" } }app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt (1)
70-77: messageId semantics differ between SMS and MMS; consider unifying or documenting.
SMS uses a hash of the object; MMS uses a protocol/parsed id. This can surprise webhook consumers.Would you prefer:
- Unify on stable provider IDs (e.g., include Telephony.Sms._ID in InboxMessage.Text and use it), or
- Keep as-is and explicitly document the difference in README “Webhooks”?
I can patch either approach.app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt (3)
159-172: Encoded-string parsing is incomplete; consume value-length and optional charset before reading textCurrent implementation peeks and conditionally consumes, then defers to parseTextString, which can leave bytes unread or misinterpret charset. Suggest a minimal, resilient approach.
Apply:
- private fun parseEncodedString(buffer: ByteBuffer): String { - if (!buffer.hasRemaining()) return "" - val mark = buffer.position() - val lenOrToken = buffer.get().toInt() and 0xFF - - buffer.position(mark) - - if (lenOrToken < 32) { - parseValueLength(buffer) - parseShortInteger(buffer) - } - - return parseTextString(buffer) - } + private fun parseEncodedString(buffer: ByteBuffer): String { + if (!buffer.hasRemaining()) return "" + val mark = buffer.position() + val first = buffer.get().toInt() and 0xFF + buffer.position(mark) + return if (first < 32) { + val len = parseValueLength(buffer) + if (len <= 0 || !buffer.hasRemaining()) return "" + // Optional charset/token byte — consume one, ignore value here + if (buffer.hasRemaining()) buffer.get() + val toRead = minOf(len - 1, buffer.remaining()) + val bytes = ByteArray(toRead) + buffer.get(bytes) + val end = bytes.indexOf(0) + val take = if (end >= 0) end else bytes.size + String(bytes, 0, take, Charsets.UTF_8) + } else { + parseTextString(buffer) + } + }
193-202: Be defensive in value-length parsing to avoid hard crashes on malformed PDUsThrowing a RuntimeException here will abort parsing; returning 0 (and letting callers handle) or throwing IllegalArgumentException with context is safer.
Apply:
- throw RuntimeException("Value length > LENGTH_QUOTE!") + // Malformed or unsupported value-length; let callers decide what to do. + return 0Optionally include the header context in an IllegalArgumentException for easier diagnostics instead of returning 0.
1-218: Add unit tests for edge cases: unknown headers, insert-address-token, and truncated valuesTo lock in the resilience, add tests that:
- Include a vendor-specific header before/after required ones and ensure parsing succeeds.
- Exercise From with insert-address-token (129) and assert “unknown”.
- Provide a truncated From value-length and assert we don’t crash.
I can sketch the test vectors if helpful.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (14)
README.md(4 hunks)app/src/main/AndroidManifest.xml(2 hunks)app/src/main/java/me/capcom/smsgateway/helpers/SubscriptionsHelper.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MessagesReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MessageEventPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/SmsEventPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt(1 hunks)app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (7)
- app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/MessagesReceiver.kt
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MessageEventPayload.kt
- app/src/main/AndroidManifest.xml
- app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt
🧰 Additional context used
🪛 LanguageTool
README.md
[grammar] ~93-~93: There might be a mistake here.
Context: ...bhooks used for real-time notifications. 🔒 Security and Privacy: - 🔐 **End-to-e...
(QB_NEW_EN)
[style] ~156-~156: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...: This permissions are optional. If you want to receive webhooks on incoming MMS messag...
(REP_WANT_TO_VB)
[style] ~156-~156: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... webhooks on incoming MMS messages, you need to grant these permissions. ### Installat...
(REP_NEED_TO_VB)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build release / build-apk
- GitHub Check: Build insecure / build-apk
🔇 Additional comments (8)
app/src/main/java/me/capcom/smsgateway/helpers/SubscriptionsHelper.kt (1)
6-6: Import looks correct.
Intent import is required for the new API surface.app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt (1)
340-340: Good call adding RECEIVE_MMS to runtime requests.
RECEIVE_MMS is dangerous and needs runtime grant. Not requesting RECEIVE_WAP_PUSH here is fine (normal permission; manifest-only).README.md (1)
92-94: Feature line reads well.
Clear that webhooks now cover SMS and MMS.app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt (2)
13-13: Import OK.
New payload type usage requires this import.
86-95: MMS payload construction looks correct.
Fields line up with InboxMessage.Mms and the new MmsReceivedPayload; nullable subject/contentClass handled as-is.app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt (2)
29-31: Good fix: use contentHashCode for byte arrays.
Prevents collisions and matches equals(Arrays.equals(...)).
33-49: Equality/hash for MMS based solely on transactionId is sensible.
This aligns identity with the MMS protocol and avoids coupling to address/date.app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/SmsEventPayload.kt (1)
6-9: Based on the lack of serialization annotations on SmsEventPayload/MessageEventPayload and the use of Gson in SendWebhookWorker (which reflects on inheritedvalproperties), constructors delegating to the base class will still producemessageId,phoneNumber, andsimNumberin the JSON payload. Gson’s default behavior includes inherited fields unless explicitly excluded. No callers rely on property reflection within SmsEventPayload itself—payloads are passed as plain objects into the worker.Please verify:
- That SendWebhookWorker indeed uses
GsonBuilder().create().toJson(payload)(reflecting inheritedvalprops).- There are no custom exclusion strategies or
@Exposeannotations limiting inherited fields.- Your downstream webhook consumers expect these three fields in the same JSON path.
If all checks pass, this consolidation is safe.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt (1)
62-68: Don’t emit simNumber=0 when slot index is -1Guard negative indices before adding 1; otherwise unknown slot (-1) becomes 0.
- val simNumber = message.subscriptionId?.let { - SubscriptionsHelper.getSimSlotIndex( - context, - it - ) - }?.let { it + 1 } + val simNumber = message.subscriptionId + ?.let { SubscriptionsHelper.getSimSlotIndex(context, it) } + ?.takeIf { it >= 0 } + ?.plus(1)Optional: extract a resolveSimNumber(context, subscriptionId) helper to reuse across call sites.
🧹 Nitpick comments (2)
app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt (1)
33-49: MMS identity by transactionId: good; add minor hardeningEquality/hash now match the intended identity. Consider also treating blank messageId as absent when constructing payloads.
If you want to enforce immutability for Data equality/hash (to avoid mutated arrays changing identity), clone the byte[] at construction time:
class Data(raw: ByteArray?, address: String, date: Date, subscriptionId: Int?) : InboxMessage(address, date, subscriptionId) { val data: ByteArray? = raw?.clone() ... }app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt (1)
86-95: MMS payload looks correct; handle blank messageIdUse isNullOrBlank to avoid forwarding empty IDs from parser.
- messageId = message.messageId ?: message.transactionId, + messageId = message.messageId?.takeUnless { it.isBlank() } ?: message.transactionId,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (4)
app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt(1 hunks)app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt
- app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build insecure / build-apk
- GitHub Check: Build release / build-apk
🔇 Additional comments (2)
app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt (1)
29-31: Correct hash for ByteArray; nice null-safetySwitching to contentHashCode aligns hashCode with equals for Data. LGTM.
app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt (1)
74-74: Centralized simNumber usageGood reuse after centralizing the mapping.
Also applies to: 82-82
e01c57b to
e26468a
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt (1)
61-66: Fix NPE risk in base hashCode when subscriptionId is null.subscriptionId is nullable; calling hashCode() directly can crash in hash-based collections.
Apply:
- result = 31 * result + subscriptionId.hashCode() + result = 31 * result + (subscriptionId?.hashCode() ?: 0)
♻️ Duplicate comments (7)
app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt (2)
110-123: Validate required headers and use safe casts to avoid crashes.Direct casts can throw when carriers omit fields. Require transactionId/messageSize; default from; tolerate absent messageId.
Apply:
- return MNotificationInd( - messageId = headers[HEADER_MESSAGE_ID] as String?, - transactionId = headers[HEADER_TRANSACTION_ID] as String, - date = (headers[HEADER_DATE] as? Long)?.let { Date(it * 1000L) }, - from = headers[HEADER_FROM] as String, - subject = headers[HEADER_SUBJECT] as String?, - messageSize = headers[HEADER_MESSAGE_SIZE] as Long, - contentClass = headers[HEADER_CONTENT_CLASS] as ContentClass? - ) + val transactionId = headers[HEADER_TRANSACTION_ID] as? String + ?: throw IllegalArgumentException("Missing transaction-id") + val from = (headers[HEADER_FROM] as? String) ?: "unknown" + val messageSize = headers[HEADER_MESSAGE_SIZE] as? Long + ?: throw IllegalArgumentException("Missing message-size") + val messageId = headers[HEADER_MESSAGE_ID] as? String + return MNotificationInd( + messageId = messageId, + transactionId = transactionId, + date = (headers[HEADER_DATE] as? Long)?.let { Date(it * 1000L) }, + from = from, + subject = headers[HEADER_SUBJECT] as String?, + messageSize = messageSize, + contentClass = headers[HEADER_CONTENT_CLASS] as ContentClass? + )
126-134: Fix FROM parsing: use value-length, handle insert-address-token, and fully consume bytes.Current code uses short-integer for length and may desync the stream or misread the address.
Apply:
- private fun parseFrom(buffer: ByteBuffer): String { - val length = parseShortInteger(buffer) - if (length <= 0 || buffer.remaining() < length) return "" - - val addressType = parseShortInteger(buffer) // 128 == address-present - require(addressType == FROM_ADDRESS_PRESENT_TOKEN) { "Unexpected address type: $addressType" } - - return parseEncodedString(buffer) - } + private fun parseFrom(buffer: ByteBuffer): String { + val length = parseValueLength(buffer) + if (length <= 0) return "" + if (buffer.remaining() < length) { + buffer.position(buffer.limit()) // truncated; bail safely + return "" + } + val addressType = parseShortInteger(buffer) // 128=address-present, 129=insert-address-token + if (addressType == 129) { // insert-address-token + val toSkip = minOf(length - 1, buffer.remaining()) + buffer.position(buffer.position() + toSkip) + return "unknown" + } + require(addressType == FROM_ADDRESS_PRESENT_TOKEN) { "Unexpected address type: $addressType" } + val toRead = (length - 1).coerceAtMost(buffer.remaining()) + val bytes = ByteArray(toRead) + buffer.get(bytes) + val end = bytes.indexOf(0) + val take = if (end >= 0) end else bytes.size + return String(bytes, 0, take, Charsets.UTF_8) + }app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt (3)
3-3: Import JUnit assertions for reliable failures.Use Assert methods instead of Kotlin’s assert.
Apply:
import org.junit.Test +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue
5-5: Add helper to avoid Byte vs Int literal mismatch.Prevents compile errors in byte array literals.
Apply:
-internal class MMSParserTest { +private fun bytesOf(vararg nums: Number) = ByteArray(nums.size) { i -> nums[i].toInt().toByte() } + +internal class MMSParserTest {
71-74: Replace Kotlin assert with JUnit assertTrue.Ensures tests fail under default runners.
Apply:
- val notification = MMSParser.parseMNotificationInd(hexToBytes(it)) - assert(notification.from.contains("19162255887")) + val notification = MMSParser.parseMNotificationInd(hexToBytes(it)) + assertTrue(notification.from.contains("19162255887"))README.md (2)
258-262: Clarify simNumber semantics (1‑based).Avoid off‑by‑one mistakes for multi‑SIM devices.
Apply (add note below the JSON example):
"payload": { "messageId": "msg_12345abcde", "message": "Received SMS text", "phoneNumber": "+19162255887", "simNumber": 1, "receivedAt": "2024-06-07T11:41:31.000+07:00" } } } } + +Note: simNumber is user-facing and 1-based (1 = SIM slot 1).
156-156: Grammar: plural agreement and brevity.Fix user-facing docs.
Apply:
-- **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: This permissions are optional. If you want to receive webhooks on incoming MMS messages, you need to grant these permissions. +- **RECEIVE_MMS**, **RECEIVE_WAP_PUSH**: These permissions are optional. Grant them to receive webhooks for incoming MMS notifications.
🧹 Nitpick comments (1)
README.md (1)
238-238: Document MMS webhook event explicitly.You mention MMS, but only show sms:received. Add mms:received example or enumerate supported events.
Apply:
-Use webhooks to receive notifications for messaging events (e.g., incoming SMS and MMS). +Use webhooks to receive notifications for messaging events (e.g., incoming SMS and MMS). Supported events include: `sms:received`, `mms:received`.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (14)
README.md(4 hunks)app/src/main/AndroidManifest.xml(2 hunks)app/src/main/java/me/capcom/smsgateway/helpers/SubscriptionsHelper.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MessagesReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt(2 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MessageEventPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/SmsEventPayload.kt(1 hunks)app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt(1 hunks)app/src/test/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParserTest.kt(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MessageEventPayload.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/MessagesReceiver.kt
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/MmsReceivedPayload.kt
- app/src/main/java/me/capcom/smsgateway/helpers/SubscriptionsHelper.kt
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/domain/WebHookEvent.kt
- app/src/main/java/me/capcom/smsgateway/modules/webhooks/payload/SmsEventPayload.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/MmsReceiver.kt
- app/src/main/AndroidManifest.xml
- app/src/main/java/me/capcom/smsgateway/ui/HomeFragment.kt
- app/src/main/java/me/capcom/smsgateway/modules/receiver/ReceiverService.kt
🧰 Additional context used
🪛 LanguageTool
README.md
[grammar] ~93-~93: There might be a mistake here.
Context: ...bhooks used for real-time notifications. 🔒 Security and Privacy: - 🔐 **End-to-...
(QB_NEW_EN)
[style] ~156-~156: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...: This permissions are optional. If you want to receive webhooks on incoming MMS messag...
(REP_WANT_TO_VB)
[style] ~156-~156: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... webhooks on incoming MMS messages, you need to grant these permissions. ### Installat...
(REP_NEED_TO_VB)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build insecure / build-apk
- GitHub Check: Build release / build-apk
🔇 Additional comments (2)
app/src/main/java/me/capcom/smsgateway/modules/receiver/data/InboxMessage.kt (2)
29-31: Correct: null-safe hash for ByteArray content.Switching to contentHashCode with a null guard is the right call.
33-49: MMS identity by transactionId looks good—confirm uniqueness across carriers.Equality/hashCode solely on transactionId simplifies de-duplication. Please confirm transactionId is stable/unique across retries and carriers you target; if not, consider also including address.
| // Parse headers until buffer is exhausted | ||
| while (buffer.hasRemaining()) { | ||
| val headerType = buffer.get().toInt() and 0xFF | ||
| val value = when (headerType) { | ||
| HEADER_MESSAGE_TYPE -> parseShortInteger(buffer) | ||
| HEADER_TRANSACTION_ID -> parseTextString(buffer) | ||
| HEADER_MMS_VERSION -> parseShortInteger(buffer) | ||
| HEADER_FROM -> parseFrom(buffer) | ||
| HEADER_SUBJECT -> parseEncodedString(buffer) | ||
| HEADER_DELIVERY_REPORT -> parseShortInteger(buffer) | ||
| HEADER_STORED -> parseShortInteger(buffer) | ||
| HEADER_MESSAGE_CLASS -> parseShortInteger(buffer) | ||
| HEADER_PRIORITY -> parseShortInteger(buffer) | ||
| HEADER_MESSAGE_SIZE -> parseLongInteger(buffer) | ||
| HEADER_EXPIRY -> parseExpiry(buffer) | ||
| HEADER_REPLY_CHARGING -> parseShortInteger(buffer) | ||
| HEADER_REPLY_CHARGING_DEADLINE -> parseShortInteger(buffer) | ||
| HEADER_REPLY_CHARGING_SIZE -> parseLongInteger(buffer) | ||
| HEADER_REPLY_CHARGING_ID -> parseTextString(buffer) | ||
| HEADER_DISTRIBUTION_INDICATOR -> parseShortInteger(buffer) | ||
| // HEADER_ELEMENT_DESCRIPTOR -> parseShortInteger(buffer) | ||
| HEADER_RECOMMENDED_RETRIEVAL_MODE -> parseShortInteger(buffer) | ||
| HEADER_RECOMMENDED_RETRIEVAL_MODE_TEXT -> parseEncodedString(buffer) | ||
| HEADER_APPLIC_ID -> parseTextString(buffer) | ||
| HEADER_REPLY_APPLIC_ID -> parseTextString(buffer) | ||
| HEADER_AUX_APPLIC_INFO -> parseTextString(buffer) | ||
| HEADER_CONTENT_CLASS -> parseContentClass(buffer) | ||
| HEADER_DRM_CONTENT -> parseShortInteger(buffer) | ||
| HEADER_REPLACE_ID -> parseTextString(buffer) | ||
| HEADER_CONTENT_LOCATION -> parseTextString(buffer) | ||
| HEADER_MESSAGE_ID -> parseTextString(buffer) | ||
| HEADER_DATE -> parseLongInteger(buffer) | ||
|
|
||
| else -> throw IllegalArgumentException("Unknown header type: $headerType") | ||
| } | ||
| headers[headerType] = value | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Don’t throw on unknown headers—skip to stay carrier‑resilient.
Unknown/vendor headers are common; throwing drops messages. Skip unknown values safely and continue.
Apply:
- // Parse headers until buffer is exhausted
+ // Parse headers until buffer is exhausted
while (buffer.hasRemaining()) {
val headerType = buffer.get().toInt() and 0xFF
val value = when (headerType) {
...
- else -> throw IllegalArgumentException("Unknown header type: $headerType")
+ else -> {
+ skipUnknownValue(buffer)
+ continue
+ }
}
headers[headerType] = value
}Add helper near the end of the file:
+ // Advance past an unknown value without desynchronizing the stream.
+ private fun skipUnknownValue(buffer: ByteBuffer) {
+ if (!buffer.hasRemaining()) return
+ val mark = buffer.position()
+ val b = buffer.get().toInt() and 0xFF
+ when {
+ b in 1..SHORT_LENGTH_MAX && buffer.remaining() >= b -> buffer.position(buffer.position() + b)
+ b == LENGTH_QUOTE -> {
+ val len = parseUnsignedInt(buffer)
+ val toSkip = minOf(len, buffer.remaining())
+ buffer.position(buffer.position() + toSkip)
+ }
+ else -> {
+ // Fallback: consume until NUL or end
+ buffer.position(mark)
+ while (buffer.hasRemaining()) if (buffer.get() == 0.toByte()) break
+ }
+ }
+ }🤖 Prompt for AI Agents
In app/src/main/java/me/capcom/smsgateway/modules/receiver/parsers/MMSParser.kt
around lines 72 to 108, the code currently throws on unknown header types which
drops messages for vendor/unknown headers; change the else branch to call a new
skipUnknownValue(buffer) helper instead of throwing, and add the provided
skipUnknownValue implementation near the end of the file; ensure the helper
safely advances the ByteBuffer without desynchronizing (handle short lengths,
quoted lengths via parseUnsignedInt, and a fallback that consumes until NUL or
end) so parsing continues for remaining headers.
| val data = byteArrayOf( | ||
| 0x8C.toByte(), | ||
| 0x82.toByte(), | ||
| 0x98.toByte(), | ||
| 0x69, | ||
| 0x64, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Use bytesOf(...) to build the payload and fix the final assertion.
Prevents type mismatch and uses Assert.
Apply:
- val data = byteArrayOf(
+ val data = bytesOf(
@@
- val notification = MMSParser.parseMNotificationInd(data)
- assert(notification.from == "+19162255887/TYPE=PLMN")
+ val notification = MMSParser.parseMNotificationInd(data)
+ assertEquals("+19162255887/TYPE=PLMN", notification.from)Also applies to: 232-235
Summary by CodeRabbit
New Features
Chores
Refactor
Tests
Documentation