Inspección de files de tipo "NeXT / Apple typedstream" versión 4 (NSArchiver)

Para un progtwig de recuperación de datos, necesito poder extraer los valores + types de los files escritos por NSArchiver, sin tener acceso a los frameworks CF / NS de Apple.

El command de file OS X informa de files como:

 NeXT/Apple typedstream data, little endian, version 4, system 1000 

¿Hay alguna documentation sobre cómo se codifican estos files o alguien ha creado un código que los pueda analizar?

Aquí hay un ejemplo de estos datos (también: descargables ):

 04 0B 73 74 72 65 61 6D 74 79 70 65 64 81 E8 03 ..streamtyped... 84 01 40 84 84 84 12 4E 53 41 74 74 72 69 62 75 ..@....NSAttribu 74 65 64 53 74 72 69 6E 67 00 84 84 08 4E 53 4F tedString....NSO 62 6A 65 63 74 00 85 92 84 84 84 08 4E 53 53 74 bject.......NSSt 72 69 6E 67 01 94 84 01 2B 06 46 65 73 6B 65 72 ring....+.Fesker 86 84 02 69 49 01 06 92 84 84 84 0C 4E 53 44 69 ...iI.......NSDi 63 74 69 6F 6E 61 72 79 00 94 84 01 69 01 92 84 ctionary....i... 96 96 1D 5F 5F 6B 49 4D 4D 65 73 73 61 67 65 50 ...__kIMMessageP 61 72 74 41 74 74 72 69 62 75 74 65 4E 61 6D 65 artAttributeName 86 92 84 84 84 08 4E 53 4E 75 6D 62 65 72 00 84 ......NSNumber.. 84 07 4E 53 56 61 6C 75 65 00 94 84 01 2A 84 99 ..NSValue....*.. 99 00 86 86 86 ..... 

Esto contiene un NSAttributedString. Tengo ejemplos similares que contienen NSMutableAttributedStrings, etc., pero finalmente todo se resuelve en NSAttributedStrings, para lo cual me gusta get el text. No me importa el rest, pero necesito saber si es válido.

Mi solución actual es utilizar el NSUnarchiver y, suponiendo que siempre debería encontrar un NSAttributedString allí, get su primer elemento y leer su text, luego volver a crear un file de él y ver si es el mismo que los datos originales. Si recibo una exception o un file diferente, supongo que el file está dañado o no es válido:

 NSData *data = [[NSData alloc] initWithBytesNoCopy:dataPtr length:dataLen freeWhenDone:false]; NSUnarchiver *a = NULL; // The algorithm simply assumes that the data contains a NSAttributedString, retrieves it, // and then recreates the NSArchived version from it in order to tell its size. @try { a = [[NSUnarchiver alloc] initForReadingWithData:data]; NSAttributedString *s = [a decodeObject]; // re-encode the string item so we can tell its length NSData *d = [NSArchiver archivedDataWithRootObject:s]; if ([d isEqualTo:[data subdataWithRange:NSMakeRange(0,d.length)]]) { lenOut = (int) d.length; okay = true; // -> lenOut is valid, though textOut might still fail, see @catch below textOut = [s.string cStringUsingEncoding:NSUTF8StringEncoding]; } else { // oops, we don't get back what we had as input, so let's better not consider this valid } } @catch (NSException *e) { // data is invalid } 

Sin embargo, hay varios problemas con el código anterior:

  1. No es x-platform. Necesito esto para trabajar también en Windows.
  2. Algunos ejemplos de datos dañados provocan un error no deseado msg escrito en stderr o syslog (no estoy seguro de cuál), como: *** mmap(size=18446744071608111104) failed (error code=12) *** error: can't allocate region *** set a breakpoint in malloc_error_break to debug ( *** mmap(size=18446744071608111104) failed (error code=12) *** error: can't allocate region *** set a breakpoint in malloc_error_break to debug un informe de error sobre esto que se cerró como "no se solucionará", por desgracia).
  3. Nada garantiza que el código NSUnarchiver sea 100% a testing de fallas. El error malloc es un ejemplo para esto. También podría get un error de bus en algunas situaciones, y eso sería fatal. Si tuviera un código personalizado para el análisis, puedo encargármelo yo mismo (y corregir los lockings que encuentre). (Actualización: Acabo de encontrar algunos datos no válidos que de hecho bloquean NSUnarchiver con un SIGSEGV).

Por lo tanto, necesito un código personalizado para decodificar este tipo de files. He mirado algunos, pero no puedo entender los códigos que usa. Al parecer, hay campos de longitud y tipo, con los types en el range de 0x81 a 0x86, aparentemente. Además, los primeros 16 bytes son el encabezado, incluido el código del sistema (0x03E8 = 1000) en el desplazamiento 14-15.

También me pregunto si el código fuente está disponible en algunas fonts antiguas de NeXT o en la versión de Windows que existía una vez, pero ¿dónde encontraría eso? ( Nota: Me dirigí a la fuente GNUstep ("core.20131003.tar.bz2"), en la que encontré su fuente NSUnarchiver, pero ese código, aparentemente desde 1998, usa su propia encoding, que no entiende este " streamtyped " ) .

Si bien no conozco ningún tipo de documentation sobre el formatting, puede encontrar la información que está buscando al verificar el código fuente público de versiones anteriores de Darwin (o tal vez OpenStep).

Por ejemplo, eche un vistazo a la implementación de typedstream en el file typedstream.m en objc-1.tar.gz disponible en este espejo de una distribución Darwin anterior .

Este código fuente debería ser capaz de leer / escribir en un typedstream . Solo asegúrese de confirmar la licencia de Apple cuando lo use.

Parte del problema aquí es que cada class en Cocoa / NeXTSTEP / OPENSTEP sabe cómo archivarse. En cada class hay un código initWithCoder: / encodeWithCoder: dentro hay una sección para types de files y otra sección para files con keys. Los files con keys son más modernos y generalmente se expresan como files XML. Estos pueden codificarse en forma binaria, pero, no nos equivoquemos, esta forma binaria NO es lo mismo que un file tipo flujo de datos. Además, están codificados para que sea fácil extraer datos individuales sin tener que leer todos los datos anteriores. Los files de Typedstream no funcionan de esta manera. Se basan en el order, lo que significa que cada elemento de cada object se escribe uno tras otro. Primero el nombre de la class, luego la versión, luego cada uno de los datos. La razón por la que GNUstep nunca implementó esto es porque el order de encoding es casi imposible de descubrir.

Cuando archiva el object raíz de un gráfico de object, llama al método encodeWithCoder: en ese object, que a su vez llama a encodeWithCoder: methods en cada uno de los objects que contiene y así sucesivamente hasta que se archiva todo el gráfico del object. Cuando esto se hace usando files con keys (NSKeyedArchiver), el file se crea y se teclea correctamente. Cuando se hace con un file de flujo typescript (NSArchiver) ocurre la misma recursión, pero cada vez que se codifica un object, simplemente descarga cada elemento en el file en cualquier order que el desarrollador considere apropiado en ese momento.

Espero que esta explicación aclare un poco las cosas. Tienes un path difícil por delante. Hubo razones para evitarlo en GNUstep. Si lo hubiéramos hecho, TODAVÍA estaríamos tratando de resolverlo.

Parece ser parte del time de ejecución de Objective-C de GNU, aunque no sea exactamente el time de ejecución (ver la discusión en: http://gcc.gnu.org/ml/gcc-patches/2010-09/msg00495.html )

Este file puede implementar esas cosas: https://github.com/gnustep/gnustep-libobjc/blob/master/archive.c

Primero, vea ¿Hay alguna forma de leer en files en formatting TypedStream alguna información interesante?

Muy probablemente, el formatting se puede convertir en algo más legible con la herramienta plutil . Esta herramienta también está disponible para Windows (viene con iTunes para Windows). Sin embargo, no estoy seguro de su licencia.

La parte problemática es el hecho de que los files contienen instancias de object convertidas en binarias. No es suficiente comprender el formatting del file, es necesario comprender cómo se almacena cada tipo.

Frank Illenberger escribió un reemploop NSUnarchiver llamado MEUnarchiver basado en el código fuente typedstream.m de 1999: https://github.com/depth42/MEUnarchiver

Se ha ampliado para admitir types más nuevos que el código fuente original desconoce. Todavía depende del time de ejecución de ObjC para proporcionar implementaciones de decodificador de NSCoding para todos los types estándar, como NSString, etc. pero, por lo demás, es bastante autónomo y me permite evitar lockings que ocurren con el código NSUnarchiver de Apple al pasar datos dañados.