From 86d081bce2a8182279a44657f7e5fed25b86329f Mon Sep 17 00:00:00 2001 From: Christian Mouchet Date: Sun, 11 Jun 2023 12:21:34 +0200 Subject: [PATCH] improved the Writer/Reader-based serialization The WriterTo and ReaderFrom standard interface should be sufficient for the serialization of lattigo objects from their pointers. Other interfaces such as BinaryMarshaller should be based on WriterTo. This is possible in an efficient way if the Writer and Reader interface expose their internal buffer. --- bgv/params.go | 2 + ckks/bootstrapping/parameters.go | 2 +- ckks/params.go | 1 + drlwe/keygen_cpk.go | 134 ++++++++++++++------------- drlwe/keygen_gal.go | 115 +++++++++++------------ drlwe/keygen_relin.go | 64 +++++++------ drlwe/keyswitch_pk.go | 102 +++++++++++---------- drlwe/keyswitch_sk.go | 80 ++++++++-------- drlwe/refresh.go | 109 +++++++++++----------- drlwe/threshold.go | 64 +++++++------ ring/poly.go | 90 +++++++++--------- rlwe/evaluationkeyset.go | 79 ++++++++-------- rlwe/gadgetciphertext.go | 72 ++++++++------- rlwe/galoiskey.go | 69 +++++++------- rlwe/metadata.go | 33 ++++--- rlwe/operand.go | 152 +++++++++++++++++-------------- rlwe/params.go | 1 + rlwe/plaintext.go | 36 ++++---- rlwe/power_basis.go | 69 +++++++------- rlwe/ringqp/poly.go | 129 +++++++++++--------------- rlwe/rlwe_benchmark_test.go | 127 +++++++++++++++++++++----- rlwe/secretkey.go | 68 +++++++------- utils/buffer/buffer.go | 135 ++++++++++++++++++++++++++- utils/buffer/reader.go | 71 +++++++-------- utils/buffer/writer.go | 74 ++++++--------- utils/structs/map.go | 69 +++++++------- utils/structs/matrix.go | 89 +++++++++--------- utils/structs/vector.go | 143 +++++++++++++++++------------ 28 files changed, 1243 insertions(+), 936 deletions(-) diff --git a/bgv/params.go b/bgv/params.go index ec9b3483..0fc0aa0e 100644 --- a/bgv/params.go +++ b/bgv/params.go @@ -214,6 +214,8 @@ func (p Parameters) Equal(other rlwe.ParametersInterface) bool { } // MarshalBinary returns a []byte representation of the parameter set. +// The representation corresponds to the JSON representation obtained +// from MarshalJSON. func (p Parameters) MarshalBinary() ([]byte, error) { return p.MarshalJSON() } diff --git a/ckks/bootstrapping/parameters.go b/ckks/bootstrapping/parameters.go index 1ea79aef..d7ca4624 100644 --- a/ckks/bootstrapping/parameters.go +++ b/ckks/bootstrapping/parameters.go @@ -211,7 +211,7 @@ func (p *Parameters) Depth() (depth int) { return p.DepthCoeffsToSlots() + p.DepthEvalMod() + p.DepthSlotsToCoeffs() } -// MarshalBinary returns a JSON representation of the the target Parameters struct on a slice of bytes. +// MarshalBinary returns a JSON representation of the bootstrapping Parameters struct. // See `Marshal` from the `encoding/json` package. func (p *Parameters) MarshalBinary() (data []byte, err error) { return json.Marshal(p) diff --git a/ckks/params.go b/ckks/params.go index 2869c833..5de0afd4 100644 --- a/ckks/params.go +++ b/ckks/params.go @@ -198,6 +198,7 @@ func (p Parameters) Equal(other rlwe.ParametersInterface) bool { } // MarshalBinary returns a []byte representation of the parameter set. +// This representation corresponds to the one returned by MarshalJSON. func (p Parameters) MarshalBinary() ([]byte, error) { return p.MarshalJSON() } diff --git a/drlwe/keygen_cpk.go b/drlwe/keygen_cpk.go index 79af408d..44630878 100644 --- a/drlwe/keygen_cpk.go +++ b/drlwe/keygen_cpk.go @@ -26,69 +26,6 @@ type PublicKeyGenCRP struct { Value ringqp.Poly } -// ShallowCopy creates a shallow copy of PublicKeyGenProtocol in which all the read-only data-structures are -// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned -// PublicKeyGenProtocol can be used concurrently. -func (ckg *PublicKeyGenProtocol) ShallowCopy() *PublicKeyGenProtocol { - prng, err := sampling.NewPRNG() - if err != nil { - panic(err) - } - - return &PublicKeyGenProtocol{ckg.params, ring.NewSampler(prng, ckg.params.RingQ(), ckg.params.Xe(), false)} -} - -// BinarySize returns the size in bytes of the object -// when encoded using Encode. -func (share *PublicKeyGenShare) BinarySize() int { - return share.Value.BinarySize() -} - -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (share *PublicKeyGenShare) MarshalBinary() (p []byte, err error) { - return share.Value.MarshalBinary() -} - -// Encode encodes the object into a binary form on a preallocated slice of bytes -// and returns the number of bytes written. -func (share *PublicKeyGenShare) Encode(p []byte) (ptr int, err error) { - return share.Value.Encode(p) -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. -func (share *PublicKeyGenShare) WriteTo(w io.Writer) (n int64, err error) { - return share.Value.WriteTo(w) -} - -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (share *PublicKeyGenShare) UnmarshalBinary(p []byte) (err error) { - return share.Value.UnmarshalBinary(p) -} - -// Decode decodes a slice of bytes generated by Encode -// on the object and returns the number of bytes read. -func (share *PublicKeyGenShare) Decode(p []byte) (n int, err error) { - return share.Value.Decode(p) -} - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. -func (share *PublicKeyGenShare) ReadFrom(r io.Reader) (n int64, err error) { - return share.Value.ReadFrom(r) -} - // NewPublicKeyGenProtocol creates a new PublicKeyGenProtocol instance func NewPublicKeyGenProtocol(params rlwe.Parameters) *PublicKeyGenProtocol { ckg := new(PublicKeyGenProtocol) @@ -145,3 +82,74 @@ func (ckg *PublicKeyGenProtocol) GenPublicKey(roundShare *PublicKeyGenShare, crp pubkey.Value[0].Copy(&roundShare.Value) pubkey.Value[1].Copy(&crp.Value) } + +// ShallowCopy creates a shallow copy of PublicKeyGenProtocol in which all the read-only data-structures are +// shared with the receiver and the temporary buffers are reallocated. The receiver and the returned +// PublicKeyGenProtocol can be used concurrently. +func (ckg *PublicKeyGenProtocol) ShallowCopy() *PublicKeyGenProtocol { + prng, err := sampling.NewPRNG() + if err != nil { + panic(err) + } + + return &PublicKeyGenProtocol{ckg.params, ring.NewSampler(prng, ckg.params.RingQ(), ckg.params.Xe(), false)} +} + +// BinarySize returns the size in bytes of the object +// when encoded using Encode. +func (share *PublicKeyGenShare) BinarySize() int { + return share.Value.BinarySize() +} + +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). +func (share *PublicKeyGenShare) WriteTo(w io.Writer) (n int64, err error) { + return share.Value.WriteTo(w) +} + +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). +func (share *PublicKeyGenShare) ReadFrom(r io.Reader) (n int64, err error) { + return share.Value.ReadFrom(r) +} + +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (share *PublicKeyGenShare) MarshalBinary() (p []byte, err error) { + return share.Value.MarshalBinary() +} + +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (share *PublicKeyGenShare) UnmarshalBinary(p []byte) (err error) { + return share.Value.UnmarshalBinary(p) +} + +// Encode encodes the object into a binary form on a preallocated slice of bytes +// and returns the number of bytes written. +func (share *PublicKeyGenShare) Encode(p []byte) (ptr int, err error) { + return share.Value.Encode(p) +} + +// Decode decodes a slice of bytes generated by Encode +// on the object and returns the number of bytes read. +func (share *PublicKeyGenShare) Decode(p []byte) (n int, err error) { + return share.Value.Decode(p) +} diff --git a/drlwe/keygen_gal.go b/drlwe/keygen_gal.go index f363f14d..c6410e15 100644 --- a/drlwe/keygen_gal.go +++ b/drlwe/keygen_gal.go @@ -2,7 +2,6 @@ package drlwe import ( "bufio" - "bytes" "encoding/binary" "fmt" "io" @@ -246,28 +245,17 @@ func (share *GaloisKeyGenShare) BinarySize() int { return 8 + share.Value.BinarySize() } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (share *GaloisKeyGenShare) MarshalBinary() (p []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = share.WriteTo(buf) - return buf.Bytes(), err -} - -// Encode encodes the object into a binary form on a preallocated slice of bytes -// and returns the number of bytes written. -func (share *GaloisKeyGenShare) Encode(p []byte) (n int, err error) { - binary.LittleEndian.PutUint64(p, share.GaloisElement) - n, err = share.Value.Encode(p[8:]) - return n + 8, err -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (share *GaloisKeyGenShare) WriteTo(w io.Writer) (n int64, err error) { switch w := w.(type) { case buffer.Writer: @@ -293,13 +281,60 @@ func (share *GaloisKeyGenShare) WriteTo(w io.Writer) (n int64, err error) { } } +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). +func (share *GaloisKeyGenShare) ReadFrom(r io.Reader) (n int64, err error) { + switch r := r.(type) { + case buffer.Reader: + + var inc int + if inc, err = buffer.ReadUint64(r, &share.GaloisElement); err != nil { + return n + int64(inc), err + } + n += int64(inc) + + var inc64 int64 + if inc64, err = share.Value.ReadFrom(r); err != nil { + return n + inc64, err + } + + return n + inc64, nil + default: + return share.ReadFrom(bufio.NewReader(r)) + } +} + +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (share *GaloisKeyGenShare) MarshalBinary() (p []byte, err error) { + buf := buffer.NewBufferSize(share.BinarySize()) + _, err = share.WriteTo(buf) + return buf.Bytes(), err +} + // UnmarshalBinary decodes a slice of bytes generated by // MarshalBinary or WriteTo on the object. func (share *GaloisKeyGenShare) UnmarshalBinary(p []byte) (err error) { - _, err = share.ReadFrom(bytes.NewBuffer(p)) + _, err = share.ReadFrom(buffer.NewBuffer(p)) return } +// Encode encodes the object into a binary form on a preallocated slice of bytes +// and returns the number of bytes written. +func (share *GaloisKeyGenShare) Encode(p []byte) (n int, err error) { + binary.LittleEndian.PutUint64(p, share.GaloisElement) + n, err = share.Value.Encode(p[8:]) + return n + 8, err +} + // Decode decodes a slice of bytes generated by Encode // on the object and returns the number of bytes read. func (share *GaloisKeyGenShare) Decode(p []byte) (n int, err error) { @@ -307,35 +342,3 @@ func (share *GaloisKeyGenShare) Decode(p []byte) (n int, err error) { n, err = share.Value.Decode(p[8:]) return n + 8, err } - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. -func (share *GaloisKeyGenShare) ReadFrom(r io.Reader) (n int64, err error) { - switch r := r.(type) { - case buffer.Reader: - - var inc int - - if inc, err = buffer.ReadUint64(r, &share.GaloisElement); err != nil { - return n + int64(inc), err - } - - n += int64(inc) - - var inc2 int64 - if inc2, err = share.Value.ReadFrom(r); err != nil { - return n + inc2, err - } - - n += inc2 - - return - default: - return share.ReadFrom(bufio.NewReader(r)) - } -} diff --git a/drlwe/keygen_relin.go b/drlwe/keygen_relin.go index 8b1b219c..e0b0f416 100644 --- a/drlwe/keygen_relin.go +++ b/drlwe/keygen_relin.go @@ -314,47 +314,55 @@ func (share *RelinKeyGenShare) BinarySize() int { return share.GadgetCiphertext.BinarySize() } +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). +func (share *RelinKeyGenShare) WriteTo(w io.Writer) (n int64, err error) { + return share.GadgetCiphertext.WriteTo(w) +} + +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). +func (share *RelinKeyGenShare) ReadFrom(r io.Reader) (n int64, err error) { + return share.GadgetCiphertext.ReadFrom(r) +} + // MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. func (share *RelinKeyGenShare) MarshalBinary() (data []byte, err error) { return share.GadgetCiphertext.MarshalBinary() } -// Encode encodes the object into a binary form on a preallocated slice of bytes -// and returns the number of bytes written. -func (share *RelinKeyGenShare) Encode(data []byte) (n int, err error) { - return share.GadgetCiphertext.Encode(data) -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. -func (share *RelinKeyGenShare) WriteTo(w io.Writer) (n int64, err error) { - return share.GadgetCiphertext.WriteTo(w) -} - // UnmarshalBinary decodes a slice of bytes generated by // MarshalBinary or WriteTo on the object. func (share *RelinKeyGenShare) UnmarshalBinary(data []byte) (err error) { return share.GadgetCiphertext.UnmarshalBinary(data) } +// Encode encodes the object into a binary form on a preallocated slice of bytes +// and returns the number of bytes written. +func (share *RelinKeyGenShare) Encode(data []byte) (n int, err error) { + return share.GadgetCiphertext.Encode(data) +} + // Decode decodes a slice of bytes generated by Encode // on the object and returns the number of bytes read. func (share *RelinKeyGenShare) Decode(data []byte) (n int, err error) { return share.GadgetCiphertext.Decode(data) } - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. -func (share *RelinKeyGenShare) ReadFrom(r io.Reader) (n int64, err error) { - return share.GadgetCiphertext.ReadFrom(r) -} diff --git a/drlwe/keyswitch_pk.go b/drlwe/keyswitch_pk.go index 6cd2c4ec..1523b06a 100644 --- a/drlwe/keyswitch_pk.go +++ b/drlwe/keyswitch_pk.go @@ -27,25 +27,6 @@ type PublicKeySwitchShare struct { rlwe.OperandQ } -// ShallowCopy creates a shallow copy of PublicKeySwitchProtocol in which all the read-only data-structures are -// shared with the receiver and the temporary bufers are reallocated. The receiver and the returned -// PublicKeySwitchProtocol can be used concurrently. -func (pcks *PublicKeySwitchProtocol) ShallowCopy() *PublicKeySwitchProtocol { - prng, err := sampling.NewPRNG() - if err != nil { - panic(err) - } - - params := pcks.params - return &PublicKeySwitchProtocol{ - noiseSampler: ring.NewSampler(prng, params.RingQ(), pcks.noise, false), - noise: pcks.noise, - EncryptorInterface: rlwe.NewEncryptor(params, nil), - params: params, - buf: params.RingQ().NewPoly(), - } -} - // NewPublicKeySwitchProtocol creates a new PublicKeySwitchProtocol object and will be used to re-encrypt a ciphertext ctx encrypted under a secret-shared key among j parties under a new // collective public-key. func NewPublicKeySwitchProtocol(params rlwe.Parameters, noise distribution.Distribution) (pcks *PublicKeySwitchProtocol) { @@ -143,53 +124,80 @@ func (pcks *PublicKeySwitchProtocol) KeySwitch(ctIn *rlwe.Ciphertext, combined * ring.CopyLvl(level, &combined.Value[1], &ctOut.Value[1]) } +// ShallowCopy creates a shallow copy of PublicKeySwitchProtocol in which all the read-only data-structures are +// shared with the receiver and the temporary bufers are reallocated. The receiver and the returned +// PublicKeySwitchProtocol can be used concurrently. +func (pcks *PublicKeySwitchProtocol) ShallowCopy() *PublicKeySwitchProtocol { + prng, err := sampling.NewPRNG() + if err != nil { + panic(err) + } + + params := pcks.params + return &PublicKeySwitchProtocol{ + noiseSampler: ring.NewSampler(prng, params.RingQ(), pcks.noise, false), + noise: pcks.noise, + EncryptorInterface: rlwe.NewEncryptor(params, nil), + params: params, + buf: params.RingQ().NewPoly(), + } +} + // BinarySize returns the size in bytes of the object // when encoded using Encode. func (share *PublicKeySwitchShare) BinarySize() int { return share.OperandQ.BinarySize() } +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). +func (share *PublicKeySwitchShare) WriteTo(w io.Writer) (n int64, err error) { + return share.OperandQ.WriteTo(w) +} + +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). +func (share *PublicKeySwitchShare) ReadFrom(r io.Reader) (n int64, err error) { + return share.OperandQ.ReadFrom(r) +} + // MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. func (share *PublicKeySwitchShare) MarshalBinary() (p []byte, err error) { return share.OperandQ.MarshalBinary() } -// Encode encodes the object into a binary form on a preallocated slice of bytes -// and returns the number of bytes written. -func (share *PublicKeySwitchShare) Encode(p []byte) (n int, err error) { - return share.OperandQ.Encode(p) -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface bufer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the bufer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/bufer/writer.go. -func (share *PublicKeySwitchShare) WriteTo(w io.Writer) (n int64, err error) { - return share.OperandQ.WriteTo(w) -} - // UnmarshalBinary decodes a slice of bytes generated by // MarshalBinary or WriteTo on the object. func (share *PublicKeySwitchShare) UnmarshalBinary(p []byte) (err error) { return share.OperandQ.UnmarshalBinary(p) } +// Encode encodes the object into a binary form on a preallocated slice of bytes +// and returns the number of bytes written. +func (share *PublicKeySwitchShare) Encode(p []byte) (n int, err error) { + return share.OperandQ.Encode(p) +} + // Decode decodes a slice of bytes generated by Encode // on the object and returns the number of bytes read. func (share *PublicKeySwitchShare) Decode(p []byte) (n int, err error) { return share.OperandQ.Decode(p) } - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface bufer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the bufer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/bufer/reader.go. -func (share *PublicKeySwitchShare) ReadFrom(r io.Reader) (n int64, err error) { - return share.OperandQ.ReadFrom(r) -} diff --git a/drlwe/keyswitch_sk.go b/drlwe/keyswitch_sk.go index ea8f872e..c5a3f295 100644 --- a/drlwe/keyswitch_sk.go +++ b/drlwe/keyswitch_sk.go @@ -1,7 +1,6 @@ package drlwe import ( - "bytes" "fmt" "io" "math" @@ -171,11 +170,51 @@ func (ckss *KeySwitchShare) BinarySize() int { return ckss.Value.BinarySize() } +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). +func (ckss *KeySwitchShare) WriteTo(w io.Writer) (n int64, err error) { + return ckss.Value.WriteTo(w) +} + +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). +func (ckss *KeySwitchShare) ReadFrom(r io.Reader) (n int64, err error) { + if ckss.Value == nil { + ckss.Value = new(ring.Poly) + } + return ckss.Value.ReadFrom(r) +} + // MarshalBinary encodes a KeySwitch share on a slice of bytes. func (ckss *KeySwitchShare) MarshalBinary() (p []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = ckss.WriteTo(buf) - return buf.Bytes(), err + return ckss.Value.MarshalBinary() +} + +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (ckss *KeySwitchShare) UnmarshalBinary(p []byte) (err error) { + if ckss.Value == nil { + ckss.Value = new(ring.Poly) + } + return ckss.Value.UnmarshalBinary(p) } // Encode encodes the object into a binary form on a preallocated slice of bytes @@ -184,24 +223,6 @@ func (ckss *KeySwitchShare) Encode(p []byte) (ptr int, err error) { return ckss.Value.Encode(p) } -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface bufer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the bufer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/bufer/writer.go. -func (ckss *KeySwitchShare) WriteTo(w io.Writer) (n int64, err error) { - return ckss.Value.WriteTo(w) -} - -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (ckss *KeySwitchShare) UnmarshalBinary(p []byte) (err error) { - _, err = ckss.ReadFrom(bytes.NewBuffer(p)) - return -} - // Decode decodes a slice of bytes generated by Encode // on the object and returns the number of bytes read. func (ckss *KeySwitchShare) Decode(p []byte) (ptr int, err error) { @@ -211,18 +232,3 @@ func (ckss *KeySwitchShare) Decode(p []byte) (ptr int, err error) { return ckss.Value.Decode(p) } - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface bufer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the bufer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/bufer/reader.go. -func (ckss *KeySwitchShare) ReadFrom(r io.Reader) (n int64, err error) { - if ckss.Value == nil { - ckss.Value = new(ring.Poly) - } - - return ckss.Value.ReadFrom(r) -} diff --git a/drlwe/refresh.go b/drlwe/refresh.go index 3e9b9c9b..7a3ead9e 100644 --- a/drlwe/refresh.go +++ b/drlwe/refresh.go @@ -2,7 +2,6 @@ package drlwe import ( "bufio" - "bytes" "io" "github.com/tuneinsight/lattigo/v4/utils/buffer" @@ -20,31 +19,17 @@ func (share *RefreshShare) BinarySize() int { return share.EncToShareShare.BinarySize() + share.ShareToEncShare.BinarySize() } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (share *RefreshShare) MarshalBinary() (p []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = share.WriteTo(buf) - return buf.Bytes(), err -} - -// Encode encodes the object into a binary form on a preallocated slice of bytes -// and returns the number of bytes written. -func (share *RefreshShare) Encode(p []byte) (n int, err error) { - if n, err = share.EncToShareShare.Encode(p[n:]); err != nil { - return - } - var inc int - inc, err = share.ShareToEncShare.Encode(p[n:]) - return n + inc, err -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (share *RefreshShare) WriteTo(w io.Writer) (n int64, err error) { switch w := w.(type) { case buffer.Writer: @@ -59,31 +44,17 @@ func (share *RefreshShare) WriteTo(w io.Writer) (n int64, err error) { } } -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (share *RefreshShare) UnmarshalBinary(p []byte) (err error) { - _, err = share.ReadFrom(bytes.NewBuffer(p)) - return -} - -// Decode decodes a slice of bytes generated by Encode -// on the object and returns the number of bytes read. -func (share *RefreshShare) Decode(p []byte) (n int, err error) { - if n, err = share.EncToShareShare.Decode(p[n:]); err != nil { - return - } - var inc int - inc, err = share.ShareToEncShare.Decode(p[n:]) - return n + inc, err -} - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (share *RefreshShare) ReadFrom(r io.Reader) (n int64, err error) { switch r := r.(type) { case buffer.Reader: @@ -97,3 +68,39 @@ func (share *RefreshShare) ReadFrom(r io.Reader) (n int64, err error) { return share.ReadFrom(bufio.NewReader(r)) } } + +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (share *RefreshShare) MarshalBinary() (p []byte, err error) { + buf := buffer.NewBufferSize(share.BinarySize()) + _, err = share.WriteTo(buf) + return buf.Bytes(), err +} + +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (share *RefreshShare) UnmarshalBinary(p []byte) (err error) { + _, err = share.ReadFrom(buffer.NewBuffer(p)) + return +} + +// Encode encodes the object into a binary form on a preallocated slice of bytes +// and returns the number of bytes written. +func (share *RefreshShare) Encode(p []byte) (n int, err error) { + if n, err = share.EncToShareShare.Encode(p[n:]); err != nil { + return + } + var inc int + inc, err = share.ShareToEncShare.Encode(p[n:]) + return n + inc, err +} + +// Decode decodes a slice of bytes generated by Encode +// on the object and returns the number of bytes read. +func (share *RefreshShare) Decode(p []byte) (n int, err error) { + if n, err = share.EncToShareShare.Decode(p[n:]); err != nil { + return + } + var inc int + inc, err = share.ShareToEncShare.Decode(p[n:]) + return n + inc, err +} diff --git a/drlwe/threshold.go b/drlwe/threshold.go index d5a48416..d4c89715 100644 --- a/drlwe/threshold.go +++ b/drlwe/threshold.go @@ -180,47 +180,55 @@ func (s *ShamirSecretShare) BinarySize() int { return s.Poly.BinarySize() } +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). +func (s *ShamirSecretShare) WriteTo(w io.Writer) (n int64, err error) { + return s.Poly.WriteTo(w) +} + +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). +func (s *ShamirSecretShare) ReadFrom(r io.Reader) (n int64, err error) { + return s.Poly.ReadFrom(r) +} + // MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. func (s *ShamirSecretShare) MarshalBinary() (p []byte, err error) { return s.Poly.MarshalBinary() } -// Encode encodes the object into a binary form on a preallocated slice of bytes -// and returns the number of bytes written. -func (s *ShamirSecretShare) Encode(p []byte) (n int, err error) { - return s.Poly.Encode(p) -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. -func (s *ShamirSecretShare) WriteTo(w io.Writer) (n int64, err error) { - return s.Poly.WriteTo(w) -} - // UnmarshalBinary decodes a slice of bytes generated by // MarshalBinary or WriteTo on the object. func (s *ShamirSecretShare) UnmarshalBinary(p []byte) (err error) { return s.Poly.UnmarshalBinary(p) } +// Encode encodes the object into a binary form on a preallocated slice of bytes +// and returns the number of bytes written. +func (s *ShamirSecretShare) Encode(p []byte) (n int, err error) { + return s.Poly.Encode(p) +} + // Decode decodes a slice of bytes generated by Encode // on the object and returns the number of bytes read. func (s *ShamirSecretShare) Decode(p []byte) (n int, err error) { return s.Poly.Decode(p) } - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. -func (s *ShamirSecretShare) ReadFrom(r io.Reader) (n int64, err error) { - return s.Poly.ReadFrom(r) -} diff --git a/ring/poly.go b/ring/poly.go index 1bb5fc90..5847fa9e 100644 --- a/ring/poly.go +++ b/ring/poly.go @@ -2,7 +2,6 @@ package ring import ( "bufio" - "bytes" "encoding/binary" "fmt" "io" @@ -117,54 +116,33 @@ func (pol *Poly) Equal(other *Poly) bool { return false } -// BinarySize returns the size in bytes of the object +// polyBinarySize returns the size in bytes of the object // when encoded using Encode. -func BinarySize(N, Level int) (size int) { +func polyBinarySize(N, Level int) (size int) { return 16 + N*(Level+1)<<3 } // BinarySize returns the size in bytes of the object // when encoded using Encode. func (pol *Poly) BinarySize() (size int) { - return BinarySize(pol.N(), pol.Level()) + return polyBinarySize(pol.N(), pol.Level()) } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (pol *Poly) MarshalBinary() (p []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = pol.WriteTo(buf) - return buf.Bytes(), err -} - -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (pol *Poly) UnmarshalBinary(p []byte) (err error) { - - N := int(binary.LittleEndian.Uint64(p)) - Level := int(binary.LittleEndian.Uint64(p[8:])) - - if size := BinarySize(N, Level); len(p) != size { - return fmt.Errorf("cannot UnmarshalBinary: len(p)=%d != %d", len(p), size) - } - - if _, err = pol.ReadFrom(bytes.NewBuffer(p)); err != nil { - return - } - - return nil -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (pol *Poly) WriteTo(w io.Writer) (int64, error) { switch w := w.(type) { - case *bufio.Writer: + case buffer.Writer: var err error @@ -191,17 +169,21 @@ func (pol *Poly) WriteTo(w io.Writer) (int64, error) { } } -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (pol *Poly) ReadFrom(r io.Reader) (int64, error) { switch r := r.(type) { - case *bufio.Reader: + case buffer.Reader: var err error var n, inc int @@ -254,6 +236,22 @@ func (pol *Poly) ReadFrom(r io.Reader) (int64, error) { } } +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (pol *Poly) MarshalBinary() (p []byte, err error) { + buf := buffer.NewBufferSize(pol.BinarySize()) + _, err = pol.WriteTo(buf) + return buf.Bytes(), err +} + +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (pol *Poly) UnmarshalBinary(p []byte) (err error) { + if _, err = pol.ReadFrom(buffer.NewBuffer(p)); err != nil { + return + } + return +} + // Encode encodes the object into a binary form on a preallocated slice of bytes // and returns the number of bytes written. func (pol *Poly) Encode(p []byte) (n int, err error) { @@ -292,7 +290,7 @@ func (pol *Poly) Decode(p []byte) (n int, err error) { Level := int(binary.LittleEndian.Uint64(p[n:])) n += 8 - if size := BinarySize(N, Level); len(p) < size { + if size := polyBinarySize(N, Level); len(p) < size { return n, fmt.Errorf("cannot Decode: len(p)=%d < ", size) } diff --git a/rlwe/evaluationkeyset.go b/rlwe/evaluationkeyset.go index 0df41f7a..dace1294 100644 --- a/rlwe/evaluationkeyset.go +++ b/rlwe/evaluationkeyset.go @@ -2,7 +2,6 @@ package rlwe import ( "bufio" - "bytes" "fmt" "io" @@ -80,27 +79,32 @@ func (evk *MemEvaluationKeySet) GetRelinearizationKey() (rk *RelinearizationKey, return nil, fmt.Errorf("RelinearizationKey is nil") } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (evk *MemEvaluationKeySet) MarshalBinary() (p []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = evk.WriteTo(buf) - return buf.Bytes(), err -} +func (evk *MemEvaluationKeySet) BinarySize() (size int) { + + size++ + if evk.Rlk != nil { + size += evk.Rlk.BinarySize() + } + + size++ + if evk.Gks != nil { + size += evk.Gks.BinarySize() + } -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (evk *MemEvaluationKeySet) UnmarshalBinary(p []byte) (err error) { - _, err = evk.ReadFrom(bytes.NewBuffer(p)) return } -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (evk *MemEvaluationKeySet) WriteTo(w io.Writer) (int64, error) { switch w := w.(type) { case buffer.Writer: @@ -156,13 +160,17 @@ func (evk *MemEvaluationKeySet) WriteTo(w io.Writer) (int64, error) { } } -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (evk *MemEvaluationKeySet) ReadFrom(r io.Reader) (n int64, err error) { switch r := r.(type) { case buffer.Reader: @@ -217,18 +225,17 @@ func (evk *MemEvaluationKeySet) ReadFrom(r io.Reader) (n int64, err error) { } } -func (evk *MemEvaluationKeySet) BinarySize() (size int) { - - size++ - if evk.Rlk != nil { - size += evk.Rlk.BinarySize() - } - - size++ - if evk.Gks != nil { - size += evk.Gks.BinarySize() - } +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (evk *MemEvaluationKeySet) MarshalBinary() (p []byte, err error) { + buf := buffer.NewBufferSize(evk.BinarySize()) + _, err = evk.WriteTo(buf) + return buf.Bytes(), err +} +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (evk *MemEvaluationKeySet) UnmarshalBinary(p []byte) (err error) { + _, err = evk.ReadFrom(buffer.NewBuffer(p)) return } diff --git a/rlwe/gadgetciphertext.go b/rlwe/gadgetciphertext.go index cefd5125..16a9ced8 100644 --- a/rlwe/gadgetciphertext.go +++ b/rlwe/gadgetciphertext.go @@ -1,7 +1,6 @@ package rlwe import ( - "bytes" "io" "github.com/google/go-cmp/cmp" @@ -66,46 +65,51 @@ func (ct *GadgetCiphertext) CopyNew() (ctCopy *GadgetCiphertext) { return &GadgetCiphertext{Value: v} } +// BinarySize returns the size in bytes of the object +// when encoded using Encode. +func (ct *GadgetCiphertext) BinarySize() (dataLen int) { + return ct.Value.BinarySize() +} + +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). +func (ct *GadgetCiphertext) WriteTo(w io.Writer) (n int64, err error) { + return ct.Value.WriteTo(w) +} + +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). +func (ct *GadgetCiphertext) ReadFrom(r io.Reader) (n int64, err error) { + return ct.Value.ReadFrom(r) +} + // MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. func (ct *GadgetCiphertext) MarshalBinary() (data []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = ct.WriteTo(buf) - return buf.Bytes(), err + return ct.Value.MarshalBinary() } // UnmarshalBinary decodes a slice of bytes generated by // MarshalBinary or WriteTo on the object. func (ct *GadgetCiphertext) UnmarshalBinary(p []byte) (err error) { - _, err = ct.ReadFrom(bytes.NewBuffer(p)) - return -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. -func (ct *GadgetCiphertext) WriteTo(w io.Writer) (n int64, err error) { - return ct.Value.WriteTo(w) -} - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. -func (ct *GadgetCiphertext) ReadFrom(r io.Reader) (n int64, err error) { - return ct.Value.ReadFrom(r) -} - -// BinarySize returns the size in bytes of the object -// when encoded using Encode. -func (ct *GadgetCiphertext) BinarySize() (dataLen int) { - return ct.Value.BinarySize() + return ct.Value.UnmarshalBinary(p) } // Encode encodes the object into a binary form on a preallocated slice of bytes diff --git a/rlwe/galoiskey.go b/rlwe/galoiskey.go index 06841b31..0a2e466f 100644 --- a/rlwe/galoiskey.go +++ b/rlwe/galoiskey.go @@ -2,7 +2,6 @@ package rlwe import ( "bufio" - "bytes" "encoding/binary" "fmt" "io" @@ -47,20 +46,23 @@ func (gk *GaloisKey) CopyNew() *GaloisKey { } } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (gk *GaloisKey) MarshalBinary() (p []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = gk.WriteTo(buf) - return buf.Bytes(), err +// BinarySize returns the size in bytes of the object +// when encoded using Encode. +func (gk *GaloisKey) BinarySize() (size int) { + return gk.EvaluationKey.BinarySize() + 16 } -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (gk *GaloisKey) WriteTo(w io.Writer) (n int64, err error) { switch w := w.(type) { case buffer.Writer: @@ -93,20 +95,17 @@ func (gk *GaloisKey) WriteTo(w io.Writer) (n int64, err error) { } } -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (gk *GaloisKey) UnmarshalBinary(p []byte) (err error) { - _, err = gk.ReadFrom(bytes.NewBuffer(p)) - return -} - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (gk *GaloisKey) ReadFrom(r io.Reader) (n int64, err error) { switch r := r.(type) { case buffer.Reader: @@ -138,10 +137,18 @@ func (gk *GaloisKey) ReadFrom(r io.Reader) (n int64, err error) { } } -// BinarySize returns the size in bytes of the object -// when encoded using Encode. -func (gk *GaloisKey) BinarySize() (size int) { - return gk.EvaluationKey.BinarySize() + 16 +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (gk *GaloisKey) MarshalBinary() (p []byte, err error) { + buf := buffer.NewBufferSize(gk.BinarySize()) + _, err = gk.WriteTo(buf) + return buf.Bytes(), err +} + +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (gk *GaloisKey) UnmarshalBinary(p []byte) (err error) { + _, err = gk.ReadFrom(buffer.NewBuffer(p)) + return } // Encode encodes the object into a binary form on a preallocated slice of bytes diff --git a/rlwe/metadata.go b/rlwe/metadata.go index 0f7bf2f6..b860ea65 100644 --- a/rlwe/metadata.go +++ b/rlwe/metadata.go @@ -53,21 +53,8 @@ func (m MetaData) BinarySize() int { return 5 + m.PlaintextScale.BinarySize() } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (m MetaData) MarshalBinary() (p []byte, err error) { - p = make([]byte, m.BinarySize()) - _, err = m.Encode(p) - return -} - -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (m *MetaData) UnmarshalBinary(p []byte) (err error) { - _, err = m.Decode(p) - return -} - -// WriteTo writes the object on an io.Writer. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. func (m *MetaData) WriteTo(w io.Writer) (int64, error) { if p, err := m.MarshalBinary(); err != nil { return 0, err @@ -80,6 +67,8 @@ func (m *MetaData) WriteTo(w io.Writer) (int64, error) { } } +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. func (m *MetaData) ReadFrom(r io.Reader) (int64, error) { p := make([]byte, m.BinarySize()) if n, err := r.Read(p); err != nil { @@ -90,6 +79,20 @@ func (m *MetaData) ReadFrom(r io.Reader) (int64, error) { } } +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (m MetaData) MarshalBinary() (p []byte, err error) { + p = make([]byte, m.BinarySize()) + _, err = m.Encode(p) + return +} + +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (m *MetaData) UnmarshalBinary(p []byte) (err error) { + _, err = m.Decode(p) + return +} + // Encode encodes the object into a binary form on a preallocated slice of bytes // and returns the number of bytes written. func (m MetaData) Encode(p []byte) (n int, err error) { diff --git a/rlwe/operand.go b/rlwe/operand.go index f887d87c..f1623d0d 100644 --- a/rlwe/operand.go +++ b/rlwe/operand.go @@ -1,13 +1,13 @@ package rlwe import ( - "bytes" "fmt" "io" "github.com/google/go-cmp/cmp" "github.com/tuneinsight/lattigo/v4/ring" "github.com/tuneinsight/lattigo/v4/rlwe/ringqp" + "github.com/tuneinsight/lattigo/v4/utils/buffer" "github.com/tuneinsight/lattigo/v4/utils/sampling" "github.com/tuneinsight/lattigo/v4/utils/structs" ) @@ -212,27 +212,23 @@ func SwitchCiphertextRingDegree(ctIn, ctOut *OperandQ) { ctOut.MetaData = ctIn.MetaData } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (op *OperandQ) MarshalBinary() (data []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = op.WriteTo(buf) - return buf.Bytes(), err +// BinarySize returns the size in bytes of the object +// when encoded using Encode. +func (op *OperandQ) BinarySize() int { + return op.MetaData.BinarySize() + op.Value.BinarySize() } -// UnmarshalBinary decodes a slice of bytes generated by MarshalBinary -// or Read on the objeop. -func (op *OperandQ) UnmarshalBinary(p []byte) (err error) { - _, err = op.ReadFrom(bytes.NewBuffer(p)) - return -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (op *OperandQ) WriteTo(w io.Writer) (n int64, err error) { if n, err = op.MetaData.WriteTo(w); err != nil { @@ -244,13 +240,17 @@ func (op *OperandQ) WriteTo(w io.Writer) (n int64, err error) { return n + inc, err } -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (op *OperandQ) ReadFrom(r io.Reader) (n int64, err error) { if op == nil { @@ -266,10 +266,18 @@ func (op *OperandQ) ReadFrom(r io.Reader) (n int64, err error) { return n + inc, err } -// BinarySize returns the size in bytes of the object -// when encoded using Encode. -func (op *OperandQ) BinarySize() int { - return op.MetaData.BinarySize() + op.Value.BinarySize() +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (op *OperandQ) MarshalBinary() (data []byte, err error) { + buf := buffer.NewBufferSize(op.BinarySize()) + _, err = op.WriteTo(buf) + return buf.Bytes(), err +} + +// UnmarshalBinary decodes a slice of bytes generated by MarshalBinary +// or Read on the objeop. +func (op *OperandQ) UnmarshalBinary(p []byte) (err error) { + _, err = op.ReadFrom(buffer.NewBuffer(p)) + return } // Encode encodes the object into a binary form on a preallocated slice of bytes @@ -280,17 +288,13 @@ func (op *OperandQ) Encode(p []byte) (n int, err error) { return 0, fmt.Errorf("cannot Encode: len(p) is too small") } - // if n, err = op.MetaData.Encode(p); err != nil { - // return - // } + if n, err = op.MetaData.Encode(p); err != nil { + return + } - // inc, err := op.Value.Encode(p[n:]) + inc, err := op.Value.Encode(p[n:]) - // return n + inc, err - - buf := bytes.NewBuffer(p[:0]) - nint64, err := op.WriteTo(buf) - return int(nint64), err + return n + inc, err } // Decode decodes a slice of bytes generated by Encode @@ -354,27 +358,23 @@ func (op *OperandQP) CopyNew() *OperandQP { return &OperandQP{Value: Value, MetaData: op.MetaData} } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (op *OperandQP) MarshalBinary() (data []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = op.WriteTo(buf) - return buf.Bytes(), err +// BinarySize returns the size in bytes of the object +// when encoded using Encode. +func (op *OperandQP) BinarySize() int { + return op.MetaData.BinarySize() + op.Value.BinarySize() } -// UnmarshalBinary decodes a slice of bytes generated by MarshalBinary -// or Read on the objeop. -func (op *OperandQP) UnmarshalBinary(p []byte) (err error) { - _, err = op.ReadFrom(bytes.NewBuffer(p)) - return -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (op *OperandQP) WriteTo(w io.Writer) (n int64, err error) { if n, err = op.MetaData.WriteTo(w); err != nil { @@ -386,13 +386,17 @@ func (op *OperandQP) WriteTo(w io.Writer) (n int64, err error) { return n + inc, err } -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (op *OperandQP) ReadFrom(r io.Reader) (n int64, err error) { if op == nil { @@ -408,10 +412,18 @@ func (op *OperandQP) ReadFrom(r io.Reader) (n int64, err error) { return n + inc, err } -// BinarySize returns the size in bytes of the object -// when encoded using Encode. -func (op *OperandQP) BinarySize() int { - return op.MetaData.BinarySize() + op.Value.BinarySize() +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (op *OperandQP) MarshalBinary() (data []byte, err error) { + buf := buffer.NewBufferSize(op.BinarySize()) + _, err = op.WriteTo(buf) + return buf.Bytes(), err +} + +// UnmarshalBinary decodes a slice of bytes generated by MarshalBinary +// or Read on the objeop. +func (op *OperandQP) UnmarshalBinary(p []byte) (err error) { + _, err = op.ReadFrom(buffer.NewBuffer(p)) + return } // Encode encodes the object into a binary form on a preallocated slice of bytes diff --git a/rlwe/params.go b/rlwe/params.go index f598fc84..70b66ef2 100644 --- a/rlwe/params.go +++ b/rlwe/params.go @@ -801,6 +801,7 @@ func (p Parameters) Equal(other ParametersInterface) (res bool) { } // MarshalBinary returns a []byte representation of the parameter set. +// This representation corresponds to the MarshalJSON representation. func (p Parameters) MarshalBinary() ([]byte, error) { return p.MarshalJSON() } diff --git a/rlwe/plaintext.go b/rlwe/plaintext.go index 2576cc85..5b68e9ac 100644 --- a/rlwe/plaintext.go +++ b/rlwe/plaintext.go @@ -48,6 +48,26 @@ func NewPlaintextRandom(prng sampling.PRNG, params ParametersInterface, level in return } +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). +func (pt *Plaintext) ReadFrom(r io.Reader) (n int64, err error) { + if n, err = pt.OperandQ.ReadFrom(r); err != nil { + return + } + + pt.Value = &pt.OperandQ.Value[0] + return +} + // UnmarshalBinary decodes a slice of bytes generated by MarshalBinary // or Read on the objeop. func (pt *Plaintext) UnmarshalBinary(p []byte) (err error) { @@ -67,19 +87,3 @@ func (pt *Plaintext) Decode(p []byte) (n int, err error) { pt.Value = &pt.OperandQ.Value[0] return } - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. -func (pt *Plaintext) ReadFrom(r io.Reader) (n int64, err error) { - if n, err = pt.OperandQ.ReadFrom(r); err != nil { - return - } - - pt.Value = &pt.OperandQ.Value[0] - return -} diff --git a/rlwe/power_basis.go b/rlwe/power_basis.go index f27b8ebc..bd329dcb 100644 --- a/rlwe/power_basis.go +++ b/rlwe/power_basis.go @@ -2,7 +2,6 @@ package rlwe import ( "bufio" - "bytes" "fmt" "io" "math/bits" @@ -164,27 +163,23 @@ func (p *PowerBasis) genPower(n int, lazy, rescale bool) (rescaltOut bool, err e return false, nil } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (p *PowerBasis) MarshalBinary() (data []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = p.WriteTo(buf) - return buf.Bytes(), err +// BinarySize returns the size in bytes of the object +// when encoded using Encode. +func (p *PowerBasis) BinarySize() (size int) { + return 1 + p.Value.BinarySize() } -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (p *PowerBasis) UnmarshalBinary(data []byte) (err error) { - _, err = p.ReadFrom(bytes.NewBuffer(data)) - return -} - -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (p *PowerBasis) WriteTo(w io.Writer) (n int64, err error) { switch w := w.(type) { @@ -207,13 +202,17 @@ func (p *PowerBasis) WriteTo(w io.Writer) (n int64, err error) { } } -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (p *PowerBasis) ReadFrom(r io.Reader) (n int64, err error) { switch r := r.(type) { case buffer.Reader: @@ -242,10 +241,18 @@ func (p *PowerBasis) ReadFrom(r io.Reader) (n int64, err error) { } } -// BinarySize returns the size in bytes of the object -// when encoded using Encode. -func (p *PowerBasis) BinarySize() (size int) { - return 1 + p.Value.BinarySize() +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (p *PowerBasis) MarshalBinary() (data []byte, err error) { + buf := buffer.NewBufferSize(p.BinarySize()) + _, err = p.WriteTo(buf) + return buf.Bytes(), err +} + +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (p *PowerBasis) UnmarshalBinary(data []byte) (err error) { + _, err = p.ReadFrom(buffer.NewBuffer(data)) + return } // Encode encodes the object into a binary form on a preallocated slice of bytes diff --git a/rlwe/ringqp/poly.go b/rlwe/ringqp/poly.go index 4375b15a..b703cfa5 100644 --- a/rlwe/ringqp/poly.go +++ b/rlwe/ringqp/poly.go @@ -2,7 +2,6 @@ package ringqp import ( "bufio" - "bytes" "io" "github.com/google/go-cmp/cmp" @@ -121,7 +120,7 @@ func (p *Poly) Resize(levelQ, levelP int) { // Assumes that each coefficient takes 8 bytes. func (p *Poly) BinarySize() (dataLen int) { - dataLen = 2 + dataLen = 1 if p.Q != nil { dataLen += p.Q.BinarySize() @@ -133,52 +132,37 @@ func (p *Poly) BinarySize() (dataLen int) { return } -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (p *Poly) WriteTo(w io.Writer) (n int64, err error) { switch w := w.(type) { case buffer.Writer: + var hasQP byte if p.Q != nil { - - var inc int - if inc, err = buffer.WriteUint8(w, 1); err != nil { - return int64(n), err - } - - n += int64(inc) - - } else { - var inc int - if inc, err = buffer.WriteUint8(w, 0); err != nil { - return int64(n), err - } - - n += int64(inc) + hasQP = hasQP | 2 } - if p.P != nil { - var inc int - if inc, err = buffer.WriteUint8(w, 1); err != nil { - return int64(n), err - } - - n += int64(inc) - } else { - var inc int - if inc, err = buffer.WriteUint8(w, 0); err != nil { - return int64(n), err - } - - n += int64(inc) + hasQP = hasQP | 1 } + var inc int + if inc, err = buffer.WriteUint8(w, hasQP); err != nil { + return int64(n), err + } + + n += int64(inc) + if p.Q != nil { var inc int64 if inc, err = p.Q.WriteTo(w); err != nil { @@ -204,47 +188,44 @@ func (p *Poly) WriteTo(w io.Writer) (n int64, err error) { } } -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (p *Poly) ReadFrom(r io.Reader) (n int64, err error) { switch r := r.(type) { case buffer.Reader: - var hasQ, hasP uint8 - + var hasQP byte var inc int - if inc, err = buffer.ReadUint8(r, &hasQ); err != nil { + if inc, err = buffer.ReadUint8(r, &hasQP); err != nil { return n + int64(inc), err } n += int64(inc) - if inc, err = buffer.ReadUint8(r, &hasP); err != nil { - return n + int64(inc), err - } - - n += int64(inc) - - if hasQ == 1 { + if hasQP&2 == 2 { if p.Q == nil { p.Q = new(ring.Poly) } - var inc int64 - if inc, err = p.Q.ReadFrom(r); err != nil { - return n + inc, err + var inc64 int64 + if inc64, err = p.Q.ReadFrom(r); err != nil { + return n + inc64, err } - n += inc + n += inc64 } - if hasP == 1 { + if hasQP&1 == 1 { if p.P == nil { p.P = new(ring.Poly) @@ -265,6 +246,20 @@ func (p *Poly) ReadFrom(r io.Reader) (n int64, err error) { } } +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (p *Poly) MarshalBinary() (data []byte, err error) { + buf := buffer.NewBufferSize(p.BinarySize()) + _, err = p.WriteTo(buf) + return buf.Bytes(), err +} + +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (p *Poly) UnmarshalBinary(data []byte) (err error) { + _, err = p.ReadFrom(buffer.NewBuffer(data)) + return err +} + // Encode encodes the object into a binary form on a preallocated slice of bytes // and returns the number of bytes written. func (p *Poly) Encode(data []byte) (n int, err error) { @@ -330,17 +325,3 @@ func (p *Poly) Decode(data []byte) (n int, err error) { return } - -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (p *Poly) MarshalBinary() (data []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = p.WriteTo(buf) - return buf.Bytes(), err -} - -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (p *Poly) UnmarshalBinary(data []byte) (err error) { - _, err = p.ReadFrom(bytes.NewBuffer(data)) - return err -} diff --git a/rlwe/rlwe_benchmark_test.go b/rlwe/rlwe_benchmark_test.go index 32127ebf..c1ac373f 100644 --- a/rlwe/rlwe_benchmark_test.go +++ b/rlwe/rlwe_benchmark_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/tuneinsight/lattigo/v4/utils/buffer" ) func BenchmarkRLWE(b *testing.B) { @@ -136,46 +137,128 @@ func benchMarshalling(tc *TestContext, b *testing.B) { params := tc.params sk := tc.sk - ct := NewEncryptor(params, sk).EncryptZeroNew(params.MaxLevel()) - buf1 := make([]byte, ct.BinarySize()) - buf := bytes.NewBuffer(buf1) - b.Run(testString(params, params.MaxLevel(), "Marshalling/WriteTo"), func(b *testing.B) { + ctf := NewEncryptor(params, sk).EncryptZeroNew(params.MaxLevel()) + ct := ctf.Value + + badbuf := bytes.NewBuffer(make([]byte, ct.BinarySize())) + b.Run(testString(params, params.MaxLevel(), "Marshalling/WriteToBadBuf"), func(b *testing.B) { for i := 0; i < b.N; i++ { - buf.Reset() - ct.WriteTo(buf) + _, err := ct.WriteTo(badbuf) + + b.StopTimer() + if err != nil { + b.Fatal(err) + } + badbuf.Reset() + b.StartTimer() } }) - require.Equal(b, ct.BinarySize(), len(buf.Bytes())) + runtime.GC() - buf2 := make([]byte, ct.BinarySize()) + bytebuff := bytes.NewBuffer(make([]byte, ct.BinarySize())) + bufiobuf := bufio.NewWriter(bytebuff) + b.Run(testString(params, params.MaxLevel(), "Marshalling/WriteToIOBuf"), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := ct.WriteTo(bufiobuf) + + b.StopTimer() + if err != nil { + b.Fatal(err) + } + bytebuff.Reset() + bufiobuf.Reset(bytebuff) + b.StartTimer() + } + }) + + runtime.GC() + + bsliceour := make([]byte, ct.BinarySize()) + ourbuf := buffer.NewBuffer(bsliceour) + b.Run(testString(params, params.MaxLevel(), "Marshalling/WriteToOurBuf"), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := ct.WriteTo(ourbuf) + + b.StopTimer() + if err != nil { + b.Fatal(err) + } + ourbuf.Reset() + b.StartTimer() + } + }) + + runtime.GC() + require.Equal(b, ct.BinarySize(), len(ourbuf.Bytes())) + + encodeBuf := make([]byte, ct.BinarySize()) b.Run(testString(params, params.MaxLevel(), "Marshalling/Encode"), func(b *testing.B) { for i := 0; i < b.N; i++ { - ct.Encode(buf2) + _, err := ct.Encode(encodeBuf) + + b.StopTimer() + if err != nil { + b.Fatal(err) + } + b.StartTimer() } }) - rdr := bytes.NewReader(buf.Bytes()) - brdr := bufio.NewReader(rdr) - var ct2 Ciphertext - b.Run(testString(params, params.MaxLevel(), "Marshalling/ReadFrom"), func(b *testing.B) { + bufcmp := ourbuf.Bytes() + require.Equal(b, bufcmp, encodeBuf) + + rdr := bytes.NewReader(ourbuf.Bytes()) + //bufiordr := bufio.NewReaderSize(rdr, len(ourbuf.Bytes())) + bufiordr := bufio.NewReader(rdr) + ct2f := NewCiphertext(tc.params, 1, tc.params.MaxLevel()) + ct2 := ct2f.Value + b.Run(testString(params, params.MaxLevel(), "Marshalling/ReadFromIO"), func(b *testing.B) { for i := 0; i < b.N; i++ { + + _, err := ct2.ReadFrom(bufiordr) + + b.StopTimer() + if err != nil { + b.Fatal(err) + } rdr.Seek(0, 0) - brdr.Reset(rdr) - ct2.ReadFrom(brdr) - // if err != nil { - // b.Fatal(err) - // } + bufiordr.Reset(rdr) + b.StartTimer() } }) - require.True(b, ct.Equal(&ct2)) - var ct3 Ciphertext + // require.True(b, ct.Equal(ct2)) + + ct3f := NewCiphertext(tc.params, 1, tc.params.MaxLevel()) + ct3 := ct3f.Value + b.Run(testString(params, params.MaxLevel(), "Marshalling/ReadFromOur"), func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := ct3.ReadFrom(ourbuf) + + b.StopTimer() + if err != nil { + b.Fatal(err) + } + ourbuf.Reset() + b.StartTimer() + } + }) + require.True(b, ct.Equal(ct3)) + + ct4f := NewCiphertext(tc.params, 1, tc.params.MaxLevel()) + ct4 := ct4f.Value b.Run(testString(params, params.MaxLevel(), "Marshalling/Decode"), func(b *testing.B) { for i := 0; i < b.N; i++ { - ct3.Decode(buf2) + _, err := ct4.Decode(encodeBuf) + + b.StopTimer() + if err != nil { + b.Fatal(err) + } + b.StartTimer() } }) - require.True(b, ct.Equal(&ct3)) + require.True(b, ct.Equal(ct4)) } diff --git a/rlwe/secretkey.go b/rlwe/secretkey.go index 5994b882..2a599e3f 100644 --- a/rlwe/secretkey.go +++ b/rlwe/secretkey.go @@ -1,7 +1,6 @@ package rlwe import ( - "bytes" "io" "github.com/google/go-cmp/cmp" @@ -46,46 +45,51 @@ func (sk *SecretKey) CopyNew() *SecretKey { return &SecretKey{*sk.Value.CopyNew()} } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (sk *SecretKey) MarshalBinary() (p []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = sk.WriteTo(buf) - return buf.Bytes(), err +// BinarySize returns the size in bytes of the object +// when encoded using Encode. +func (sk *SecretKey) BinarySize() (dataLen int) { + return sk.Value.BinarySize() } -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (sk *SecretKey) WriteTo(w io.Writer) (n int64, err error) { return sk.Value.WriteTo(w) } +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). +func (sk *SecretKey) ReadFrom(r io.Reader) (n int64, err error) { + return sk.Value.ReadFrom(r) +} + +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (sk *SecretKey) MarshalBinary() (p []byte, err error) { + return sk.Value.MarshalBinary() +} + // UnmarshalBinary decodes a slice of bytes generated by // MarshalBinary or WriteTo on the object. func (sk *SecretKey) UnmarshalBinary(p []byte) (err error) { - _, err = sk.ReadFrom(bytes.NewBuffer(p)) - return -} - -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. -func (sk *SecretKey) ReadFrom(r io.Reader) (n int64, err error) { - return sk.Value.ReadFrom(r) -} - -// BinarySize returns the size in bytes of the object -// when encoded using Encode. -func (sk *SecretKey) BinarySize() (dataLen int) { - return sk.Value.BinarySize() + return sk.Value.UnmarshalBinary(p) } // Encode encodes the object into a binary form on a preallocated slice of bytes diff --git a/utils/buffer/buffer.go b/utils/buffer/buffer.go index 194dba1d..3f080a0b 100644 --- a/utils/buffer/buffer.go +++ b/utils/buffer/buffer.go @@ -1,2 +1,135 @@ -// Package buffer implement methods to write and read slices on bufio.Writer and bufio.Reader. +// Package buffer implement methods for efficiently writing and reading values +// to and from io.Writer and io.Reader that also expose their internal buffers. package buffer + +import ( + "fmt" + "io" +) + +// Writer is an interface for writers that expose their internal +// buffers. +// This interface is notably implemented by the bufio.Writer type +// (see https://pkg.go.dev/bufio#Writer) and by the Buffer type. +type Writer interface { + io.Writer + Flush() (err error) + AvailableBuffer() []byte + Available() int +} + +// Reader is an interface for readers that expose their internal +// buffers. +// This interface is notably implemented by the bufio.Reader type +// (see https://pkg.go.dev/bufio#Reader) and by the Buffer type. +type Reader interface { + io.Reader + Size() int + Peek(n int) ([]byte, error) + Discard(n int) (discarded int, err error) +} + +// Buffer is a simple []byte-based buffer that complies to the +// Writer and Reader interfaces. This type assumes that its +// backing slice has a fixed size and won't attempt to extend +// it. Instead, writes beyond capacity will result in an error. +type Buffer struct { + buf []byte + n int + off int +} + +// NewBuffer creates a new Buffer struct with buff as a backing +// []byte. The read and write offset are initialized at buff[0]. +// Hence, writing new data will overwrite the content of buff. +func NewBuffer(buff []byte) *Buffer { + b := new(Buffer) + b.buf = buff + return b +} + +// NewBufferSize creates a new Buffer with size capacity. +func NewBufferSize(size int) *Buffer { + b := new(Buffer) + b.buf = make([]byte, size) + return b +} + +// Write writes p into b. It returns the number of bytes written +// and an error if attempting to write passed the initial capacity +// of the buffer. Note that the case where p shares the same backing +// memory as b is optimized. +func (b *Buffer) Write(p []byte) (n int, err error) { + if len(p)+b.n > cap(b.buf) { + return 0, fmt.Errorf("buffer too small") + } + inc := copy(b.buf[b.n:], p) // This is optimized if &b.buf[b.n:][0] == &p[0] + b.n += inc + return inc, nil +} + +// Flush doesn't do anything on this slice-based buffer. +func (b *Buffer) Flush() (err error) { + return nil +} + +// AvailableBuffer returns an empty buffer with b.Available() capacity, to be +// directly appended to and passed to a Write call. The buffer is only valid +// until the next write operation on b. +func (b *Buffer) AvailableBuffer() []byte { + return b.buf[b.n:][:0] +} + +// Available returns the number of bytes available for writes on the buffer. +func (b *Buffer) Available() int { + return len(b.buf) - b.n +} + +// Bytes returns the backing slice. +func (b *Buffer) Bytes() []byte { + return b.buf +} + +// Reset re-initializes the read and write offsets of b. +func (b *Buffer) Reset() { + b.n = 0 + b.off = 0 +} + +// Read reads len(p) bytes from the read offset of b into p. It returns the +// number n of bytes read and an error if n < len(p). +func (b *Buffer) Read(p []byte) (n int, err error) { + n = copy(p, b.buf[b.off:]) + b.off += n + if n < len(p) { + return n, io.EOF + } + return n, nil +} + +// Size returns the size of the buffer available for read. +func (b *Buffer) Size() int { + return len(b.buf) - b.off +} + +// Peek returns the next n bytes without advancing the read offset, directly +// as a reslice of the internal buffer. It returns an error if the number of +// returned bytes is smaller than n. +func (b *Buffer) Peek(n int) ([]byte, error) { + if b.off+n > len(b.buf) { + return b.buf[b.off:], io.EOF + } + return b.buf[b.off : b.off+n], nil +} + +// Discard skips the next n bytes, returning the number of bytes discarded. If +// Discard skips fewer than n bytes, it also returns an error. +func (b *Buffer) Discard(n int) (discarded int, err error) { + remain := len(b.buf) - b.off + if n > remain { + b.off = len(b.buf) + return remain, io.EOF + } + b.off += n + return n, nil +} diff --git a/utils/buffer/reader.go b/utils/buffer/reader.go index 8d4957a2..84e90666 100644 --- a/utils/buffer/reader.go +++ b/utils/buffer/reader.go @@ -3,22 +3,11 @@ package buffer import ( "encoding/binary" "fmt" - "io" "github.com/tuneinsight/lattigo/v4/utils" ) -// Reader defines a interface comprising of the minimum subset -// of methods defined by the type bufio.Reader necessary to run -// the functions defined in this file. -// See the documentation of bufio.Reader: https://pkg.go.dev/bufio. -type Reader interface { - io.Reader - Size() int - Peek(n int) ([]byte, error) - Discard(n int) (discarded int, err error) -} - +// ReadInt reads an int values from r and stores the result into *c. func ReadInt(r Reader, c *int) (n int, err error) { if c == nil { @@ -28,46 +17,48 @@ func ReadInt(r Reader, c *int) (n int, err error) { return ReadUint64(r, utils.PointyIntToPointUint64(c)) } +// ReadUint8 reads a byte from r and stores the result into *c. func ReadUint8(r Reader, c *uint8) (n int, err error) { if c == nil { return 0, fmt.Errorf("cannot ReadUint8: c is nil") } - var bb = [1]byte{} - - if n, err = r.Read(bb[:]); err != nil { - return + slice, err := r.Peek(1) + if err != nil { + return len(slice), err } // Reads one byte - *c = uint8(bb[0]) + *c = uint8(slice[0]) - return n, nil + return r.Discard(1) } +// ReadUint8Slice reads a slice of byte from r and stores the result into c. func ReadUint8Slice(r Reader, c []uint8) (n int, err error) { return r.Read(c) } +// ReadUint16 reads a uint16 from r and stores the result into *c. func ReadUint16(r Reader, c *uint16) (n int, err error) { if c == nil { return 0, fmt.Errorf("cannot ReadUint16: c is nil") } - var bb = [2]byte{} - - if n, err = r.Read(bb[:]); err != nil { - return + slice, err := r.Peek(2) + if err != nil { + return len(slice), err } // Reads one byte - *c = binary.LittleEndian.Uint16(bb[:]) + *c = binary.LittleEndian.Uint16(slice) - return n, nil + return r.Discard(2) } +// ReadUint16Slice reads a slice of uint16 from r and stores the result into c. func ReadUint16Slice(r Reader, c []uint16) (n int, err error) { // c is empty, return @@ -84,8 +75,7 @@ func ReadUint16Slice(r Reader, c []uint16) (n int, err error) { // Then returns the writen bytes if slice, err = r.Peek(size); err != nil { - fmt.Println(err) - return + return len(slice), err } buffered := len(slice) >> 1 @@ -121,24 +111,25 @@ func ReadUint16Slice(r Reader, c []uint16) (n int, err error) { return n + inc, nil } +// ReadUint32 reads a uint32 from r and stores the result into *c. func ReadUint32(r Reader, c *uint32) (n int, err error) { if c == nil { return 0, fmt.Errorf("cannot ReadUint32: c is nil") } - var bb = [4]byte{} - - if n, err = r.Read(bb[:]); err != nil { - return + slice, err := r.Peek(4) + if err != nil { + return len(slice), err } // Reads one byte - *c = binary.LittleEndian.Uint32(bb[:]) + *c = binary.LittleEndian.Uint32(slice) - return n, nil + return r.Discard(4) } +// ReadUint32Slice reads a slice of uint32 from r and stores the result into c. func ReadUint32Slice(r Reader, c []uint32) (n int, err error) { // c is empty, return @@ -156,8 +147,7 @@ func ReadUint32Slice(r Reader, c []uint32) (n int, err error) { // Then returns the writen bytes if slice, err = r.Peek(size); err != nil { - fmt.Println(err) - return + return len(slice), err } buffered := len(slice) >> 2 @@ -193,24 +183,25 @@ func ReadUint32Slice(r Reader, c []uint32) (n int, err error) { return n + inc, nil } +// ReadUint64 reads a uint64 from r and stores the result into c. func ReadUint64(r Reader, c *uint64) (n int, err error) { if c == nil { return 0, fmt.Errorf("cannot ReadUint64: c is nil") } - var bb = [8]byte{} - - if n, err = r.Read(bb[:]); err != nil { - return + bytes, err := r.Peek(8) + if err != nil { + return len(bytes), err } // Reads one byte - *c = binary.LittleEndian.Uint64(bb[:]) + *c = binary.LittleEndian.Uint64(bytes) - return n, nil + return r.Discard(8) } +// ReadUint64Slice reads a slice of uint64 from r and stores the result into c. func ReadUint64Slice(r Reader, c []uint64) (n int, err error) { // c is empty, return diff --git a/utils/buffer/writer.go b/utils/buffer/writer.go index 0b024017..c11c7052 100644 --- a/utils/buffer/writer.go +++ b/utils/buffer/writer.go @@ -2,32 +2,24 @@ package buffer import ( "encoding/binary" - "io" ) -// Writer defines a interface comprising of the minimum subset -// of methods defined by the type bufio.Writer necessary to run -// the functions defined in this file. -// See the documentation of bufio.Writer: https://pkg.go.dev/bufio. -type Writer interface { - io.Writer - Flush() (err error) - AvailableBuffer() []byte - Available() int -} - +// WriteInt writes an int c to w. func WriteInt(w Writer, c int) (n int, err error) { return WriteUint64(w, uint64(c)) } +// WriteUint8 writes a byte c to w. func WriteUint8(w Writer, c uint8) (n int, err error) { return w.Write([]byte{c}) } +// WriteUint8Slice writes a slice of bytes c to w. func WriteUint8Slice(w Writer, c []uint8) (n int, err error) { return w.Write(c) } +// WriteUint16 writes a uint16 c to w. func WriteUint16(w Writer, c uint16) (n int, err error) { buf := w.AvailableBuffer() @@ -38,13 +30,12 @@ func WriteUint16(w Writer, c uint16) (n int, err error) { } } - var bb = [2]byte{} - binary.LittleEndian.PutUint16(bb[:], c) - buf = append(buf, bb[:]...) + binary.LittleEndian.PutUint16(buf[:2], c) - return w.Write(buf) + return w.Write(buf[:2]) } +// WriteUint16Slice writes a slice of uint16 c to w. func WriteUint16Slice(w Writer, c []uint16) (n int, err error) { if len(c) == 0 { @@ -64,13 +55,10 @@ func WriteUint16Slice(w Writer, c []uint16) (n int, err error) { available = w.Available() >> 1 } - var bb = [2]byte{} - if N := len(c); N <= available { // If there is enough space in the available buffer - + buf = buf[:N<<1] for i := 0; i < N; i++ { - binary.LittleEndian.PutUint16(bb[:], c[i]) - buf = append(buf, bb[:]...) + binary.LittleEndian.PutUint16(buf[i<<2:(i<<2)+2], c[i]) } return w.Write(buf) @@ -78,8 +66,8 @@ func WriteUint16Slice(w Writer, c []uint16) (n int, err error) { // First fills the space for i := 0; i < available; i++ { - binary.LittleEndian.PutUint16(bb[:], c[i]) - buf = append(buf, bb[:]...) + buf = buf[:available<<1] + binary.LittleEndian.PutUint16(buf[i<<1:(i<<1)+2], c[i]) } var inc int @@ -102,6 +90,7 @@ func WriteUint16Slice(w Writer, c []uint16) (n int, err error) { return n + inc, nil } +// WriteUint32 writes a uint32 c into w. func WriteUint32(w Writer, c uint32) (n int, err error) { buf := w.AvailableBuffer() @@ -112,13 +101,12 @@ func WriteUint32(w Writer, c uint32) (n int, err error) { } } - var bb = [4]byte{} - binary.LittleEndian.PutUint32(bb[:], c) - buf = append(buf, bb[:]...) - + buf = buf[:4] + binary.LittleEndian.PutUint32(buf, c) return w.Write(buf) } +// WriteUint32Slice writes a slice of uint32 c into w. func WriteUint32Slice(w Writer, c []uint32) (n int, err error) { if len(c) == 0 { @@ -138,22 +126,18 @@ func WriteUint32Slice(w Writer, c []uint32) (n int, err error) { available = w.Available() >> 2 } - var bb = [4]byte{} - if N := len(c); N <= available { // If there is enough space in the available buffer - + buf = buf[:N<<2] for i := 0; i < N; i++ { - binary.LittleEndian.PutUint32(bb[:], c[i]) - buf = append(buf, bb[:]...) + binary.LittleEndian.PutUint32(buf[i<<2:(i<<2)+4], c[i]) } - return w.Write(buf) } // First fills the space + buf = buf[:available<<2] for i := 0; i < available; i++ { - binary.LittleEndian.PutUint32(bb[:], c[i]) - buf = append(buf, bb[:]...) + binary.LittleEndian.PutUint32(buf[i<<2:(i<<2)+4], c[i]) } var inc int @@ -176,6 +160,7 @@ func WriteUint32Slice(w Writer, c []uint32) (n int, err error) { return n + inc, nil } +// WriteUint64 writes a uint64 c into w. func WriteUint64(w Writer, c uint64) (n int, err error) { buf := w.AvailableBuffer() @@ -186,13 +171,12 @@ func WriteUint64(w Writer, c uint64) (n int, err error) { } } - var bb = [8]byte{} - binary.LittleEndian.PutUint64(bb[:], c) - buf = append(buf, bb[:]...) + binary.LittleEndian.PutUint64(buf[:8], c) - return w.Write(buf) + return w.Write(buf[:8]) } +// WriteUint64Slice writes a slice of uint64 into w. func WriteUint64Slice(w Writer, c []uint64) (n int, err error) { if len(c) == 0 { @@ -212,22 +196,18 @@ func WriteUint64Slice(w Writer, c []uint64) (n int, err error) { available = w.Available() >> 3 } - var bb = [8]byte{} - if N := len(c); N <= available { // If there is enough space in the available buffer - + buf = buf[:N<<3] for i := 0; i < N; i++ { - binary.LittleEndian.PutUint64(bb[:], c[i]) - buf = append(buf, bb[:]...) + binary.LittleEndian.PutUint64(buf[i<<3:(i<<3)+8], c[i]) } - return w.Write(buf) } // First fills the space + buf = buf[:available<<3] for i := 0; i < available; i++ { - binary.LittleEndian.PutUint64(bb[:], c[i]) - buf = append(buf, bb[:]...) + binary.LittleEndian.PutUint64(buf[i<<3:(i<<3)+8], c[i]) } var inc int diff --git a/utils/structs/map.go b/utils/structs/map.go index 77275f5f..bd72aa75 100644 --- a/utils/structs/map.go +++ b/utils/structs/map.go @@ -2,7 +2,6 @@ package structs import ( "bufio" - "bytes" "encoding/binary" "fmt" "io" @@ -32,13 +31,17 @@ func (m Map[K, T]) CopyNew() *Map[K, T] { return &mcpy } -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (m *Map[K, T]) WriteTo(w io.Writer) (n int64, err error) { if w, isWritable := any(new(T)).(io.WriterTo); !isWritable { @@ -78,13 +81,17 @@ func (m *Map[K, T]) WriteTo(w io.Writer) (n int64, err error) { } } -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (m *Map[K, T]) ReadFrom(r io.Reader) (n int64, err error) { if r, isReadable := any(new(T)).(io.ReaderFrom); !isReadable { @@ -113,7 +120,7 @@ func (m *Map[K, T]) ReadFrom(r io.Reader) (n int64, err error) { } n += int64(inc1) - var val *T = new(T) + var val = new(T) var inc2 int64 if inc2, err = any(val).(io.ReaderFrom).ReadFrom(r); err != nil { return n + inc2, err @@ -148,6 +155,20 @@ func (m Map[K, T]) BinarySize() (size int) { return } +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (m *Map[K, T]) MarshalBinary() (p []byte, err error) { + buf := buffer.NewBufferSize(m.BinarySize()) + _, err = m.WriteTo(buf) + return buf.Bytes(), err +} + +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (m *Map[K, T]) UnmarshalBinary(p []byte) (err error) { + _, err = m.ReadFrom(buffer.NewBuffer(p)) + return +} + // Encode encodes the object into a binary form on a preallocated slice of bytes // and returns the number of bytes written. func (m *Map[K, T]) Encode(p []byte) (n int, err error) { @@ -200,7 +221,7 @@ func (m *Map[K, T]) Decode(p []byte) (n int, err error) { n += 8 var inc int - var val *T = new(T) + var val = new(T) if inc, err = any(val).(Decoder).Decode(p[n:]); err != nil { return n + inc, err } @@ -210,17 +231,3 @@ func (m *Map[K, T]) Decode(p []byte) (n int, err error) { return } - -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (m *Map[K, T]) MarshalBinary() (p []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = m.WriteTo(buf) - return buf.Bytes(), err -} - -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (m *Map[K, T]) UnmarshalBinary(p []byte) (err error) { - _, err = m.ReadFrom(bytes.NewBuffer(p)) - return -} diff --git a/utils/structs/matrix.go b/utils/structs/matrix.go index 2de13939..5d4eb23f 100644 --- a/utils/structs/matrix.go +++ b/utils/structs/matrix.go @@ -2,7 +2,6 @@ package structs import ( "bufio" - "bytes" "encoding/binary" "fmt" "io" @@ -33,13 +32,33 @@ func (m Matrix[T]) CopyNew() *Matrix[T] { return &mcpy } -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// BinarySize returns the size in bytes of the object +// when encoded using Encode. +func (m Matrix[T]) BinarySize() (size int) { + + if s, isSizable := any(new(T)).(BinarySizer); !isSizable { + panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), s)) + } + + size += 8 + + for _, v := range m { + size += (*Vector[T])(&v).BinarySize() + } + return +} + +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (m *Matrix[T]) WriteTo(w io.Writer) (n int64, err error) { if w, isWritable := any(new(T)).(io.WriterTo); !isWritable { @@ -71,13 +90,17 @@ func (m *Matrix[T]) WriteTo(w io.Writer) (n int64, err error) { } } -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (m *Matrix[T]) ReadFrom(r io.Reader) (n int64, err error) { if r, isReadable := any(new(T)).(io.ReaderFrom); !isReadable { @@ -113,19 +136,17 @@ func (m *Matrix[T]) ReadFrom(r io.Reader) (n int64, err error) { } } -// BinarySize returns the size in bytes of the object -// when encoded using Encode. -func (m Matrix[T]) BinarySize() (size int) { +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (m *Matrix[T]) MarshalBinary() (p []byte, err error) { + buf := buffer.NewBufferSize(m.BinarySize()) + _, err = m.WriteTo(buf) + return buf.Bytes(), err +} - if s, isSizable := any(new(T)).(BinarySizer); !isSizable { - panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), s)) - } - - size += 8 - - for _, v := range m { - size += (*Vector[T])(&v).BinarySize() - } +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (m *Matrix[T]) UnmarshalBinary(p []byte) (err error) { + _, err = m.ReadFrom(buffer.NewBuffer(p)) return } @@ -177,17 +198,3 @@ func (m *Matrix[T]) Decode(p []byte) (n int, err error) { return n, nil } - -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (m *Matrix[T]) MarshalBinary() (p []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = m.WriteTo(buf) - return buf.Bytes(), err -} - -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (m *Matrix[T]) UnmarshalBinary(p []byte) (err error) { - _, err = m.ReadFrom(bytes.NewBuffer(p)) - return -} diff --git a/utils/structs/vector.go b/utils/structs/vector.go index cd2d77da..a03e39ea 100644 --- a/utils/structs/vector.go +++ b/utils/structs/vector.go @@ -2,8 +2,6 @@ package structs import ( "bufio" - "bytes" - "encoding" "fmt" "io" @@ -12,21 +10,22 @@ import ( "github.com/tuneinsight/lattigo/v4/utils/buffer" ) -type binarySerializer interface { - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler - io.WriterTo - io.ReaderFrom - // Encoder - // Decoder -} +// type binarySerializer interface { +// encoding.BinaryMarshaler +// encoding.BinaryUnmarshaler +// io.WriterTo +// io.ReaderFrom +// // Encoder +// // Decoder +// } type Vector[T any] []T // CopyNew creates a copy of the oject. func (v Vector[T]) CopyNew() *Vector[T] { - if c, isCopiable := any(new(T)).(CopyNewer[T]); !isCopiable { + var ct *T + if c, isCopiable := any(ct).(CopyNewer[T]); !isCopiable { panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), c)) } @@ -37,30 +36,50 @@ func (v Vector[T]) CopyNew() *Vector[T] { return &vcpy } -// WriteTo writes the object on an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Writer, which defines -// a subset of the method of the bufio.Writer. -// If w is not compliant to the buffer.Writer interface, it will be wrapped in -// a new bufio.Writer. -// For additional information, see lattigo/utils/buffer/writer.go. +// BinarySize returns the size in bytes of the object +// when encoded using Encode. +func (v Vector[T]) BinarySize() (size int) { + + var st *T + if s, isSizable := any(st).(BinarySizer); !isSizable { + panic(fmt.Errorf("vector component of type %T does not comply to %T", st, s)) + } + + size += 8 + for _, c := range v { + size += any(&c).(BinarySizer).BinarySize() + } + return +} + +// WriteTo writes the object on an io.Writer. It implements the io.WriterTo +// interface, and will write exactly object.BinarySize() bytes on w. +// +// Unless w implements the buffer.Writer interface (see lattigo/utils/buffer/writer.go), +// it will be wrapped into a bufio.Writer. Since this requires allocations, it +// is preferable to pass a buffer.Writer directly: +// +// - When writing multiple times to a io.Writer, it is preferable to first wrap the +// io.Writer in a pre-allocated bufio.Writer. +// - When writing to a pre-allocated var b []byte, it is preferable to pass +// buffer.NewBuffer(b) as w (see lattigo/utils/buffer/buffer.go). func (v *Vector[T]) WriteTo(w io.Writer) (n int64, err error) { - if w, isWritable := any(new(T)).(io.WriterTo); !isWritable { - return 0, fmt.Errorf("vector component of type %T does not comply to %T", new(T), w) + var o *T + if wt, isWritable := any(o).(io.WriterTo); !isWritable { + return 0, fmt.Errorf("vector component of type %T does not comply to %T", o, wt) } switch w := w.(type) { case buffer.Writer: - vval := *v var inc int - if inc, err = buffer.WriteInt(w, len(vval)); err != nil { + if inc, err = buffer.WriteInt(w, len(*v)); err != nil { return int64(inc), err } n += int64(inc) - for _, c := range vval { + for _, c := range *v { inc, err := any(&c).(io.WriterTo).WriteTo(w) n += inc if err != nil { @@ -75,20 +94,24 @@ func (v *Vector[T]) WriteTo(w io.Writer) (n int64, err error) { } } -// ReadFrom reads on the object from an io.Writer. -// To ensure optimal efficiency and minimal allocations, the user is encouraged -// to provide a struct implementing the interface buffer.Reader, which defines -// a subset of the method of the bufio.Reader. -// If r is not compliant to the buffer.Reader interface, it will be wrapped in -// a new bufio.Reader. -// For additional information, see lattigo/utils/buffer/reader.go. +// ReadFrom reads on the object from an io.Writer. It implements the +// io.ReaderFrom interface. +// +// Unless r implements the buffer.Reader interface (see see lattigo/utils/buffer/reader.go), +// it will be wrapped into a bufio.Reader. Since this requires allocation, it +// is preferable to pass a buffer.Reader directly: +// +// - When reading multiple values from a io.Reader, it is preferable to first +// first wrap io.Reader in a pre-allocated bufio.Reader. +// - When reading from a var b []byte, it is preferable to pass a buffer.NewBuffer(b) +// as w (see lattigo/utils/buffer/buffer.go). func (v *Vector[T]) ReadFrom(r io.Reader) (n int64, err error) { - if r, isReadable := any(new(T)).(io.ReaderFrom); !isReadable { - return 0, fmt.Errorf("vector component of type %T does not comply to %T", new(T), r) + var rt *T + if r, isReadable := any(rt).(io.ReaderFrom); !isReadable { + return 0, fmt.Errorf("vector component of type %T does not comply to %T", rt, r) } - // TODO: when has access to Reader's buffer, call Decode ? switch r := r.(type) { case buffer.Reader: var size int @@ -119,18 +142,17 @@ func (v *Vector[T]) ReadFrom(r io.Reader) (n int64, err error) { } } -// BinarySize returns the size in bytes of the object -// when encoded using Encode. -func (v Vector[T]) BinarySize() (size int) { +// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. +func (v *Vector[T]) MarshalBinary() (p []byte, err error) { + buf := buffer.NewBufferSize(v.BinarySize()) + _, err = v.WriteTo(buf) + return buf.Bytes(), err +} - if s, isSizable := any(new(T)).(BinarySizer); !isSizable { - panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), s)) - } - - size += 8 - for _, c := range v { - size += any(&c).(BinarySizer).BinarySize() - } +// UnmarshalBinary decodes a slice of bytes generated by +// MarshalBinary or WriteTo on the object. +func (v *Vector[T]) UnmarshalBinary(p []byte) (err error) { + _, err = v.ReadFrom(buffer.NewBuffer(p)) return } @@ -138,8 +160,9 @@ func (v Vector[T]) BinarySize() (size int) { // and returns the number of bytes written. func (v *Vector[T]) Encode(b []byte) (n int, err error) { - if e, isEncodable := any(new(T)).(Encoder); !isEncodable { - panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), e)) + var et *T + if e, isEncodable := any(et).(Encoder); !isEncodable { + panic(fmt.Errorf("vector component of type %T does not comply to %T", et, e)) } vval := *v @@ -149,7 +172,7 @@ func (v *Vector[T]) Encode(b []byte) (n int, err error) { var inc int for _, c := range vval { - if inc, err := any(&c).(Encoder).Encode(b[n:]); err != nil { + if inc, err = any(&c).(Encoder).Encode(b[n:]); err != nil { return n + inc, err } n += inc @@ -166,7 +189,7 @@ func (v *Vector[T]) Decode(p []byte) (n int, err error) { panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), d)) } - size := int(binary.LittleEndian.Uint64(p[n:])) // TODO: there is a bug here but it is not caught by the tests. + size := int(binary.LittleEndian.Uint64(p)) n += 8 if cap(*v) < size { @@ -185,16 +208,20 @@ func (v *Vector[T]) Decode(p []byte) (n int, err error) { return } -// MarshalBinary encodes the object into a binary form on a newly allocated slice of bytes. -func (v *Vector[T]) MarshalBinary() (p []byte, err error) { - buf := bytes.NewBuffer([]byte{}) - _, err = v.WriteTo(buf) - return buf.Bytes(), err +type Equatable[T any] interface { + Equal(*T) bool } -// UnmarshalBinary decodes a slice of bytes generated by -// MarshalBinary or WriteTo on the object. -func (v *Vector[T]) UnmarshalBinary(p []byte) (err error) { - _, err = v.ReadFrom(bytes.NewBuffer(p)) - return +func (v Vector[T]) Equal(other Vector[T]) bool { + + if d, isEquatable := any(new(T)).(Equatable[T]); !isEquatable { + panic(fmt.Errorf("vector component of type %T does not comply to %T", new(T), d)) + } + + isEqual := true + for i, v := range v { + isEqual = isEqual && any(&v).(Equatable[T]).Equal(&other[i]) + } + + return isEqual }