diff --git a/README.md b/README.md index bda8211..4dfad69 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,9 @@ Añade la dependencia a tu `pom.xml`: ```xml com.flamingo - llp-protocol - 3.0.1 + llp-core + 3.1.0 - ``` ### Autenticación en GitHub Packages @@ -52,7 +51,7 @@ Ve a: GitHub → Settings → Developer Settings → Personal Access Tokens Crea un token con el permiso `read:packages`. -**2. Configura `~/.m2/settings.xml**`** +**2. Configura `~/.m2/settings.xml`** ```xml @@ -62,10 +61,9 @@ Crea un token con el permiso `read:packages`. TU_TOKEN - ``` -**3. Añade el repositorio a tu `pom.xml**` +**3. Añade el repositorio a tu `pom.xml`** ```xml @@ -75,14 +73,12 @@ Crea un token con el permiso `read:packages`. https://maven.pkg.github.com/flamicomm/llp-protocol-java - ``` **4. Verifica** ```bash mvn clean install - ``` --- @@ -102,16 +98,18 @@ LLPFrameBuilder builder = LLP.frameBuilder().build(); byte[] frame = builder.build(ByteBuffer.wrap("hello device".getBytes())); // uart.write(frame); // Example: send via your preferred transport - ``` ### Análisis incremental de un flujo de datos (entrada / inbound) ```java import com.flamingo.comm.llp.core.LLP; -import com.flamingo.comm.llp.core.LLPIncrementalParser; import com.flamingo.comm.llp.core.LLPFrame; +import com.flamingo.comm.llp.core.LLPIncrementalParser; import com.flamingo.comm.llp.core.FinalNode; +import com.flamingo.comm.llp.core.FailureNode; +import com.flamingo.comm.llp.core.UnknownNode; +import com.flamingo.comm.llp.core.TransportErrorCode; LLPIncrementalParser parser = LLP.incrementalParser() .maxPayloadBytes(4096) @@ -125,26 +123,25 @@ while ((b = in.read()) != -1) { parser.feed((byte) b); for (LLPFrame frame : parser.pollFrames()) { - // Navigate the node chain - frame.chain().visit(visitor -> { - visitor.onFinalNode(node -> { + // Navigate the node chain using the visitor pattern + frame.chain().visit(visitor -> visitor + .on(FinalNode.class, node -> { byte[] payload = new byte[node.getPayload().remaining()]; node.getPayload().get(payload); System.out.println("Received: " + new String(payload)); - }); - visitor.onUnknownNode(node -> - System.out.println("Unknown layer skipped: ID=" + node.getId())); - visitor.onFailureNode(node -> + }) + .on(UnknownNode.class, node -> + System.out.println("Unknown layer skipped: ID=" + node.getId())) + .on(FailureNode.class, node -> System.err.println("Layer failed: ID=" + node.getId() - + " reason=" + node.getErrorReason())); - }); + + " reason=" + node.getErrorReason())) + ); } for (TransportErrorCode error : parser.pollErrors()) { System.err.println("Transport error: " + error); } } - ``` ### Análisis de tramas completas (no streaming) @@ -154,7 +151,6 @@ LLPFrameParser parser = LLP.frameParser().build(); // rawFrame is an LLPRawFrame produced by LLPTransportDeframer LLPFrame frame = parser.parse(rawFrame); - ``` ### Uso de plugins de capas @@ -181,7 +177,6 @@ parsed.chain().getNode(RoutingNode.class).ifPresent(node -> { System.out.println("Device: " + node.getMetadata().deviceId()); System.out.println("Group: " + node.getMetadata().group()); }); - ``` --- @@ -194,7 +189,6 @@ El envoltorio más externo validado por la capa de transporte: ``` [MAGIC 2B][LENGTH 2B][PAYLOAD NB][CRC16 2B] - ``` | Campo | Tamaño | Valor / Descripción | @@ -214,7 +208,6 @@ El payload contiene una cadena recursiva de capas opcionales seguida de los dato [LAYER_ID][META_LENGTH][METADATA ...][ next layer or final ] ↓ [0x00][RAW PAYLOAD BYTES] - ``` | Campo | Tamaño | Descripción | @@ -231,7 +224,7 @@ El payload contiene una cadena recursiva de capas opcionales seguida de los dato | `0` | **Final node (Nodo final)** — no hay más capas; los bytes restantes son el payload crudo de la aplicación. | | `1–127` | **Passthrough layer (Capa de paso)** — los metadatos pueden omitirse; el contenido del payload no cambia. | | `128–254` | **Transform layer (Capa de transformación)** — el payload fue modificado (encriptado, comprimido, etc.); no puede omitirse sin la librería de la capa. | -| `255` | Reservado | +| `255` | **Reservado** — reservado para uso futuro; los parsers deben tratarlo como desconocido y saltarlo si es posible. | --- @@ -250,19 +243,34 @@ com.flamingo.comm.llp/ │ ├── LLPFrame.java # Parsed frame with node chain │ ├── LLPRawFrame.java # Transport-level validated frame │ ├── NodeChain.java # Immutable ordered chain of nodes +│ ├── NodeVisitor.java # Type-safe visitor for node traversal │ ├── FinalNode.java # Terminal node (raw payload) │ ├── UnknownNode.java # Skipped unknown passthrough layer -│ └── FailureNode.java # Failed-to-parse layer node +│ ├── FailureNode.java # Failed-to-parse layer node +│ ├── ByteArrayFrameBuilder.java # Default frame builder (byte[] output) +│ ├── CoreParseErrorReason.java # Core-level parse error reasons +│ ├── TransportErrorCode.java # Transport-level error codes +│ ├── FrameBuildException.java # Exception for frame build failures +│ ├── LayerParserProvider.java # Functional interface for parser lookup +│ ├── LayerParserRegistry.java # SPI-based layer parser registry +│ └── SimpleFrameParser.java # Internal default frame parser │ -└── spi/ # SPI contracts for layer plugins - ├── LLPLayerParser.java # Interface for inbound layer parsing - ├── LLPLayerBuilder.java # Interface for outbound layer building - ├── LLPNode.java # Base node interface - ├── LayerParseResult.java # Sealed result type (Success | Failure) - ├── LayerBuildResult.java # Sealed result type (UnmodifiedPayload | TransformedPayload | Failure) - ├── LayerParseInput.java # Read-only metadata + payload for parsing - └── LayerBuildPayload.java # Read-only payload for building - +├── spi/ # SPI contracts for layer plugins +│ ├── LLPLayerParser.java # Interface for inbound layer parsing +│ ├── LLPLayerBuilder.java # Interface for outbound layer building +│ ├── LLPNode.java # Base node interface +│ ├── LayerParseResult.java # Sealed result type (Success | Failure) +│ ├── LayerBuildResult.java # Sealed result type (Success.UnmodifiedPayload | Success.TransformedPayload | Failure) +│ ├── LayerParseInput.java # Read-only metadata + payload for parsing +│ ├── LayerBuildPayload.java # Read-only payload for building +│ ├── ParseErrorReason.java # Interface for parse error reasons +│ └── BuildErrorReason.java # Interface for build error reasons +│ +└── util/ # Internal utilities + ├── ByteWriter.java # Utility for writing byte sequences + ├── CRC16CCITT.java # CRC16-CCITT calculation + ├── LayerIds.java # Layer ID rules and classification + └── Statistics.java # Transport statistics tracking ``` ### Puntos de entrada @@ -285,7 +293,6 @@ LLPIncrementalParser incremental = LLP.incrementalParser() .maxPayloadBytes(8192) .timeoutMs(1000) .build(); - ``` ### Tubería de entrada (Inbound pipeline) @@ -296,7 +303,6 @@ byte stream └── LLPRawFrame └── SimpleFrameParser (layer chain parsing via SPI registry) └── LLPFrame → NodeChain → [Node, Node, ..., FinalNode] - ``` ### Tubería de salida (Outbound pipeline) @@ -307,7 +313,6 @@ ByteBuffer payload └── byte[] (layered payload) └── LLPTransportFramer (magic · length · stuffing · CRC) └── byte[] (transport frame ready to transmit) - ``` --- @@ -323,10 +328,9 @@ Las nuevas capas de protocolo se implementan como módulos de Maven independient ```xml com.flamingo - llp-protocol - 3.0.1 + llp-core + 3.1.0 - ``` **2. Implementa `LLPLayerParser` (entrada)** @@ -347,18 +351,21 @@ public class RoutingLayerParser implements LLPLayerParser { String group = reader.readUtf8(reader.readUInt8()); int ttl = reader.readUInt8(); - return LayerParseResult.success( + return new LayerParseResult.Success( new RoutingNode(new RoutingMetadata(deviceId, group, ttl)), input.payload() // passthrough: payload unchanged ); } catch (Exception e) { - return LayerParseResult.failure(ParseErrorReason.INVALID_METADATA); + return new LayerParseResult.Failure( + MyErrorReason.INVALID_METADATA + ); } } } - ``` +> **Nota:** `LayerParseResult.Success` y `LayerParseResult.Failure` son records internos de la sealed interface `LayerParseResult`. Usa sus constructores directamente. Para errores, puedes implementar `ParseErrorReason` con tus propios enums, o usar `CoreParseErrorReason` del paquete `core`. + **3. Implementa `LLPLayerBuilder` (salida)** ```java @@ -387,10 +394,9 @@ public class RoutingLayerBuilder implements LLPLayerBuilder { .toBytes(); // Passthrough: payload is not modified - return LayerBuildResult.unmodified(ByteBuffer.wrap(metadata)); + return new LayerBuildResult.Success.UnmodifiedPayload(ByteBuffer.wrap(metadata)); } } - ``` **4. Registro vía SPI** @@ -399,7 +405,6 @@ Crea el archivo `src/main/resources/META-INF/services/com.flamingo.comm.llp.spi. ``` com.example.llp.routing.RoutingLayerParser - ``` La librería base lo descubrirá y registrará automáticamente al inicio. @@ -410,6 +415,7 @@ La librería base lo descubrirá y registrará automáticamente al inicio. | --- | --- | --- | --- | | `1–127` | Passthrough | Sin cambios — puede omitirse | No | | `128–254` | Transform | Modificado (encriptado/comprimido) | Sí | +| `255` | Reservado | Tratado como desconocido y saltado | No | --- @@ -421,18 +427,21 @@ Tras el análisis, el `NodeChain` de la trama contiene una secuencia ordenada de | --- | --- | --- | | `LLPNode` | Implementaciones SPI (capas personalizadas) | `getId()` | | `FinalNode` | Siempre — marca el final de la cadena con bytes crudos | `getId()`, `getPayload()` | -| `UnknownNode` | El Layer ID no tiene manejador y es de tipo passthrough | `getId()`, `getMetadata()` | -| `FailureNode` | Falló el análisis de la capa (error de plugin o metadatos corruptos) | `getId()`, `getErrorReason()`, `getCause()`, `getMetadata()` | +| `UnknownNode` | El Layer ID no tiene manejador y es de tipo passthrough o reservado | `getId()`, `getMetadata()` | +| `FailureNode` | Falló el análisis de la capa (error de plugin, metadatos corruptos o falta de FinalNode) | `getId()`, `getErrorReason()`, `getCause()`, `getMetadata()` | ### Navegación por la cadena ```java -// Option A — visitor (recommended for production code) -frame.chain().visit(visitor -> { - visitor.onFinalNode(node -> handlePayload(node.getPayload())); - visitor.onUnknownNode(node -> log.debug("Skipped layer {}", node.getId())); - visitor.onFailureNode(node -> log.warn("Failed layer {}: {}", node.getId(), node.getErrorReason())); -}); +// Option A — visitor pattern (recommended for production code) +frame.chain().visit(visitor -> visitor + .on(FinalNode.class, node -> handlePayload(node.getPayload())) + .on(UnknownNode.class, node -> + System.out.println("Skipped layer " + node.getId())) + .on(FailureNode.class, node -> + System.err.println("Failed layer " + node.getId() + + ": " + node.getErrorReason())) +); // Option B — find a specific node type frame.chain().getNode(RoutingNode.class) @@ -447,7 +456,6 @@ LLPNode deepest = frame.chain().getDeepestNode(); if (deepest instanceof FinalNode final) { process(final.getPayload()); } - ``` --- @@ -456,15 +464,15 @@ if (deepest instanceof FinalNode final) { La versión 3 introduce un nuevo modelo de tramas en capas y una API pública rediseñada. La API de la v2 ha sido eliminada. -| v2.x | v3.x | -| --- |-----------------------------------------------------------------------| -| `LLP.newParser()` | `LLP.incrementalParser().build()` | -| `parser.processByte(b)` | `parser.feed(b)` + `parser.pollFrames()` | -| `parser.addListener(...)` | Maneja los resultados desde `pollFrames()` / `pollErrors()` | -| `LLP.buildData(type, payload)` | `LLP.frameBuilder().build()` + `.build(payload)` | -| `LLPFrame.getType()` | Eliminado — el tipo de mensaje es ahora un asunto de la capa | -| `LLPFrame.getId()` | Eliminado — el ID de transacción es ahora un asunto de la capa | -| `LLPMessageType` | Eliminado — define los tipos de mensajes en tu capa | +| v2.x | v3.x | +| --- | --- | +| `LLP.newParser()` | `LLP.incrementalParser().build()` | +| `parser.processByte(b)` | `parser.feed(b)` + `parser.pollFrames()` | +| `parser.addListener(...)` | Maneja los resultados desde `pollFrames()` / `pollErrors()` | +| `LLP.buildData(type, payload)` | `LLP.frameBuilder().build()` + `.build(payload)` | +| `LLPFrame.getType()` | Eliminado — el tipo de mensaje es ahora un asunto de la capa | +| `LLPFrame.getId()` | Eliminado — el ID de transacción es ahora un asunto de la capa | +| `LLPMessageType` | Eliminado — define los tipos de mensajes en tu capa | | Formato de trama única | Modelo de cebolla en capas (layered onion model) con capas opcionales | El formato de la trama cambió significativamente en la v3 para soportar el modelo de capas. Las tramas v2 y v3 **no son compatibles a nivel de red (wire-compatible)**. @@ -480,6 +488,8 @@ mvn test # Run tests with coverage report mvn verify +# Run tests including slow timing vectors +mvn test -Pslow-tests ``` La suite de pruebas cubre: @@ -490,6 +500,7 @@ La suite de pruebas cubre: * Análisis incremental (streaming) a través de los tres métodos `feed()`. * Registro SPI (detección de duplicados, registro manual, descubrimiento SPI). * Casos límite (edge cases): payloads vacíos, longitudes de metadatos extendidas, fallos no omitibles (non-skippable). +* Vectores de prueba de especificación LLP: 199 vectores oficiales que validan conformancia wire-compatible. --- @@ -518,18 +529,22 @@ Todo el código, comentarios, Javadoc y nombres de variables deben escribirse en ## 📜 Licencia -Licencia MIT — ver [LICENSE](https://www.google.com/search?q=LICENSE) +Licencia MIT — ver [LICENSE](LICENSE) + +LLP Specification v3.1.0 — Copyright © 2026 Flamingo Communications + +This specification is maintained as the authoritative reference for the LLP protocol. All implementations should reference this document as the canonical behaviour definition. --- ## ✍️ Autor -Creado por **flamicomm** +Creado por **Famingo Communications** --- -**Versión:** 3.0.1 +**Versión:** 3.1.0 -**Última actualización:** 2026-05-13 +**Última actualización:** 2026-05-17 **Objetivo Java:** 21+ \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0e5aa69..9ef05df 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.flamingo llp-core - 3.0.1 + 3.1.0 jar LLP Core