Frame-by-frame IMQ with an outgoing filter; HTTP interceptor hook
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2551888ed6
commit
2190230f81
6 files changed
+485
-24
No files matched your search
@@ -40,10 +40,11 @@ func TypeName(t uint64) string {
|
||||
return fmt.Sprintf("type %d", t)
|
||||
}
|
||||
|
||||
// Frame is one message: its type and the size of its data.
|
||||
// Frame is one message: its type and data (Size is len(Data)).
|
||||
type Frame struct {
|
||||
Type uint64
|
||||
Size int
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// ErrMalformed means the stream isn't IMQ framing; a Reader stops decoding after it.
|
||||
@@ -76,6 +77,12 @@ func (r *Reader) Write(data []byte) ([]Frame, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Next reads the frame at the start of b: the frame and its length in b, a length of 0 if b holds only part of it,
|
||||
// or ErrMalformed.
|
||||
func Next(b []byte) (Frame, int, error) {
|
||||
return parse(b)
|
||||
}
|
||||
|
||||
// maxFrame bounds a frame's size, so a stream that isn't IMQ isn't buffered without end.
|
||||
const maxFrame = 16 << 20
|
||||
|
||||
@@ -116,6 +123,7 @@ func parse(b []byte) (Frame, int, error) {
|
||||
return f, 0, nil
|
||||
}
|
||||
f.Size = int(size)
|
||||
f.Data = b[i : i+int(size) : i+int(size)]
|
||||
return f, i + int(size), nil
|
||||
default:
|
||||
return f, 0, ErrMalformed
|
||||
|
||||
@@ -36,12 +36,12 @@ func TestReaderSplitsFrames(t *testing.T) {
|
||||
}
|
||||
got = append(got, frames...)
|
||||
}
|
||||
want := []Frame{{1302, 5}, {3103, 300}, {1301, 0}}
|
||||
want := []Frame{{1302, 5, []byte("hello")}, {3103, 300, bytes.Repeat([]byte{1}, 300)}, {1301, 0, nil}}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
if got[i].Type != want[i].Type || got[i].Size != want[i].Size || !bytes.Equal(got[i].Data, want[i].Data) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
@@ -63,3 +63,21 @@ func TestTypeName(t *testing.T) {
|
||||
t.Fatal(TypeName(3104), TypeName(7))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendMessageRoundTrip(t *testing.T) {
|
||||
m := SendMessage{OpID: 300, Queue: "/chat/1", Mount: "messages", Message: []byte(`{"message": "*use 80 1"}`)}
|
||||
data := m.Marshal()
|
||||
got, err := ParseSendMessage(data)
|
||||
if err != nil || got.OpID != 300 || got.Queue != "/chat/1" || got.Mount != "messages" || string(got.Message) != string(m.Message) {
|
||||
t.Fatalf("%+v %v", got, err)
|
||||
}
|
||||
// A frame of it reads back as one frame with the same data.
|
||||
var r Reader
|
||||
frames, err := r.Write(EncodeFrame(C2gSendMessage, data))
|
||||
if err != nil || len(frames) != 1 || frames[0].Type != C2gSendMessage || !bytes.Equal(frames[0].Data, data) {
|
||||
t.Fatalf("%v %v", frames, err)
|
||||
}
|
||||
if _, err := ParseSendMessage(Result(1, 0)); err == nil {
|
||||
t.Fatal("a G2cResult parsed as a C2gSendMessage")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package imq
|
||||
|
||||
import "errors"
|
||||
|
||||
// Message types the proxy reads or writes.
|
||||
const (
|
||||
C2gSendMessage = 1304
|
||||
G2cResult = 3102
|
||||
)
|
||||
|
||||
// SendMessage is a C2gSendMessage: a message the client sends to a queue's mount.
|
||||
type SendMessage struct {
|
||||
OpID uint64
|
||||
Queue string
|
||||
Mount string
|
||||
Message []byte
|
||||
}
|
||||
|
||||
// field is one protobuf field: its number and either a varint or bytes.
|
||||
type field struct {
|
||||
num uint64
|
||||
varint uint64
|
||||
bytes []byte
|
||||
isLen bool
|
||||
}
|
||||
|
||||
func fields(b []byte) ([]field, error) {
|
||||
var out []field
|
||||
for i := 0; i < len(b); {
|
||||
key, n := varint(b[i:])
|
||||
if n <= 0 {
|
||||
return nil, ErrMalformed
|
||||
}
|
||||
i += n
|
||||
f := field{num: key >> 3}
|
||||
switch key & 7 {
|
||||
case 0:
|
||||
v, n := varint(b[i:])
|
||||
if n <= 0 {
|
||||
return nil, ErrMalformed
|
||||
}
|
||||
f.varint, i = v, i+n
|
||||
case 2:
|
||||
l, n := varint(b[i:])
|
||||
if n <= 0 || l > uint64(len(b)-i-n) {
|
||||
return nil, ErrMalformed
|
||||
}
|
||||
i += n
|
||||
f.bytes, f.isLen, i = b[i:i+int(l)], true, i+int(l)
|
||||
default:
|
||||
return nil, ErrMalformed
|
||||
}
|
||||
out = append(out, f)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ParseSendMessage reads a C2gSendMessage's data.
|
||||
func ParseSendMessage(data []byte) (SendMessage, error) {
|
||||
fs, err := fields(data)
|
||||
if err != nil {
|
||||
return SendMessage{}, err
|
||||
}
|
||||
var m SendMessage
|
||||
seen := 0
|
||||
for _, f := range fs {
|
||||
switch {
|
||||
case f.num == 1 && !f.isLen:
|
||||
m.OpID, seen = f.varint, seen|1
|
||||
case f.num == 2 && f.isLen:
|
||||
m.Queue, seen = string(f.bytes), seen|2
|
||||
case f.num == 3 && f.isLen:
|
||||
m.Mount, seen = string(f.bytes), seen|4
|
||||
case f.num == 4 && f.isLen:
|
||||
m.Message, seen = f.bytes, seen|8
|
||||
default:
|
||||
return SendMessage{}, errors.New("imq: unexpected field in C2gSendMessage")
|
||||
}
|
||||
}
|
||||
if seen != 15 {
|
||||
return SendMessage{}, errors.New("imq: C2gSendMessage is missing fields")
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Marshal encodes the message's data, fields in the client's order.
|
||||
func (m SendMessage) Marshal() []byte {
|
||||
var b []byte
|
||||
b = appendVarintField(b, 1, m.OpID)
|
||||
b = appendBytesField(b, 2, []byte(m.Queue))
|
||||
b = appendBytesField(b, 3, []byte(m.Mount))
|
||||
return appendBytesField(b, 4, m.Message)
|
||||
}
|
||||
|
||||
// Result encodes a G2cResult's data: the answer to the operation opID (status 0 is success).
|
||||
func Result(opID, status uint64) []byte {
|
||||
return appendVarintField(appendVarintField(nil, 1, opID), 2, status)
|
||||
}
|
||||
|
||||
// EncodeFrame wraps a message's data in a frame of the given type.
|
||||
func EncodeFrame(t uint64, data []byte) []byte {
|
||||
return appendBytesField(appendVarintField(nil, 1, t), 2, data)
|
||||
}
|
||||
|
||||
func appendVarint(b []byte, v uint64) []byte {
|
||||
for v >= 0x80 {
|
||||
b = append(b, byte(v)|0x80)
|
||||
v >>= 7
|
||||
}
|
||||
return append(b, byte(v))
|
||||
}
|
||||
|
||||
func appendVarintField(b []byte, num, v uint64) []byte {
|
||||
return appendVarint(appendVarint(b, num<<3), v)
|
||||
}
|
||||
|
||||
func appendBytesField(b []byte, num uint64, v []byte) []byte {
|
||||
b = appendVarint(appendVarint(b, num<<3|2), uint64(len(v)))
|
||||
return append(b, v...)
|
||||
}
|
||||
Reference in new issue
Block a user