Skip to content

Commit 0746d81

Browse files
committed
Identifiy Int16BE in V1 extractor
1 parent bb38ece commit 0746d81

5 files changed

Lines changed: 197 additions & 25 deletions

File tree

libmdr/include/mdr/ProtocolV1T1.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1096,8 +1096,8 @@ namespace mdr::v1::t1
10961096
{
10971097
// CODEGEN Ignore OUT_OF_RANGE is expected
10981098
AtCommandMessageType messageType{}; // 0x0
1099-
UInt8 commandLength{}; // 0x1
1100-
MDRPrefixedString command2; // 0x2
1099+
Int16BE commandLength{}; // 0x1
1100+
MDRPrefixedString command2; // 0x3
11011101
// CODEGEN Ignore OUT_OF_RANGE is expected
11021102
AtCommandMessageType messageType2{AtCommandMessageType::REQUEST};
11031103
UInt8 commandLength2{};
@@ -3687,7 +3687,7 @@ namespace mdr::v1::t1
36873687
Command command{Command::CONNECT_RET_PROTOCOL_INFO}; // 0x0
36883688
// CODEGEN Ignore OUT_OF_RANGE is expected
36893689
CommonCapabilityInquiredType type{}; // 0x1
3690-
UInt8 protocolVersion{}; // 0x2
3690+
Int16BE protocolVersion{}; // 0x2
36913691

36923692
MDR_DEFINE_TRIVIAL_SERIALIZATION(RetProtocolInfo);
36933693
};

libmdr/src/Headphones.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ namespace mdr
301301
{
302302
return SetLastError(
303303
MDR_RESULT_ERROR_MALFORMED_PAYLOAD,
304-
"CONNECT_RET_PROTOCOL_INFO must be 3 bytes (V1) or 8 bytes (V2)");
304+
"CONNECT_RET_PROTOCOL_INFO size is not valid");
305305
}
306306

307307
Awake(AWAIT_PROTOCOL_INFO);

tooling/ida/generated/v1_t1.json

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8403,7 +8403,9 @@
84038403
"cpp_name": "AtCommandParam",
84048404
"discriminator_field": null,
84058405
"discriminator_value": null,
8406-
"evidence": [],
8406+
"evidence": [
8407+
"parser reads commandLength from local bytes 1..2"
8408+
],
84078409
"factory_signature": null,
84088410
"fields": [
84098411
{
@@ -8416,7 +8418,7 @@
84168418
"wire_kind": "pod"
84178419
},
84188420
{
8419-
"cpp_type": "UInt8",
8421+
"cpp_type": "Int16BE",
84208422
"default": "{}",
84218423
"name": "commandLength",
84228424
"offset": 1,
@@ -8428,7 +8430,7 @@
84288430
"cpp_type": "MDRPrefixedString",
84298431
"default": null,
84308432
"name": "command2",
8431-
"offset": 2,
8433+
"offset": 3,
84328434
"semantic_rules": [],
84338435
"source_type": "LNSString;",
84348436
"wire_kind": "string"
@@ -24854,6 +24856,8 @@
2485424856
"discriminator_field": null,
2485524857
"discriminator_value": null,
2485624858
"evidence": [
24859+
"parser reads protocolVersion from local bytes 2..3",
24860+
"serializer writes 2 byte(s) for protocolVersion",
2485724861
"_create_JavaIoByteArrayOutputStream_init",
2485824862
"writeWithInt:",
2485924863
"_JreThrowNullPointerException",
@@ -24882,7 +24886,7 @@
2488224886
"wire_kind": "pod"
2488324887
},
2488424888
{
24885-
"cpp_type": "UInt8",
24889+
"cpp_type": "Int16BE",
2488624890
"default": "{}",
2488724891
"name": "protocolVersion",
2488824892
"offset": 2,
@@ -25813,10 +25817,10 @@
2581325817
"writeWithInt:",
2581425818
"-[THMSGV1T1RetSystemCapability_AssignableSettingsCapability getType]",
2581525819
"_JreThrowNullPointerException",
25816-
"writeToWithJavaIoByteArrayOutputStream:",
2581725820
"-[THMSGV1T1AsCapabilityKeyList writeToWithJavaIoByteArrayOutputStream:]",
2581825821
"_create_JavaLangUnsupportedOperationException_init",
25819-
"_objc_exception_throw"
25822+
"_objc_exception_throw",
25823+
"writeToWithJavaIoByteArrayOutputStream:"
2582025824
],
2582125825
"factory_signature": null,
2582225826
"fields": [
@@ -37141,12 +37145,12 @@
3714137145
"-[THMSGV1T1TestCommandType byteCode]",
3714237146
"-[THMSGV1T1TargetType byteCode]",
3714337147
"sub_1012CA5A4",
37148+
"writeToWithJavaIoByteArrayOutputStream:",
3714437149
"-[THMSGV1T1AtCommandParam writeToWithJavaIoByteArrayOutputStream:]",
3714537150
"-[THMSGV1T1AtCommandMessageType byteCode]",
3714637151
"sub_1012CB8FC",
3714737152
"_JavaUtilArrays_copyOfRangeWithByteArray_withInt_withInt_",
37148-
"writeWithByteArray:withInt:withInt:",
37149-
"writeToWithJavaIoByteArrayOutputStream:"
37153+
"writeWithByteArray:withInt:withInt:"
3715037154
],
3715137155
"factory_signature": null,
3715237156
"fields": [

tooling/ida/j2objc/extractors.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,170 @@ def _getter_span(
479479
or self._getter_byte_span(getter)
480480
)
481481

482+
@staticmethod
483+
def _field_ivar_name(field: FieldDecl) -> str:
484+
return f"m{field.name[:1].upper()}{field.name[1:]}_"
485+
486+
def _serializer_primitive_width(
487+
self,
488+
serializer: JavaMethod | MethodSymbol | None,
489+
field: FieldDecl,
490+
) -> int | None:
491+
if serializer is None:
492+
return None
493+
text = self._decompile_text(serializer)
494+
ivar = re.escape(self._field_ivar_name(field))
495+
writes = re.findall(
496+
r"objc_msgSend\([^;]*?\"writeWithInt:\"\s*,\s*([^;]*?)\);",
497+
text,
498+
re.DOTALL,
499+
)
500+
matching = [
501+
expression
502+
for expression in writes
503+
if re.search(rf"(?:self|[A-Za-z_][A-Za-z0-9_]*)->{ivar}\b",
504+
expression)
505+
]
506+
return len(matching) if 1 <= len(matching) <= 8 else None
507+
508+
def _parser_primitive_span(
509+
self,
510+
parser: JavaMethod,
511+
field: FieldDecl,
512+
) -> tuple[int, int] | None:
513+
text = self._decompile_text(parser)
514+
ivar = re.escape(self._field_ivar_name(field))
515+
target = re.search(
516+
rf"(?:self|[A-Za-z_][A-Za-z0-9_]*)->{ivar}\s*=\s*([^;]+);",
517+
text,
518+
)
519+
if target is None:
520+
return None
521+
assignments = {
522+
name: expression
523+
for name, expression in re.findall(
524+
r"(?m)^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*([^;]+);",
525+
text,
526+
)
527+
}
528+
529+
def byte_indices(expression: str, seen: set[str]) -> set[int]:
530+
indices = {
531+
int(value, 0)
532+
for value in re.findall(
533+
r"IOSByteArray_buffer_\s*\+\s*"
534+
r"(0x[0-9A-Fa-f]+|\d+)",
535+
expression,
536+
)
537+
}
538+
if re.search(r"IOSByteArray_buffer_\s*\]", expression):
539+
indices.add(0)
540+
for name in re.findall(r"\b[A-Za-z_][A-Za-z0-9_]*\b",
541+
expression):
542+
if name in seen or name not in assignments:
543+
continue
544+
indices.update(
545+
byte_indices(assignments[name], {*seen, name})
546+
)
547+
return indices
548+
549+
indices = sorted(byte_indices(target.group(1), set()))
550+
if not indices or indices != list(range(indices[0], indices[-1] + 1)):
551+
return None
552+
return indices[0], len(indices)
553+
554+
def _primitive_span_evidence(
555+
self,
556+
class_name: str,
557+
serializer: JavaMethod | MethodSymbol | None,
558+
parsers: list[JavaMethod],
559+
fields: list[FieldDecl],
560+
) -> tuple[list[FieldDecl], dict[str, int], list[str]]:
561+
output: list[FieldDecl] = []
562+
offsets: dict[str, int] = {}
563+
evidence: list[str] = []
564+
shift = 0
565+
for field in fields:
566+
adjusted_offset = (
567+
field.offset + shift if field.offset is not None else None
568+
)
569+
descriptor = (field.source_type or "").lstrip("+-")
570+
if descriptor not in ("I", "J"):
571+
output.append(replace(field, offset=adjusted_offset))
572+
continue
573+
serializer_width = self._serializer_primitive_width(
574+
serializer, field
575+
)
576+
parser_spans = {
577+
span
578+
for parser in parsers
579+
if (span := self._parser_primitive_span(parser, field))
580+
is not None
581+
}
582+
if len(parser_spans) > 1:
583+
raise ExtractionError(
584+
f"contradictory parser spans for "
585+
f"{class_name}.{field.name}: "
586+
f"{sorted(parser_spans)}"
587+
)
588+
parser_span = next(iter(parser_spans), None)
589+
parser_width = parser_span[1] if parser_span is not None else None
590+
widths = {
591+
width
592+
for width in (serializer_width, parser_width)
593+
if width is not None
594+
}
595+
if len(widths) > 1:
596+
raise ExtractionError(
597+
f"serializer/parser width mismatch for "
598+
f"{class_name}.{field.name}: "
599+
f"serializer={serializer_width}, parser={parser_width}"
600+
)
601+
if not widths:
602+
output.append(replace(field, offset=adjusted_offset))
603+
continue
604+
width = next(iter(widths))
605+
sized = self._sized_primitive(descriptor, width)
606+
if sized is None:
607+
raise ExtractionError(
608+
f"unsupported inferred width {width} for "
609+
f"{class_name}.{field.name} ({descriptor})"
610+
)
611+
if sized == field.cpp_type:
612+
output.append(replace(field, offset=adjusted_offset))
613+
continue
614+
# Nested parsers receive a child slice after its discriminator,
615+
# so parser indices prove width but are not always absolute wire
616+
# offsets. Preserve the declaration's accumulated offset.
617+
start = adjusted_offset
618+
old_size = _FIXED_SIZES.get(field.cpp_type)
619+
if old_size is None:
620+
output.append(replace(field, offset=start))
621+
continue
622+
output.append(
623+
replace(
624+
field,
625+
cpp_type=sized,
626+
wire_kind="pod",
627+
offset=start,
628+
)
629+
)
630+
if start is not None:
631+
offsets[field.name] = start
632+
shift += width - old_size
633+
if parser_span is not None:
634+
evidence.append(
635+
f"parser reads {field.name} from local bytes "
636+
f"{parser_span[0]}.."
637+
f"{parser_span[0] + parser_span[1] - 1}"
638+
)
639+
if serializer_width is not None:
640+
evidence.append(
641+
f"serializer writes {serializer_width} byte(s) "
642+
f"for {field.name}"
643+
)
644+
return output, offsets, evidence
645+
482646
def _fixed_count_from_getter(self, getter: JavaMethod) -> int | None:
483647
text = self._decompile_text(getter)
484648
if '"size"' not in text and " size" not in text:
@@ -2606,6 +2770,21 @@ def extract_payload(
26062770
getter_offsets[name] = static_offset
26072771
field_evidence.extend(static_evidence)
26082772

2773+
raw_fields, primitive_offsets, primitive_evidence = (
2774+
self._primitive_span_evidence(
2775+
class_name, serializer_method, parser_methods, raw_fields
2776+
)
2777+
)
2778+
for name, primitive_offset in primitive_offsets.items():
2779+
known_offset = getter_offsets.get(name)
2780+
if known_offset is not None and known_offset != primitive_offset:
2781+
raise ExtractionError(
2782+
f"contradictory offsets for {class_name}.{name}: "
2783+
f"inferred={known_offset}, parser={primitive_offset}"
2784+
)
2785+
getter_offsets[name] = primitive_offset
2786+
field_evidence.extend(primitive_evidence)
2787+
26092788
if (
26102789
serializer_method is None
26112790
and not has_command_stream

tooling/ida/j2objc/render.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,9 @@ def _render_function_type_helpers(
2727
enum: EnumDecl,
2828
table: int,
2929
) -> list[str]:
30-
alias = f"FunctionType_Table{table}"
3130
qualified = f"t{table}::FunctionType"
3231
lines = [
33-
f" static const char* format_as({alias} value)",
32+
f" static const char* format_as({qualified} value)",
3433
" {",
3534
f" using enum {qualified};",
3635
" switch (value)",
@@ -46,7 +45,7 @@ def _render_function_type_helpers(
4645
" }",
4746
" }",
4847
"",
49-
f" static bool is_valid({alias} value)",
48+
f" static bool is_valid({qualified} value)",
5049
" {",
5150
f" using enum {qualified};",
5251
" switch (value)",
@@ -102,16 +101,6 @@ def _render_v2_shared_types(
102101
f" }} // namespace t{table}",
103102
]
104103
)
105-
for table in (1, 2):
106-
lines.extend(
107-
[
108-
"",
109-
# Keep source compatibility and make the parent namespace
110-
# overloads available to cross-table payload fields.
111-
f" using FunctionType_Table{table} = "
112-
f"t{table}::FunctionType;",
113-
]
114-
)
115104
for table in (1, 2):
116105
lines.append("")
117106
lines.extend(

0 commit comments

Comments
 (0)