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 }