Skip to content

Instantly share code, notes, and snippets.

@ldmsys
Created December 23, 2025 09:52
Show Gist options
  • Select an option

  • Save ldmsys/f286868037891fc8fc5c6e4a62d6e9c5 to your computer and use it in GitHub Desktop.

Select an option

Save ldmsys/f286868037891fc8fc5c6e4a62d6e9c5 to your computer and use it in GitHub Desktop.
diff --git a/srsepc/hdr/mme/nas.h b/srsepc/hdr/mme/nas.h
index 88364cdcd..c8409b8f8 100644
--- a/srsepc/hdr/mme/nas.h
+++ b/srsepc/hdr/mme/nas.h
@@ -247,6 +247,9 @@ public:
bool pack_tracking_area_update_reject(srsran::byte_buffer_t* nas_buffer, uint8_t emm_cause);
bool pack_attach_accept(srsran::byte_buffer_t* nas_buffer);
+ // Generic SMS Packing Function
+ bool pack_sms_notification(srsran::byte_buffer_t* nas_buffer, const std::string& msg_text);
+
/* Security functions */
bool integrity_check(srsran::byte_buffer_t* pdu, bool warn_failure = true);
bool short_integrity_check(srsran::byte_buffer_t* pdu);
@@ -289,6 +292,9 @@ private:
// Timer functions
bool start_t3413();
bool expire_t3413();
+
+ // SMS RP-Message Reference Counter
+ uint8_t m_next_rp_msg_ref = 0;
};
} // namespace srsepc
diff --git a/srsepc/src/mme/nas.cc b/srsepc/src/mme/nas.cc
index 7e9834d06..e81401f1c 100644
--- a/srsepc/src/mme/nas.cc
+++ b/srsepc/src/mme/nas.cc
@@ -1157,6 +1157,191 @@ bool nas::handle_attach_complete(srsran::byte_buffer_t* nas_rx)
m_logger.info("Sending EMM Information");
}
m_emm_ctx.state = EMM_STATE_REGISTERED;
+
+ // Send SMS Notification
+ srsran::unique_byte_buffer_t nas_tx = srsran::make_byte_buffer();
+ if (nas_tx) {
+ // Enter desired text (ASCII characters recommended)
+ std::string welcome_msg = "Welcome to KAIST EE515 by Prof. Yongdae Kim!";
+
+ if (pack_sms_notification(nas_tx.get(), welcome_msg)) {
+ m_s1ap->send_downlink_nas_transport(
+ m_ecm_ctx.enb_ue_s1ap_id,
+ m_ecm_ctx.mme_ue_s1ap_id,
+ nas_tx.get(),
+ m_ecm_ctx.enb_sri
+ );
+ srsran::console("Sent Welcome SMS to UE: %s\n", welcome_msg.c_str());
+ }
+ }
+
+ return true;
+}
+
+// 3GPP TS 23.038: Packs 8-bit ASCII characters into 7-bit septets
+void convert_string_to_gsm7(const std::string& input, std::vector<uint8_t>& output, uint8_t& septet_count) {
+ uint32_t bit_buffer = 0;
+ int bit_count = 0;
+ septet_count = 0;
+
+ for (char c : input) {
+ // Note: Commercial implementations require a full GSM mapping table (e.g., '@' -> 0x00).
+ // Applying 0x7F masking here for standard alphanumeric ASCII compatibility.
+ uint8_t val = c & 0x7F;
+
+ bit_buffer |= (val << bit_count);
+ bit_count += 7;
+ septet_count++;
+
+ if (bit_count >= 8) {
+ output.push_back(static_cast<uint8_t>(bit_buffer & 0xFF));
+ bit_buffer >>= 8;
+ bit_count -= 8;
+ }
+ }
+
+ if (bit_count > 0) {
+ output.push_back(static_cast<uint8_t>(bit_buffer & 0xFF));
+ }
+}
+
+// TP-SCTS Format: YY MM DD HH MM SS TZ (7 bytes)
+// Each byte is swapped BCD: e.g., 23 -> 0x32
+uint8_t to_swapped_bcd (int num) {
+ int tens = (num / 10) % 10;
+ int units = num % 10;
+ return (units << 4) | tens;
+}
+
+void get_current_scts(uint8_t* buf) {
+ time_t now = time(nullptr);
+ struct tm* t = localtime(&now);
+
+ buf[0] = to_swapped_bcd((t->tm_year % 100)); // Year
+ buf[1] = to_swapped_bcd(t->tm_mon + 1); // Month
+ buf[2] = to_swapped_bcd(t->tm_mday); // Day
+ buf[3] = to_swapped_bcd(t->tm_hour); // Hour
+ buf[4] = to_swapped_bcd(t->tm_min); // Minute
+ buf[5] = to_swapped_bcd(t->tm_sec); // Second
+ buf[6] = 0x00; // TimeZone (Local/Unknown)
+}
+
+bool nas::pack_sms_notification(srsran::byte_buffer_t* nas_buffer, const std::string& msg_text)
+{
+ m_logger.info("Packing SMS Notification: '%s'", msg_text.c_str());
+
+ // Layer 1: TP-User-Data Packing (GSM 7-bit)
+ std::vector<uint8_t> tp_ud_packed;
+ uint8_t septet_count = 0;
+ convert_string_to_gsm7(msg_text, tp_ud_packed, septet_count);
+
+ // Layer 1~3: SMS Payload Construction (CP-DATA)
+ srsran::unique_byte_buffer_t cp_data_buf = srsran::make_byte_buffer();
+ if (!cp_data_buf) {
+ m_logger.error("Failed to allocate buffer for SMS payload");
+ return false;
+ }
+
+ uint8_t* ptr = cp_data_buf->msg;
+ int len = 0;
+
+ // [Layer 3] CP-DATA Header (TS 24.011)
+ ptr[len++] = 0x09; // Protocol Discriminator (SMS)
+ ptr[len++] = 0x01; // Message Type (CP-DATA)
+ int cp_len_idx = len++;
+
+ // [Layer 2] RP-DATA Header (TS 24.011)
+ int rp_start_idx = len;
+ ptr[len++] = 0x01; // RP-Message Type (Network->MS)
+ ptr[len++] = m_next_rp_msg_ref++; // RP-Message Reference
+
+ // RP-Originator Address (SMSC Addr: 0000)
+ ptr[len++] = 0x04; ptr[len++] = 0x80; ptr[len++] = 0x00; ptr[len++] = 0x00; ptr[len++] = 0x00;
+ ptr[len++] = 0x00; // RP-Destination Address Length (0 for DL)
+ int rp_ud_len_idx = len++;;
+
+ // [Layer 1] TP-PDU (SMS-DELIVER) (TS 23.040)
+ int tp_start_idx = len;
+ ptr[len++] = 0x04; // TP-MTI (SMS-DELIVER, No More Msg)
+ ptr[len++] = 0x04; ptr[len++] = 0x91; ptr[len++] = 0x21; ptr[len++] = 0x43; // TP-OA (Sender: +1234)
+ ptr[len++] = 0x00; // TP-PID
+ ptr[len++] = 0x00; // TP-DCS (7bit default)
+
+ // Dynamic Timestamp (TP-SCTS)
+ get_current_scts(&ptr[len]);
+ len += 7;
+
+ // TP-UDL: Uncompressed length (number of septets)
+ ptr[len++] = septet_count;
+
+ memcpy(&ptr[len], tp_ud_packed.data(), tp_ud_packed.size());
+ len += tp_ud_packed.size();
+
+ // Length Fixup
+ ptr[rp_ud_len_idx] = len - tp_start_idx;
+ ptr[cp_len_idx] = len - rp_start_idx;
+ cp_data_buf->N_bytes = len;
+
+ // Layer 4: NAS Transport Packing & Security
+ LIBLTE_MME_DOWNLINK_NAS_TRANSPORT_MSG_STRUCT dl_nas_transport;
+
+ // Copy Payload
+ dl_nas_transport.nas_msg.N_bytes = cp_data_buf->N_bytes;
+ if(cp_data_buf->N_bytes <= LIBLTE_MAX_MSG_SIZE_BYTES) {
+ memcpy(dl_nas_transport.nas_msg.msg, cp_data_buf->msg, cp_data_buf->N_bytes);
+ } else {
+ m_logger.error("SMS Payload too large");
+ return false;
+ }
+
+ // Set Security Header Type (Integrity & Ciphered)
+ uint8_t sec_hdr_type = LIBLTE_MME_SECURITY_HDR_TYPE_INTEGRITY_AND_CIPHERED;
+
+ // Increment Downlink NAS Count
+ m_sec_ctx.dl_nas_count++;
+
+ // Pack Downlink NAS Transport using LibLTE
+ LIBLTE_ERROR_ENUM err = liblte_mme_pack_downlink_nas_transport_msg(
+ &dl_nas_transport,
+ sec_hdr_type,
+ m_sec_ctx.dl_nas_count,
+ (LIBLTE_BYTE_MSG_STRUCT*)nas_buffer
+ );
+
+ if (err!= LIBLTE_SUCCESS) {
+ m_logger.error("Error packing Downlink NAS Transport");
+ return false;
+ }
+
+ // Encrypt NAS message
+ cipher_encrypt(nas_buffer);
+
+ // Integrity protect NAS message
+ uint8_t mac[4];
+ integrity_generate(nas_buffer, mac);
+ memcpy(&nas_buffer->msg[1], mac, 4); // MAC offset is 1 (after header byte)
+
+ m_logger.info("Packed SMS Notification with Security (Seq: %d)", m_sec_ctx.dl_nas_count);
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment