This is an automated email from the ASF dual-hosted git repository.

piotr pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iggy.git


The following commit(s) were added to refs/heads/master by this push:
     new 3e0c9e438 fix(go): correct buffer size for CreateUser and 
UpdatePermissions serialization (#3097)
3e0c9e438 is described below

commit 3e0c9e438fe30d63f33a2d3768be90cdd4848465
Author: Atharva Lade <[email protected]>
AuthorDate: Tue Apr 14 09:23:19 2026 -0500

    fix(go): correct buffer size for CreateUser and UpdatePermissions 
serialization (#3097)
    
    The Go SDK's `CreateUser` and `UpdatePermissions` commands had incorrect
    buffer size calculations for the `has_permissions` flag byte, causing a
    1-byte over-allocation in `CreateUser` (non-nil path) and an
    index-out-of-bounds panic in `UpdatePermissions` (nil path).
    
    Closes #2981
---
 foreign/go/internal/command/user.go      |   6 +-
 foreign/go/internal/command/user_test.go | 209 +++++++++++++++++++++++++++++++
 2 files changed, 212 insertions(+), 3 deletions(-)

diff --git a/foreign/go/internal/command/user.go 
b/foreign/go/internal/command/user.go
index 628140a43..a0470d9ab 100644
--- a/foreign/go/internal/command/user.go
+++ b/foreign/go/internal/command/user.go
@@ -37,7 +37,7 @@ func (c *CreateUser) Code() Code {
 func (c *CreateUser) MarshalBinary() ([]byte, error) {
        capacity := 4 + len(c.Username) + len(c.Password)
        if c.Permissions != nil {
-               capacity += 1 + 4 + c.Permissions.Size()
+               capacity += 4 + c.Permissions.Size()
        }
 
        bytes := make([]byte, capacity)
@@ -116,10 +116,10 @@ func (u *UpdatePermissions) MarshalBinary() ([]byte, 
error) {
        if err != nil {
                return nil, err
        }
-       length := len(userIdBytes)
+       length := len(userIdBytes) + 1
 
        if u.Permissions != nil {
-               length += 1 + 4 + u.Permissions.Size()
+               length += 4 + u.Permissions.Size()
        }
 
        bytes := make([]byte, length)
diff --git a/foreign/go/internal/command/user_test.go 
b/foreign/go/internal/command/user_test.go
new file mode 100644
index 000000000..c1d9cd4c6
--- /dev/null
+++ b/foreign/go/internal/command/user_test.go
@@ -0,0 +1,209 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package command
+
+import (
+       "bytes"
+       "encoding/binary"
+       "testing"
+
+       iggcon "github.com/apache/iggy/foreign/go/contracts"
+)
+
+func TestSerialize_CreateUser_NilPermissions(t *testing.T) {
+       request := CreateUser{
+               Username: "u",
+               Password: "p",
+               Status:   iggcon.Active,
+       }
+
+       serialized, err := request.MarshalBinary()
+       if err != nil {
+               t.Fatalf("Failed to serialize CreateUser: %v", err)
+       }
+
+       // Wire format: 
[username_len:u8][username][password_len:u8][password][status:u8][has_permissions:u8]
+       expected := []byte{
+               0x01, // username_len
+               0x75, // 'u'
+               0x01, // password_len
+               0x70, // 'p'
+               0x01, // status (Active=1)
+               0x00, // has_permissions=0
+       }
+
+       if !bytes.Equal(serialized, expected) {
+               t.Errorf("CreateUser nil 
permissions:\nExpected:\t%v\nGot:\t\t%v", expected, serialized)
+       }
+}
+
+func TestSerialize_CreateUser_WithPermissions(t *testing.T) {
+       perms := &iggcon.Permissions{
+               Global: iggcon.GlobalPermissions{
+                       ManageServers: true,
+               },
+               Streams: nil,
+       }
+
+       request := CreateUser{
+               Username:    "user",
+               Password:    "pass",
+               Status:      iggcon.Inactive,
+               Permissions: perms,
+       }
+
+       serialized, err := request.MarshalBinary()
+       if err != nil {
+               t.Fatalf("Failed to serialize CreateUser: %v", err)
+       }
+
+       permBytes, err := perms.MarshalBinary()
+       if err != nil {
+               t.Fatalf("Failed to serialize Permissions: %v", err)
+       }
+
+       // 1(username_len) + 4(username) + 1(password_len) + 4(password) + 
1(status) + 1(has_perm) + 4(perm_len) + permBytes
+       expectedLen := 1 + 4 + 1 + 4 + 1 + 1 + 4 + len(permBytes)
+       if len(serialized) != expectedLen {
+               t.Fatalf("Expected length %d, got %d", expectedLen, 
len(serialized))
+       }
+
+       pos := 0
+
+       if serialized[pos] != 4 {
+               t.Errorf("username_len: expected 4, got %d", serialized[pos])
+       }
+       pos++
+       if string(serialized[pos:pos+4]) != "user" {
+               t.Errorf("username: expected 'user', got %q", 
serialized[pos:pos+4])
+       }
+       pos += 4
+
+       if serialized[pos] != 4 {
+               t.Errorf("password_len: expected 4, got %d", serialized[pos])
+       }
+       pos++
+       if string(serialized[pos:pos+4]) != "pass" {
+               t.Errorf("password: expected 'pass', got %q", 
serialized[pos:pos+4])
+       }
+       pos += 4
+
+       if serialized[pos] != 2 {
+               t.Errorf("status: expected 2 (Inactive), got %d", 
serialized[pos])
+       }
+       pos++
+
+       if serialized[pos] != 1 {
+               t.Errorf("has_permissions: expected 1, got %d", serialized[pos])
+       }
+       pos++
+
+       permLen := binary.LittleEndian.Uint32(serialized[pos : pos+4])
+       if int(permLen) != len(permBytes) {
+               t.Errorf("permissions_len: expected %d, got %d", 
len(permBytes), permLen)
+       }
+       pos += 4
+
+       if !bytes.Equal(serialized[pos:], permBytes) {
+               t.Errorf("permissions payload mismatch")
+       }
+}
+
+func TestSerialize_UpdatePermissions_NilPermissions(t *testing.T) {
+       userId, err := iggcon.NewIdentifier(uint32(42))
+       if err != nil {
+               t.Fatalf("Failed to create identifier: %v", err)
+       }
+
+       request := UpdatePermissions{
+               UserID:      userId,
+               Permissions: nil,
+       }
+
+       serialized, err := request.MarshalBinary()
+       if err != nil {
+               t.Fatalf("Failed to serialize UpdatePermissions: %v", err)
+       }
+
+       userIdBytes, _ := userId.MarshalBinary()
+
+       // [user_id bytes][has_permissions:u8=0]
+       expectedLen := len(userIdBytes) + 1
+       if len(serialized) != expectedLen {
+               t.Fatalf("Expected length %d, got %d", expectedLen, 
len(serialized))
+       }
+
+       if !bytes.Equal(serialized[:len(userIdBytes)], userIdBytes) {
+               t.Errorf("UserID bytes mismatch")
+       }
+
+       if serialized[len(userIdBytes)] != 0 {
+               t.Errorf("has_permissions: expected 0, got %d", 
serialized[len(userIdBytes)])
+       }
+}
+
+func TestSerialize_UpdatePermissions_WithPermissions(t *testing.T) {
+       userId, err := iggcon.NewIdentifier(uint32(7))
+       if err != nil {
+               t.Fatalf("Failed to create identifier: %v", err)
+       }
+
+       perms := &iggcon.Permissions{
+               Global: iggcon.GlobalPermissions{
+                       ManageServers: true,
+                       ReadUsers:     true,
+               },
+               Streams: nil,
+       }
+
+       request := UpdatePermissions{
+               UserID:      userId,
+               Permissions: perms,
+       }
+
+       serialized, err := request.MarshalBinary()
+       if err != nil {
+               t.Fatalf("Failed to serialize UpdatePermissions: %v", err)
+       }
+
+       userIdBytes, _ := userId.MarshalBinary()
+       permBytes, _ := perms.MarshalBinary()
+
+       // [user_id][has_permissions:u8=1][permissions_len:u32_le][permissions]
+       expectedLen := len(userIdBytes) + 1 + 4 + len(permBytes)
+       if len(serialized) != expectedLen {
+               t.Fatalf("Expected length %d, got %d", expectedLen, 
len(serialized))
+       }
+
+       pos := len(userIdBytes)
+
+       if serialized[pos] != 1 {
+               t.Errorf("has_permissions: expected 1, got %d", serialized[pos])
+       }
+       pos++
+
+       permLen := binary.LittleEndian.Uint32(serialized[pos : pos+4])
+       if int(permLen) != len(permBytes) {
+               t.Errorf("permissions_len: expected %d, got %d", 
len(permBytes), permLen)
+       }
+       pos += 4
+
+       if !bytes.Equal(serialized[pos:], permBytes) {
+               t.Errorf("permissions payload mismatch")
+       }
+}

Reply via email to