Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

xrootd: introduce a XRootD (un)marshal interface and generator #214

Merged
merged 1 commit into from
Jun 2, 2018

Conversation

EgorMatirov
Copy link
Contributor

@EgorMatirov EgorMatirov commented Jun 1, 2018

This helps to generate boilerplate code which is used for encoding
and decoding of the requests and the responses.

It's a bit involved version of the #210.
@sbinet, thank you very much for your help!

Copy link
Member

@sbinet sbinet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a first pass.
but it looks good already :)

@@ -7,23 +7,13 @@ package client // import "go-hep.org/x/hep/xrootd/client"
import (
"context"

"go-hep.org/x/hep/xrootd/protocol"
"go-hep.org/x/hep/xrootd/protocol/login"
)

// Login initializes a server connection using username
// and token which can be supplied by the previous redirection response.
func (client *Client) Login(ctx context.Context, username string, token string) (login.Response, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/username string, token string/username, token string/

@@ -149,6 +150,16 @@ func (client *Client) consume(ctx context.Context) {
}
}

// Send sends the request to the server and stores the response inside the resp.
func (client *Client) Send(ctx context.Context, resp interface{}, req protocol.Request) error {
Copy link
Member

@sbinet sbinet Jun 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldn't we request that resp honours protocol.Response instead of the empty interface{} ?

with:

package protocol

type Response interface {
    RespID() uint16 // just to prevent passing a Request instead of a Response (and vice versa)
    MarshalXrd
    UnmarshalXrd
}

return err
}

return protocol.Unmarshal(serverResponse, resp)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

then we could write:

return resp.UnmarshalXrd(data)

@@ -149,6 +150,16 @@ func (client *Client) consume(ctx context.Context) {
}
}

// Send sends the request to the server and stores the response inside the resp.
func (client *Client) Send(ctx context.Context, resp interface{}, req protocol.Request) error {
serverResponse, err := client.call(ctx, req)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/serverResponse/data/

if err != nil {
cancel()
defer cancel()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is putting back defer a deliberate change? (or just a merge carried over by some previous CL?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, looks like a strange merge from somewhere (Have no idea how it leaked here...).

RequestLevel RequestLevel
// SetIsMeta sets whether this server has meta attribute.
func (resp *Response) SetIsMeta(v bool) {
if v {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use an early return to help the compiler inline calls of this method.

)
// SetIsProxy sets whether this server has proxy attribute.
func (resp *Response) SetIsProxy(v bool) {
if v {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use an early return to help the compiler inline calls of this method.

options |= ReturnSecurityRequirements
// SetIsSupervisor sets whether this server has supervisor attribute.
func (resp *Response) SetIsSupervisor(v bool) {
if v {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use an early return to help the compiler inline calls of this method.


// SetForceSecurity sets whether signing is forced.
func (resp *Response) SetForceSecurity(v bool) {
if v {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use an early return to help the compiler inline calls of this method.

}

// Response is a response for the `Protocol` request. See details in the xrootd protocol specification.
type Response struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somehow, the SetIsXyz methods on this protocol.Response type feel a bit wrong to me: calling those won't impact back the server from which we got them.
I understand you introduced them so you could create a Response to test against in the Client tests.

Perhaps we could just leave the flags field of Response exported (so s/flags/Flags/) and re-export the IsXyz bitmasks ?
the protocol package isn't super crowded so it's fine to populate it somewhat.

that way, you could create a want value like so:

want := protocol.Response{
	BinaryProtocolVersion: protocolVersion,
	Flags:                 protocol.IsManager | protocol.IsServer | protocol.IsMeta |
                               protocol.IsProxy   | protocol.IsSupervisor,
	SecurityLevel:         xrdproto.Pedantic,
	SecurityOverrides:     []xrdproto.SecurityOverride{{1, xrdproto.SignNeeded}},
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand you introduced them so you could create a Response to test against in the Client tests.

was thinking about using them for the server implementation, not just tests. However, I don't argue about usage of Flags, it's fine too. :)

// UnmarshalXrd must copy the data if it wishes to retain the data after
// returning.
type Unmarshaler interface {
UnmarshalXrd(data []byte) (int, error)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok... to address this annoying (to me, at least) issue of this extra int return value, what about modifying the (un)marshalers to something like:

type Marshaler interface {
    MarshalXrd(buf *Buffer) error
}

type Unmarshaler interface {
    UnmarshalXrd(buf *Buffer) error
}

where Buffer is the union of the current Encoder and Decoder type.

that way, the current issue of unmarshaling protocol/protocol.Request:

// UnmarshalXrd implements xrootd/protocol.Unmarshaler
func (o *Response) UnmarshalXrd(data []byte) (pos int, err error) {
	dec := xrdenc.NewDecoder(data)
	o.BinaryProtocolVersion = dec.ReadI32()
	o.flags = flags(dec.ReadI32())
	if dec.Available() > 0 {
		dec.Skip(1)
		dec.Skip(1)
		o.SecurityVersion = dec.ReadU8()
		o.SecurityOptions = securityOptions(dec.ReadU8())
		o.SecurityLevel = protocol.SecurityLevel(dec.ReadU8())
		o.SecurityOverrides = make([]protocol.SecurityOverride, dec.ReadU8())
		for i := 0; i < len(o.SecurityOverrides); i++ {
			n, err := o.SecurityOverrides[i].UnmarshalXrd(dec.Bytes())
			if err != nil {
				return 0, err
			}
			dec.Skip(n)
		}
	}
	return dec.Pos(), err
}

would become:

// UnmarshalXrd implements xrootd/protocol.Unmarshaler
func (o *Response) UnmarshalXrd(buf *xrdproto.Buffer) error {
	o.BinaryProtocolVersion = buf.ReadI32()
	o.flags = flags(buf.ReadI32())
	if buf.Len() > 0 {
		buf.Skip(1)
		buf.Skip(1)
		o.SecurityVersion = buf.ReadU8()
		o.SecurityOptions = securityOptions(buf.ReadU8())
		o.SecurityLevel = protocol.SecurityLevel(buf.ReadU8())
		o.SecurityOverrides = make([]protocol.SecurityOverride, buf.ReadU8())
		for i := 0; i < len(o.SecurityOverrides); i++ {
			err := o.SecurityOverrides[i].UnmarshalXrd(buf)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

most notably, the dec.Skip(n) statement (which could easily be overlooked and fogotten) just disappeared.

what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about keeping Encoder and Decoder and passing them instead of xrdproto.Buffer? Because we don't need to perform any writing in UnmarshalXrd and any reading in MarshalXrd.
So it will look like:

type Marshaler interface {
	MarshalXrd(enc *xrdenc.Encoder) error
}

type Unmarshaler interface {
	UnmarshalXrd(dec *xrdenc.Decoder) error
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then, what about renaming 'Decoder' into 'RBuffer' and 'Encoder' into 'WBuffer'?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

dec := xrdenc.NewDecoder(data)
o.BinaryProtocolVersion = dec.ReadI32()
o.flags = flags(dec.ReadI32())
if dec.Available() > 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please invert the logic and apply an early return.

func (o Response) MarshalXrd() (data []byte, err error) {
var enc xrdenc.Encoder
enc.WriteI32(o.BinaryProtocolVersion)
enc.WriteI32(int32(o.flags))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hum... why isn't the marshaling handling the optional writing part, symmetric to what is done on the unmarshaling side?

This helps to generate boilerplate code which is used for encoding
and decoding of the requests and the responses.
Copy link
Member

@sbinet sbinet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@sbinet sbinet merged commit dfaefec into go-hep:master Jun 2, 2018
@sbinet sbinet mentioned this pull request Jun 2, 2018
@EgorMatirov EgorMatirov deleted the xrootd-marshaler branch June 5, 2018 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants