← Blog

JOJO’S SHENANIGANS

Por dentro do Mibo, parte 2: túneis P2P, decoders nativos e o nascimento do Noctua

Da sinalização UDP ao áudio e vídeo no Noctua: datagramas, PTCP, bridges C, filas e os erros que a implementação nos obrigou a entender.

Caminho do túnel P2P aos decoders nativos e ao Noctua.

TL;DR: o que foi construído#

Chegamos a vídeo e áudio no Noctua usando um SDK independente; transformar esse resultado em um aplicativo e migrar para o player da Core revelou problemas que o primeiro frame não mostrava.

Para 12 câmeras, o Mibo Cloud para Android anuncia pacotes de R$ 193,90 a R$ 439,90 por mês, conforme o prazo de retenção. O serviço guarda eventos, não vídeo contínuo. Mais adiante, há uma conta hipotética de armazenamento próprio; ela não representa um recurso já disponível no Noctua.

Uma câmera comprada não deveria impor um único destino para suas imagens. Essa escolha precisa respeitar a lei e os direitos das pessoas filmadas. O Código Civil, art. 1.228 trata do uso da propriedade, e o CDC, art. 6º, III exige informação clara ao consumidor. O art. 39, I, do CDC veda a venda casada. Os dados reunidos aqui, porém, não sustentam essa acusação: a própria Intelbras documenta alternativas locais para modelos compatíveis.

A primeira parte terminava em um frame recebido da câmera autorizada e decodificado por um cliente independente, sem executar o aplicativo ou SDK proprietário. Colocar esse fluxo em um aplicativo trouxe perguntas menos fotogênicas: para onde o cliente pode conectar, como remontar bytes fora de ordem, quando enviar controles, quem libera cada buffer e quanto atraso aceitar antes de descartar um frame.

O recorte é uma build de laboratório do Noctua com o SDK e a Core Engine usados nos testes. O código do Noctua e do SDK ainda é privado. Vídeo da câmera B e áudio audível foram confirmados no aplicativo para macOS ARM64 antes da migração para o player atual da Core. Essa confirmação e os smokes anteriores de NativeAOT são evidências históricas; não validam automaticamente a nova integração A/V. A compatibilidade com outros modelos, firmwares, redes e sistemas ainda precisa ser medida.

As capturas são da interface real; as legendas indicam quando a tela contém dados de execução e quando mostra apenas uma grade vazia. Já os dois laboratórios interativos usam bytes inventados a partir de estruturas verificadas. Eles não acessam câmeras nem enviam pacotes. Publicar uma captura real com identificadores apagados pareceria mais fiel do que de fato seria.

Grade real do Noctua com quatro posições sem câmeras atribuídas
Noctua / macOS ARM64 / interface real, sem feeds; não comprova reproduçãoAbrir captura ↗

Doze câmeras: gravação, streaming e o custo da dependência#

As câmeras foram compradas e o vídeo autorizado já chega ao cliente. Guardar continuamente as imagens de 12 aparelhos em um arquivo próprio, pesquisável e exportável é outra tarefa: depende das interfaces de cada modelo e de um gravador que ainda não existe aqui. A assinatura para reter eventos acrescenta custo recorrente ao hardware. Também cria dependência de aplicativo, firmware e serviço que podem mudar enquanto sensor e lente continuam funcionando. É um risco de perda de utilidade, não uma conclusão jurídica de obsolescência programada. A Intelbras documenta formas de gravação local para modelos compatíveis.

A GDI oficial da Intelbras oferece API HTTP e criação de fluxo de vídeo, mas exige cadastro empresarial com CNPJ e plano de vídeo com cotas de requisições e banda. É uma opção real de integração, não uma API residencial aberta e ilimitada; não a testamos nem a incluímos no cálculo do Mibo Cloud abaixo.

A própria Intelbras documenta microSD, DVR/NVR via ONVIF ou Intelbras-1 para câmeras compatíveis. Essas rotas merecem avaliação por modelo e topologia. O Mibo Cloud é outra coisa: seus termos dizem que a assinatura é por dispositivo e que o upload de vídeo é acionado por movimento. Três, sete ou trinta dias são períodos de retenção dos eventos, não uma promessa de gravação contínua de cada segundo. O Mibo+ oferece recursos como backup do cartão e foto em nuvem, mas não deve ser confundido com um plano de vídeo Mibo Cloud.

Quanto custariam 12 câmeras no Mibo Cloud#

A tabela pública “Novos Preços Mibo Cloud Android” da Intelbras lista pacotes de 1 a 15 câmeras. Para 12 câmeras compatíveis no mesmo pacote, os valores anunciados são:

Tabela de dados 01
Retenção de eventos1 câmera/mês12 câmeras/mês12 câmeras/ano à vista
Basic — 3 diasR$ 16,90R$ 193,90R$ 1.939,90
Standard — 7 diasR$ 20,50R$ 214,90R$ 2.099,90
Premium — 30 diasR$ 40,50R$ 439,90R$ 4.099,90

O preço anual é o valor anunciado para pagamento à vista, não mensal × 12. Os termos publicados em outra página ainda exibem preços unitários diferentes, e a loja do sistema operacional pode ter outra oferta; esta é uma tabela anunciada para Android, não uma cotação de checkout. A elegibilidade dos 12 modelos, possíveis canais adicionais por múltiplas lentes e a oferta de uma conta específica não foram verificadas. Nenhum plano foi contratado para esta análise.

Um cenário hipotético: gravações no destino escolhido pelo proprietário#

O destino dessas imagens poderia ser um disco local ou um serviço contratado pelo proprietário. Esse é o motivo do exercício abaixo: estimar uma alternativa de armazenamento, sem pressupor acesso à nuvem da Intelbras.

Imagine um gravador que conserve o fluxo comprimido de 12 câmeras, 24 horas por dia, e envie lotes ao Cloudflare R2 a cada quatro horas. O gravador ainda é hipotético, mas o SDK já expõe H.264 Annex B e AAC ADTS por OpenEncodedMediaAsync e EncodedUnits(), além da API decodificada de RGBA e PCM. O que falta é conservar essas unidades em arquivos reproduzíveis, montar o contêiner, gerir retenção e implementar upload contínuo. A existência da API comprimida não entrega essas funções nem exige recodificar os frames.

O exemplar iM3B-C apresentou vídeo Full HD; na observação relatada do app, a métrica recebida pelo SDK ficou abaixo de 700 kb/s na maior parte do tempo. Essa métrica é uma média cumulativa dos registros DHAV aceitos, sem overhead de rede, e não há medição prolongada publicada para as 12 câmeras. Usando 500 kb/s por câmera apenas como hipótese, os lotes somariam 10,8 GB a cada quatro horas e 30 dias de retenção ocupariam 1.944 GB. Pelo preço do R2 Standard, seriam cerca de US$ 29,01/mês só de armazenamento, ou R$ 145,05 com câmbio ilustrativo de R$ 5 por dólar. Essa é uma aproximação em regime estável, com a janela já preenchida, sem retenção extra e supondo os 10 GB-mês gratuitos ainda disponíveis na conta. O R2 cobra pela média dos picos diários no período de faturamento e arredonda as unidades faturáveis. O primeiro mês, partindo de um bucket vazio e enchendo a janela, tem outra ocupação média; cadência de uploads e exclusões também pode alterar o valor.

O Mibo Cloud Premium anuncia R$ 439,90/mês para 12 câmeras compatíveis, mas seus termos descrevem retenção de eventos acionados por movimento, não vídeo contínuo. Os termos também dizem que as gravações usam o fluxo econômico, enquanto a observação que motivou a hipótese do R2 foi em Full HD; não atribuímos uma resolução específica ao fluxo econômico. Os valores, portanto, não comparam serviços equivalentes em cobertura temporal nem em qualidade de vídeo. A estimativa do R2 também exclui gravador, energia, conexão, operações, tributos e possíveis bytes adicionais de áudio e contêiner. O bitrate médio dos 12 aparelhos e a viabilidade da gravação prolongada ainda precisam ser medidos; enviar em lotes muda a necessidade de disco e banda, não o volume total armazenado.

A conta serve para dimensionar um destino escolhido para sessões autorizadas. Não há, nesta build, gravação 24/7 ou upload próprio; o limite atual do SDK é de quatro streams simultâneos.

Do primeiro frame a um cliente de verdade#

Um script encerra depois de provar que consegue decodificar um frame. O Noctua precisa continuar quando a câmera não responde, quando a seleção muda durante a abertura ou quando a janela fecha com um bloco ainda no decoder. Cada operação precisa ser cancelável; cada frame, liberado por quem o recebeu. Respostas tardias de uma conta não podem alcançar a sessão seguinte.

As responsabilidades ficaram assim: o SDK Mibo cuida de autenticação, autorização, descoberta e transporte, oferecendo APIs de unidades comprimidas e de frames decodificados. O aplicativo cuida da seleção, da grade, da recuperação limitada e da fonte audível. No caminho atual do app, a Core também decodifica as unidades comprimidas e cuida do player, da janela, dos controles, dos pixels e da saída de áudio. O SDK não depende da cena nem abre um dispositivo de som. Essa separação permite testar uma falha de rede sem precisar renderizar uma janela e testar um relógio de áudio sem conversar com o fornecedor.

Cada frame é um recurso, não um objeto descartável no sentido coloquial. Pixels e PCM usam buffers de tamanho significativo. Um consumidor lento não pode transformar cada frame atrasado em mais memória retida. A API torna o descarte explícito, e as filas possuem capacidade fixa. Uma imagem retirada de uma fila passa a ser responsabilidade do consumidor; uma imagem perdida por saturação precisa ser descartada pelo dono da fila. O detalhe faz diferença porque um array devolvido ao pool cedo demais pode continuar sendo escrito enquanto outro código o lê.

Na grade 1/2/4, cada posição tem seu próprio ciclo de abertura, leitura e fechamento. A substituição aguarda a limpeza local antes de iniciar outra sessão naquela posição. Falhas não devem interromper as demais câmeras. O orçamento de quatro streams do SDK continua sendo um limite real: desenhar nove retângulos não oferece capacidade para nove câmeras. Também não presumimos PTZ a partir da presença de um botão no design.

O diagnóstico registra camada, atividade correlacionada, tentativa e duração. O código de erro fica disponível; mensagens arbitrárias da rede não entram no log. Assim, “vídeo indisponível” pode ser rastreado até uma etapa concreta, sem despejar dados da conta para descobrir o que aconteceu.

Quem conversa com quem: hosts e fronteiras de confiança#

Na baseline examinada, app-sp2.easy4ipcloud.com e app-br.easy4ipcloud.com participaram do caminho HTTPS da conta. www-v2.easy4ipcloud.com apareceu na sinalização; www.easy4ipcloud.com, como alternativa na configuração autenticada. São papéis observados nesta execução, não uma lista permanente de servidores. O nome de um host, sozinho, não diz por onde a mídia passou.

O papel de cada destino observado#

  • app-br.easy4ipcloud.com — entrada da conta. Vimos chamadas HTTPS /pcs/v1 para consultar a verificação em duas etapas e obter GetToken. A resposta devolveu token, sessão e rota de entrada; não autorizou qualquer URL retornada nem identificou um peer de vídeo.
  • app-sp2.easy4ipcloud.com — sessão autenticada. Login, GlobalConfigGet, inventário e detalhes da câmera usaram esse host na baseline. A rota pode mudar, por isso o cliente só aceita hosts de conta conhecidos.
  • www-v2.easy4ipcloud.com — sinalização inicial. A configuração autenticada o apresentou como serviço p2p-v2; nesta execução, o registro e a descoberta usaram UDP na porta 8800. Uma resposta pode indicar outro destino: o nome não demonstra que a mídia passa por esse servidor.
  • www.easy4ipcloud.com — serviço alternativo. A entrada p2p pode fornecer uma segunda pool DNS para validar o destino Info. Sua presença na configuração não prova que houve fallback de sinalização.
  • Info, candidatos e peer — destinos descobertos. O Info aceito teve de ser um IPv4 público na pool DNS admitida, porta 8803. A negociação produziu candidatos para o caminho de mídia; IPs de câmera, portas efêmeras e topologia variam. Um candidato remoto não ganha acesso livre à rede local.

O GlobalConfigGet fornece serviços por tipo, host e porta. O SDK seleciona p2p-v2 para a sinalização inicial e considera p2p apenas na validação adicional de Info. Por isso resolve e compara os endereços em vez de confiar no campo devolvido pelo serviço.

O aviso da própria Dahua sobre domínios Imou Cloud descreve www.easy4ipcloud.com para login/registro de dispositivos e IPs dinâmicos. É contexto da família de serviços, não uma especificação desta sessão Mibo: o papel que medimos aqui veio das chamadas e da configuração da baseline, e pode ser diferente do papel de um dispositivo se registrando. Não atribuímos app-br, app-sp2 ou www-v2 a uma entidade operadora específica só por compartilharem o domínio.

API HTTPS, descoberta UDP, Info e peer separados por fronteiras de validação
01 / Hosts públicos observados; endereços de peers omitidosAbrir diagrama ↗

O bootstrap de vídeo começa com informações obtidas para o dispositivo solicitado. O leitor vincula credenciais cifradas ao identificador esperado e extrai os serviços. A configuração autenticada é uma entrada para a política, não uma autorização genérica para conectar a qualquer coisa escrita nela. Precisamos validar esquema, porta, forma do endereço e associação à operação. Depois dessa fase, a descoberta UDP introduz novos dados e, portanto, uma nova fronteira.

Há uma diferença prática entre “o serviço respondeu” e “o endereço da resposta é seguro para o nosso cliente”. Um consumidor ingênuo poderia converter um campo de rede em um mecanismo de sondagem arbitrária da rede local. Nosso cliente limita destinos públicos de Info ao conjunto DNS admitido e trata candidatos locais por uma política específica. A exclusão de gateway, DNS e endereços próprios vem das interfaces do host; não deixamos a mensagem remota definir quais máquinas locais seriam seguras.

Nem toda sessão é um “túnel direto”. O serviço pode ajudar a encontrar o peer sem carregar os bytes de mídia; um canal chamado proxy pode estar dentro de uma conexão direta com a câmera. Para identificar a rota de uma execução, é preciso correlacionar socket, endpoint e tráfego. Nome de classe e string do binário não bastam.

Os hosts públicos são úteis na documentação e no diagnóstico. Endereços de câmeras, portas efêmeras, URLs de sessão e identificadores não precisam acompanhar essa visibilidade. Nas figuras, qualquer endereço inventado será explicitamente documental. A política de log também mantém os destinos rejeitados fora da saída: o motivo da rejeição é observável sem oferecer um inventário da rede privada.

SK: o material criptográfico que acompanha o aplicativo#

O SK é material criptográfico compartilhado do perfil do aplicativo; ele não é a senha da conta nem uma identidade exclusiva do terminal.

A origem estudada é o Mibo Smart Android 3.1.1, build 100315. O Noctua possui um perfil de compatibilidade versionado para essa baseline. O material distribuído no aplicativo de release foi tratado como artefato pseudo-sensível: exige identificação e cuidado de uso, mas não podemos fundamentar segurança na suposição de que o proprietário do binário jamais conseguirá observá-lo. Esta postagem identifica o artefato sem publicar seu conteúdo literal.

Na configuração do Noctua, o principal do aplicativo e sua credencial são entradas separadas. O parser observado prefixa o AK bruto com default\ antes de entregá-lo ao assinador SaaS. A credencial chamada SK participa desse caminho de assinatura. Isso é distinto dos parâmetros estáticos usados na proteção de credenciais de câmera e de Info. Um asset de configuração pode carregar todos esses valores, mas a proximidade no arquivo não significa que tenham o mesmo papel criptográfico.

O identificador SHA-256 calculado sobre os bytes UTF-8 da credencial armazenada nesta baseline é:

0c2a64505a55cf78b346da99fa508b6c79a231a26002447b593c40baed86f842

A interface mostra apenas seus primeiros seis bytes, 0C2A64505A55, para comparação visual. Esse prefixo não é uma assinatura do fornecedor, um certificado de procedência ou uma prova de autorização. Ele apenas ajuda a detectar que duas builds estão usando materiais diferentes. Mesmo o hash completo identifica conteúdo; a procedência depende dos registros da extração e da versão examinada.

Material do aplicativo, identidade do terminal, credencial da conta e sessão têm papéis distintos
02 / Quatro identidades que não devem virar um único tokenAbrir diagrama ↗

O assinador trabalha sobre os bytes JSON descomprimidos. Calcula o digest do corpo e monta uma representação canônica com método, caminho, tipo de conteúdo e headers em ordem fixa. A posição do header de sessão, quando presente, e a quebra de linha final são parte dos bytes assinados. A credencial do aplicativo usa uma passagem MD5 em hexadecimal antes de HMAC-SHA256; a variante de senha usa uma segunda passagem. Reproduzimos esse formato para compatibilidade. Não o recomendamos como construção nova de armazenamento de senha.

A forma abaixo é sintética; \n representa o byte LF, e o digest e o principal foram substituídos por marcadores. Não é uma requisição assinável.

POST\n
/pcs/v1/example\n
<body-md5-base64>\n
application/json\n
x-pcs-apiver:<revision>\n
x-pcs-client-ua:<serialized-UA>\n
x-pcs-date:<date>\n
x-pcs-nonce:<nonce>\n
x-pcs-username:<principal>\n

Uma mudança de serialização pode quebrar a assinatura sem alterar o significado aparente do JSON. Espaços, ordem de propriedades e a versão de um header são detalhes que os testes de vetores precisam fixar. Na implementação, os vetores públicos são sintéticos: conseguimos testar canonicalização e derivação sem transformar um segredo de release em fixture. A propriedade importante é a equivalência dos bytes, não uma resposta plausível em um log.

O que possuir o SK permite — e o que ainda depende do servidor#

Possuir o material do aplicativo permite reproduzir a etapa que o utiliza; acesso à conta e às câmeras continua exigindo outras condições verificadas pelo serviço.

O terminal do Noctua recebe identificadores novos, gerados localmente, e persiste sua identidade para não parecer uma instalação nova a cada execução. Os demais campos do perfil imitam a forma aceita da baseline Android. Isso não atesta que o desktop é um aparelho Android e não constitui, por si, confiança do servidor. A geração de ttid e terminalId é uma decisão distinta de conhecer o SK.

A conta ainda passa pelo fluxo de autenticação e, quando exigido, pelo desafio de dois fatores. Solicitar o código e submetê-lo são ações próprias. Um código aceito precisa produzir a transição correta até login e sessão; simplesmente fechar a tela de desafio não basta. Nosso aplicativo persiste a sessão somente após sucesso e a restaura pelo caminho do SDK, sem presumir que um ticket local ainda será aceito.

A autorização de câmera não deriva da presença de um nome na UI. Ao abrir uma seleção, o SDK consulta detalhes e vincula a operação à conta e à geração da sessão. A troca ou rejeição da sessão invalida mídia e seleções. Não tratamos fingerprint, inventário em cache ou conhecimento do protocolo como substitutos da autorização do servidor.

Esse mapa também explica por que criptografar um asset embarcado não resolve a confidencialidade contra quem executa o cliente. Se o aplicativo precisa usar o valor, existe algum ponto em que ele, ou uma representação operacional equivalente, pode ser observado. A proteção de assets ajuda outras propriedades de distribuição; uma aplicação cliente não transforma um segredo compartilhado em prova individual de identidade. O limite precisa estar no protocolo e nas decisões do servidor.

Construindo o caminho P2P#

Partimos dos detalhes autorizados da câmera e da configuração de serviço. A sinalização usa um socket UDP conectado ao destino selecionado. Nesse contexto, “conectado” não significa handshake como TCP: ajuda a fixar o peer esperado para o socket e a forma de envio/recepção. Não oferece confiabilidade de entrega nem transforma o protocolo em um canal autenticado criptograficamente.

A sequência observada inclui registro inicial, descoberta de disponibilidade, consulta Info e registro com o sal recebido. Os nomes das operações nos ajudaram a organizar o fluxo, mas os testes são feitos sobre o formato de cada datagrama e suas relações. O número de sequência esperado precisa aparecer exatamente uma vez, o comprimento deve corresponder ao corpo e respostas desconhecidas não podem avançar o estado.

Registro, descoberta, Info, registro com sal, probe e canal PTCP
03 / Abertura: cada seta atravessa uma validaçãoAbrir diagrama ↗

Limitamos tempo e trabalho. Uma troca de sinalização tem orçamento de seis segundos e até 64 datagramas. Uma enxurrada de respostas malformadas não pode deixar uma abertura pendurada para sempre. Cancelamento atravessa o fluxo até o socket e a limpeza; estados incompletos não ficam disponíveis para uma tentativa futura como se fossem uma sessão pronta.

Um login bem-sucedido não explica uma falha de mídia. Nos diagnósticos, detalhes da câmera e configuração chegavam normalmente, mas o probe falhava. Separar as etapas evitou revisitar senha e 2FA quando a investigação já precisava olhar para a rede. A atividade de abertura liga esses eventos sem registrar token ou identificador privado.

Registro e Info: uma resposta válida ainda precisa de um destino aceitável#

O endereço de Info deve ser público, IPv4, na porta observada 8803, e pertencer ao conjunto DNS permitido do serviço primário. Quando existe configuração autenticada do serviço alternativo, suas respostas DNS públicas podem ser consideradas. Porta inválida, endereço não público e ausência no pool são causas distintas. Essa distinção virou parte do diagnóstico porque uma única mensagem de “destino inválido” escondia decisões muito diferentes.

Em uma execução, um shard público anunciado ficou fora dos pools admitidos. A correção não foi permitir qualquer IPv4 público. O SDK descartou aquele endpoint e fez uma consulta Info à entrada de registro já utilizada, na porta esperada, depois de validar essa entrada pelo mesmo pool DNS. A alternativa produziu Info utilizável na câmera autorizada. Se essa consulta falhar, a abertura termina; não tentamos o shard rejeitado por uma porta dos fundos.

A observação sustenta uma rota alternativa de serviço, não a alegação de que o aplicativo oficial executa exatamente esse algoritmo de seleção. Para afirmar isso, seria necessária uma correlação específica no aplicativo oficial. Mantemos essa diferença escrita porque uma solução funcional pode ser compatível sem reproduzir todas as decisões internas do fornecedor.

Também há um risco residual: a sinalização UDP analisada não ganha autenticação forte apenas porque verificamos sequência e origem do socket. As restrições reduzem caminhos indesejados, mas não justificam chamar a mensagem de uma prova criptográfica do destino. É por isso que a validação do candidato e sua identidade continuam necessárias na etapa seguinte.

Candidatos locais, públicos e o probe de identidade#

A variante implementada aceita uma lista textual canônica de até oito endereços RFC1918, selecionando no máximo dois peers distintos. A sub-rede diretamente conectada precisa estar entre /24 e /30, e a porta deve ser não privilegiada. Endereços próprios, gateway, DNS, rede e broadcast são excluídos. Formatos codificados ou variantes de criptografia desconhecidas não são adivinhados. Isso limita o escopo da sondagem e torna a política testável com dados inventados.

A presença na mesma sub-rede não autentica a câmera. O probe complementa todos os bytes de uma estrutura que lembra STUN. Antes do complemento, aparecem tipo, comprimento, cookie 0x2112A442, transação de 12 bytes e atributo de identidade. A coincidência com elementos de STUN orienta leitura, mas não demonstra equivalência com uma implementação completa de ICE, STUN ou TURN.

Uma resposta precisa carregar a identidade descoberta e uma transação aceita no intercâmbio atual. Também há a variante em que o peer inicia uma transação e o cliente a responde dentro dessa troca. Aceitar só o magic, o tamanho ou uma porta conhecida permitiria que um datagrama de outra abertura parecesse progresso. O cliente precisa manter as relações entre mensagens, não apenas validar cada uma isoladamente.

Se uma tentativa local torna o socket terminal, não devemos reutilizá-lo para um candidato público. A sequência de registro e descoberta pertence ao socket; abrir outro e copiar apenas um booleano “registrado” também seria incorreto. A implementação preserva o erro original e reconstrói o caminho que precisa de um novo socket. O caso do macOS adiante mostrou por que esse detalhe era diagnóstico e comportamental ao mesmo tempo.

Anatomia dos datagramas#

A estrutura fica mais clara quando cada byte tem um offset, uma unidade e uma regra de aceitação.1

O primeiro explorador contém quatro exemplos sintéticos completos no nível que pretendem ilustrar. O exemplo de sinalização tem corpo textual inventado; o probe usa transação e identidade inventadas; o cabeçalho PTCP não carrega mídia; o wrapper do proxy contém apenas quatro bytes PLAY, e não uma requisição válida inteira. Não apresentamos esses valores como pacotes prontos para abrir uma câmera.

LAB 01 / DATAGRAMAS

Escolha um pacote e selecione um campo para acompanhar seus bytes.

UDP / HTTP-shaped

485454502f312e3120323030204f4b0d0a435365713a20370d0a436f6e74656e742d4c656e6774683a20340d0a0d0a626f6479

Campos; offsets em bytes a partir de zero
OffsetBytesCampoInterpretação
017StatusLinha de status; não é uma conexão HTTP/TCP.
179CSeqSequência esperada: 7. Cabeçalho único.
2621Content-LengthQuatro bytes de corpo, com separador CRLF.
474BodyCorpo sintético: body. Não contém resposta de câmera.

Candidate / probe

0001000c2112a4420102030405060708090a0b0c802a00081122334455667788

Campos; offsets em bytes a partir de zero
OffsetBytesCampoInterpretação
02Type0x0001, requisição; big-endian.
22Length12 bytes de atributos.
44Cookie0x2112A442; a semelhança com STUN não prova equivalência.
812TransactionIdentificador inventado para este exemplo.
204AttributeTipo 0x802A, comprimento 8.
248IdentityIdentidade sintética. Complementar todos os bytes para o fio.

PTCP / header

505443500000000c00000008000000000000006400000050

Campos; offsets em bytes a partir de zero
OffsetBytesCampoInterpretação
04MagicASCII PTCP.
44Send offsetOffset de envio 12; big-endian.
84Receive offsetPróximo byte contíguo esperado: 8.
121Extension control0: sem extensão neste exemplo.
131FlagsFlags de controle; zero no exemplo.
142WindowCampo de janela; zero ilustrativo.
164ClockRelógio sintético 100.
204EchoEco sintético 80.

Proxy / wrapper

100000040102030400000000504c4159

Campos; offsets em bytes a partir de zero
OffsetBytesCampoInterpretação
01Type0x10 identifica este wrapper.
11ReservedZero no exemplo.
22LengthComprimento interno 4; big-endian.
44ChannelCanal inventado 0x01020304.
84ReservedBytes não interpretados neste exemplo.
124PayloadTexto PLAY abreviado, não uma requisição completa.

Pranchas vetoriais: UDP · Probe · PTCP · Proxy.

Na sinalização, a aparência HTTP ajuda a reconhecer linhas de texto, mas o datagrama continua sendo a unidade externa. O parser exige uma única ocorrência de CSeq, vinculada à requisição, e um único Content-Length que descreva os bytes restantes. O exemplo contém quatro bytes de corpo. Se um comprimento inventar mais bytes do que chegaram, não há uma leitura TCP subsequente que complete aquela mensagem: a resposta deve ser descartada.

No probe, a alternância entre bytes antes e depois do complemento evidencia uma transformação simples que confunde inspeção visual. O cookie deixa de aparecer com os bytes familiares na captura do fio. O complemento é reversível e não oferece confidencialidade. A dificuldade inicial foi localizar o ponto correto de observação, não quebrar uma cifra. Especificar se um dump é pré ou pós-transformação evita comparar dois pontos diferentes como se fossem o mesmo pacote.

No PTCP, os quatro primeiros bytes são ASCII PTCP. Os campos de envio e recepção de quatro bytes usam big-endian. Em seguida vêm controle de extensão, flags, janela, relógio e eco. O controle de extensão é zero neste exemplo, mas a parte 1 documenta pacotes com extensões: nesses casos, o corpo começa depois da região de extensão, não necessariamente no byte 24. O cabeçalho fixo ocupa 24 bytes, mas isso não significa que um datagrama inteiro tenha esse tamanho. Uma captura de apenas um iovec pode conter o cabeçalho e perder o restante do payload. Esse erro de observador já apareceu na pesquisa inicial.

O wrapper interno acrescenta outra camada: tipo 0x10, comprimento big-endian no offset 2 e identificador do canal no offset 4. Seu cabeçalho tem 12 bytes. Contar apenas o payload de aplicação ao avançar o offset externo desalinhará a próxima mensagem, pois os offsets PTCP contam os bytes encapsulados. No envio, fragmentamos requisições em wrappers com até 1.280 bytes internos. Esse limite é da implementação, não uma descoberta universal da MTU da rede.

A prancha de cada pacote existe também como SVG separado. O explorador é uma lente sobre o mesmo conteúdo, não um requisito para compreendê-lo. Selecionar um campo destaca o intervalo e exibe sua interpretação; sem JavaScript, as quatro tabelas continuam presentes. Isso permite ler o artigo offline e inspecionar os exemplos sem habilitar uma conexão sequer aos hosts descritos.

PTCP: remontagem, ACKs e retransmissões sobre UDP#

O parser de mídia só recebe bytes contíguos; o recebimento de um datagrama não é sinônimo de progresso na aplicação.1

O laboratório abaixo usa um fluxo abstrato de bytes e omite os wrappers proxy. Seus offsets e ACKs pertencem ao modelo, não são valores literais dos pacotes PTCP. Na parte 1, três bytes de aplicação avançam 15 bytes no transporte por incluírem o wrapper de 12 bytes; aqui, quatro bytes abstratos avançam quatro.

Imagine três blocos de quatro bytes: ABCD, EFGH e IJKL, com offsets 0, 4 e 8. Se chega primeiro o bloco do offset 4, temos dados pendentes, mas o próximo byte contíguo esperado continua sendo zero. Quando chega o offset 0, os dois blocos podem ser liberados juntos, e o próximo offset esperado salta para 8. A chegada física foi reordenada; o consumidor recebeu uma sequência contínua.

LAB 02 / PTCP

Modelo abstrato sem wrappers proxy: offsets e ACKs não são valores literais do PTCP. A remontagem entrega bytes em ordem: compara conflitos pendentes e descarta duplicatas já consumidas sem comparar seu conteúdo.

ENVIOREMONTAGEMPARSERAguardando ação do leitor ACK do modelo 0Pendentes 0Parser 0
parser / stdout

Sequência esperada

  1. Em ordem
    Chegada
    0/ABCD → 4/EFGH → 8/IJKL
    ACK do modelo
    4 → 8 → 12
  2. Reordenação
    Chegada
    4/EFGH → 0/ABCD → 8/IJKL
    ACK do modelo
    0 → 8 → 12
  3. Perda e retransmissão
    Chegada
    0/ABCD → drop(4) → 8/IJKL → 4/EFGH
    ACK do modelo
    4 → 4 → 4 → 12
  4. Conflito pendente
    Chegada
    4/EFGH → 4/XXXX → 0/ABCD
    ACK do modelo
    0 → rejeição terminal

O segundo laboratório permite avançar essas situações sem ruído de uma captura. Os cenários são determinísticos e pequenos. Na perda, o bloco do meio não chega na primeira vez, o último permanece pendente e a retransmissão fecha a lacuna. A sequência de ACK mostrada corresponde ao próximo byte contíguo esperado, não a uma contagem de pacotes. A animação avança somente por ação do leitor e pode ser pausada.

Duplicatas também precisam de semântica. Para o mesmo offset ainda pendente, bytes diferentes são um conflito: o SDK rejeita a sessão em vez de escolher “o último vence”. Um fragmento inteiramente anterior ao cursor de entrega é descartado e recebe ACK sem comparação do conteúdo; ele não reescreve bytes já consumidos. Um fragmento que atravessa esse cursor é rejeitado. A simulação acompanha essa distinção: EFGH versus XXXX conflitam enquanto estão pendentes, mas uma duplicata já consumida não é revalidada. Gaps excessivos e ACKs além do que enviamos também são rejeitados pelo SDK. O laboratório continua sendo um modelo pequeno, não toda a máquina de estados PTCP.

A implementação real tem limites de armazenamento e tempo que o desenho simplifica. Cada pendência permite no máximo quatro envios totais: o inicial e até três retransmissões, com intervalo mínimo de 400 ms entre envios. O agendamento e a rede podem aumentar esse intervalo. Não é um algoritmo de controle de congestionamento geral equivalente ao TCP. O objetivo aqui é compatibilidade com o caminho observado, mantendo os recursos limitados e tornando explícitas as variantes não suportadas.

Há ainda a diferença entre ACK do transporte e resposta do protocolo interno. Um ACK pode confirmar que os bytes da requisição foram recebidos sem que a operação de reprodução tenha sido aceita. O cliente precisa aguardar e validar o controle correspondente. Da mesma forma, bytes contíguos podem conter uma resposta, parte de um registro ou vários itens juntos. A remontagem não elimina o trabalho de enquadramento da próxima camada.

Os testes usam fragmentação e coalescência para pressionar essa fronteira. Se um parser funciona apenas quando cada chamada de leitura coincide com uma mensagem inteira, ele depende de um acidente do transporte. A variante correta mantém estado suficiente para esperar bytes, mas não permite que um comprimento hostil faça a memória crescer indefinidamente. É essa combinação de estado e limite que transforma um desenho de protocolo em código sustentável.

Por que o primeiro frame não garante um stream estável#

Depois da confirmação do candidato, a troca PTCP prepara um canal de proxy escolhido para aquela sessão e espera o CONN correspondente. Só então os bytes da requisição de reprodução entram nesse canal. O SDK envia PLAY pelo método observado e continua processando respostas de controle enquanto recebe mídia. O fluxo não vira um arquivo de vídeo puro depois do primeiro sucesso.

Uma das falhas mais instrutivas parecia uma desconexão periódica. No caminho HD do Noctua, o peer fechava a reprodução por volta de 65,6 segundos sem keepalive de aplicação. Existia uma observação anterior de 90 segundos em outra condição Eco, então não usamos esse número como timeout universal. A conclusão útil era que ACKs e tráfego de mídia não substituíam o controle que a aplicação esperava.

A implementação passou a enviar o método 2 a cada 20 segundos após PLAY aceito, com apenas um controle pendente. Se a resposta ainda não chegou no intervalo seguinte, a sessão falha. O fechamento mantém sua sequência reservada. Isso é diferente de reabrir continuamente e dizer que o vídeo ficou estável: o critério de continuidade precisa observar a mesma sessão e os mesmos recursos, sem esconder uma nova abertura atrás da UI.

A resposta desse keepalive também nos obrigou a refinar o parser. A variante observada é um 200, com CSeq correspondente, sem cabeçalho de comprimento e sem corpo. Uma resposta comum de PLAY não recebe essa exceção. Apenas o CSeq do keepalive realmente pendente pode usar a forma vazia. Essa regra específica resolveu o erro http-private-length-missing sem aceitar qualquer HTTP truncado como um controle válido.

Headers duplicados, CSeq errado, status inesperado e esquemas de comprimento ambíguos continuam sendo rejeitados. A exceção existe no fluxo privado interno; não altera o parser HTTP da conta nem a sinalização UDP. Essa delimitação é importante: corrigir uma peculiaridade observada em uma camada não deve relaxar validações em todas as outras.

O aplicativo pode oferecer recuperação limitada para falhas transitórias, com recursos novos e cancelamento. Ele não tenta repetir automaticamente autenticação rejeitada nem transforma uma política de destino em timeout recuperável. Contadores de tentativa e eventos de abertura tornam o comportamento visível. O usuário deve conseguir distinguir uma câmera recebendo frames de uma interface apenas tentando outra vez.

Do túnel aos registros de áudio e vídeo#

O fluxo interno intercala respostas de controle e registros de mídia. Um wrapper de seis bytes iniciado por $ declara o tamanho de um registro DHAV. O cabeçalho DHAV tem 24 bytes na variante estudada; o trailer contém dhav e repete o comprimento em little-endian. Há extensões de cabeçalho e um byte aditivo de enquadramento. Esse byte ajuda a detectar inconsistência estrutural, mas não é um MAC criptográfico.

Para registros protegidos, a extensão B5 descreve um prefixo claro, um trecho protegido e os dados necessários à verificação e ao OFB. Os comprimentos de três bytes são little-endian. Antes de acessar esses intervalos, verificamos se a extensão cabe no cabeçalho e se o trecho cabe no registro. A ordem importa: interpretar um comprimento e decifrar primeiro para validar depois expõe rotinas inferiores a entradas que já deveriam ter sido descartadas.

Offsets relativos à tag B5, na extensão de 44 bytes:

Tabela de dados 02
OffsetBytesField
01B5 tag
11Size = 44
21Variant = 1
33Clear prefix length, LE
63Protected span length, LE
916Verifier seed
252CRC16 of derived verifier
2716OFB IV
431Uninterpreted suffix

O verificador observado termina em apenas 16 bits, e OFB não autentica o conteúdo. Portanto, não descrevemos essa proteção como garantia forte de integridade. O decoder continua recebendo material hostil mesmo quando o registro passou pela checagem. A segurança do parser e a robustez do codec não são substituídas pela presença de AES em algum ponto do caminho.

Na baseline de áudio, a combinação aceita resulta em um access unit ADTS AAC-LC, sem CRC, a 16 kHz, mono e com um raw data block. O comprimento declarado no ADTS precisa coincidir com a unidade completa. Frequência diferente, múltiplos blocos ou truncamento falham antes da chamada nativa. Essa allowlist descreve o firmware observado; não é um catálogo dos formatos de todas as câmeras Mibo.

Vídeo e áudio possuem contadores finos separados de 16 bits, em milissegundos, com wraparound. A camada de relógio ancora o primeiro áudio ao primeiro vídeo pelo contador compartilhado, usando o calendário empacotado como checagem de consistência, não como relógio confiável. O SDK permite que áudio chegue antes: guarda no máximo 128 registros e um MiB nessa fase. Ao ultrapassar o orçamento, falha explicitamente.

Depois de estabelecida a origem, os timestamps são relativos à fonte. Áudio pode começar com tempo negativo. O SDK entrega a saída dos decoders sem ordenar globalmente por apresentação; a política de reprodução pertence ao consumidor. Essa separação evita que a biblioteca assuma o dispositivo de som ou o ritmo da UI. Também exige que o app não confunda chegada de um pacote com o instante em que ele deve ser ouvido.

FFmpeg funcionou; distribuí-lo trouxe outras decisões#

A primeira integração nos permitiu verificar o caminho de mídia com uma implementação independente. Isso reduziu incerteza: um erro de frame podia estar no enquadramento, na desproteção ou no formato, e comparar a saída ajudava a separar essas causas. A troca posterior não apaga essa utilidade. Mantivemos comparações offline como evidência específica dos corpora testados, sem presumir equivalência universal entre decoders.

O projeto FFmpeg documenta uma base LGPL e componentes opcionais GPL. Desabilitar GPL e nonfree é uma escolha de build, não uma dispensa de obrigações de redistribuição. Para nosso pacote estático, estudamos também a possibilidade de relinkar: fonte correspondente, configuração e materiais necessários precisam combinar com o artefato distribuído. Um link para uma branch móvel não identifica sozinho esse conjunto. A LGPL 2.1 é a referência normativa, não esta descrição do laboratório.

O desafio de engenharia era conciliar .NET NativeAOT, uma bridge pequena, cinco ABIs previstas e uma distribuição que ainda é privada. SDK, app e fontes dos decoders possuem ciclos distintos. Mesmo que pretendamos abrir nosso código futuramente, uma intenção não altera o que uma distribuição presente precisa oferecer. Preferimos resolver a composição real do pacote a depender dessa promessa.

Nosso uso observado era estreito: H.264 para imagem e AAC-LC para áudio. Avaliamos bibliotecas específicas e adotamos libavc e libxaac, mantendo a API decodificada do SDK em RGBA8 e PCM16. A API comprimida permite delegar a reprodução a outro consumidor. Isso reduziu o escopo da integração, mas trouxe trabalho próprio: validação de formatos, conversão de cor, alocadores, patches por plataforma e revisão da fronteira C. Uma biblioteca menor continua sendo código nativo complexo.

A regra de fonte passou a ser objetiva: commit fixado, arquivo de entrada verificado e script de build conhecido. O mesmo commit pode produzir tarballs diferentes por versão ou ambiente do Git; o script aceita os hashes registrados para o host, em vez de aceitar silenciosamente qualquer arquivo com o nome esperado. Resolver proveniência não equivale a provar segurança do codec, mas impede trocar a dependência sem perceber.

Há adaptações locais, incluindo macOS ARM64 para libxaac e a implementação de threads de libavc no Windows. Compilar para outro RID não é apenas trocar um sufixo. O fato de termos um script para cinco ABIs não demonstra execução aceita em cinco plataformas. As matrizes de build e os testes nativos precisam registrar o que realmente rodou. Essa distinção acompanha o pacote e o artigo.

Duas bibliotecas OSS, uma fronteira pequena em C#

Usamos libavc para H.264 e libxaac para AAC. As licenças dos projetos fixados são Apache-2.0; mantemos LICENSE, NOTICE aplicável e registro de alterações como parte da preparação de distribuição. A concessão de patente da Apache-2.0 tem escopo definido. Ela não é uma garantia genérica sobre toda patente essencial dos padrões de mídia.

Na publicação NativeAOT, os arquivos estáticos dos codecs e os adaptadores C entram no vínculo do executável. Na execução gerenciada de desenvolvimento, o carregamento das bibliotecas nativas é um caminho diferente. Tivemos inclusive uma falha diagnosticada como biblioteca ausente nesse contexto. Corrigir a cópia ou resolução da dependência de desenvolvimento não prova nem invalida o vínculo estático da publicação.

A interface deliberadamente não exporta o grande universo de opções dos codecs nem ponteiros nativos. No caminho decodificado, o SDK enquadra as unidades e retorna frames RGBA8 e PCM16; os tipos concretos dos decoders ficam atrás do adaptador. No caminho comprimido, OpenEncodedMediaAsync e EncodedUnits() entregam unidades H.264 Annex B e AAC ADTS com descarte explícito, sem passar pelos decoders do SDK. O player atual da Core consome esse segundo caminho. Isso facilita trocar o decoder e reduz as combinações de configuração que precisam de testes, sem eliminar as vulnerabilidades possíveis nas bibliotecas.

Fluxo de unidade validada para send, probe de saída, cópia e descarte entre .NET e C
04 / Caminho decodificado do SDK; a API comprimida delega a decodificação ao consumidorAbrir diagrama ↗

Quem aloca, quem copia e quem libera#

Entradas são emprestadas durante a chamada; saídas pertencem a um dono até a transferência e o descarte.

O send recebe um ponteiro válido somente durante a chamada síncrona, comprimento e timestamp. O adaptador C valida esses argumentos independentemente do wrapper .NET. H.264 está limitado a um MiB por unidade, dimensões até 1920×1080 e formatos observados. A variante de vídeo rejeita B frames porque exigiriam reordenação de apresentação e outro contrato temporal. A matriz e a faixa de cor vêm do VUI quando disponível; sem ele, usamos o comportamento limitado BT.601 documentado.

A saída usa duas chamadas. Primeiro, receive informa que há um resultado pendente e retorna metadados, sem copiar pixels. O lado gerenciado valida dimensões e tamanho antes de alugar um array. A segunda chamada recebe capacidade e copia RGBA8 ou PCM16. Um destino curto retorna a condição de capacidade insuficiente sem consumir o frame. Não confiamos num comprimento simplesmente porque foi produzido pelo codec.

Assinaturas do adaptador de vídeo do caminho decodificado do SDK; a interface AAC possui símbolos separados e informa samples em vez de dimensões. A API comprimida não chama esses decoders:

int mibo_decoder_send(MiboAvcDecoder *decoder,
    const uint8_t *data, int size, int64_t pts);
int mibo_decoder_receive(MiboAvcDecoder *decoder,
    uint8_t *output, int capacity,
    int *width, int *height, int64_t *pts);
void mibo_decoder_destroy(MiboAvcDecoder **handle);

Os retornos da ABI são pequenos: -1 rejeita, 0 indica ausência de saída, -2 permite consultar ou corrigir capacidade e 1 conclui a cópia. O exemplo é descritivo; os símbolos de vídeo e áudio são distintos. Depois da transferência, o frame gerenciado possui o array e o devolve limpo ao pool no descarte. O buffer temporário de entrada é limpo após o uso. Nenhum ponteiro precisa sobreviver num objeto da UI.

Libavc usa callbacks de alocação com registro externo à memória exposta ao codec: até 4.096 alocações e 96 MiB por decoder. Libxaac possui seu próprio limite de 16 blocos e 32 MiB. No adaptador de vídeo, uma liberação desconhecida ou repetida marca falha, e blocos liberados são limpos e mantidos em quarentena até a destruição. Isso reduz a possibilidade de reutilização imediata dentro daquele ciclo, mas não contém uma escrita arbitrária no processo.

O wrapper gerenciado serializa envio, recebimento e destruição. destroy recebe o endereço do handle e o zera antes da limpeza. Repetir descarte pelo mesmo armazenamento não tenta liberar novamente aquele recurso. Um ponteiro copiado ou forjado, porém, não vira uma entrada suportada pela ABI. É importante documentar isso para não anunciar “double-free impossível” como se qualquer chamada C hostil estivesse isolada.

Os bugs que a revisão encontrou na própria bridge#

O primeiro caminho começava com decoder ainda não inicializado. A rotina obtinha dimensões, alocava YUV e depois podia falhar ao validar VUI, matriz ou modo. Se outro send reutilizasse aquele handle, planos e dimensões antigos poderiam ser entregues ao codec. Havia também a possibilidade de substituir o ponteiro de uma nova alocação sem liberar a anterior. O fluxo típico da sessão descartava o decoder após falha, mas a ABI e um retry futuro não poderiam depender dessa coincidência.

A correção tornou a falha terminal e organizou a limpeza para não permitir reutilização parcial. Isso não encerrou a revisão. Mesmo com o handle já inicializado, uma unidade que mudasse dimensões chegaria ao codec antes de o adaptador receber os novos valores. Verificar largura e altura depois da chamada não protege, por si só, um plano de saída dimensionado para o primeiro frame.

Passamos a reservar planos YUV420 completos para o limite suportado: 3.110.400 bytes até 1920×1080. Um frame posterior dentro desse teto tem capacidade de saída mesmo antes da validação posterior detectar a mudança; a mudança ainda rejeita a sessão e exige um handle novo. O teste sintético de 64×64 para 1080p cobre essa transição específica. Ele não prova segurança para dimensões arbitrárias nem para toda corrupção interna possível no codec.

Também mantemos testes de alocação e harnesses com sanitizadores conforme a plataforma. Instrumentar apenas o adaptador não equivale a compilar toda a dependência com ASan. O limite residual permanece dentro de libavc e libxaac, no mesmo processo do app. Para conter uma corrupção arbitrária de decoder, seria necessária outra arquitetura de isolamento. Não acrescentamos essa promessa à documentação de uma bridge que não a implementa.

O benchmark da ABI existe para medir o custo do contrato: consulta, cópia, validação e decodificação têm custos distintos. Um número de buffers por segundo depende de resolução, conteúdo, hardware e caminho medido. Nesta postagem, não reutilizamos um resultado isolado para prometer nove câmeras simultâneas. A evidência útil precisa vir acompanhada da carga e do escopo da medição.

A/V no Noctua: filas, relógios e seleção de áudio#

O player atual recebe unidades comprimidas; a sessão da Core avança pelo tempo decorrido da cena, não pelo relógio estimado da saída de áudio.

O SDK mantém dois contratos. OpenMediaAsync e MediaFrames() entregam RGBA8 e PCM16 decodificados. OpenEncodedMediaAsync e EncodedUnits() entregam H.264 Annex B e AAC ADTS, com timestamps relativos à mesma origem de fonte. O Noctua atual usa esse segundo caminho: copia as unidades para StreamingSession e descarta o objeto recebido do SDK. Apenas uma câmera selecionada pode emitir som, e o padrão continua silenciado.

O VideoPlayer chama Advance(elapsed) a partir das atualizações da cena. A sessão faz uma correção limitada em direção ao atraso escolhido em relação ao vídeo mais recente; um worker decodifica as unidades devidas e o player recebe a imagem pendente. Pausar congela esse relógio, mas não interrompe a chegada de unidades. A janela ao vivo configurada pelo Noctua é de um minuto, limitada também a 32 MiB de dados comprimidos por sessão; o teto de bytes pode encurtar a retenção. Esse limite não inclui toda a memória de codecs, pixels e texturas.

Unidades comprimidas do SDK entram na sessão da Core; o tempo da cena avança a apresentação e a decodificação alimenta vídeo e PCM
05 / Player atual: relógio da cena, cache comprimido e saídas separadasAbrir diagrama ↗

A saída observada de áudio continua sendo PCM16 little-endian, mono a 16 kHz, entregue à entrada ao vivo da Core. Ela é misturada no dispositivo existente; o ring tem capacidade de 500 ms a 48 kHz e avanço inicial de apresentação de 100 ms. A conversão de taxas inteiras usa retenção de ordem zero. Essas características da saída não fazem dela o relógio mestre do player nem demonstram sincronismo acústico: a latência física do dispositivo permanece fora da observação direta.

O caminho anterior do app usava o relógio estimado do áudio selecionado para liberar vídeo de uma fila de oito frames, cerca de 63,3 MiB em RGBA 1080p. Ele guardava até oito blocos PCM de 8.000 bytes antes da primeira imagem, descartava vídeo mais de 100 ms atrasado e reiniciava a apresentação em descontinuidades acima de 500 ms. A revisão de saturação dessa fila resolveu um problema real de starvation. Esses números descrevem aquela implementação e seus testes; não são os limites nem a política de agendamento do player atual. Os 128 registros de startup do SDK, descritos antes, pertencem a outra fronteira e continuam separados.

No caminho atual, a seleção audível controla qual PCM segue para a saída. Trocar ou fechar uma câmera, sair da conta e suspender o app cancelam leitura e descartam os recursos correspondentes. Silenciar não deve ser anunciado como recolhimento de samples já entregues ao hardware. A política de esvaziamento e recuperação de áudio do caminho anterior também não é prova automática do comportamento da nova integração.

A confirmação humana de áudio audível e vídeo da câmera B ocorreu antes dessa migração. Os testes do player atual verificam cache, composição, posse de recursos e transições de reprodução; build e capturas sintéticas verificam outros aspectos. Nenhum deles transfere aquela confirmação humana para a nova implementação. Precisamos repetir a aceitação com dispositivos autorizados, medir sincronismo perceptual e observar sessões prolongadas, inclusive no artefato NativeAOT distribuído.

O erro 10058 atravessou o protocolo e chegou ao macOS#

O usuário iniciava o app pelo iTerm com dotnet run. A câmera local falhava logo no envio do probe, enquanto as chamadas HTTPS e o registro remoto funcionavam. O diagnóstico mantinha o código normalizado de socket 10058. Em outra forma de iniciar o processo, o mesmo caminho podia funcionar. Isso apontava para uma diferença de contexto, mas ainda não identificava sua causa.

Primeiro corrigimos o mascaramento do erro. Um socket em estado terminal podia provocar uma exceção de estado numa operação subsequente, escondendo a falha de envio. Depois instrumentamos apenas metadados da chamada nativa: resultado, errno e classificação privada/pública do destino. Não precisávamos registrar payload nem endereço para descobrir que o envio UDP retornava EPIPE, errno 32, naquela execução.

A prova seguinte veio do Network.framework no mesmo processo. Um diagnóstico temporário, sem envio de mídia adicional, mostrou localNetworkDenied. O macOS estava negando aquele acesso local no contexto de lançamento usado pelo usuário. Isso é mais concreto que inferir permissão a partir de um código .NET ou da ausência de um popup. A documentação de Local Network Privacy da Apple é a referência para o comportamento da plataforma.

O usuário habilitou a entrada correspondente em Privacidade e Segurança → Rede Local, reiniciou o contexto e confirmou o feed da câmera e o áudio. Não foi necessário desabilitar firewall, aceitar destinos arbitrários ou insistir em retries. Os hooks e o driver temporário foram removidos do código entregue. O evento importante foi a diferença entre permissão negada e câmera indisponível, não a existência de uma ferramenta de instrumentação permanente.

Essa investigação também impôs um cuidado de escrita: podemos atribuir aquela reprodução confirmada ao bloqueio de rede local. Não podemos concluir retroativamente que todo 10058 histórico tinha a mesma causa. Um código de erro é uma pista; o diagnóstico fica mais forte quando a observação do sistema, o contexto do processo e a mudança controlada concordam.

O laboratório hoje e o que ainda falta validar#

A grade real 1/2/4 possui aberturas e fechamentos independentes. O áudio pertence à seleção e usa a saída da Core. A sessão autenticada é persistida no armazenamento seguro, e uma rejeição do serviço invalida o ticket correspondente. O diagnóstico permite procurar etapas e distinguir a camada. Ainda não chamamos isso de uma matriz completa de compatibilidade ou de um produto pronto para qualquer dispositivo Mibo.

As capturas editoriais a seguir separam interface e evidência de mídia. O estado sem feed não comprova reprodução; a aba de servidores agrupa os hosts invocados, enquanto o mapa da figura 01 explica a política; nenhum deles autoriza hosts arbitrários. O aplicativo conserva a linguagem visual da Core, mas as decisões de conta e transporte continuam no domínio Noctua/Mibo. A UI não recebe autoridade extra por exibir um estado verde.

Explorador de eventos com filtros, camadas, operações e paginação
Noctua / eventos reais de inventário; sem dados de conta ou mídiaAbrir captura ↗
Eventos agrupados pelo host público invocado pelo cliente
Noctua / aba Servidores; o mapa de política está na figura 01Abrir captura ↗

Precisamos repetir aceitação nativa por plataforma e ABI, observar sessões prolongadas, medir o orçamento com mais streams autorizados e avaliar sincronismo perceptual. PTZ depende de contrato e câmera compatível; nove streams dependem de capacidade e dispositivos, não só layout. A validação de áudio NativeAOT da nova integração deve ser registrada separadamente dos smokes anteriores de vídeo.

Também há tarefas de distribuição: composição final do binário, notices, fontes e patches das dependências, assinatura e instalação. Temos intenção de abrir o código no futuro. Isso não converte o repositório privado atual em fonte pública nem garante um cronograma. O valor imediato deste laboratório é registrar decisões e resultados de forma que outra rodada de trabalho possa refutá-los e melhorá-los.

Apêndice A: o custo da obscuridade está caindo#

Boa parte da engenharia reversa consiste em ligar pistas espalhadas. A string aparece em um método Java, um objeto atravessa JNI, uma estrutura muda de forma no código nativo, e os mesmos bytes reaparecem transformados no socket. Antes de interpretar o algoritmo, é preciso descobrir quais dessas peças pertencem ao mesmo caminho. Modelos com contexto maior ajudam a manter mais desse mapa disponível e a propor conexões que podemos investigar.

Essas ferramentas ajudam a avançar mais rápido, mas cada ligação ainda precisa ser conferida na bancada. O modelo pode comparar representações canônicas, apontar diferenças entre duas capturas, sugerir uma hipótese de endianness e produzir um teste sintético. Também pode insistir em uma chamada que não é executada ou atribuir confiança demais a um nome de função. A pesquisa inicial encontrou justamente esse tipo de limite: instrumentar o símbolo errado pode produzir ausência de evidência, e um hook incompleto pode produzir um pacote falso.

Não há aqui um benchmark entre modelos nem uma curva universal do custo de engenharia reversa. Nesta pesquisa, modelos ajudaram a ler e relacionar artefatos diferentes com mais rapidez. Isso enfraquece a aposta de proteger um valor compartilhado apenas tornando difícil entender o cliente.

A análise vale independentemente de código aberto. Um binário fechado ainda precisa executar o mecanismo para funcionar. Ofuscação pode aumentar custo, e proteções de integridade podem dificultar instrumentação; elas não devem ser confundidas com autorização sobre recursos do servidor. Se extrair uma constante concede poder excessivo, o ponto frágil está no alcance desse poder. Se a constante apenas identifica o aplicativo e o serviço exige credenciais, sessão e permissões próprias, essas fronteiras continuam relevantes.

O SK mostra por que a escolha das palavras importa. Chamá-lo simplesmente de “segredo” pode sugerir exclusividade por instalação; chamá-lo de “público” pode minimizar seu uso operacional. Material criptográfico compartilhado distribuído no cliente descreve melhor o que foi observado: uma baseline identificada e uma função específica. A postura de engenharia é assumir que o material poderá ser observado e limitar o que isso significa no servidor.

Há uma armadilha nesse ganho de contexto: uma explicação fluente pode juntar fatos de versões incompatíveis. Commits, hashes e níveis de evidência ajudaram a manter uma hipótese antiga separada de uma execução nova. Registrar contradições e tentativas que falharam tornou a análise mais auditável.

Contexto maior ajuda a conectar evidências; não transforma hipótese em prova#

No caso dos pacotes, uma hipótese sobre o cabeçalho virou um vetor com offsets e tamanhos. Na mídia, uma hipótese de desproteção foi comparada a uma saída independente. Na bridge, o código aparentemente seguro foi submetido a um segundo send após falha e a uma transição de resolução. No macOS, um erro de socket virou uma pergunta ao framework de rede no mesmo processo. Cada etapa exigiu um observador adequado à conclusão.

Há tarefas em que o modelo é especialmente útil: construir uma lista de invariantes, ler caminhos de erro, procurar ownership inconsistente e escrever testes de limites. Há outras em que a resposta tem de vir de fora dele: o servidor aceitou o login? O primeiro frame foi apresentado? O som parou? A câmera manteve a mesma sessão? Essas perguntas precisam de execução, e algumas precisam de observação humana ou equipamento de medição.

A revisão do YUV é um exemplo de como usar dúvida de forma concreta. O fluxo gerenciado normal descartava o decoder em erro, mas alguém precisava perguntar sobre a ABI chamada diretamente. Depois da primeira correção, alguém precisava perguntar sobre a primeira unidade maior antes da checagem de dimensões. O progresso veio de mudar a pergunta e testar o caminho, não de repetir uma avaliação genérica de que “o código está seguro”.

Uma conclusão prática fica para o desenho de sistemas: as fronteiras precisam continuar úteis mesmo quando o cliente for compreendido. Autorizar no servidor, vincular respostas à operação, limitar memória e trabalho, registrar estados sem segredos e tratar mídia como hostil são decisões verificáveis. A obscuridade pode permanecer como custo adicional de análise. Ela não precisa carregar sozinha o peso da segurança do produto.

Apêndice B: bancada, versões e reprodução#

Tabela de dados 03
ComponenteReferência desta postagem
Perfil do aplicativo estudadoAndroid 3.1.1, build 100315
libavcFonte pública; revisão registrada na bancada
libxaacFonte pública; revisão registrada na bancada
Confirmação anterior ao player CoremacOS ARM64; áudio audível e vídeo da câmera B
Exemplos dos exploradoresSintéticos; não reproduzem uma sessão autorizável

A bancada combinou análise do APK instalado, inspeção de código nativo, instrumentação de metadados, testes offline de parsers e execução independente. A documentação interna da bancada registra os detalhes do SDK, a fronteira dos decoders, o build nativo e os fluxos do app. As revisões exatas ficam nesses registros de auditoria; o texto público descreve a build e os resultados, sem exigir acesso ao nosso repositório privado.

Para exercitar os exemplos, basta usar os controles locais da página ou ler as tabelas. A troca do probe é um XOR de cada byte com 0xFF; aplicar duas vezes recupera o exemplo. Na remontagem, verifique as sequências de ACK: normal 4,8,12; reordenação 0,8,12; perda 4,4,4,12; conflito entre fragmentos pendentes é terminal sem liberar esses bytes; duplicatas já consumidas são descartadas sem comparar seu conteúdo. Esses testes verificam o modelo didático, não toda a implementação de transporte.

As ferramentas de desenvolvimento também fazem verificações: campos cobrem exatamente os bytes dos exemplos, o corpo declarado bate com o tamanho e a simulação reinicia sem estado anterior. A página não possui fetch, WebSocket, armazenamento de conta ou formulário de login. Seus módulos são locais e carregados apenas aqui. Um teste de navegador com e sem JavaScript integra a revisão, porque conteúdo inacessível sem animação seria uma dependência desnecessária para explicar protocolo.

Uma reprodução com câmera real exige conta e dispositivo autorizados, parâmetros compatíveis e aceitação do serviço. Credenciais, URLs privadas, APKs e capturas brutas ficam fora da publicação. Essa ausência não é uma lacuna preenchida pelos dados inventados: os exemplos explicam o formato, enquanto os registros de laboratório sustentam as observações de execução. Manter os dois separados preserva o significado de “reproduzível”.

Apêndice C: white label, plataforma e autoria do hardware#

A análise identifica uma linhagem de software Dahua/Lechange/Imou; ela não identifica, por si só, quem fabricou a placa da iM3B-C.

“White label” descreve um arranjo possível em que uma empresa comercializa uma experiência sob sua marca usando componentes de outra. Aqui temos camadas distintas. Intelbras é a marca e a fornecedora da experiência Mibo Smart apresentada ao usuário; o pacote Android estudado é br.com.intelbras.mibocam, e a página oficial apresenta o aplicativo como plataforma Intelbras. No APK instalado, porém, o ponto de entrada com.mm.android.easy4ip.Easy4ipApplication, módulos Lechange, LCSDK, bibliotecas CommonSDK/netsdk e a configuração OEM mostram que partes substanciais da pilha de conta, P2P e mídia pertencem a uma família tecnológica anterior à interface Mibo. “Configuração OEM” é o nome e a função vistos no código; não é um contrato comercial recuperado.

Há um elo público para essa família: a Dahua apresentou o Easy4ip Cloud como seu serviço de monitoramento e P2P; apresentou Lechange como marca de consumo e depois renomeou essa marca para Imou. Isso ajuda a interpretar os nomes encontrados no APK e nos hosts. É uma inferência de linhagem da plataforma, apoiada pelo binário analisado e por fontes da Dahua; não demonstra que o app Mibo seja um APK Imou sem modificações, nem quem opera cada servidor hoje.

O fabricante físico da câmera B exige outra prova: etiqueta e homologação do exemplar, documentação de fabricação do modelo, ou uma correspondência de hardware/firmware verificável. O identificador comercial iM3B-C, strings DAHUAKEY, classes Lechange, formato DHAV e domínios Easy4ip indicam compatibilidade e componentes de software; nenhum deles é uma declaração de autoria da placa, sensor, montagem ou firmware completo. As evidências disponíveis não permitem afirmar que a Dahua fabricou a iM3B-C. Símbolos tampouco estabelecem licença, suporte ou responsabilidade contratual entre Intelbras e Dahua.

A distinção muda a engenharia do Noctua. A marca determina a relação do usuário com conta, produto e suporte; a pilha observada determina os bytes que o cliente precisa entender; o fabricante físico, ainda não atribuído, seria relevante para variantes de firmware e compatibilidade. Nosso SDK reproduz apenas o caminho autorizado medido, sem incorporar bibliotecas proprietárias do APK. Uma câmera com a mesma marca ou uma biblioteca com o mesmo nome pode exigir outro fluxo.

Apêndice D: trabalhos de terceiros e o limite do flash de firmware#

Referências que ajudam, sem substituir a medição desta câmera#

Os projetos dh-p2p e dahua-p2p implementam túneis P2P da família Dahua. Foram úteis como comparação de perguntas e estruturas; como mostramos na parte 1, o cabeçalho PTCP medido no Mibo não é intercambiável com o layout publicado nesses projetos. A integração Dahua para Home Assistant de rroller documenta uma fronteira prática semelhante: login de nuvem não é conta local do dispositivo, e alguns modelos Imou/Lechange não expõem a API CGI local. Isso não prova ausência dessa API nem incompatibilidade ONVIF na nossa iM3B-C.

Há também uma pesquisa independente do hub Intelbras MCA 1002 que encontrou símbolos IMOU_* e um domínio Easy4ip no firmware do hub. Ela reforça que componentes dessa família aparecem em outro produto Intelbras, mas o MCA 1002 não é uma câmera iM3B-C: não transfere fabricante, SoC, layout de flash ou suporte a firmware entre os dois. Esses autores recebem crédito pelo que mediram; nossa atribuição do APK e da sessão Mibo vem da baseline própria.

Flash não é uma opção de produto demonstrada#

A Intelbras documenta a atualização de firmware pelo aplicativo e publica um procedimento por cartão para alguns modelos iM com sufixo C. Nenhuma dessas fontes mostra que a iM3B-C estudada aceita imagem arbitrária, downgrade ou firmware alternativo. O Dahua Firmware Mod Kit de BotoX extrai e recompõe imagens de outras câmeras Dahua; seu teste declarado foi em outro modelo. Ser da mesma família de software não torna uma imagem compatível com esta placa.

O guia de backup da OpenIPC explica por que uma cópia integral antes de qualquer escrita é essencial: bootloader, calibração, identidade e credenciais podem estar na flash. Um dump bruto também pode revelar Wi-Fi e chaves P2P, portanto não é material para anexo público. Nesta pesquisa não fizemos dump, flash, downgrade ou alteração persistente da câmera. Se algum dia houver investigação de firmware para um exemplar próprio e isolado, ela terá de começar por identificação física verificável, preservação privada do estado original e um caminho de recuperação testado. O plano de gravação do Noctua não depende de prometer esse flash.

Referências#


  1. Os laboratórios usam valores sintéticos para preservar identidades e mídia privadas. Não reproduzem uma sessão real nem enviam pacotes. O segundo é um modelo didático de offsets e remontagem, não toda a máquina de estados PTCP. ↩︎ ↩︎