<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Семён сохраняет полезное_)</title><generator>teletype.in</generator><description><![CDATA[Канал о электронике и связи.
Демократии тут нет и не будет! Бан раздается за любое &quot;Ну очевидно, же&quot;]]></description><image><url>https://img4.teletype.in/files/72/cd/72cd0ee4-dcf9-4ee2-a039-0a50d64b3566.png</url><title>Семён сохраняет полезное_)</title><link>https://teletype.in/@pole_sam</link></image><link>https://teletype.in/@pole_sam?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=pole_sam</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/pole_sam?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/pole_sam?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Sun, 06 Sep 2026 09:58:25 GMT</pubDate><lastBuildDate>Sun, 06 Sep 2026 09:58:25 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@pole_sam/uLxXaREJc5k</guid><link>https://teletype.in/@pole_sam/uLxXaREJc5k?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=pole_sam</link><comments>https://teletype.in/@pole_sam/uLxXaREJc5k?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=pole_sam#comments</comments><dc:creator>pole_sam</dc:creator><title>Как превратить бинарный CP-лог модема SIMCom A7665E в читаемый</title><pubDate>Sat, 29 Aug 2026 19:53:33 GMT</pubDate><description><![CDATA[Отладочные логи мобильных модемов часто выглядят как поток непонятных бинарных данных. Внутри могут находиться сообщения о регистрации в сети, выборе PLMN, параметрах serving cell, APN, IP-адресах и состоянии протоколов LTE. Однако без знания транспортного формата и таблицы сообщений прошивки этот поток практически бесполезен.]]></description><content:encoded><![CDATA[
  <h2 id="V5dc">Введение</h2>
  <p id="hip4">Отладочные логи мобильных модемов часто выглядят как поток непонятных бинарных данных. Внутри могут находиться сообщения о регистрации в сети, выборе PLMN, параметрах serving cell, APN, IP-адресах и состоянии протоколов LTE. Однако без знания транспортного формата и таблицы сообщений прошивки этот поток практически бесполезен.</p>
  <p id="8DSu">В этой статье разберём, как устроено распознавание debug-лога модема SIMCom A7665E на базе ASR «Crane» ASR1603. Цель — пройти весь путь от байтов, поступающих через USB, до человекочитаемой строки:</p>
  <pre id="EHs6">байтовый поток → бинарная запись → форматное сообщение → декодированные аргументы</pre>
  <p id="OK8k">Практически задача состоит из двух независимых частей:</p>
  <ul id="OVAH">
    <li id="lzEQ">определить границы бинарных записей;</li>
    <li id="iawi">понять смысл каждой записи по её идентификаторам и payload.</li>
  </ul>
  <p id="xfbd">Первую проблему можно решить детерминированно. Вторая требует базы форматных строк, соответствующей конкретной сборке прошивки.</p>
  <hr />
  <h2 id="1">1. Исходные условия</h2>
  <p id="AabM">В качестве устройства используется модем SIMCom A7665E</p>
  <p id="DqWX">Важно не перепутать этот поток с Qualcomm DIAG. CP-log ASR имеет другой бинарный формат, поэтому инструменты, рассчитанные на Qualcomm DIAG, например <code>qc_debug_monitor</code>, его не распознают.</p>
  <p id="Wzic">Итоговая архитектура декодера выглядит так:</p>
  <pre id="6HGy">/dev/ttyUSBx
     │
     │ AT+ECPLOG=1
     ▼
поток бинарных данных
     │
     ▼
asr_proto.py
байты → Record
     │
     ▼
asr_mdb.py
(msg_type, fmt_id) + payload → строка
     │
     ▼
asr_debug_monitor.py
захват, фильтрация, цветной вывод, replay</pre>
  <p id="6CmS">Такое разделение ответственности удобно по нескольким причинам:</p>
  <ul id="zNWc">
    <li id="kPUJ">транспортный парсер не зависит от базы форматов;</li>
    <li id="uxvp">декодер семантики не зависит от последовательного порта;</li>
    <li id="xZzW">один и тот же протокол можно использовать в CLI, web-мониторе или тестовой системе;</li>
    <li id="vhVC">каждый слой можно тестировать отдельно.</li>
  </ul>
  <hr />
  <h2 id="2--cp-log">2. Поиск CP-log-порта</h2>
  <h2 id="ttyusb0">Почему нельзя полагаться на <code>ttyUSB0</code></h2>
  <p id="CrGd">После перезагрузки модема Linux может назначить интерфейсам другие имена:</p>
  <pre id="96W4">до перезагрузки:     ttyUSB0, ttyUSB1, ttyUSB2
после перезагрузки:  ttyUSB3, ttyUSB4, ttyUSB5</pre>
  <p id="UqhE">Поэтому поиск порта по имени ненадёжен. Лучше использовать USB VID и номер интерфейса в USB-топологии.</p>
  <p id="RQey">В рассматриваемом устройстве CP-log находится на интерфейсе <code>#2</code>. В <code>pyserial</code> его можно найти по полю <code>location</code>:</p>
  <pre id="JMin">from serial.tools import list_ports

MODEM_VID = 0x1e0e
DEBUG_IFACE = 2

def find_debug_port(iface=DEBUG_IFACE, vid=MODEM_VID):
    fallback = None

    for port in list_ports.comports():
        if port.vid != vid:
            continue

        location = port.location or &quot;&quot;

        if location.endswith(f&quot;:1.{iface}&quot;):
            return port.device

        fallback = fallback or port.device

    return fallback</pre>
  <p id="NG7a">Ключевая проверка:</p>
  <pre id="rKri">location.endswith(&quot;:1.2&quot;)</pre>
  <p id="MAVU">Номер <code>ttyUSBx</code> может измениться, а номер USB-интерфейса в конкретной композиции обычно остаётся постоянным.</p>
  <h2 id="8Voq">Включение потока</h2>
  <p id="MSSK">CP-log не начинает передаваться автоматически. Сначала на тот же порт необходимо отправить AT-команду:</p>
  <pre id="AIQx">ENABLE_CMD = b&quot;AT+ECPLOG=1\r\n&quot;
DISABLE_CMD = b&quot;AT+ECPLOG=0\r\n&quot;</pre>
  <p id="iM0M">Пример открытия устройства:</p>
  <pre id="JJrL">import serial
import time

def open_device(device, baudrate=115200):
    ser = serial.Serial(device, baudrate, timeout=0.2)

    ser.reset_input_buffer()
    ser.write(ENABLE_CMD)
    ser.flush()

    time.sleep(0.4)

    # Удаляем эхо команды и текстовый ответ OK
    ser.reset_input_buffer()

    return ser</pre>
  <p id="1IyD">Здесь есть важная особенность: после <code>AT+ECPLOG=1</code> модем сначала может вернуть обычный текстовый ответ:</p>
  <pre id="8Ysu">AT+ECPLOG=1
OK</pre>
  <p id="vPCD">Затем начинается бинарный поток. Если не очистить входной буфер, парсер может попытаться интерпретировать ASCII-ответ как начало бинарной записи.</p>
  <p id="H6GH">При завершении работы полезно отключить лог:</p>
  <pre id="VVAC">ser.write(DISABLE_CMD)
ser.flush()
time.sleep(0.1)
ser.reset_input_buffer()
ser.close()</pre>
  <hr />
  <h2 id="3">3. Формат бинарной записи</h2>
  <h2 id="ubjE">Как была найдена граница сообщений</h2>
  <p id="fsPk">В дампе регулярно повторяется последовательность:</p>
  <pre id="bBVb">маленькое число → несколько нулей → полезная нагрузка</pre>
  <p id="PrUW">Это позволяет предположить, что первые два байта являются длиной записи в формате little-endian.</p>
  <p id="iVXc">Алгоритм проверки простой:</p>
  <ol id="ltNT">
    <li id="GBQz">прочитать первые два байта;</li>
    <li id="gqSf">интерпретировать их как <code>u16</code>;</li>
    <li id="uoWO">перейти вперёд на указанное количество байт;</li>
    <li id="MD6Q">проверить, похожи ли следующие два байта на очередную длину;</li>
    <li id="l7Ub">повторить процедуру до конца буфера.</li>
  </ol>
  <p id="ZIYu">Если цепочка проходит через весь дамп без рассинхронизации, гипотеза подтверждается.</p>
  <p id="6Alv">Такой формат обладает свойством самосинхронизации: если в поток попал мусор, длина обычно выходит за разумные пределы, и парсер может начать поиск следующей потенциальной записи.</p>
  <h2 id="7rQZ">Заголовок</h2>
  <p id="Nurd">Аргументы сообщения</p>
  <p id="VSwH">В Python структура описывается так:</p>
  <pre id="Ub4l">import struct

HDR = struct.Struct(&quot;&lt;HHHHHHI&quot;)
HDR_LEN = HDR.size  # 16 байт</pre>
  <p id="aj7y">Порядок полей:</p>
  <pre id="CwfQ">length, flags, channel, seq, msg_type, fmt_id, timestamp</pre>
  <h2 id="ygey">Таймстамп</h2>
  <p id="hfGU">Поле <code>timestamp</code> не является временем в микросекундах или миллисекундах. Экспериментальное сопоставление с известными интервалами показало, что устройство использует частоту 32768 Гц:</p>
  <pre id="InD3">TS_HZ = 32768

def timestamp_seconds(timestamp):
    return timestamp / TS_HZ</pre>
  <p id="aoWG">Например:</p>
  <pre id="01r3">timestamp = 2 572 383
time      = 2 572 383 / 32 768 ≈ 78.503 с</pre>
  <p id="2Nmr">Это абсолютное время работы устройства до момента включения ECPLOG. В пользовательском интерфейсе удобнее вычесть timestamp первой записи и показывать относительное время:</p>
  <pre id="ut3n">[+0.000000] ...
[+0.003174] ...
[+0.006287] ...</pre>
  <hr />
  <h2 id="4">4. Потоковый парсер</h2>
  <p id="gX1F">Парсер должен корректно работать в двух режимах:</p>
  <ul id="zva4">
    <li id="45NA">с готовым бинарным файлом;</li>
    <li id="X9Zh">с живым потоком, где одна запись может прийти несколькими частями.</li>
  </ul>
  <h2 id="3y14">Разбор одной записи</h2>
  <pre id="Eg2b">MIN_REC = 16
MAX_REC = 16384

def take_record(buf, offset, flush=False):
    end = len(buf)

    if offset + 2 &gt; end:
        return None, offset, True

    length = buf[offset] | (buf[offset + 1] &lt;&lt; 8)

    if length &lt; MIN_REC or length &gt; MAX_REC:
        # Некорректная длина: пробуем синхронизацию со следующего байта
        return None, offset + 1, False

    if offset + length &gt; end:
        if flush:
            # Финальный обрезанный хвост
            return None, offset + 1, False

        # Для живого потока ждём следующую порцию данных
        return None, offset, True

    fields = HDR.unpack_from(buf, offset)

    record = {
        &quot;length&quot;: fields[0],
        &quot;flags&quot;: fields[1],
        &quot;channel&quot;: fields[2],
        &quot;seq&quot;: fields[3],
        &quot;msg_type&quot;: fields[4],
        &quot;fmt_id&quot;: fields[5],
        &quot;timestamp&quot;: fields[6],
        &quot;payload&quot;: bytes(
            buf[offset + HDR_LEN:offset + length]
        ),
        &quot;offset&quot;: offset,
    }

    return record, offset + length, False</pre>
  <h2 id="HLZH">Инкрементальный режим</h2>
  <pre id="pfxt">class StreamParser:
    def __init__(self):
        self.buffer = bytearray()

    def feed(self, chunk):
        self.buffer.extend(chunk)

        offset = 0
        end = len(self.buffer)

        while offset &lt; end:
            record, next_offset, need_more = take_record(
                self.buffer,
                offset,
                flush=False
            )

            if need_more:
                break

            offset = next_offset

            if record is not None:
                yield record

        if offset:
            del self.buffer[:offset]

        # Защита от бесконечного роста при потере синхронизации
        if len(self.buffer) &gt; 1 &lt;&lt; 20:
            del self.buffer[:len(self.buffer) - (1 &lt;&lt; 16)]</pre>
  <p id="udSn">Если в середине записи пришёл только заголовок и часть payload, она остаётся во внутреннем буфере. Следующий вызов <code>feed()</code> продолжает разбор.</p>
  <hr />
  <h2 id="5">5. Разбор первой записи</h2>
  <p id="bfmo">Рассмотрим пример записи размером 34 байта:</p>
  <pre id="eTHt">22 00 | 00 00 | 00 00 | 01 00 | 2b 00 | 96 14 |
5f 40 27 00 |
02 00 63 00 78 08 12 00 01 00 00 00 02 00 00 00 00 00</pre>
  <p id="YQni">Разметка:</p>
  <pre id="7mcY">length     flags      channel    seq
22 00      00 00      00 00      01 00

msg_type   fmt_id     timestamp
2b 00      96 14      5f 40 27 00

payload
02 00 63 00 78 08 12 00 01 00 00 00 02 00 00 00 00 00</pre>
  <p id="QS7G">В данном случае <code>msg_type = 0x2b</code> соответствует текстовой записи. Однако payload содержит бинарный префикс, поэтому его нельзя безусловно интерпретировать как обычную C-строку.</p>
  <hr />
  <h2 id="6">6. Типы сообщений</h2>
  <p id="v9sT">Поле <code>msg_type</code> определяет, как нужно обрабатывать запись:</p>
  <pre id="dnnO">TYPE_TEXT = 0x2B

TYPE_NAMES = {
    0x2B: &quot;TEXT&quot;,
    0x1D: &quot;TRACE&quot;,
    0x17: &quot;SIGNAL&quot;,
    0x1A: &quot;T1A&quot;,
    0x16: &quot;T16&quot;,
    0x0E: &quot;T0E&quot;,
    0x03: &quot;T03&quot;,
}</pre>
  <h2 id="text">TEXT-записи</h2>
  <p id="nzXJ">TEXT-записи уже содержат готовые фрагменты текста. Их можно декодировать без базы форматных строк, извлекая печатные ASCII-последовательности.</p>
  <pre id="tIqc">def printable_runs(data, min_run=3):
    runs = []
    current = bytearray()

    for byte in data:
        if 32 &lt;= byte &lt; 127:
            current.append(byte)
        else:
            if len(current) &gt;= min_run:
                runs.append(current.decode(&quot;latin-1&quot;))
            current.clear()

    if len(current) &gt;= min_run:
        runs.append(current.decode(&quot;latin-1&quot;))

    return runs</pre>
  <p id="689M">Для текстовых записей достаточно использовать короткий порог:</p>
  <pre id="lsqx">runs = printable_runs(payload, min_run=3)
text = &quot; &quot;.join(runs)</pre>
  <h2 id="terse">Terse-записи</h2>
  <p id="upv9">Terse-записи содержат не сам текст, а:</p>
  <ul id="cu0T">
    <li id="uY5y">идентификатор форматной строки;</li>
    <li id="Y65C">бинарные аргументы.</li>
  </ul>
  <p id="Dc7q">Их можно представить как удалённый вызов:</p>
  <pre id="TC9k">diagPrintf(&quot;Serving Cell: rsrp = %d&quot;, rsrp);</pre>
  <p id="UXSP">На провод передаются только:</p>
  <pre id="95Ot">fmt_id + бинарное значение rsrp</pre>
  <p id="qqtj">Сама строка формата находится в базе данных прошивки.</p>
  <hr />
  <h2 id="7--fileline">7. Записи <code>file:line</code> без базы</h2>
  <p id="ObEZ">Часть assert-сообщений содержит имя исходного файла и номер строки:</p>
  <pre id="TfgR">rrcdsutils.c\0\xa2\x0a\x00\x00</pre>
  <p id="dSCI">То есть payload имеет вид:</p>
  <pre id="lgBf">имя файла + NUL + номер строки u32</pre>
  <p id="95nX">Такой формат можно распознать независимо от версии базы:</p>
  <pre id="ahNy">import re

SOURCE_RE = re.compile(
    r&quot;\.(c|h|cpp|cc|cxx)$&quot;,
    re.IGNORECASE
)

def decode_file_line(payload):
    nul = payload.find(b&quot;\x00&quot;)

    if nul &lt; 4:
        return None

    raw_name = payload[:nul]

    if not all(32 &lt;= byte &lt; 127 for byte in raw_name):
        return None

    name = raw_name.decode(&quot;latin-1&quot;)

    if not SOURCE_RE.search(name):
        return None

    name = name.replace(&quot;\\&quot;, &quot;/&quot;).rsplit(&quot;/&quot;, 1)[-1]

    if len(payload) &lt; nul + 5:
        return None

    line = int.from_bytes(
        payload[nul + 1:nul + 5],
        &quot;little&quot;
    )

    return name, line</pre>
  <p id="ZL5B">Пример результата:</p>
  <pre id="nsPi">rrcdsutils.c:2722</pre>
  <p id="ViNf">Строгая проверка необходима: случайный бинарный payload тоже может содержать печатные байты. Условие с расширением исходного файла снижает вероятность ложного распознавания.</p>
  <hr />
  <h2 id="8">8. База форматных строк</h2>
  <h2 id="m7nZ">Зачем нужна база</h2>
  <p id="RaAb">Terse-сообщение само по себе неполно. Например, запись может содержать:</p>
  <pre id="abea">msg_type = 0x1d
fmt_id   = 0x0b3d
payload  = бинарные аргументы</pre>
  <p id="pBj1">Чтобы понять, что означают эти байты, нужна запись из базы:</p>
  <pre id="Q0pV">&quot;updates ( %ld , %d ) measUpdateTime: ( 0x%lx ) , ( rsrp = %d )&quot;</pre>
  <p id="84n3">В случае ASR используется база <code>cp_MDB.txt</code> формата CATStudio <code>TxtDbHeader</code>.</p>
  <h2 id="mUgJ">Секции базы</h2>
  <p id="uQjt">Наиболее важны три секции:</p>
  <ul id="aBYu">
    <li id="WO2u"><code>EnumValsRes</code> — таблица форматных строк;</li>
    <li id="h2CU"><code>Enums</code> — преобразование числовых значений в имена;</li>
    <li id="QZXW"><code>Signals</code> — описания структур для <code>%S{...}</code>.</li>
  </ul>
  <h2 id="1Mvd">Извлечение секций</h2>
  <p id="wnB3">В начале файла находится индекс с байтовыми смещениями:</p>
  <pre id="0pPl">EnumValsRes,1234,987654
Enums,987654,1048576
Signals,1048576,4567890</pre>
  <p id="Ehs3">Пример загрузки:</p>
  <pre id="KyHY">def parse_sections(raw):
    marker = raw.find(b&quot;&lt;end&gt;&quot;)

    if marker &lt; 0:
        return {}

    header = raw[:marker].decode(
        &quot;latin-1&quot;,
        &quot;replace&quot;
    )

    sections = {}

    for line in header.splitlines():
        parts = line.split(&quot;,&quot;)

        if len(parts) != 3:
            continue

        name, start, end = parts

        if start.isdigit() and end.isdigit():
            sections[name] = (
                int(start),
                int(end)
            )

    return sections</pre>
  <h2 id="Rmba">Таблица форматных строк</h2>
  <p id="ylkW">Строка в <code>EnumValsRes</code> выглядит примерно так:</p>
  <pre id="FhHA">table,id,flag,flag,GROUP,MODULE,NAME,diagPrintf(&quot;формат&quot;, args);</pre>
  <p id="bb2Y">Запятые могут встречаться внутри самой форматной строки, поэтому разделять строку нужно только по первым семи запятым:</p>
  <pre id="NoFM">parts = line.split(&quot;,&quot;, 7)</pre>
  <p id="bFob">Для поддержки всех вариантов вызова используется регулярное выражение:</p>
  <pre id="HPWv">import re

FORMAT_RE = re.compile(
    r&#x27;diag(?:Text|Struct)?Printf\s*\(&#x27;
    r&#x27;\s*&quot;((?:[^&quot;\\]|\\.)*)&quot;&#x27;
)</pre>
  <p id="p2Pr">Оно распознаёт:</p>
  <pre id="VUpv">diagPrintf(...)
TextPrintf(...)
StructPrintf(...)</pre>
  <p id="QmvP">Это исправляет важную ошибку: если искать только <code>diagPrintf</code>, записи <code>diagTextPrintf</code> и <code>diagStructPrintf</code> останутся без форматной строки и будут ошибочно показаны как hex.</p>
  <hr />
  <h2 id="9">9. Подбор подходящей базы</h2>
  <p id="ssBR">База форматных строк должна соответствовать сборке прошивки. Даже если две версии используют один и тот же чипсет, идентификаторы сообщений могут отличаться.</p>
  <p id="AF9D">Для выбора наиболее подходящей базы удобно использовать измеримые метрики.</p>
  <h2 id="key-match">Key-match</h2>
  <p id="jypT">Первая метрика — доля пар <code>(msg_type, fmt_id)</code>, найденных в базе:</p>
  <pre id="VIVM">distinct_keys = {
    (record[&quot;msg_type&quot;], record[&quot;fmt_id&quot;])
    for record in terse_records
}

matched = {
    key for key in distinct_keys
    if key in mdb_entries
}

key_match = len(matched) / len(distinct_keys)</pre>
  <p id="lUoq">Эта метрика показывает, совпадает ли схема идентификаторов.</p>
  <h2 id="scalar-clean">Scalar-clean</h2>
  <p id="mXzA">Вторая метрика проверяет, удалось ли полностью разобрать аргументы скалярных сообщений без маркера <code>&lt;?&gt;</code>.</p>
  <pre id="N5kE">scalar-clean =
число сообщений без underflow /
общее число проверяемых сообщений</pre>
  <p id="p0Vu">Она проверяет уже не только идентификаторы, но и ширины аргументов.</p>
  <hr />
  <h2 id="10---int--16">10. Главная особенность: <code>int</code> занимает 16 бит</h2>
  <p id="Zdfj">Это наиболее нетривиальная часть реверса.</p>
  <h2 id="i6fj">Симптом</h2>
  <p id="f3EN">При стандартной интерпретации printf-аргументов каждый <code>%d</code> ожидает 4 байта. Но результаты выглядели неправильно:</p>
  <pre id="26rT">rsrp = 104922215</pre>
  <p id="T0Lm">или:</p>
  <pre id="9Y9L">rsrp = &lt;?&gt;</pre>
  <p id="1z6P">Причина — payload заканчивался раньше, чем ожидал декодер.</p>
  <h2 id="YdnK">Проверка гипотезы</h2>
  <p id="CTkG">Для каждого сообщения можно вычислить ожидаемый размер аргументов и сравнить его с фактической длиной payload:</p>
  <pre id="spNI">def fit_score(records, short_width):
    exact = 0
    underflow = 0
    leftover = 0

    for record in records:
        fmt = get_format(record)

        if not fmt:
            continue

        required = calculate_argument_size(
            fmt,
            short_width=short_width,
            long_width=4
        )

        actual = len(record[&quot;payload&quot;])

        if required == actual:
            exact += 1
        elif required &gt; actual:
            underflow += 1
        else:
            leftover += 1

    return exact, underflow, leftover</pre>
  <p id="rjXk">При ширине 2 байта количество underflow уменьшается примерно в пять раз.</p>
  <h2 id="gRsK">Практическое правило</h2>
  <p id="1W9r">Для ASR-терс-сообщений используется следующее соответствие:</p>
  <pre id="zjgb">if conversion in &quot;eEfFgG&quot;:
    width = 8 if length in (&quot;l&quot;, &quot;ll&quot;, &quot;L&quot;) else 4
elif conversion == &quot;p&quot;:
    width = 4
elif length == &quot;ll&quot;:
    width = 8
elif length == &quot;hh&quot;:
    width = 1
elif length == &quot;l&quot;:
    width = 4
elif length in (&quot;L&quot;, &quot;z&quot;, &quot;j&quot;, &quot;t&quot;):
    width = 4
elif length == &quot;h&quot;:
    width = 2
else:
    width = 2</pre>
  <p id="bOlS">Значения <code>%d</code> и <code>%i</code> необходимо интерпретировать как знаковые:</p>
  <pre id="1QeR">value = int.from_bytes(
    payload[pos:pos + width],
    &quot;little&quot;,
    signed=conversion in &quot;di&quot;
)</pre>
  <hr />
  <h2 id="11">11. Сквозной пример декодирования</h2>
  <p id="5cVd">Рассмотрим запись:</p>
  <pre id="iLf0">msg_type = 0x1d
fmt_id   = 0x0b3d
payload  = e40c0000 d100 021c0f00 61e4</pre>
  <p id="8Y5t">Форматная строка:</p>
  <pre id="NtiQ">&quot;updates ( %ld , %d ) &#x27;s measUpdateTime: &quot;
&quot;( 0x%lx ) , ( rsrp = %d ) &quot;</pre>
  <p id="m4OD">Результат:</p>
  <pre id="KW0g">updates ( 3300, 209 ) measUpdateTime:
( 0xf1c02 ), ( rsrp = -7071 )</pre>
  <p id="Qibr">При ошибочной ширине <code>%d = 4 байта</code> декодер начал бы читать следующие аргументы с неправильных смещений и в итоге получил бы <code>&lt;?&gt;</code>.</p>
  <hr />
  <h2 id="12--s">12. Поддержка <code>%S{...}</code> и структур</h2>
  <p id="QBRT">Некоторые диагностические сообщения передают не скаляры, а структуры:</p>
  <pre id="4EsE">%S{EmmPrintDebugInfoInd_MobileId}</pre>
  <p id="bCST">Описание полей находится в секции <code>Signals</code>.</p>
  <p id="tL4X">Минимальная модель структуры:</p>
  <pre id="9ngN">structs = {
    &quot;Example&quot;: [
        (&quot;field_a&quot;, &quot;int&quot;, 1, &quot;&quot;),
        (&quot;field_b&quot;, &quot;enum&quot;, 1, &quot;StateType&quot;),
        (&quot;field_c&quot;, &quot;pointer&quot;, 1, &quot;&quot;),
    ]
}</pre>
  <p id="dcrL">Декодер обрабатывает:</p>
  <ul id="JXXS">
    <li id="gWlK">скалярные поля;</li>
    <li id="tGyp">enum;</li>
    <li id="4bsU">указатели;</li>
    <li id="k97N">вложенные структуры;</li>
    <li id="cBXV">массивы;</li>
    <li id="1mQp">рекурсию.</li>
  </ul>
  <p id="0KcX">Упрощённая схема:</p>
  <pre id="ec6w">def decode_struct(name, payload, pos=0, depth=0):
    if depth &gt; 6:
        return f&quot;{{{name}?}}&quot;, pos

    fields = structs.get(name)

    if fields is None:
        return f&quot;{{{name}?}}&quot;, pos

    result = []

    for field_name, field_type, count, detail in fields:
        values = []

        for _ in range(count):
            if field_type == &quot;enum&quot;:
                value = read_u32(payload, pos)
                pos += 4
                value = enums.get(detail, {}).get(
                    value,
                    str(value)
                )

            elif field_type == &quot;pointer&quot;:
                value = read_u32(payload, pos)
                pos += 4
                value = hex(value)

            elif field_type == &quot;int&quot;:
                value = read_u32(payload, pos, signed=True)
                pos += 4

            elif detail in structs:
                value, pos = decode_struct(
                    detail,
                    payload,
                    pos,
                    depth + 1
                )

            else:
                value = &quot;&lt;?&gt;&quot;

            values.append(value)

        rendered = (
            values[0]
            if count == 1
            else &quot;[&quot; + &quot;,&quot;.join(map(str, values)) + &quot;]&quot;
        )

        result.append(f&quot;{field_name}={rendered}&quot;)

    return &quot;{&quot; + &quot;, &quot;.join(result) + &quot;}&quot;, pos</pre>
  <h2 id="vwkz">Почему структуры могут декодироваться неверно</h2>
  <p id="yUmc">Секция <code>Signals</code> из близкой базы может не полностью соответствовать конкретной сборке модема. Например, поля большой EMM-структуры могут иметь другую раскладку или тип.</p>
  <p id="b6JO">В результате появляются значения вроде:</p>
  <pre id="fOaO">mcc = -1727901696</pre>
  <p id="ZHLK">В таких случаях проблема находится не в транспортном парсере, а в несовпадении описания структуры. Исправить её можно только точной базой <code>cp_MDB.txt</code> для конкретной версии прошивки.</p>
  <hr />
  <h2 id="13">13. Приоритет способов декодирования</h2>
  <p id="DAx5">Одна запись может быть распознана несколькими способами. Поэтому форматтеру нужен строгий порядок выбора:</p>
  <pre id="jWY2">1. TEXT-запись
   └─ готовая строка, база не нужна

2. file:line
   └─ имя исходного файла и номер строки

3. terse-декодирование через MDB
   └─ форматная строка + бинарные аргументы

4. встроенная ASCII-строка
   └─ длинный печатный фрагмент внутри payload

5. raw hex
   └─ неизвестный или неподдерживаемый payload</pre>
  <p id="lRIn">Формат вывода может выглядеть так:</p>
  <pre id="HjYG">[+0.000000] [TEXT] +ZDON
[+0.003174] [NAS] New Selected PLMN (250,1)
[+0.006287] [RRC] Serving Cell rsrp=-110.5 dBm
[+0.009460] [ASSERT] rrcdsutils.c:2722</pre>
  <p id="FcLg">Большие необработанные записи лучше скрывать по умолчанию и включать отдельным флагом:</p>
  <pre id="sjDl">./asr_debug_monitor.py --replay capture.bin -t</pre>
  <p id="wJnW">где <code>-t</code> показывает terse-записи, а:</p>
  <pre id="hF41">./asr_debug_monitor.py --replay capture.bin -a</pre>
  <p id="MmN8">показывает все записи, включая объёмные signal-сообщения.</p>
  <hr />
  <h2 id="14----seq">14. Контроль потерь по <code>seq</code></h2>
  <p id="ES8X">Поле <code>seq</code> — 16-битный счётчик записей. Оно позволяет заметить потерю данных на USB или переполнение буфера.</p>
  <p id="8XKj">Поскольку счётчик циклический, сравнение выполняется по модулю 2162^{16}216:</p>
  <pre id="0PEP">def sequence_gap(previous, current):
    return (current - previous - 1) &amp; 0xFFFF</pre>
  <p id="JeNR">Практическая проверка:</p>
  <pre id="GclP">gap = (record.seq - last_seq - 1) &amp; 0xFFFF

if 0 &lt; gap &lt; 0x8000:
    dropped += gap</pre>
  <p id="wWXc">Ограничение <code>gap &lt; 0x8000</code> нужно, чтобы отличать нормальное переполнение счётчика от большого обратного скачка.</p>
  <p id="jGpF">В интерфейсе можно показывать:</p>
  <pre id="Sd1i">[warning] потеряно записей: 12</pre>
  <p id="6So1">Это особенно полезно при высокоскоростном логе, когда USB-порт, пользовательский процесс или внутренний буфер не успевают обработать весь поток.</p>
  <hr />
  <h2 id="15">15. Верификация</h2>
  <p id="EM1m">Декодер необходимо проверять на двух уровнях.</p>
  <h2 id="oR4J">Автономные тесты</h2>
  <p id="Unbt">Полезно протестировать отдельно:</p>
  <ul id="ezl6">
    <li id="8Gqq">разбор заголовка;</li>
    <li id="IifS">length-framing;</li>
    <li id="AMFg">повторную синхронизацию после мусора;</li>
    <li id="qezt">инкрементальный <code>StreamParser</code>;</li>
    <li id="6Eqo">TEXT-записи;</li>
    <li id="47sS"><code>file:line</code>;</li>
    <li id="4BJP">разбор MDB;</li>
    <li id="9Ihr"><code>%S{...}</code>;</li>
    <li id="uOGj"><code>%e{...}</code>;</li>
    <li id="rAzS">16-битные аргументы.</li>
  </ul>
  <p id="cKzK">В исходном проекте такой набор из девяти тестов проходит на синтетической базе и искусственно сформированных записях.</p>
  <h2 id="3dgQ">Сквозная проверка</h2>
  <p id="izYy">Затем необходимо проверить полный путь:</p>
  <pre id="5Aog">A7665E
  → USB CP-log
  → StreamParser
  → MDB decoder
  → человекочитаемый вывод</pre>
  <p id="5uis">В корректно декодированных сообщениях должны распознаваться, например:</p>
  <ul id="tSvn">
    <li id="P5e1"><code>+ZDON</code>;</li>
    <li id="PGLL">выбранная PLMN;</li>
    <li id="ZYBz">serving cell;</li>
    <li id="JbUY">RSRP и RSRQ;</li>
    <li id="j28R">APN;</li>
    <li id="2awn">IP-адрес;</li>
    <li id="gYrv">события NAS и RRC;</li>
    <li id="xncU">сообщения IPC между L1 и protocol stack.</li>
  </ul>
  <hr />
  <h2 id="16">16. Ограничения метода</h2>
  <p id="wdN1">Даже при правильно найденном транспортном формате часть сообщений может оставаться неполностью декодированной.</p>
  <p id="WKFB">Особенно важно различать ошибки разных уровней:</p>
  <pre id="JnVN">неправильная длина записи
    → проблема транспортного парсера

правильная запись, но &lt;?&gt;
    → проблема формата или ширины аргумента

правильный текст, но неверные поля структуры
    → несовпадение Signals с конкретной прошивкой</pre>
  <p id="6NXp">Транспортный формат при этом может быть полностью понятен, даже если семантическая расшифровка остаётся неполной.</p>
  <hr />
  <h2 id="17">17. Запуск декодера</h2>
  <p id="T3fE">Примерный набор команд:</p>
  <pre id="Kj1K">cd /home/sam/work/modems_debug/asr_debug</pre>
  <p id="ThEK">Живой захват с автоматическим поиском порта:</p>
  <pre id="XL0x">./asr_debug_monitor.py</pre>
  <p id="cRFI">Разбор сохранённого файла:</p>
  <pre id="h8Cm">./asr_debug_monitor.py \
    --replay boot_registration.bin</pre>
  <p id="qRL0">Разбор terse-записей:</p>
  <pre id="YfcI">./asr_debug_monitor.py \
    --replay boot_registration.bin \
    -t</pre>
  <p id="oHIg">Вывод всех записей:</p>
  <pre id="QuAd">./asr_debug_monitor.py \
    --replay boot_registration.bin \
    -a</pre>
  <p id="jhVH">Использование точной базы:</p>
  <pre id="yD3T">./asr_debug_monitor.py \
    -m /path/to/exact/cp_MDB.txt</pre>
  <p id="gXAP">Для захвата, переживающего ребут модема, нужен внешний wrapper:</p>
  <ol id="r2E9">
    <li id="luXb">дождаться появления нужного USB-интерфейса;</li>
    <li id="HBc7">открыть порт;</li>
    <li id="oNsb">отправить <code>AT+ECPLOG=1</code>;</li>
    <li id="RCOp">записывать необработанный поток;</li>
    <li id="Fk5F">обнаружить исчезновение порта;</li>
    <li id="DaXC">дождаться повторной USB-энумерации;</li>
    <li id="w0La">открыть новую сессию в отдельном файле.</li>
  </ol>
  <p id="DuEe">Так можно захватывать загрузку модема с самого начала, даже если во время ребута меняются имена <code>ttyUSBx</code>.</p>
  <hr />
  <h2 id="x0s0">Заключение</h2>
  <p id="FRgz">Распознавание бинарного CP-лога ASR состоит из нескольких независимых уровней:</p>
  <pre id="k5L8">USB-порт
  → AT+ECPLOG=1
  → length-framing
  → 16-байтный заголовок
  → msg_type + fmt_id
  → база форматных строк
  → декодирование printf-аргументов
  → структуры, enum и timestamp
  → читаемый трейс</pre>
  <p id="hX32">Самая важная практическая находка — нестандартная упаковка аргументов: обычные <code>%d</code>, <code>%u</code> и <code>%x</code> в terse-сообщениях ASR занимают 2 байта, а <code>%l...</code> — 4 байта. Без этого правила идентификаторы сообщений могут находиться правильно, но значения будут «плыть», а декодер будет регулярно выдавать <code>&lt;?&gt;</code>.</p>
  <p id="0WEU">Второй важный вывод — транспорт и семантику нужно разрабатывать отдельно. Формат записи можно восстановить и протестировать независимо от базы. А база, в свою очередь, может заменяться без изменений в протоколе и потоковом парсере.</p>
  <p id="xDt1">Для полного покрытия остаётся получить точную <code>cp_MDB.txt</code> под прошивку <code>A7665M5_B01V01</code>. После этого должны исчезнуть ошибки в больших структурах, неизвестные enum и большая часть необработанных бинарных хвостов.</p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@pole_sam/CAcPKudWlw4</guid><link>https://teletype.in/@pole_sam/CAcPKudWlw4?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=pole_sam</link><comments>https://teletype.in/@pole_sam/CAcPKudWlw4?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=pole_sam#comments</comments><dc:creator>pole_sam</dc:creator><title>Как используются данные SIM‑карты в GSM (2G): аутентификация, ключи, IMSI, A3/A8</title><pubDate>Sat, 15 Aug 2026 18:36:57 GMT</pubDate><description><![CDATA[Всем привет! В GSM (2G) SIM‑карта хранит постоянный секретный ключ абонента Ki и идентификатор IMSI; при регистрации в сети используется механизм «вызов‑ответ»: сеть посылает случайное число RAND, SIM вычисляет подписанный ответ SRES = A3(Ki, RAND) и сеансовый ключ шифрования Kc = A8(Ki, RAND), после чего сеть проверяет SRES и при успехе включает шифрование A5.]]></description><content:encoded><![CDATA[
  <p id="VDme">Всем привет! В GSM (2G) SIM‑карта хранит постоянный секретный ключ абонента Ki и идентификатор IMSI; при регистрации в сети используется механизм «вызов‑ответ»: сеть посылает случайное число RAND, SIM вычисляет подписанный ответ SRES = A3(Ki, RAND) и сеансовый ключ шифрования Kc = A8(Ki, RAND), после чего сеть проверяет SRES и при успехе включает шифрование A5.</p>
  <p id="uy4r"><strong>Важно: Ki никогда не передаётся по радиоинтерфейсу и не выводится из SIM; вся криптография «вызов‑ответ» выполняется внутри SIM.jucs+2</strong></p>
  <h2 id="2">2. Роли сетевых элементов (кратко)</h2>
  <ul id="Qrmv">
    <li id="dzx6">MS (Mobile Station) = ME (телефон) + SIM.</li>
    <li id="HqES">BTS/BSC — радиодоступ.</li>
    <li id="eIA2">MSC/VLR — коммутация и локальное управление сессиями; VLR хранит «триплеты» аутентификации для абонента.</li>
    <li id="hRa0">HLR — домашний реестр абонентов.</li>
    <li id="ANBL">AuC (Authentication Center) — генерирует RAND, SRES, Kc (триплеты) на основе IMSI и Ki.</li>
  </ul>
  <h2 id="3---gsm-2g">3. Протокол аутентификации GSM (2G): пошагово</h2>
  <p id="VB6Q">Ниже — упрощённая последовательность при регистрации/доступе к услугам.</p>
  <pre id="qj9P">text[MS/SIM]                         [MSC/VLR]                         [HLR/AuC]
    |                                |                                |
    | 1. Location Update / CM Service Request (IMSI или TMSI)         |
    |---------------------------------------------------------------&gt;|
    |                                | 2. Запрос триплетов для IMSI   |
    |                                |------------------------------&gt;|
    |                                | 3. Возврат N триплетов         |
    |                                | (RAND_i, SRES_i, Kc_i)         |
    |                                |&lt;------------------------------|
    |                                |                                |
    | 4. AUTHENTICATION REQUEST      |                                |
    |    (RAND)                      |                                |
    |&lt;-------------------------------|                                |
    | 5. SIM: SRES&#x27; = A3(Ki, RAND)   |                                |
    |     Kc&#x27;   = A8(Ki, RAND)       |                                |
    | 6. AUTHENTICATION RESPONSE     |                                |
    |    (SRES&#x27;)                     |                                |
    |------------------------------&gt;|                                |
    |                                | 7. Проверка: SRES&#x27; == SRES?    |
    |                                |    Если да → аутентификация OK |
    | 8. Cipher Mode Command (A5/x)  |                                |
    |&lt;-------------------------------|                                |
    | 9. Включение шифрования A5/x с Kc&#x27;                               |
    |&lt;================================================================&gt;|</pre>
  <p id="r1XJ">Ключевые моменты:</p>
  <ul id="JYUI">
    <li id="w02J">Сеть заранее получает от AuC «триплеты» (RAND, SRES, Kc) для данного IMSI; при аутентификации она посылает только RAND.</li>
    <li id="3MKM">SIM вычисляет SRES&#x27; и Kc&#x27; по тем же формулам; VLR сравнивает SRES&#x27; с ожидаемым SRES.</li>
    <li id="loVT">После успеха сеть инициирует Cipher Mode Command и далее трафик шифруется A5/x с ключом Kc.</li>
  </ul>
  <h2 id="4--a3--a8">4. Алгоритмы A3 и A8: что именно считается</h2>
  <p id="SMqY">Формально стандарт GSM не фиксирует одну конкретную реализацию A3/A8; операторы выбирают алгоритм (часто COMP128‑1/2/3, milenage‑подобные варианты в 2G‑совместимых реализациях и др.).</p>
  <ul id="w5zA">
    <li id="kg76">A3: SRES = A3(Ki, RAND) → 32‑битный ответ.</li>
    <li id="fNwD">A8: Kc = A8(Ki, RAND) → 64‑битный сеансовый ключ для A5/x.</li>
  </ul>
  <p id="niDa">На практике A3 и A8 часто реализованы как единая функция, выдающая (SRES, Kc) из (Ki, RAND).</p>
  <h2 id="5--imsi">5. Роль IMSI и почему он важен</h2>
  <ul id="MVE8">
    <li id="AHqq">IMSI используется для идентификации абонента в HLR/AuC и получения правильного Ki (и триплетов).</li>
    <li id="LHax">При первой регистрации в новой зоне MS обычно передаёт IMSI (если нет валидного TMSI), что делает IMSI видимым для любой базовой станции в радиусе.</li>
  </ul>
  <ul id="v2Yb">
    <li id="5NY1">В дальнейшем сеть старается использовать TMSI вместо IMSI для конфиденциальности, но IMSI всё равно может быть запрошен (например, при сбоях или специальных процедурах).</li>
    <li id="qoc1">TMSI (Temporary Mobile Subscriber Identity) — это временный идентификатор абонента, который сеть выдаёт мобильному устройству после успешной регистрации, чтобы вместо постоянного IMSI использовать его в радиоканале для повышения конфиденциальности.</li>
  </ul>
  <h2 id="6---gsm-2g---sim">6. Риски безопасности GSM (2G), связанные с SIM и аутентификацией</h2>
  <p id="rEuK">GSM (2G) имеет ряд фундаментальных ограничений, которые напрямую касаются использования данных SIM:</p>
  <ol id="Pgy3">
    <li id="uoRn"><strong>Односторонняя аутентификация</strong></li>
    <ul id="Df8A">
      <li id="4473">Сеть аутентифицирует телефон (MS), но телефон не аутентифицирует сеть.</li>
      <li id="Aoyn">Это позволяет IMSI‑catcher&#x27;у (фейковой базовой станции) притворяться «легальной» сетью: телефон отправляет IMSI/TMSI, проходит «аутентификацию» у злоумышленника, а трафик может не шифроваться или шифроваться слабым алгоритмом.</li>
    </ul>
    <li id="xPOc"><strong>Слабость шифрования A5/1 (и A5/2)</strong></li>
    <ul id="YwyZ">
      <li id="WKaj">A5/1 (64‑битный ключ Kc) уязвим к атакам по заранее вычисленным таблицам (rainbow tables); практическая расшифровка возможна за секунды–минуты на обычном железе.</li>
      <li id="LNgs">A5/2 ещё слабее и практически не защищает.</li>
      <li id="TBQc">Злоумышленник может принудительно переключить сессию на A5/0 (без шифрования) или слабый A5/x.</li>
    </ul>
    <li id="ANCp"><strong>Зависимость от реализации A3/A8</strong></li>
    <ul id="9YIl">
      <li id="n2TF">Ранние популярные реализации (например, COMP128‑1) имели криптографические слабости, позволяющие при достаточном числе запросов извлекать Ki.</li>
      <li id="XaXw">Даже при более стойких A3/A8, 32‑битный SRES и 64‑битный Kc ограничивают криптостойкость по современным меркам.</li>
    </ul>
    <li id="v3LU"><strong>Перехват IMSI</strong></li>
    <ul id="k8J8">
      <li id="XMVy">Поскольку IMSI передаётся в открытом виде при первой регистрации, IMSI‑catcher может собирать IMSI всех устройств в зоне покрытия.</li>
    </ul>
  </ol>
  <p id="VwaX">Эти ограничения — причина, по которой в 3G/4G/5G введена взаимная аутентификация (сеть тоже доказывается абоненту), более длинные ответы (RES вместо SRES), ключи CK/IK и более стойкие алгоритмы (Milenage, AES‑based и др.).</p>
  <h2 id="7----sim">7. Сводная схема «данные SIM → параметры сессии»</h2>
  <pre id="6q7I">textSIM (Ki, IMSI, A3/A8)
       │
       │ IMSI → HLR/AuC → выбор Ki
       │
       └─► AuC: для каждого RAND_i
               SRES_i = A3(Ki, RAND_i)
               Kc_i   = A8(Ki, RAND_i)
               триплет (RAND_i, SRES_i, Kc_i) → VLR

При аутентификации:
  VLR → MS: RAND
  SIM: SRES&#x27; = A3(Ki, RAND)
       Kc&#x27;   = A8(Ki, RAND)
  MS → VLR: SRES&#x27;
  VLR: если SRES&#x27; == SRES → OK, шифрование A5/x с Kc&#x27;</pre>
  <hr />

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@pole_sam/MUgk4kD28C5</guid><link>https://teletype.in/@pole_sam/MUgk4kD28C5?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=pole_sam</link><comments>https://teletype.in/@pole_sam/MUgk4kD28C5?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=pole_sam#comments</comments><dc:creator>pole_sam</dc:creator><title>Данные вещательных каналов сотовой вышки (2G/GSM и LTE): что можно получить и как декодировать</title><pubDate>Mon, 03 Aug 2026 07:11:42 GMT</pubDate><description><![CDATA[Сотовая вышка (BTS в 2G, eNodeB в LTE) непрерывно передаёт в эфир «системную информацию» — набор параметров, которые нужны телефону (UE), чтобы обнаружить сеть, синхронизироваться и подготовиться к подключению. Это не «перехват трафика» — это открытые широковещательные (broadcast) данные, которые предназначены для приёма всеми устройствами в зоне покрытия. Их декодирование полностью пассивно, законно в большинстве юрисдикций и не затрагивает трафик абонентов.]]></description><content:encoded><![CDATA[
  <p id="aRmQ">Сотовая вышка (BTS в 2G, eNodeB в LTE) непрерывно передаёт в эфир «системную информацию» — набор параметров, которые нужны телефону (UE), чтобы обнаружить сеть, синхронизироваться и подготовиться к подключению. Это не «перехват трафика» — это открытые широковещательные (broadcast) данные, которые предназначены для приёма всеми устройствами в зоне покрытия. Их декодирование полностью пассивно, законно в большинстве юрисдикций и не затрагивает трафик абонентов.</p>
  <p id="ytlS">Ниже — какие каналы есть, что они несут и что из этого можно понять о вышке и сети.</p>
  <hr />
  <h2 id="1">1. Что вообще вещает вышка</h2>
  <p id="MJ6S">Любая вышка передаёт на нисходящей частоте (downlink) набор логических каналов. Для пассивного наблюдателя важны два класса:</p>
  <ul id="35bH">
    <li id="jnhI"><strong>Каналы синхронизации</strong> — позволяют обнаружить сигнал, получить тайминг и идентификатор соты.</li>
    <li id="WETF"><strong>Вещательные каналы</strong> (BCCH в 2G, BCH/DL-SCH в LTE) — несут системную информацию (System Information) с параметрами сети.</li>
  </ul>
  <p id="o79B">Телефон при включении делает ровно то же самое: ищет сигнал синхронизации, читает системную информацию и решает, можно ли подключиться. С помощью SDR (Software Defined Radio) и открытых инструментов этот процесс можно повторить без телефона.</p>
  <hr />
  <h2 id="2-gsm-2g">2. GSM (2G)</h2>
  <h2 id="4okX">Каналы и сообщения</h2>
  <p id="J1Kn">Вещательные данные GSM передаются на <strong>BCCH</strong> (Broadcast Control Channel), который передаётся непрерывно и служит стабильным опорным «маяком» соты. Основное содержимое BCCH — это сообщения <strong>System Information (SI)</strong>, описанные в <a href="https://www.etsi.org/deliver/etsi_ts/144000_144099/144018/17.00.00_60/ts_144018v170000p.pdf" target="_blank">ETSI TS 144 018</a> (3GPP TS 44.018). </p>
  <h2 id="bcch">Что можно извлечь из BCCH</h2>
  <ul id="4uKZ">
    <li id="Llja"><strong>MCC</strong> (Mobile Country Code) — страна оператора</li>
    <li id="R8mq"><strong>MNC</strong> (Mobile Network Code) — сам оператор</li>
    <li id="qBqZ"><strong>LAC</strong> (Location Area Code) — код зоны местоположения</li>
    <li id="gMDE"><strong>Cell ID (CI)</strong> — идентификатор соты → вместе с MCC+MNC+LAC+CI образует <strong>CGI</strong> (Cell Global Identity)</li>
    <li id="qGAb"><strong>ARFCN</strong> — номер радиоканала → пересчитывается в частоту</li>
    <li id="ZhGq"><strong>BSIC</strong> (Base Station Identity Code) — цветовой код вышки</li>
    <li id="CDVM"><strong>CCCH конфигурация</strong> — сколько каналов paging, AGCH и т.д.</li>
    <li id="Kqfb"><strong>Параметры RACH</strong> — как телефону запрашивать доступ</li>
    <li id="SF4w"><strong>Параметры выбора соты</strong> — минимальный уровень приёма (C1/C2), приоритеты</li>
    <li id="yuDJ"><strong>Список соседей</strong> — частоты соседних сот (для handover/reselection)</li>
    <li id="qVYn"><strong>Признак GPRS/EDGE</strong> — поддерживает ли сота пакетную передачу</li>
    <li id="dznm"><strong>Cell Barring</strong> — заблокирована ли сота для доступа</li>
    <li id="KGy2"><strong>Информация о 3G-соседях</strong> — частоты UMTS</li>
  </ul>
  <h2 id="pvZR">Синхронизация</h2>
  <p id="0KwF">До чтения BCCH нужно сначала найти сигнал. В GSM для этого служат:</p>
  <ul id="GAA5">
    <li id="sECW"><strong>FCCH</strong> (Frequency Correction Channel) — чистый синус на фиксированном смещении, позволяет подстроить частоту приёмника.</li>
    <li id="faTb"><strong>SCH</strong> (Synchronisation Channel) — несёт <strong>BSIC</strong> и номер кадра, позволяет выровнять тайминг.</li>
  </ul>
  <hr />
  <h2 id="3-lte-4g">3. LTE (4G)</h2>
  <h2 id="YrkS">Каналы и сообщения</h2>
  <p id="bWmS">В LTE системная информация разделена на <strong>MIB</strong> (Master Information Block) и набор <strong>SIB</strong> (System Information Blocks), описанные в <a href="https://www.3glteinfo.com/messages/lte/lte-rrc/" target="_blank">3GPP TS 36.331</a>.</p>
  <h2 id="mib">MIB</h2>
  <p id="9cBk">Передаётся на <strong>PBCH</strong> (Physical Broadcast Channel) с периодичностью 40 мс (с повторениями каждые 10 мс). Содержит самый минимум:</p>
  <ul id="mRpc">
    <li id="XPzT"><strong>DL Bandwidth</strong> соты (в числе Resource Blocks: 1.4 / 3 / 5 / 10 / 15 / 20 МГц)</li>
    <li id="qkIW"><strong>PHICH</strong> конфигурация</li>
    <li id="TSb4"><strong>SFN</strong> (System Frame Number)</li>
  </ul>
  <h2 id="sib1">SIB1</h2>
  <p id="zNrI">Передаётся на DL-SCH с периодичностью 80 мс (с повторениями). Несёт:</p>
  <ul id="S5JF">
    <li id="zkK3"><strong>PLMN identity list</strong> (MCC+MNC, может быть несколько)</li>
    <li id="DWYU"><strong>Tracking Area Code (TAC)</strong></li>
    <li id="sYoX"><strong>Cell Identity</strong> — 28-битный E-UTRAN Cell Identifier (ECI). В макросетях часто раскладывается на eNodeB ID (старшие 20 бит) и локальный/секторный Cell ID (младшие 8 бит), но это не универсальное правило</li>
    <li id="HegO"><strong>Cell bar status</strong> (доступна ли сота)</li>
    <li id="1Pxm"><strong>Cell selection info</strong> (минимальный требуемый уровень приёма, Q-RxLevMin)</li>
    <li id="mnBe"><strong>p-Max</strong> — максимальная разрешённая мощность UE</li>
    <li id="e262"><strong>Frequency band indicator</strong></li>
    <li id="iTlj"><strong>TDD конфигурация</strong> (если TDD)</li>
    <li id="Schg"><strong>SI-window length</strong> и <strong>schedulingInfoList</strong> — расписание остальных SIB</li>
    <li id="14cD"><strong>System info value tag</strong> — версия информации</li>
  </ul>
  <h2 id="sib">Остальные SIB</h2>
  <p id="6Nh1">Передаются в сообщениях SystemInformation (SI) по расписанию из SIB1:</p>
  <p id="V15w"><strong>SIB2</strong></p>
  <p id="kzvV">Общая конфигурация радио: параметры RACH, PRACH, UL-частота, ширина UL-канала</p>
  <p id="HXGN"><strong>SIB3</strong></p>
  <p id="smxn">Параметры reselection внутри той же частоты</p>
  <p id="m5Uu"><strong>SIB4</strong></p>
  <p id="Nc2Q">Информация о соседях intra-frequency</p>
  <p id="VVSn"><strong>SIB5</strong></p>
  <p id="AlF6">Информация о соседях inter-frequency (другие частоты LTE)</p>
  <p id="2s0k"><strong>SIB6</strong></p>
  <p id="pT1W">Информация о соседях 3G (UTRA)</p>
  <p id="32EY"><strong>SIB7</strong></p>
  <p id="MbiJ">Информация о соседях 2G (GERAN)</p>
  <p id="CEze"><strong>SIB8</strong></p>
  <p id="CaL0">Информация о соседях CDMA2000</p>
  <p id="XVyR"><strong>SIB9</strong></p>
  <p id="9De5">Home eNodeB identifier (femtocell)</p>
  <p id="CqlW"><strong>SIB10</strong></p>
  <p id="IPfu">ETWS — первичное уведомление (Earthquake &amp; Tsunami Warning)</p>
  <p id="MJCC"><strong>SIB11</strong></p>
  <p id="7udv">ETWS — вторичное уведомление</p>
  <p id="gsFi"><strong>SIB12</strong></p>
  <p id="WhPS">CMAS — коммерческие предупреждения (Cell Broadcast)</p>
  <p id="JZCV"><strong>SIB13</strong></p>
  <p id="a3oO">MBMS/MBSFN-конфигурация</p>
  <p id="iGBQ"><strong>SIB14</strong></p>
  <p id="AX3y">Extended Access Barring (EAB)</p>
  <p id="bJB2"><strong>SIB15</strong></p>
  <p id="V0tN">MBMS Service Area Info</p>
  <p id="DuMz"><strong>SIB16</strong></p>
  <p id="uNqO">Информация о времени (GPS/UTC)</p>
  <hr />
  <h2 id="4">4. Как это декодируется на практике (концептуальный пайплайн)</h2>
  <pre id="A4JK">textSDR-приёмник (I/Q) → настройка на частоту → синхронизация → демодуляция → L2/L3 декодирование → Wireshark / ASN.1</pre>
  <h2 id="gsm-2g">Инструменты для GSM (2G)</h2>
  <ul id="R95X">
    <li id="5MoA"><strong>gr-gsm</strong> (GNU Radio GSM decoder) — основной инструмент. Компоненты:</li>
    <ul id="ZXLZ">
      <li id="01gM"><code>grgsm_scanner</code> — сканирование эфира, поиск вышек, вывод ARFCN, BSIC, Cell ID</li>
      <li id="xGyf"><code>grgsm_capture</code> — запись сигнала в .cfile</li>
      <li id="NTUX"><code>grgsm_decode</code> — декодирование BCCH, SDCCH и др. → GSMTAP в Wireshark</li>
      <li id="aj9f"><code>grgsm_livemon</code> — интерактивный мониторинг одного канала в реальном времени</li>
    </ul>
    <li id="GIBQ"><strong>Wireshark</strong> — анализ декодированных пакетов с фильтром <code>gsmtap</code></li>
    <li id="jqwn"><strong>DragonOS</strong> — готовый Linux-дистрибутив со всем предустановленным</li>
  </ul>
  <p id="BNT1">(<a href="https://github.com/ptrkrysik/gr-gsm/wiki/Usage:-Decoding-How-To" target="_blank">gr-gsm Wiki: Usage</a>, <a href="https://www.rtl-sdr.com/rtl-sdr-tutorial-analyzing-gsm-with-airprobe-and-wireshark/comment-page-1/" target="_blank">RTL-SDR Tutorial</a>)</p>
  <h2 id="lte-4g">Инструменты для LTE (4G)</h2>
  <ul id="hgFk">
    <li id="gIB0"><strong>srsRAN</strong> (бывший srsLTE) — открытый стек 4G/5G. Утилита <code>cell_search</code> находит соты и получает параметры из MIB; для декодирования SIB нужны специальные receive-only конфигурации/примеры srsRAN, LTE-Cell-Scanner или обёртки вроде lte-sib-parser. <code>srsue</code> в режиме только приёма может парсить SIB2–SIB6.</li>
    <li id="bgOg"><strong>LTE-Cell-Scanner</strong> — OpenCL-ускоренный сканер от JiaoXianjun: от I/Q-сэмплов до SIB-сообщений в ASN.1. Поддерживает FDD и TDD, RTL-SDR/HackRF/BladeRF.</li>
    <li id="jAFF"><strong>lte-sib-parser</strong> — обёртка над cell_search и srsue для рекурсивного сканирования по SIB5.</li>
    <li id="T7cP"><strong>free5GRAN</strong> — для 5G NR (MIB + SIB1).</li>
  </ul>
  <p id="PiUa">(<a href="https://github.com/JiaoXianjun/LTE-Cell-Scanner" target="_blank">LTE-Cell-Scanner на GitHub</a>, <a href="https://docs.srsran.com/projects/4g/en/latest/app_notes/source/nbiot/source/index.html" target="_blank">srsRAN docs</a>, <a href="https://github.com/godfuzz3r/lte-sib-parser" target="_blank">lte-sib-parser</a>)</p>
  <hr />
  <h2 id="5"><strong>Что это даёт на практике</strong></h2>
  <ul id="vOM0">
    <li id="sOp2"><strong>Идентификация оператора и страны</strong> по MCC+MNC.</li>
    <li id="NUfU"><strong>Картирование покрытия</strong> — зная Cell ID + частоту + уровень сигнала, можно составить карту расположения и охвата базовых станций (без доступа к базам операторов). Сторонние проекты вроде <a href="https://opencellid.org" target="_blank">OpenCelliD</a> и <a href="https://cellmapper.net" target="_blank">CellMapper</a> агрегируют именно такие данные.</li>
    <li id="PAqV"><strong>Понимание топологии сети</strong> — какие соты являются соседями, какие частоты используются в зоне, какие поколения (2G/3G/LTE) присутствуют.</li>
    <li id="3Cwh"><strong>Оценка нагрузки</strong> — косвенно, по количеству paging-сообщений (но это уже не чисто broadcast, аpaging-канал, требующий чуть более активного прослушивания CCCH).</li>
    <li id="lcAw"><strong>Проверка конфигурации</strong> — для исследовательских и образовательных целей: какая ширина канала, какие параметры RACH, закрыта ли сота и т.д.</li>
    <li id="tQJB"><strong>Косвенная оценка активности</strong> — по объёму paging-трафика можно грубо судить о нагрузке, но это не системная информация и не позволяет отслеживать конкретных абонентов.</li>
  </ul>
  <hr />
  <h2 id="6---broadcast">6. Чего из broadcast-данных получить нельзя</h2>
  <p id="HGWU">Важно зафиксировать границы:</p>
  <ul id="feIE">
    <li id="GVDG"><strong>GPS-координаты вышки</strong> не передаются в эфир. Координаты можно определить только косвенно — по триангуляции сигнала или по внешним базам данных (OpenCelliD).</li>
    <li id="RBrj"><strong>Содержимое звонков, SMS и интернет-трафика</strong> — не относится к broadcast и может быть зашифровано. В GSM может использоваться A5/0 (без шифрования), A5/1 или A5/3; в LTE — SNOW 3G или AES. Перехват и расшифровка трафика выходят за рамки данной заметки.</li>
    <li id="NgN6"><strong>IMSI абонентов</strong> не передаётся в broadcast/System Information. В 2G/3G/LTE постоянный идентификатор может появляться в отдельных процедурах идентификации до установления защиты, но это не вещательные данные. В 5G для этого используется SUCI.</li>
    <li id="DYr9"><strong>Точная нагрузка/количество абонентов</strong> — broadcast не несёт такой информации. Paging-канал может дать лишь грубую оценку совокупной активности сети и не является средством отслеживания конкретных абонентов.</li>
    <li id="VE1L"><strong>Точное местоположение абонента</strong> — требует данных из ядра сети (core network) или активного слежения за конкретным устройством, что выходит далеко за рамки пассивного декодирования вещательных каналов.</li>
  </ul>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@pole_sam/t8AbMPpD8pM</guid><link>https://teletype.in/@pole_sam/t8AbMPpD8pM?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=pole_sam</link><comments>https://teletype.in/@pole_sam/t8AbMPpD8pM?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=pole_sam#comments</comments><dc:creator>pole_sam</dc:creator><title>POLQA и с чем его едят</title><pubDate>Sat, 18 Jul 2026 18:53:47 GMT</pubDate><description><![CDATA[<img src="https://img1.teletype.in/files/89/f5/89f55740-20d0-46f0-846c-51831fb28972.png"></img>Привет всем! Сегодня постараюсь рассказать о POLQA.
POLQA расшифровывается как Perceptual Objective Listening Quality Analysis. Это full-reference метод: он сравнивает эталонный речевой сигнал с ухудшенной версией после прохождения через сеть, кодек или устройство, а затем вычисляет оценку, близкую к MOS — Mean Opinion Score. Проще говоря, POLQA пытается математически смоделировать то, как человеческое ухо и восприятие оценивают качество речи.]]></description><content:encoded><![CDATA[
  <p id="gnUR">Привет всем! Сегодня постараюсь рассказать о POLQA.<br />POLQA расшифровывается как <em>Perceptual Objective Listening Quality Analysis</em>. Это full-reference метод: он сравнивает эталонный речевой сигнал с ухудшенной версией после прохождения через сеть, кодек или устройство, а затем вычисляет оценку, близкую к MOS — Mean Opinion Score. Проще говоря, POLQA пытается математически смоделировать то, как человеческое ухо и восприятие оценивают качество речи.</p>
  <h3 id="kHVZ"><br /><strong>Зачем он нужен</strong></h3>
  <p id="dx8b">Главная ценность POLQA в том, что он помогает измерять качество голоса <strong>воспроизводимо и сравнимо</strong> между лабораториями, устройствами и сценариями тестирования. Его используют для бенчмаркинга сетей, проверки кодеков, оптимизации VoIP/VoLTE и диагностики проблем с голосом в канале связи. Это особенно важно, когда качество портят не только потери пакетов, но и джиттер, компенсация задержек, time-scaling, шумоподавление и другие современные механизмы обработки.</p>
  <h3 id="X2PH"><br />Что было раньше</h3>
  <p id="42Kf">До POLQA в инженерной практике использовали разные методы объективной оценки качества речи, включая простые энергетические и шумовые метрики, а также более продвинутые психоакустические модели; однако они либо слабо коррелировали с субъективным восприятием, либо были ограничены узким классом искажений. Затем появился PESQ, который стал важным шагом вперед благодаря моделированию слухового восприятия.</p>
  <h3 id="lk6M">Как возник POLQA</h3>
  <p id="OlFK"><br />POLQA создавался как ответ на эти ограничения, то есть преемник PESQ, с акцентом на более точное моделирование восприятия речи в условиях современных телекоммуникаций. PESQ был спроектирован в первую очередь для <strong>узкополосной телефонной речи</strong> и сравнительно стабильных соединений, а не для современных сетей с переменной задержкой, джиттером и более широким частотным диапазоном. PESQ хорошо решал задачу оценки качества для классических телефонных и кодековых сценариев, но в VoIP и IP-сетях сигнал часто сталкивается с <strong>непостоянной задержкой</strong> и адаптацией jitter buffer, из-за чего точное выравнивание эталонного и тестового сигнала становится сложнее. Кроме того, в новых сетях ухудшения связаны не только с кодированием, но и с пакетными потерями, перепаковкой, временными сдвигами и другими эффектами передачи по IP, которые выходят за рамки более ранней модели применения PESQ</p>
  <h2 id="5it9">История создания</h2>
  <p id="ipev">Работы над POLQA начались в ITU-T Study Group 12 в 2006 году под рабочим названием P.OLQA. В 2009–2010 годах прошёл конкурс кандидатов. Лучшие модели от трёх компаний — <strong>OPTICOM</strong> (Германия), <strong>SwissQual</strong> (Rohde &amp; Schwarz, Швейцария) и <strong>TNO</strong> (Нидерланды) — были объединены в единый алгоритм.</p>
  <ul id="U6QE">
    <li id="l8up">Первая редакция — 2011 год.</li>
    <li id="Owkn">Вторая (исправления и улучшения) — 2014 год.</li>
    <li id="USzp">Третья (POLQA v3 / Edition 3) — 2018 год (актуальная), с поддержкой Full Band и оптимизацией под современные сети.</li>
  </ul>
  <p id="GmJM">POLQA разработан специально для новых реалий: VoIP, HD Voice, 3G/4G/VoLTE, 5G, современных кодеков (EVS, Opus и др.).</p>
  <h3 id="xBAw">Основные характеристики</h3>
  <ul id="WiMx">
    <li id="qXU0"><strong>Тип алгоритма</strong>: Full Reference (FR) — требует и reference-сигнал (чистый), и degraded-сигнал (после прохождения через систему).</li>
    <li id="z7JH"><strong>Диапазоны</strong>:</li>
    <ul id="4JQ2">
      <li id="pWdB">Narrowband (NB): 300–3400 Гц (классическая телефония).</li>
      <li id="sbBY">Super-Wideband (SWB): до 14 кГц (HD Voice).</li>
      <li id="ckrD">Full Band (FB, с v3): до 20–24 кГц (48 кГц сэмплирование).</li>
    </ul>
    <li id="N7UG"><strong>Выход</strong>: MOS-LQO (Listening Quality Objective) от 1 (плохо) до 5 (отлично). Также дополнительные индикаторы.</li>
  </ul>
  <h3 id="JvLU">Как работает POLQA (высокоуровнево)</h3>
  <p id="Am7n">Алгоритм состоит из двух основных частей:</p>
  <ol id="ONsb">
    <li id="pe4n"><strong>Temporal Alignment (временное выравнивание)</strong>:</li>
    <ul id="Rucq">
      <li id="0FKJ">Решает сложные проблемы VoIP: внезапные скачки задержки (shift-jitter), постепенное time-scaling (растяжение/сжатие речи), packet loss и т.д.</li>
      <li id="nkpI">Использует macro frame, корреляцию, backtracking (аналог Viterbi алгоритма), обработку pitch-preserving техник (типа PSOLA).</li>
      <li id="OU2F">Оценивает sample rate differences и компенсирует их.</li>
    </ul>
    <li id="bNnX"><strong>Perceptual Model (психоакустическая модель)</strong>:</li>
    <ul id="clVW">
      <li id="T2VU">Моделирует человеческое слуховое восприятие: преобразование в Bark-scale (критические полосы), loudness (Sone), masking effects (частотное и временное).</li>
      <li id="s8my"><strong>Idealization</strong>: &quot;идеализирует&quot; reference-сигнал (убирает мелкие шумы, корректирует timbre/level), чтобы имитировать Absolute Category Rating (ACR) тесты, где слушатели оценивают без прямого сравнения.</li>
      <li id="v0Uq">Вычисляет disturbance density (плотность искажений) отдельно для additive и subtractive distortion, с учётом сильных и слабых эффектов.</li>
      <li id="SrRI">Cognitive model преобразует perceptibility в annoyance (раздражаемость), учитывая level variations, reverberation, noise и т.д.</li>
      <li id="leSV">Финальный mapping в MOS-LQO с полиномиальной коррекцией.</li>
    </ul>
  </ol>

]]></content:encoded></item></channel></rss>