Skip to content

Commit

Permalink
[Winlogbeat] Remove redundant code and move to common package (#24560)
Browse files Browse the repository at this point in the history
* Move safe reader to libbeat/common/encoding/xml package

* Remove redundant code and move to winlogbeat/sys package

* Add changelog entry

* Add safe_reader.go header

* Apply suggestions from code review

Co-authored-by: Andrew Kroh <[email protected]>

* Update CHANGELOG.next.asciidoc

Co-authored-by: Andrew Kroh <[email protected]>
  • Loading branch information
marc-gr and andrewkroh authored Mar 19, 2021
1 parent 7a0aa49 commit 02000c3
Show file tree
Hide file tree
Showing 12 changed files with 119 additions and 236 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.

package sys
package xml

import (
"bytes"
Expand All @@ -25,18 +25,18 @@ import (
"unicode/utf8"
)

// The type xmlSafeReader escapes UTF control characters in the io.Reader
// A SafeReader escapes UTF control characters in the io.Reader
// it wraps, so that it can be fed to Go's xml parser.
// Characters for which `unicode.IsControl` returns true will be output as
// an hexadecimal unicode escape sequence "\\uNNNN".
type xmlSafeReader struct {
type SafeReader struct {
inner io.Reader
backing [256]byte
buf []byte
code []byte
}

var _ io.Reader = (*xmlSafeReader)(nil)
var _ io.Reader = (*SafeReader)(nil)

func output(n int) (int, error) {
if n == 0 {
Expand All @@ -46,7 +46,7 @@ func output(n int) (int, error) {
}

// Read implements the io.Reader interface.
func (r *xmlSafeReader) Read(d []byte) (n int, err error) {
func (r *SafeReader) Read(d []byte) (n int, err error) {
if len(r.code) > 0 {
n = copy(d, r.code)
r.code = r.code[n:]
Expand All @@ -73,6 +73,6 @@ func (r *xmlSafeReader) Read(d []byte) (n int, err error) {
return output(n)
}

func newXMLSafeReader(rawXML []byte) io.Reader {
return &xmlSafeReader{inner: bytes.NewReader(rawXML)}
func NewSafeReader(rawXML []byte) *SafeReader {
return &SafeReader{inner: bytes.NewReader(rawXML)}
}
21 changes: 21 additions & 0 deletions winlogbeat/sys/buffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,24 @@ func (b *ByteBuffer) Bytes() []byte {
func (b *ByteBuffer) Len() int {
return b.offset
}

// PtrAt returns a pointer to the given offset of the buffer.
func (b *ByteBuffer) PtrAt(offset int) *byte {
if offset > b.offset-1 {
return nil
}
return &b.buf[offset]
}

// Reserve reserves n bytes by increasing the buffer's length. It may allocate
// a new underlying buffer discarding any existing contents.
func (b *ByteBuffer) Reserve(n int) {
b.offset = n

if n > cap(b.buf) {
// Allocate new larger buffer with len=n.
b.buf = make([]byte, n)
} else {
b.buf = b.buf[:n]
}
}
48 changes: 48 additions & 0 deletions winlogbeat/sys/bufferpool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. 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 sys

import (
"sync"
)

// bufferPool contains a pool of PooledByteBuffer objects.
var bufferPool = sync.Pool{
New: func() interface{} { return &PooledByteBuffer{ByteBuffer: NewByteBuffer(1024)} },
}

// PooledByteBuffer is an expandable buffer backed by a byte slice.
type PooledByteBuffer struct {
*ByteBuffer
}

// NewPooledByteBuffer return a PooledByteBuffer from the pool. The returned value must
// be released with Free().
func NewPooledByteBuffer() *PooledByteBuffer {
b := bufferPool.Get().(*PooledByteBuffer)
b.Reset()
return b
}

// Free returns the PooledByteBuffer to the pool.
func (b *PooledByteBuffer) Free() {
if b == nil {
return
}
bufferPool.Put(b)
}
4 changes: 3 additions & 1 deletion winlogbeat/sys/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ import (
"fmt"
"strconv"
"time"

libxml "github.com/elastic/beats/v7/libbeat/common/encoding/xml"
)

// UnmarshalEventXML unmarshals the given XML into a new Event.
func UnmarshalEventXML(rawXML []byte) (Event, error) {
var event Event
decoder := xml.NewDecoder(newXMLSafeReader(rawXML))
decoder := xml.NewDecoder(libxml.NewSafeReader(rawXML))
err := decoder.Decode(&event)
return event, err
}
Expand Down
56 changes: 11 additions & 45 deletions winlogbeat/sys/strings.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,57 +18,23 @@
package sys

import (
"errors"
"fmt"
"strings"
"unicode/utf16"
)

var ErrBufferTooSmall = errors.New("buffer too small")

// UTF16BytesToString returns a string that is decoded from the UTF-16 bytes.
// The byte slice must be of even length otherwise an error will be returned.
// The integer returned is the offset to the start of the next string with
// buffer if it exists, otherwise -1 is returned.
func UTF16BytesToString(b []byte) (string, int, error) {
if len(b)%2 != 0 {
return "", 0, fmt.Errorf("Slice must have an even length (length=%d)", len(b))
}

offset := -1

// Find the null terminator if it exists and re-slice the b.
if nullIndex := indexNullTerminator(b); nullIndex > -1 {
if len(b) > nullIndex+2 {
offset = nullIndex + 2
}

b = b[:nullIndex]
}

s := make([]uint16, len(b)/2)
for i := range s {
s[i] = uint16(b[i*2]) + uint16(b[(i*2)+1])<<8
}

return string(utf16.Decode(s)), offset, nil
}
"github.com/elastic/beats/v7/libbeat/common"
)

// indexNullTerminator returns the index of a null terminator within a buffer
// containing UTF-16 encoded data. If the null terminator is not found -1 is
// returned.
func indexNullTerminator(b []byte) int {
if len(b) < 2 {
return -1
}
// UTF16BytesToString converts the given UTF-16 bytes to a string.
func UTF16BytesToString(b []byte) (string, error) {
// Use space from the ByteBuffer pool as working memory for the conversion.
bb := NewPooledByteBuffer()
defer bb.Free()

for i := 0; i < len(b); i += 2 {
if b[i] == 0 && b[i+1] == 0 {
return i
}
if err := common.UTF16ToUTF8Bytes(b, bb); err != nil {
return "", err
}

return -1
// This copies the UTF-8 bytes to create a string.
return string(bb.Bytes()), nil
}

// RemoveWindowsLineEndings replaces carriage return line feed (CRLF) with
Expand Down
49 changes: 1 addition & 48 deletions winlogbeat/sys/strings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
package sys

import (
"bytes"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -30,59 +29,13 @@ func TestUTF16BytesToString(t *testing.T) {
input := "abc白鵬翔\u145A6"
utf16Bytes := common.StringToUTF16Bytes(input)

output, _, err := UTF16BytesToString(utf16Bytes)
output, err := UTF16BytesToString(utf16Bytes)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, input, output)
}

func TestUTF16BytesToStringOffset(t *testing.T) {
in := bytes.Join([][]byte{common.StringToUTF16Bytes("one"), common.StringToUTF16Bytes("two"), common.StringToUTF16Bytes("three")}, []byte{0, 0})

output, offset, err := UTF16BytesToString(in)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "one", output)
assert.Equal(t, 8, offset)

in = in[offset:]
output, offset, err = UTF16BytesToString(in)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "two", output)
assert.Equal(t, 8, offset)

in = in[offset:]
output, offset, err = UTF16BytesToString(in)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "three", output)
assert.Equal(t, -1, offset)
}

func TestUTF16BytesToStringOffsetWithEmptyString(t *testing.T) {
in := bytes.Join([][]byte{common.StringToUTF16Bytes(""), common.StringToUTF16Bytes("two")}, []byte{0, 0})

output, offset, err := UTF16BytesToString(in)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "", output)
assert.Equal(t, 2, offset)

in = in[offset:]
output, offset, err = UTF16BytesToString(in)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "two", output)
assert.Equal(t, -1, offset)
}

func BenchmarkUTF16BytesToString(b *testing.B) {
utf16Bytes := common.StringToUTF16Bytes("A logon was attempted using explicit credentials.")

Expand Down
10 changes: 6 additions & 4 deletions winlogbeat/sys/wineventlog/bookmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (

"github.com/pkg/errors"
"golang.org/x/sys/windows"

"github.com/elastic/beats/v7/winlogbeat/sys"
)

// Bookmark is a handle to an event log bookmark.
Expand All @@ -43,16 +45,16 @@ func (b Bookmark) XML() (string, error) {
return "", errors.Wrap(err, "failed to determine necessary buffer size for EvtRender")
}

bb := newByteBuffer()
bb := sys.NewPooledByteBuffer()
bb.Reserve(int(bufferUsed * 2))
defer bb.free()
defer bb.Free()

err = _EvtRender(NilHandle, EvtHandle(b), EvtRenderBookmark, uint32(len(bb.buf)), &bb.buf[0], &bufferUsed, nil)
err = _EvtRender(NilHandle, EvtHandle(b), EvtRenderBookmark, uint32(bb.Len()), bb.PtrAt(0), &bufferUsed, nil)
if err != nil {
return "", errors.Wrap(err, "failed to render bookmark XML")
}

return UTF16BytesToString(bb.buf)
return sys.UTF16BytesToString(bb.Bytes())
}

// NewBookmarkFromEvent returns a Bookmark pointing to the given event record.
Expand Down
Loading

0 comments on commit 02000c3

Please sign in to comment.