{"id":36975129,"url":"https://github.com/hayabusa-cloud/framer","last_synced_at":"2026-01-13T22:03:21.508Z","repository":{"id":329261975,"uuid":"1115603740","full_name":"hayabusa-cloud/framer","owner":"hayabusa-cloud","description":"Portable message framing for Go over stream transports","archived":false,"fork":false,"pushed_at":"2025-12-19T13:18:50.000Z","size":141,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-21T17:35:35.995Z","etag":null,"topics":["framing","golang","io"],"latest_commit_sha":null,"homepage":"https://code.hybscloud.com/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hayabusa-cloud.png","metadata":{"files":{"readme":"README.es.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-13T07:09:09.000Z","updated_at":"2025-12-19T13:16:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/hayabusa-cloud/framer","commit_stats":null,"previous_names":["hayabusa-cloud/framer"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/hayabusa-cloud/framer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hayabusa-cloud%2Fframer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hayabusa-cloud%2Fframer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hayabusa-cloud%2Fframer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hayabusa-cloud%2Fframer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hayabusa-cloud","download_url":"https://codeload.github.com/hayabusa-cloud/framer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hayabusa-cloud%2Fframer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28399504,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-13T14:36:09.778Z","status":"ssl_error","status_checked_at":"2026-01-13T14:35:19.697Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framing","golang","io"],"created_at":"2026-01-13T22:03:21.425Z","updated_at":"2026-01-13T22:03:21.479Z","avatar_url":"https://github.com/hayabusa-cloud.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# framer — límites de mensaje sobre E/S de flujo\n\n[![Go Reference](https://pkg.go.dev/badge/code.hybscloud.com/framer.svg)](https://pkg.go.dev/code.hybscloud.com/framer)\n[![Go Report Card](https://goreportcard.com/badge/github.com/hayabusa-cloud/framer)](https://goreportcard.com/report/github.com/hayabusa-cloud/framer)\n[![Coverage Status](https://codecov.io/gh/hayabusa-cloud/framer/graph/badge.svg)](https://codecov.io/gh/hayabusa-cloud/framer)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n**Idiomas / Languages:** [English](README.md) | [简体中文](README.zh-CN.md) | [日本語](README.ja.md) | Español | [Français](README.fr.md)\n\nFraming de mensajes portable para Go. Conserva “un mensaje por `Read`/`Write`” sobre transportes tipo stream.\n\nAlcance: `framer` resuelve la preservación de límites de mensaje en transportes de flujo.\n\n## En resumen\n\n- Resuelve problemas de límites de mensaje en flujos de bytes (TCP, Unix stream, pipes).\n- Pass-through en transportes que ya preservan límites (UDP, Unix datagram, WebSocket, SCTP).\n- Formato wire portable; orden de bytes configurable.\n\n## Por qué\n\nMuchos transportes son flujos de bytes (TCP, Unix stream, pipes). Un solo `Read` puede devolver una fracción de un mensaje de aplicación, o varios mensajes concatenados. `framer` restaura los límites: en modo stream, un `Read` devuelve exactamente un payload de mensaje y un `Write` emite exactamente un mensaje enmarcado.\n\n## Adaptación de protocolo\n\n- `BinaryStream` (transportes stream: TCP, TLS-over-TCP, Unix stream, pipes): agrega un prefijo de longitud; lee/escribe mensajes completos.\n- `SeqPacket` (p. ej., SCTP, WebSocket): pass-through; el transporte ya preserva límites.\n- `Datagram` (p. ej., UDP, Unix datagram): pass-through; el transporte ya preserva límites.\n\nSelecciona al construir vía `WithProtocol(...)` (hay variantes de lectura/escritura) o usa los helpers de transporte (ver Options).\n\n## Wire format\n\nPrefijo de longitud compacto de tamaño variable, seguido por bytes de payload. El orden de bytes para la longitud extendida es configurable: `WithByteOrder`, o por dirección `WithReadByteOrder` / `WithWriteByteOrder`.\n\n## Formato de datos del frame\n\nEl esquema de framing de `framer` es intencionalmente compacto:\n\n- Byte de cabecera `H0` + bytes opcionales de longitud extendida.\n- Sea `L` la longitud del payload en bytes.\n  - Si `0 ≤ L ≤ 253` (`0x00..0xFD`): `H0 = L`. Sin bytes extra.\n  - Si `254 ≤ L ≤ 65535` (`0x0000..0xFFFF`): `H0 = 0xFE` y los siguientes 2 bytes codifican `L` como entero sin signo de 16 bits en el orden configurado.\n  - Si `65536 ≤ L ≤ 2^56-1`: `H0 = 0xFF` y los siguientes 7 bytes llevan los 56 bits bajos de `L` en el orden configurado.\n    - Big-endian: bytes `[1..7]` son los 56 bits bajos de `L` en big-endian.\n    - Little-endian: bytes `[1..7]` son los 56 bits bajos de `L` en little-endian.\n\nLímites y errores:\n- La longitud máxima de payload soportada es `2^56-1`; valores mayores producen `framer.ErrTooLong`.\n- Con un límite de lectura (`WithReadLimit`), longitudes mayores fallan con `framer.ErrTooLong`.\n\n## Inicio rápido\n\nInstala con `go get`:\n```shell\ngo get code.hybscloud.com/framer\n```\n\n```go\nc1, c2 := net.Pipe()\ndefer c1.Close()\ndefer c2.Close()\n\nw := framer.NewWriter(c1, framer.WithWriteTCP())\nr := framer.NewReader(c2, framer.WithReadTCP())\n\ngo func() { _, _ = w.Write([]byte(\"hello\")) }()\n\nbuf := make([]byte, 64)\nn, err := r.Read(buf)\nif err != nil {\n    panic(err)\n}\nfmt.Printf(\"got: %q\\n\", buf[:n])\n```\n\n## Options\n\n- `WithProtocol(proto Protocol)` — elige `BinaryStream`, `SeqPacket` o `Datagram` (hay variantes de lectura/escritura).\n- Orden de bytes: `WithByteOrder`, o `WithReadByteOrder` / `WithWriteByteOrder`.\n- `WithReadLimit(n int)` — limita el tamaño máximo del payload al leer.\n- `WithRetryDelay(d time.Duration)` — política de would-block; helpers: `WithNonblock()` / `WithBlock()`.\n\nHelpers de transporte (presets):\n- `WithReadTCP` / `WithWriteTCP` (`BinaryStream`, BigEndian en orden de red)\n- `WithReadUDP` / `WithWriteUDP` (`Datagram`, BigEndian)\n- `WithReadWebSocket` / `WithWriteWebSocket` (`SeqPacket`, BigEndian)\n- `WithReadSCTP` / `WithWriteSCTP` (`SeqPacket`, BigEndian)\n- `WithReadUnix` / `WithWriteUnix` (`BinaryStream`, BigEndian)\n- `WithReadUnixPacket` / `WithWriteUnixPacket` (`Datagram`, BigEndian)\n- `WithReadLocal` / `WithWriteLocal` (`BinaryStream`, orden nativo)\n\nMás: GoDoc https://pkg.go.dev/code.hybscloud.com/framer\n\n## Contrato de semántica (Semantics Contract)\n\n### Taxonomía de errores\n\n| Error | Significado | Acción del llamador |\n|-------|-------------|---------------------|\n| `nil` | Operación completada con éxito | Continúa; `n` refleja el progreso total |\n| `io.EOF` | Fin de stream (no hay más mensajes) | Deja de leer; terminación normal |\n| `io.ErrUnexpectedEOF` | El stream terminó a mitad de mensaje (header o payload incompleto) | Trátalo como fatal; posible corrupción o desconexión |\n| `io.ErrShortBuffer` | Buffer destino demasiado pequeño para el payload | Reintenta con un buffer más grande |\n| `io.ErrShortWrite` | El destino aceptó menos bytes que los provistos | Reintenta o trátalo como fatal según el contexto |\n| `io.ErrNoProgress` | El Reader subyacente no avanzó (`n==0, err==nil`) con buffer no vacío | Trátalo como fatal; indica un `io.Reader` roto |\n| `framer.ErrWouldBlock` | No es posible avanzar ahora sin esperar | Reintenta más tarde (tras poll/event); `n` puede ser \u003e0 |\n| `framer.ErrMore` | Hubo progreso; seguirán más completions del mismo op | Procesa y vuelve a llamar |\n| `framer.ErrTooLong` | El mensaje excede límites o el máximo del wire format | Rechaza; posiblemente fatal |\n| `framer.ErrInvalidArgument` | Reader/Writer nil o configuración inválida | Corrige la configuración |\n\n### Tablas de resultados\n\n**`Reader.Read(p []byte) (n int, err error)`** — modo BinaryStream\n\n| Condición | n | err |\n|----------|---|-----|\n| Mensaje completo entregado | payload length | `nil` |\n| `len(p) \u003c payload length` | 0 | `io.ErrShortBuffer` |\n| Payload excede ReadLimit | 0 | `ErrTooLong` |\n| El subyacente devuelve would-block | bytes leídos hasta ahora | `ErrWouldBlock` |\n| El subyacente devuelve more | bytes leídos hasta ahora | `ErrMore` |\n| EOF en el límite de mensaje | 0 | `io.EOF` |\n| EOF a mitad de header o payload | bytes leídos | `io.ErrUnexpectedEOF` |\n\n**`Writer.Write(p []byte) (n int, err error)`** — modo BinaryStream\n\n| Condición | n | err |\n|----------|---|-----|\n| Mensaje enmarcado completo emitido | `len(p)` | `nil` |\n| Payload excede el máximo (2^56-1) | 0 | `ErrTooLong` |\n| El subyacente devuelve would-block | bytes de payload escritos | `ErrWouldBlock` |\n| El subyacente devuelve more | bytes de payload escritos | `ErrMore` |\n\n**`Reader.WriteTo(dst io.Writer) (n int64, err error)`**\n\n| Condición | n | err |\n|----------|---|-----|\n| Transferencia hasta EOF | bytes totales de payload | `nil` |\n| Reader subyacente devuelve would-block | bytes de payload escritos | `ErrWouldBlock` |\n| Reader subyacente devuelve more | bytes de payload escritos | `ErrMore` |\n| `dst` devuelve would-block | bytes de payload escritos | `ErrWouldBlock` |\n| Mensaje excede el buffer interno (64KiB por defecto) | bytes hasta ahora | `ErrTooLong` |\n| Stream terminó a mitad de mensaje | bytes hasta ahora | `io.ErrUnexpectedEOF` |\n\n**`Writer.ReadFrom(src io.Reader) (n int64, err error)`**\n\n| Condición | n | err |\n|----------|---|-----|\n| Chunks codificados hasta src EOF | bytes totales de payload | `nil` |\n| `src` devuelve would-block | bytes de payload escritos | `ErrWouldBlock` |\n| `src` devuelve more | bytes de payload escritos | `ErrMore` |\n| Writer subyacente devuelve would-block | bytes de payload escritos | `ErrWouldBlock` |\n\n**`Forwarder.ForwardOnce() (n int, err error)`**\n\n| Condición | n | err |\n|----------|---|-----|\n| Un mensaje reenviado completamente | bytes de payload (fase de escritura) | `nil` |\n| Fuente packet devuelve `(n\u003e0, io.EOF)` | bytes de payload (fase de escritura) | `nil` (la próxima llamada devuelve `io.EOF`) |\n| No hay más mensajes | 0 | `io.EOF` |\n| Would-block en fase de lectura | bytes leídos en esta llamada | `ErrWouldBlock` |\n| Would-block en fase de escritura | bytes escritos en esta llamada | `ErrWouldBlock` |\n| Mensaje excede el buffer interno | 0 | `io.ErrShortBuffer` |\n| Mensaje excede ReadLimit | 0 | `ErrTooLong` |\n| Stream terminó a mitad de mensaje | bytes hasta ahora | `io.ErrUnexpectedEOF` |\n\n### Clasificación de operaciones\n\n| Operación | Comportamiento de límites | Caso de uso |\n|----------|----------------------------|------------|\n| `Reader.Read` | **Preserva límites**: 1 llamada = 1 mensaje | Procesamiento por mensaje |\n| `Writer.Write` | **Preserva límites**: 1 llamada = 1 mensaje | Envío por mensaje |\n| `Reader.WriteTo` | **Chunking**: stream de bytes de payload (no wire format) | Transferencia eficiente; NO preserva límites |\n| `Writer.ReadFrom` | **Chunking**: cada chunk de `src` se vuelve un mensaje | Codificación eficiente; NO preserva límites aguas arriba |\n| `Forwarder.ForwardOnce` | **Relay con límites**: decodifica uno, re-encodifica uno | Proxy con preservación de límites |\n\n### Política de bloqueo\n\nPor defecto, framer es **no bloqueante** (`WithNonblock()`): devuelve `ErrWouldBlock` inmediatamente.\n\n- `WithBlock()` — hace yield (`runtime.Gosched`) y reintenta ante would-block\n- `WithRetryDelay(d)` — duerme `d` y reintenta ante would-block\n- `RetryDelay` negativo (por defecto) — devuelve `ErrWouldBlock` inmediatamente\n\nNingún método oculta bloqueo a menos que se configure explícitamente.\n\n## Fast paths\n\n`framer` implementa fast paths del stdlib para interoperar con motores tipo `io.Copy` y con `iox.CopyPolicy`:\n\n- `(*Reader).WriteTo(io.Writer)` — transfiere eficientemente payloads a `dst`.\n  - Stream (`BinaryStream`): procesa un mensaje por vez y escribe solo bytes de payload. Si `ReadLimit == 0`, usa un tope conservador (64KiB); mensajes más grandes devuelven `framer.ErrTooLong`.\n  - Packet (`SeqPacket`/`Datagram`): pass-through.\n  - `framer.ErrWouldBlock` y `framer.ErrMore` se propagan sin cambios, con el conteo reflejando bytes escritos.\n\n- `(*Writer).ReadFrom(io.Reader)` — chunk-to-message: cada chunk leído de `src` se codifica como un mensaje y se escribe vía `w.Write`.\n  - Es eficiente pero no preserva límites de mensaje de `src`.\n  - En protocolos con límites preservados, se comporta como pass-through.\n  - `framer.ErrWouldBlock` y `framer.ErrMore` se propagan sin cambios.\n\nRecomendación: en bucles no bloqueantes, prefiere `iox.CopyPolicy` con política de reintentos (p. ej., `PolicyRetry`) para manejar explícitamente `ErrWouldBlock` / `ErrMore`.\n\n**Nota sobre recuperación de escrituras parciales:** Al usar `iox.Copy` con destinos no bloqueantes, pueden ocurrir escrituras parciales. Si la fuente no implementa `io.Seeker`, `iox.Copy` devuelve `iox.ErrNoSeeker` para evitar pérdida silenciosa de datos. Para fuentes no buscables (p. ej., sockets de red), usa `iox.CopyPolicy` con `PolicyRetry` para errores semánticos del lado de escritura, asegurando que todos los bytes leídos se escriban antes de retornar.\n\n## Reenvío\n\n- Proxy a nivel wire (motores de bytes): usa `iox.CopyPolicy` y fast paths estándar (`WriterTo`/`ReaderFrom`). Maximiza throughput cuando no necesitas preservar límites de nivel superior.\n- Relay por mensaje (preserva límites): usa `framer.NewForwarder(dst, src, ...)` y llama `ForwardOnce()` en tu poll loop. Decodifica exactamente un mensaje desde `src` y lo re-encodifica como exactamente un mensaje hacia `dst`.\n  - Semántica no bloqueante: `ForwardOnce` devuelve `(n\u003e0, framer.ErrWouldBlock|framer.ErrMore)` cuando hubo progreso parcial; reintenta con la misma instancia.\n  - Límites: `io.ErrShortBuffer` si el buffer interno es insuficiente; `framer.ErrTooLong` si excede `WithReadLimit`.\n  - Cero asignaciones en steady-state tras la construcción; el buffer interno se reutiliza.\n\n## Licencia\n\nMIT — ver `LICENSE`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhayabusa-cloud%2Fframer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhayabusa-cloud%2Fframer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhayabusa-cloud%2Fframer/lists"}