github.com/iDigitalFlame/xmt@v0.5.4/c2/task/script.go (about)

     1  // Copyright (C) 2020 - 2023 iDigitalFlame
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU General Public License as published by
     5  // the Free Software Foundation, either version 3 of the License, or
     6  // any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU General Public License
    14  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    15  //
    16  
    17  package task
    18  
    19  import (
    20  	"github.com/iDigitalFlame/xmt/com"
    21  	"github.com/iDigitalFlame/xmt/data"
    22  	"github.com/iDigitalFlame/xmt/util/xerr"
    23  )
    24  
    25  const (
    26  	flagChannel uint8 = 1 << iota
    27  	flagNoReturnOutput
    28  	flagStopOnError
    29  )
    30  
    31  // Script is a Tasklet type that allows for chaining the results of multiple
    32  // Tasks in a single instance to be run as one.
    33  //
    34  // All script tasks will be run in the same thread and will execute in order
    35  // until all tasks are complete.
    36  //
    37  // Each Script has two boolean options, 'Output' (default: true), which determines
    38  // if the Script result should be returned and 'StopOnError' (default: false),
    39  // which will determine the action taken if an error occurs in one of the Script
    40  // tasks.
    41  type Script struct {
    42  	d data.Chunk
    43  	f uint8
    44  }
    45  
    46  // Clear will reset the Script and empty its contents.
    47  //
    48  // This does not remove the error and output settings.
    49  func (s *Script) Clear() {
    50  	s.d.Clear()
    51  }
    52  
    53  // Size returns the internal size of the backing Script buffer, similar to len(s).
    54  func (s *Script) Size() int {
    55  	return s.d.Size()
    56  }
    57  
    58  // Empty returns true if this Script's backing buffer is empty.
    59  func (s *Script) Empty() bool {
    60  	return s.d.Empty()
    61  }
    62  
    63  // Output controls the 'return output' setting for this Script.
    64  //
    65  // If set to True (the default), the results of all executed Tasks in this
    66  // script will return their resulting output (if applicable and with no errors).
    67  // Otherwise, False will disable output and all Task output will be ignored,
    68  // unless errors occur.
    69  func (s *Script) Output(e bool) {
    70  	if e {
    71  		s.f = s.f &^ flagNoReturnOutput
    72  	} else {
    73  		s.f |= flagNoReturnOutput
    74  	}
    75  }
    76  
    77  // IsOutput returns true if the 'return output' setting is set to true.
    78  func (s *Script) IsOutput() bool {
    79  	return s.f&flagNoReturnOutput == 0
    80  }
    81  
    82  // Channel (if true) will set this Script payload to enable Channeling mode
    83  // (if supported) before running.
    84  //
    85  // NOTE: There is not a way to Scripts to disable channeling themselves.
    86  func (s *Script) Channel(e bool) {
    87  	if e {
    88  		s.f |= flagChannel
    89  	} else {
    90  		s.f = s.f &^ flagChannel
    91  	}
    92  }
    93  
    94  // IsChannel returns true if the 'channel' setting is set to true.
    95  func (s *Script) IsChannel() bool {
    96  	return s.f&flagChannel != 0
    97  }
    98  
    99  // Payload returns the raw, underlying bytes in this Script.
   100  // If this script is empty the return will be empty.
   101  func (s *Script) Payload() []byte {
   102  	if s.d.Empty() {
   103  		return nil
   104  	}
   105  	return s.d.Payload()
   106  }
   107  
   108  // Replace will clear the Script data and replace it with the supplied byte
   109  // array.
   110  //
   111  // It is the callers responsibility to ensure that the first type bytes are
   112  // correct values for error and output.
   113  func (s *Script) Replace(b []byte) {
   114  	s.d.Clear()
   115  	s.d.Write(b)
   116  }
   117  
   118  // StopOnError controls the 'stop on error' setting for this Script.
   119  //
   120  // If set to True, the Script will STOP processing if one of the Tasks returns
   121  // an error during runtime, otherwise False (the default), will report the error
   122  // in the chain and will keep going.
   123  func (s *Script) StopOnError(e bool) {
   124  	if e {
   125  		s.f |= flagStopOnError
   126  	} else {
   127  		s.f = s.f &^ flagStopOnError
   128  	}
   129  }
   130  
   131  // IsStopOnError returns true if the 'stop on error' setting is set to true.
   132  func (s *Script) IsStopOnError() bool {
   133  	return s.f&flagStopOnError != 0
   134  }
   135  
   136  // Truncate discards all but the first n unread bytes from the underlying buffer
   137  // but continues to use the same allocated storage.
   138  //
   139  // This will return an error if n is negative or greater than the length of the
   140  // buffer.
   141  func (s *Script) Truncate(n int) error {
   142  	return s.d.Truncate(n)
   143  }
   144  
   145  // Add will add the supplied Task (in Packet form), to the Script. If this Script
   146  // was not initialized, it will be initialized with the default options first.
   147  //
   148  // This function will return an error if the Packet supplied is invalid for
   149  // Script usage.
   150  //
   151  // An invalid Script Packet is one of the following:
   152  //   - Any fragmented Packet
   153  //   - Any Packet with control (error/oneshot/proxy/multi/frag) Flags set
   154  //   - Any NoP Packet
   155  //   - Any Packet with a System ID
   156  //   - Any Script
   157  func (s *Script) Add(n *com.Packet) error {
   158  	if n == nil || n.ID == 0 || n.ID < MvRefresh || n.Flags > 0 || n.ID == MvScript {
   159  		return xerr.Sub("invalid Packet", 0x69)
   160  	}
   161  	s.d.WriteUint8(n.ID)
   162  	s.d.WriteBytes(n.Payload())
   163  	return nil
   164  }
   165  
   166  // NewScript returns a new Script instance with the Settings for 'stop on error'
   167  // and 'return output' set to the values specified.
   168  //
   169  // Non initialized Scripts can be used instead of calling this function directly.
   170  func NewScript(errors, output bool) *Script {
   171  	var s Script
   172  	if errors {
   173  		s.f |= flagStopOnError
   174  	}
   175  	if !output {
   176  		s.f |= flagNoReturnOutput
   177  	}
   178  	return &s
   179  }
   180  
   181  // AddTasklet will add the supplied Tasklet result, to the Script. If this Script
   182  // was not initialized, it will be initialized with the default options first.
   183  //
   184  // This function will return an error if the Packet supplied is invalid for
   185  // Script usage or the Tasklet action returned an error or is invalid.
   186  //
   187  // An invalid Script Packet is one of the following:
   188  // - Any fragmented Packet
   189  // - Any Packet with control (error/oneshot/proxy/multi/frag) Flags set
   190  // - Any NoP Packet
   191  // - Any Packet with a System ID
   192  // - Any Script
   193  func (s *Script) AddTasklet(t Tasklet) error {
   194  	if t == nil {
   195  		return xerr.Sub("empty or nil Tasklet", 0x6A)
   196  	}
   197  	n, err := t.Packet()
   198  	if err != nil {
   199  		return err
   200  	}
   201  	return s.Add(n)
   202  }
   203  
   204  // Packet will take the configured Script options/data and will return a Packet
   205  // and any errors that may occur during building.
   206  //
   207  // This allows the Script struct to fulfil the 'Tasklet' interface.
   208  //
   209  // C2 Details:
   210  //
   211  //	ID: MvScript
   212  //
   213  //	Input:
   214  //	    bool      // Option 'output'
   215  //	    bool      // Option 'stop on error'
   216  //	    ...uint8  // Packet ID
   217  //	    ...[]byte // Packet Data
   218  //	Output:
   219  //	    ...uint8  // Result Packet ID
   220  //	    ...bool   // Result is not error
   221  //	    ...[]byte // Result Data
   222  func (s *Script) Packet() (*com.Packet, error) {
   223  	if s.d.Empty() {
   224  		return nil, xerr.Sub("script is empty", 0x6B)
   225  	}
   226  	n := &com.Packet{ID: MvScript}
   227  	n.WriteUint8(s.f)
   228  	s.d.Seek(0, 0)
   229  	if n.Write(s.d.Payload()); s.IsChannel() {
   230  		n.Flags |= com.FlagChannel
   231  	}
   232  	return n, nil
   233  }
   234  
   235  // Append will add the supplied Tasks (in Packet form), to the Script. If this
   236  // Script was not initialized, it will be initialized with the default options first.
   237  //
   238  // This function is like 'Add' but takes a vardict of multiple Packets to be added
   239  // in as single call.
   240  //
   241  // This function will return an error if any of the Packets supplied are invalid
   242  // for Script usage.
   243  //
   244  // An invalid Script Packet is one of the following:
   245  // - Any fragmented Packet
   246  // - Any Packet with control (error/oneshot/proxy/multi/frag) Flags set
   247  // - Any NoP Packet
   248  // - Any Packet with a System ID
   249  func (s *Script) Append(n ...*com.Packet) error {
   250  	if len(n) == 0 {
   251  		return nil
   252  	}
   253  	if len(n) == 1 {
   254  		return s.Add(n[0])
   255  	}
   256  	for i := range n {
   257  		if err := s.Add(n[i]); err != nil {
   258  			return err
   259  		}
   260  	}
   261  	return nil
   262  }