<commit_before>// (C) Copyright 2021 Hewlett Packard Enterprise Development LP
//
// Licensed 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 oneview

import (
	"errors"
	"github.com/HewlettPackard/oneview-golang/ov"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceServerHardware() *schema.Resource {
	return &schema.Resource{
		Create: resourceServerHardwareCreate,
		Read:   resourceServerHardwareRead,
		Update: resourceServerHardwareUpdate,
		Delete: resourceServerHardwareDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"configuration_state": {
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
			},
			"force": {
				Type:     schema.TypeBool,
				Computed: true,
				Optional: true,
			},
			"licensing_intent": {
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
			},
			"hostname": {
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
			},
			"location_uri": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"maintenance_mode": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"name": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"one_time_boot": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"password": {
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
			},
			"power_state": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"type": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"uri": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"server_group_uri": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"server_hardware_type_uri": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"server_power_state": {
				Optional: true,
				Computed: true,
				Type:     schema.TypeList,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"power_state": {
							Type:     schema.TypeString,
							Optional: true,
						},
						"power_control": {
							Type:     schema.TypeString,
							Optional: true,
						},
					},
				},
			},
			"server_profile_uri": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"uuid": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"virtual_serial_number": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"virtual_uuid": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"mp_hosts_and_ranges": {
				Optional: true,
				Computed: true,
				Type:     schema.TypeSet,
				Elem: &schema.Schema{
					Type: schema.TypeString,
				},
			},
			"mp_ip_address": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"mp_firmware_version": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"mp_dns_name": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"uid_state": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"username": {
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
			},
		},
	}
}

func resourceServerHardwareCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	hardware := ov.ServerHardware{
		Hostname:           d.Get("hostname").(string),
		Username:           d.Get("username").(string),
		Password:           d.Get("password").(string),
		Force:              d.Get("force").(bool),
		LicensingIntent:    d.Get("licensing_intent").(string),
		ConfigurationState: d.Get("configuration_state").(string),
	}
	err := config.ovClient.AddRackServer(hardware)
	if err != nil {
		d.SetId("")
		return err
	}

	sh, _ := config.ovClient.GetServerHardwareByName(d.Get("hostname").(string))

	d.SetId(sh.UUID.String())
	return resourceServerHardwareRead(d, meta)
}

func resourceServerHardwareRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	servHard, err := config.ovClient.GetServerHardwareByName(d.Get("hostname").(string))
	if err != nil || servHard.URI.IsNil() {
		d.SetId("")
		return nil
	}
	d.SetId(d.Id())
	d.Set("configuration_state", d.Get("configuration_state").(string))
	d.Set("host_name", servHard.Hostname)
	d.Set("force", servHard.Force)
	d.Set("licensing_intent", servHard.LicensingIntent)
	d.Set("maintenance_mode", servHard.MaintenanceMode)
	d.Set("name", servHard.Name)
	d.Set("one_time_boot", servHard.OneTimeBoot)
	d.Set("location_uri", servHard.LocationURI.String())
	d.Set("password", d.Get("password").(string))
	d.Set("power_state", servHard.PowerState)
	d.Set("type", servHard.Type)
	d.Set("uri", servHard.URI.String())
	d.Set("server_group_uri", servHard.ServerGroupURI.String())
	d.Set("server_hardware_type_uri", servHard.ServerHardwareTypeURI.String())
	d.Set("server_profile_uri", servHard.ServerProfileURI.String())
	d.Set("uuid", servHard.UUID.String())
	d.Set("virtual_serial_number", servHard.VirtualSerialNumber.String())
	d.Set("virtual_uuid", servHard.VirtualUUID)
	d.Set("mp_ip_address", servHard.MpIpAddress)
	d.Set("mp_firmware_version", servHard.MpFirwareVersion)
	d.Set("mp_dns_name", servHard.MpDnsName)
	d.Set("uid_state", servHard.UidState)
	d.Set("user_name", servHard.Username)

	return nil
}

func resourceServerHardwareUpdate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	if d.HasChange("one_time_boot") {
		err := config.ovClient.SetOneTimeBoot(d.Id(), d.Get("one_time_boot").(string))

		if err != nil {
			d.SetId("")
			return err
		}

	} else if d.HasChange("maintenance_mode") {
		err := config.ovClient.SetMaintenanceMode(d.Id(), d.Get("maintenance_mode").(string))
		if err != nil {
			d.SetId("")
			return err
		}

	} else if d.HasChange("uid_state") {
		err := config.ovClient.SetUidState(d.Id(), d.Get("uid_state").(string))
		if err != nil {
			d.SetId("")
			return err
		}
	} else if d.HasChange("server_power_state") {
		powerMap := make(map[string]interface{})
		powerStates := d.Get("server_power_state").([]interface{})
		for _, powerState := range powerStates {
			powerMap = powerState.(map[string]interface{})
		}

		powerInput := map[string]interface{}{
			"powerState":   powerMap["power_state"],
			"powerControl": powerMap["power_control"],
		}

		err := config.ovClient.SetPowerState(d.Id(), powerInput)
		if err != nil {
			d.SetId("")
			return err
		}
	} else if d.HasChange("username") || d.HasChange("password") || d.HasChange("configuration_state") {
		return errors.New("Fields like username, password and configuration_state cannot be changed")
	}
	d.SetId(d.Id())

	return resourceServerHardwareRead(d, meta)
}

func resourceServerHardwareDelete(d *schema.ResourceData, meta interface{}) error {
	return nil
}
<commit_msg>Change condition to support multiple attributes<commit_after>// (C) Copyright 2021 Hewlett Packard Enterprise Development LP
//
// Licensed 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 oneview

import (
	"errors"
	"github.com/HewlettPackard/oneview-golang/ov"
	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func resourceServerHardware() *schema.Resource {
	return &schema.Resource{
		Create: resourceServerHardwareCreate,
		Read:   resourceServerHardwareRead,
		Update: resourceServerHardwareUpdate,
		Delete: resourceServerHardwareDelete,
		Importer: &schema.ResourceImporter{
			State: schema.ImportStatePassthrough,
		},

		Schema: map[string]*schema.Schema{
			"configuration_state": {
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
			},
			"force": {
				Type:     schema.TypeBool,
				Computed: true,
				Optional: true,
			},
			"licensing_intent": {
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
			},
			"hostname": {
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
			},
			"location_uri": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"maintenance_mode": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"name": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"one_time_boot": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"password": {
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
			},
			"power_state": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"type": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"uri": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"server_group_uri": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"server_hardware_type_uri": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"server_power_state": {
				Optional: true,
				Computed: true,
				Type:     schema.TypeList,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"power_state": {
							Type:     schema.TypeString,
							Optional: true,
						},
						"power_control": {
							Type:     schema.TypeString,
							Optional: true,
						},
					},
				},
			},
			"server_profile_uri": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"uuid": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"virtual_serial_number": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"virtual_uuid": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"mp_hosts_and_ranges": {
				Optional: true,
				Computed: true,
				Type:     schema.TypeSet,
				Elem: &schema.Schema{
					Type: schema.TypeString,
				},
			},
			"mp_ip_address": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"mp_firmware_version": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"mp_dns_name": {
				Type:     schema.TypeString,
				Computed: true,
			},
			"uid_state": {
				Type:     schema.TypeString,
				Optional: true,
				Computed: true,
			},
			"username": {
				Type:     schema.TypeString,
				Computed: true,
				Optional: true,
			},
		},
	}
}

func resourceServerHardwareCreate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	hardware := ov.ServerHardware{
		Hostname:           d.Get("hostname").(string),
		Username:           d.Get("username").(string),
		Password:           d.Get("password").(string),
		Force:              d.Get("force").(bool),
		LicensingIntent:    d.Get("licensing_intent").(string),
		ConfigurationState: d.Get("configuration_state").(string),
	}
	err := config.ovClient.AddRackServer(hardware)
	if err != nil {
		d.SetId("")
		return err
	}

	sh, _ := config.ovClient.GetServerHardwareByName(d.Get("hostname").(string))

	d.SetId(sh.UUID.String())
	return resourceServerHardwareRead(d, meta)
}

func resourceServerHardwareRead(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)

	servHard, err := config.ovClient.GetServerHardwareByName(d.Get("hostname").(string))
	if err != nil || servHard.URI.IsNil() {
		d.SetId("")
		return nil
	}
	d.SetId(d.Id())
	d.Set("configuration_state", d.Get("configuration_state").(string))
	d.Set("host_name", d.Get("configuration_state").(string))
	d.Set("force", servHard.Force)
	d.Set("licensing_intent", servHard.LicensingIntent)
	d.Set("maintenance_mode", servHard.MaintenanceMode)
	d.Set("name", servHard.Name)
	d.Set("one_time_boot", servHard.OneTimeBoot)
	d.Set("location_uri", servHard.LocationURI.String())
	d.Set("password", d.Get("password").(string))
	d.Set("power_state", servHard.PowerState)
	d.Set("type", servHard.Type)
	d.Set("uri", servHard.URI.String())
	d.Set("server_group_uri", servHard.ServerGroupURI.String())
	d.Set("server_hardware_type_uri", servHard.ServerHardwareTypeURI.String())
	d.Set("server_profile_uri", servHard.ServerProfileURI.String())
	d.Set("uuid", servHard.UUID.String())
	d.Set("virtual_serial_number", servHard.VirtualSerialNumber.String())
	d.Set("virtual_uuid", servHard.VirtualUUID)
	d.Set("mp_ip_address", servHard.MpIpAddress)
	d.Set("mp_firmware_version", servHard.MpFirwareVersion)
	d.Set("mp_dns_name", servHard.MpDnsName)
	d.Set("uid_state", servHard.UidState)
	d.Set("username", d.Get("username").(string))

	return nil
}

func resourceServerHardwareUpdate(d *schema.ResourceData, meta interface{}) error {
	config := meta.(*Config)
	if d.HasChange("one_time_boot") {
		err := config.ovClient.SetOneTimeBoot(d.Id(), d.Get("one_time_boot").(string))

		if err != nil {
			d.SetId("")
			return err
		}

	}
	if d.HasChange("maintenance_mode") {
		err := config.ovClient.SetMaintenanceMode(d.Id(), d.Get("maintenance_mode").(string))
		if err != nil {
			d.SetId("")
			return err
		}

	}
	if d.HasChange("uid_state") {
		err := config.ovClient.SetUidState(d.Id(), d.Get("uid_state").(string))
		if err != nil {
			d.SetId("")
			return err
		}
	}
	if d.HasChange("server_power_state") {
		powerMap := make(map[string]interface{})
		powerStates := d.Get("server_power_state").([]interface{})
		for _, powerState := range powerStates {
			powerMap = powerState.(map[string]interface{})
		}

		powerInput := map[string]interface{}{
			"powerState":   powerMap["power_state"],
			"powerControl": powerMap["power_control"],
		}

		err := config.ovClient.SetPowerState(d.Id(), powerInput)
		if err != nil {
			d.SetId("")
			return err
		}
	}
	if d.HasChange("username") || d.HasChange("password") || d.HasChange("configuration_state") {
		return errors.New("Fields like username, password and configuration_state cannot be changed")
	}
	d.SetId(d.Id())

	return resourceServerHardwareRead(d, meta)
}

func resourceServerHardwareDelete(d *schema.ResourceData, meta interface{}) error {
	return errors.New("Server hardware delete Operation is not supported.")
}
<|endoftext|>


<commit_before>package sentinel

import (
	"fmt"
	"github.com/fzzy/radix/extra/pubsub"
	"github.com/fzzy/radix/redis"
	"log"
	"strconv"
	"strings"
)

type SentinelClient struct {
	subscriptionclient *pubsub.SubClient
	sentineladdress	   string
	redisclient        *redis.Client
}

type MasterSwitchedEvent struct {
	Name          string
	OldMasterIp   string
	OldMasterPort int
	NewMasterIp   string
	NewMasterPort int
}

func NewClient(sentineladdr string) (*SentinelClient, error) {

	log.Print("Connecting to sentinel@", sentineladdr)

	redisclient, err := redis.Dial("tcp", sentineladdr)

	if err != nil {
		log.Print("Error connecting to sentinel@", sentineladdr, err.Error())
		return nil, err
	}

	log.Print("Connected to sentinel@", sentineladdr)

	redissubscriptionclient := pubsub.NewSubClient(redisclient)

	client := new(SentinelClient)
	client.redisclient = redisclient
	client.subscriptionclient = redissubscriptionclient
	client.sentineladdress = sentineladdr
	return client, nil
}

func (client *SentinelClient) FindConnectedSentinels(clustername string) (bool, error) {

	r := client.redisclient.Cmd("SENTINEL", "SENTINELS", clustername)
	l := r.String()
	// TODO : Investigate why r.List() should return the correct datatype but doesn't
	// TODO : Parse into an array of arrays
	log.Printf("Sentinels : Sentinels : %s \n", l)

	return false, nil
}

func (client *SentinelClient) StartMonitoring(switchmasterchannel chan MasterSwitchedEvent) error {

	//TODO : fix radix client - doesn't support PSubscribe
	subr := client.subscriptionclient.Subscribe("+switch-master") //, "+slave-reconf-done ")

	if subr.Err != nil {
		return subr.Err
	}

	go client.loopSubscription(switchmasterchannel)

	return nil
}

func (sub *SentinelClient) loopSubscription(switchmasterchannel chan MasterSwitchedEvent) {
	for {
		r := sub.subscriptionclient.Receive()
		if r.Timeout() {
			continue
		}
		if r.Err == nil {
			fmt.Printf("Subscription Message : Channel : %s : %s\n", r.Channel, r.Message)

			if r.Channel == "+switch-master" {
				bits := strings.Split(r.Message, " ")

				oldmasterport, _ := strconv.Atoi(bits[2])
				newmasterport, _ := strconv.Atoi(bits[4])

				event := MasterSwitchedEvent{Name: bits[0], OldMasterIp: bits[1], OldMasterPort: oldmasterport, NewMasterIp: bits[3], NewMasterPort: newmasterport}
				switchmasterchannel <- event
			}
		}else {
			fmt.Printf("Subscription Message : Channel : Error %s \n", r.Err)
		}
	}
}
<commit_msg>fix formatting error<commit_after>package sentinel

import (
	"fmt"
	"github.com/fzzy/radix/extra/pubsub"
	"github.com/fzzy/radix/redis"
	"log"
	"strconv"
	"strings"
)

type SentinelClient struct {
	subscriptionclient *pubsub.SubClient
	sentineladdress	   string
	redisclient        *redis.Client
}

type MasterSwitchedEvent struct {
	Name          string
	OldMasterIp   string
	OldMasterPort int
	NewMasterIp   string
	NewMasterPort int
}

func NewClient(sentineladdr string) (*SentinelClient, error) {

	log.Printf("Connecting to sentinel@%s", sentineladdr)

	redisclient, err := redis.Dial("tcp", sentineladdr)

	if err != nil {
		log.Printf("Error connecting to sentinel@%s", sentineladdr, err.Error())
		return nil, err
	}

	log.Printf("Connected to sentinel@%s", sentineladdr)

	redissubscriptionclient := pubsub.NewSubClient(redisclient)

	client := new(SentinelClient)
	client.redisclient = redisclient
	client.subscriptionclient = redissubscriptionclient
	client.sentineladdress = sentineladdr
	return client, nil
}

func (client *SentinelClient) FindConnectedSentinels(clustername string) (bool, error) {

	r := client.redisclient.Cmd("SENTINEL", "SENTINELS", clustername)
	l := r.String()
	// TODO : Investigate why r.List() should return the correct datatype but doesn't
	// TODO : Parse into an array of arrays
	log.Printf("Sentinels : Sentinels : %s \n", l)

	return false, nil
}

func (client *SentinelClient) StartMonitoring(switchmasterchannel chan MasterSwitchedEvent) error {

	//TODO : fix radix client - doesn't support PSubscribe
	subr := client.subscriptionclient.Subscribe("+switch-master") //, "+slave-reconf-done ")

	if subr.Err != nil {
		return subr.Err
	}

	go client.loopSubscription(switchmasterchannel)

	return nil
}

func (sub *SentinelClient) loopSubscription(switchmasterchannel chan MasterSwitchedEvent) {
	for {
		r := sub.subscriptionclient.Receive()
		if r.Timeout() {
			continue
		}
		if r.Err == nil {
			fmt.Printf("Subscription Message : Channel : %s : %s\n", r.Channel, r.Message)

			if r.Channel == "+switch-master" {
				bits := strings.Split(r.Message, " ")

				oldmasterport, _ := strconv.Atoi(bits[2])
				newmasterport, _ := strconv.Atoi(bits[4])

				event := MasterSwitchedEvent{Name: bits[0], OldMasterIp: bits[1], OldMasterPort: oldmasterport, NewMasterIp: bits[3], NewMasterPort: newmasterport}
				switchmasterchannel <- event
			}
		}else {
			fmt.Printf("Subscription Message : Channel : Error %s \n", r.Err)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2016 Andriy Pylypenko. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package sippy

import (
    "crypto/rand"
    "errors"
    "fmt"
    "math/big"
    "runtime"
    "sync"

    "sippy/conf"
    "sippy/net"
    "sippy/sdp"
    "sippy/types"
)

type Rtp_proxy_session struct {
    call_id                 string
    from_tag                string
    to_tag                  string
    _rtp_proxy_client       sippy_types.RtpProxyClient
    max_index               int
    l4r                     *local4remote
    notify_socket           string
    notify_tag              string
    insert_nortpp           bool
    caller                  _rtpps_side
    callee                  _rtpps_side
    session_lock            sync.Locker
    config                  sippy_conf.Config
    inflight_lock           sync.Mutex
    inflight_cmd            *rtpp_cmd
    rtpp_wi                 chan *rtpp_cmd
}

type rtpproxy_update_result struct {
    rtpproxy_address    string
    rtpproxy_port       string
    family              string
    sendonly            bool
}

type rtpp_cmd struct {
    cmd         string
    cb          func(string)
    rtp_proxy_client sippy_types.RtpProxyClient
}

func (self *rtpproxy_update_result) Address() string {
    return self.rtpproxy_address
}

func NewRtp_proxy_session(config sippy_conf.Config, rtp_proxy_clients []sippy_types.RtpProxyClient, call_id, from_tag, to_tag, notify_socket, notify_tag string, session_lock sync.Locker, callee_origin *sippy_sdp.SdpOrigin) (*Rtp_proxy_session, error) {
    var err error
    self := &Rtp_proxy_session{
        notify_socket   : notify_socket,
        notify_tag      : notify_tag,
        call_id         : call_id,
        from_tag        : from_tag,
        to_tag          : to_tag,
        insert_nortpp   : false,
        max_index       : -1,
        session_lock    : session_lock,
        config          : config,
        rtpp_wi         : make(chan *rtpp_cmd, 50),
    }
    self.caller.otherside = &self.callee
    self.callee.otherside = &self.caller
    self.caller.owner = self
    self.callee.owner = self
    self.caller.session_exists = false
    self.callee.session_exists = false
    // RFC4566
    // *******
    // For privacy reasons, it is sometimes desirable to obfuscate the
    // username and IP address of the session originator.  If this is a
    // concern, an arbitrary <username> and private <unicast-address> MAY be
    // chosen to populate the "o=" field, provided that these are selected
    // in a manner that does not affect the global uniqueness of the field.
    // *******
    addr := "192.0.2.1" // 192.0.2.0/24 (TEST-NET-1)
    self.caller.origin, err = sippy_sdp.NewSdpOrigin(addr)
    if err != nil {
        return nil, err
    }
    if callee_origin != nil {
        self.callee.origin = callee_origin.GetCopy()
        // New session means new RTP port so the SDP is now different and the SDP
        // version must be increased.
        self.callee.origin.IncVersion()
    } else {
        self.callee.origin, err = sippy_sdp.NewSdpOrigin(addr)
        if err != nil {
            return nil, err
        }
    }
    online_clients := []sippy_types.RtpProxyClient{}
    for _, cl := range rtp_proxy_clients {
        if cl.IsOnline() {
            online_clients = append(online_clients, cl)
        }
    }
    n := len(online_clients)
    if n == 0 {
        return nil, fmt.Errorf("No online RTP proxy client has been found")
    }
    if idx, err := rand.Int(rand.Reader, big.NewInt(int64(n))); err != nil {
        self._rtp_proxy_client = online_clients[0]
    } else {
        self._rtp_proxy_client = online_clients[idx.Int64()]
    }
    if self.call_id == "" {
        buf := make([]byte, 16)
        rand.Read(buf)
        self.call_id = fmt.Sprintf("%x", buf)
    }
    if from_tag == "" {
        buf := make([]byte, 16)
        rand.Read(buf)
        self.from_tag = fmt.Sprintf("%x", buf)
    }
    if to_tag == "" {
        buf := make([]byte, 16)
        rand.Read(buf)
        self.to_tag = fmt.Sprintf("%x", buf)
    }
    self.caller.from_tag = self.from_tag
    self.caller.to_tag = self.to_tag
    self.callee.to_tag = self.from_tag
    self.callee.from_tag = self.to_tag
    runtime.SetFinalizer(self, rtp_proxy_session_destructor)
    return self, nil
}
/*
    def version(self, result_callback):
        self.send_command("V", self.version_result, result_callback)

    def version_result(self, result, result_callback):
        result_callback(result)
*/
func (self *Rtp_proxy_session) PlayCaller(prompt_name string, times int/*= 1*/, result_callback func(string)/*= nil*/, index int /*= 0*/) {
    self.caller._play(prompt_name, times, result_callback, index)
}

func (self *Rtp_proxy_session) send_command(cmd string, cb func(string)) {
    if rtp_proxy_client := self._rtp_proxy_client; rtp_proxy_client != nil {
        self.inflight_lock.Lock()
        defer self.inflight_lock.Unlock()
        new_cmd := &rtpp_cmd{ cmd, cb, rtp_proxy_client }
        if self.inflight_cmd == nil {
            self.inflight_cmd = new_cmd
            rtp_proxy_client.SendCommand(cmd, self.cmd_done)
        } else {
            self.rtpp_wi <- new_cmd
        }
    }
}

func (self *Rtp_proxy_session) cmd_done(res string) {
    self.inflight_lock.Lock()
    done_cmd := self.inflight_cmd
    select {
        case self.inflight_cmd = <-self.rtpp_wi:
            self.inflight_cmd.rtp_proxy_client.SendCommand(self.inflight_cmd.cmd, self.cmd_done)
        default:
            self.inflight_cmd = nil
    }
    self.inflight_lock.Unlock()
    if done_cmd != nil && done_cmd.cb != nil {
        self.session_lock.Lock()
        done_cmd.cb(res)
        self.session_lock.Unlock()
    }
}

func (self *Rtp_proxy_session) StopPlayCaller(result_callback func(string)/*= nil*/, index int/*= 0*/) {
    self.caller._stop_play(result_callback, index)
}

func (self *Rtp_proxy_session) StartRecording(rname/*= nil*/ string, result_callback func(string)/*= nil*/, index int/*= 0*/) {
    if ! self.caller.session_exists {
        self.caller.update("0.0.0.0", "0", func(*rtpproxy_update_result) { self._start_recording(rname, result_callback, index) }, "", index, "IP4")
        return
    }
    self._start_recording(rname, result_callback, index)
}

func (self *Rtp_proxy_session) _start_recording(rname string, result_callback func(string), index int) {
    if rname == "" {
        command := fmt.Sprintf("R %s-%d %s %s", self.call_id, index, self.from_tag, self.to_tag)
        self.send_command(command, func (r string) { self.command_result(r, result_callback) })
        return
    }
    command := fmt.Sprintf("C %s-%d %s.a %s %s", self.call_id, index, rname, self.from_tag, self.to_tag)
    self.send_command(command, func(string) { self._start_recording1(rname, result_callback, index) })
}

func (self *Rtp_proxy_session) _start_recording1(rname string, result_callback func(string), index int) {
    command := fmt.Sprintf("C %s-%d %s.o %s %s", self.call_id, index, rname, self.to_tag, self.from_tag)
    self.send_command(command, func (r string) { self.command_result(r, result_callback) })
}

func (self *Rtp_proxy_session) command_result(result string, result_callback func(string)) {
    //print "%s.command_result(%s)" % (id(self), result)
    if result_callback != nil {
        result_callback(result)
    }
}

func (self *Rtp_proxy_session) Delete() {
    if self._rtp_proxy_client == nil {
        return
    }
    for self.max_index >= 0 {
        command := fmt.Sprintf("D %s-%d %s %s", self.call_id, self.max_index, self.from_tag, self.to_tag)
        self.send_command(command, nil)
        self.max_index--
    }
    self._rtp_proxy_client = nil
}

func (self *Rtp_proxy_session) OnCallerSdpChange(sdp_body sippy_types.MsgBody, result_callback func(sippy_types.MsgBody)) error {
    return self.caller._on_sdp_change(sdp_body, result_callback)
}

func (self *Rtp_proxy_session) OnCalleeSdpChange(sdp_body sippy_types.MsgBody, result_callback func(sippy_types.MsgBody)) error {
    return self.callee._on_sdp_change(sdp_body, result_callback)
}

func rtp_proxy_session_destructor(self *Rtp_proxy_session) {
    self.Delete()
}

func (self *Rtp_proxy_session) CallerSessionExists() bool { return self.caller.session_exists }

func (self *Rtp_proxy_session) SetCallerLaddress(addr string) {
    self.caller.laddress = addr
}

func (self *Rtp_proxy_session) SetCallerRaddress(addr *sippy_net.HostPort) {
    self.caller.raddress = addr
}

func (self *Rtp_proxy_session) SetCalleeLaddress(addr string) {
    self.callee.laddress = addr
}

func (self *Rtp_proxy_session) SetCalleeRaddress(addr *sippy_net.HostPort) {
    self.callee.raddress = addr
}

func (self *Rtp_proxy_session) SetInsertNortpp(v bool) {
    self.insert_nortpp = v
}

func (self *Rtp_proxy_session) SetAfterCallerSdpChange(cb func(sippy_types.RtpProxyUpdateResult)) {
    self.caller.after_sdp_change = cb
}

func (self *Rtp_proxy_session) CalleeOrigin() *sippy_sdp.SdpOrigin {
    if self == nil {
        return nil
    }
    return self.callee.origin
}

func (self Rtp_proxy_session) SBindSupported() (bool, error) {
    rtp_proxy_client := self._rtp_proxy_client
    if rtp_proxy_client == nil {
        return true, errors.New("the session already deleted")
    }
    return rtp_proxy_client.SBindSupported(), nil
}

func (self Rtp_proxy_session) IsLocal() (bool, error) {
    rtp_proxy_client := self._rtp_proxy_client
    if rtp_proxy_client == nil {
        return true, errors.New("the session already deleted")
    }
    return rtp_proxy_client.IsLocal(), nil
}

func (self Rtp_proxy_session) TNotSupported() (bool, error) {
    rtp_proxy_client := self._rtp_proxy_client
    if rtp_proxy_client == nil {
        return true, errors.New("the session already deleted")
    }
    return rtp_proxy_client.TNotSupported(), nil
}

func (self Rtp_proxy_session) GetProxyAddress() (string, error) {
    rtp_proxy_client := self._rtp_proxy_client
    if rtp_proxy_client == nil {
        return "", errors.New("the session already deleted")
    }
    return rtp_proxy_client.GetProxyAddress(), nil
}
<commit_msg>Re-use the SdpOrigin, not copy it, so it's possible to use several rtpproxy sessions interchangeably during a call.<commit_after>// Copyright (c) 2003-2005 Maxim Sobolev. All rights reserved.
// Copyright (c) 2006-2014 Sippy Software, Inc. All rights reserved.
// Copyright (c) 2016 Andriy Pylypenko. All rights reserved.
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package sippy

import (
    "crypto/rand"
    "errors"
    "fmt"
    "math/big"
    "runtime"
    "sync"

    "sippy/conf"
    "sippy/net"
    "sippy/sdp"
    "sippy/types"
)

type Rtp_proxy_session struct {
    call_id                 string
    from_tag                string
    to_tag                  string
    _rtp_proxy_client       sippy_types.RtpProxyClient
    max_index               int
    l4r                     *local4remote
    notify_socket           string
    notify_tag              string
    insert_nortpp           bool
    caller                  _rtpps_side
    callee                  _rtpps_side
    session_lock            sync.Locker
    config                  sippy_conf.Config
    inflight_lock           sync.Mutex
    inflight_cmd            *rtpp_cmd
    rtpp_wi                 chan *rtpp_cmd
}

type rtpproxy_update_result struct {
    rtpproxy_address    string
    rtpproxy_port       string
    family              string
    sendonly            bool
}

type rtpp_cmd struct {
    cmd         string
    cb          func(string)
    rtp_proxy_client sippy_types.RtpProxyClient
}

func (self *rtpproxy_update_result) Address() string {
    return self.rtpproxy_address
}

func NewRtp_proxy_session(config sippy_conf.Config, rtp_proxy_clients []sippy_types.RtpProxyClient, call_id, from_tag, to_tag, notify_socket, notify_tag string, session_lock sync.Locker, callee_origin *sippy_sdp.SdpOrigin) (*Rtp_proxy_session, error) {
    var err error
    self := &Rtp_proxy_session{
        notify_socket   : notify_socket,
        notify_tag      : notify_tag,
        call_id         : call_id,
        from_tag        : from_tag,
        to_tag          : to_tag,
        insert_nortpp   : false,
        max_index       : -1,
        session_lock    : session_lock,
        config          : config,
        rtpp_wi         : make(chan *rtpp_cmd, 50),
    }
    self.caller.otherside = &self.callee
    self.callee.otherside = &self.caller
    self.caller.owner = self
    self.callee.owner = self
    self.caller.session_exists = false
    self.callee.session_exists = false
    // RFC4566
    // *******
    // For privacy reasons, it is sometimes desirable to obfuscate the
    // username and IP address of the session originator.  If this is a
    // concern, an arbitrary <username> and private <unicast-address> MAY be
    // chosen to populate the "o=" field, provided that these are selected
    // in a manner that does not affect the global uniqueness of the field.
    // *******
    addr := "192.0.2.1" // 192.0.2.0/24 (TEST-NET-1)
    self.caller.origin, err = sippy_sdp.NewSdpOrigin(addr)
    if err != nil {
        return nil, err
    }
    if callee_origin != nil {
        // New session means new RTP port so the SDP is now different and the SDP
        // version must be increased.
        callee_origin.IncVersion()
        self.callee.origin = callee_origin
    } else {
        self.callee.origin, err = sippy_sdp.NewSdpOrigin(addr)
        if err != nil {
            return nil, err
        }
    }
    online_clients := []sippy_types.RtpProxyClient{}
    for _, cl := range rtp_proxy_clients {
        if cl.IsOnline() {
            online_clients = append(online_clients, cl)
        }
    }
    n := len(online_clients)
    if n == 0 {
        return nil, fmt.Errorf("No online RTP proxy client has been found")
    }
    if idx, err := rand.Int(rand.Reader, big.NewInt(int64(n))); err != nil {
        self._rtp_proxy_client = online_clients[0]
    } else {
        self._rtp_proxy_client = online_clients[idx.Int64()]
    }
    if self.call_id == "" {
        buf := make([]byte, 16)
        rand.Read(buf)
        self.call_id = fmt.Sprintf("%x", buf)
    }
    if from_tag == "" {
        buf := make([]byte, 16)
        rand.Read(buf)
        self.from_tag = fmt.Sprintf("%x", buf)
    }
    if to_tag == "" {
        buf := make([]byte, 16)
        rand.Read(buf)
        self.to_tag = fmt.Sprintf("%x", buf)
    }
    self.caller.from_tag = self.from_tag
    self.caller.to_tag = self.to_tag
    self.callee.to_tag = self.from_tag
    self.callee.from_tag = self.to_tag
    runtime.SetFinalizer(self, rtp_proxy_session_destructor)
    return self, nil
}
/*
    def version(self, result_callback):
        self.send_command("V", self.version_result, result_callback)

    def version_result(self, result, result_callback):
        result_callback(result)
*/
func (self *Rtp_proxy_session) PlayCaller(prompt_name string, times int/*= 1*/, result_callback func(string)/*= nil*/, index int /*= 0*/) {
    self.caller._play(prompt_name, times, result_callback, index)
}

func (self *Rtp_proxy_session) send_command(cmd string, cb func(string)) {
    if rtp_proxy_client := self._rtp_proxy_client; rtp_proxy_client != nil {
        self.inflight_lock.Lock()
        defer self.inflight_lock.Unlock()
        new_cmd := &rtpp_cmd{ cmd, cb, rtp_proxy_client }
        if self.inflight_cmd == nil {
            self.inflight_cmd = new_cmd
            rtp_proxy_client.SendCommand(cmd, self.cmd_done)
        } else {
            self.rtpp_wi <- new_cmd
        }
    }
}

func (self *Rtp_proxy_session) cmd_done(res string) {
    self.inflight_lock.Lock()
    done_cmd := self.inflight_cmd
    select {
        case self.inflight_cmd = <-self.rtpp_wi:
            self.inflight_cmd.rtp_proxy_client.SendCommand(self.inflight_cmd.cmd, self.cmd_done)
        default:
            self.inflight_cmd = nil
    }
    self.inflight_lock.Unlock()
    if done_cmd != nil && done_cmd.cb != nil {
        self.session_lock.Lock()
        done_cmd.cb(res)
        self.session_lock.Unlock()
    }
}

func (self *Rtp_proxy_session) StopPlayCaller(result_callback func(string)/*= nil*/, index int/*= 0*/) {
    self.caller._stop_play(result_callback, index)
}

func (self *Rtp_proxy_session) StartRecording(rname/*= nil*/ string, result_callback func(string)/*= nil*/, index int/*= 0*/) {
    if ! self.caller.session_exists {
        self.caller.update("0.0.0.0", "0", func(*rtpproxy_update_result) { self._start_recording(rname, result_callback, index) }, "", index, "IP4")
        return
    }
    self._start_recording(rname, result_callback, index)
}

func (self *Rtp_proxy_session) _start_recording(rname string, result_callback func(string), index int) {
    if rname == "" {
        command := fmt.Sprintf("R %s-%d %s %s", self.call_id, index, self.from_tag, self.to_tag)
        self.send_command(command, func (r string) { self.command_result(r, result_callback) })
        return
    }
    command := fmt.Sprintf("C %s-%d %s.a %s %s", self.call_id, index, rname, self.from_tag, self.to_tag)
    self.send_command(command, func(string) { self._start_recording1(rname, result_callback, index) })
}

func (self *Rtp_proxy_session) _start_recording1(rname string, result_callback func(string), index int) {
    command := fmt.Sprintf("C %s-%d %s.o %s %s", self.call_id, index, rname, self.to_tag, self.from_tag)
    self.send_command(command, func (r string) { self.command_result(r, result_callback) })
}

func (self *Rtp_proxy_session) command_result(result string, result_callback func(string)) {
    //print "%s.command_result(%s)" % (id(self), result)
    if result_callback != nil {
        result_callback(result)
    }
}

func (self *Rtp_proxy_session) Delete() {
    if self._rtp_proxy_client == nil {
        return
    }
    for self.max_index >= 0 {
        command := fmt.Sprintf("D %s-%d %s %s", self.call_id, self.max_index, self.from_tag, self.to_tag)
        self.send_command(command, nil)
        self.max_index--
    }
    self._rtp_proxy_client = nil
}

func (self *Rtp_proxy_session) OnCallerSdpChange(sdp_body sippy_types.MsgBody, result_callback func(sippy_types.MsgBody)) error {
    return self.caller._on_sdp_change(sdp_body, result_callback)
}

func (self *Rtp_proxy_session) OnCalleeSdpChange(sdp_body sippy_types.MsgBody, result_callback func(sippy_types.MsgBody)) error {
    return self.callee._on_sdp_change(sdp_body, result_callback)
}

func rtp_proxy_session_destructor(self *Rtp_proxy_session) {
    self.Delete()
}

func (self *Rtp_proxy_session) CallerSessionExists() bool { return self.caller.session_exists }

func (self *Rtp_proxy_session) SetCallerLaddress(addr string) {
    self.caller.laddress = addr
}

func (self *Rtp_proxy_session) SetCallerRaddress(addr *sippy_net.HostPort) {
    self.caller.raddress = addr
}

func (self *Rtp_proxy_session) SetCalleeLaddress(addr string) {
    self.callee.laddress = addr
}

func (self *Rtp_proxy_session) SetCalleeRaddress(addr *sippy_net.HostPort) {
    self.callee.raddress = addr
}

func (self *Rtp_proxy_session) SetInsertNortpp(v bool) {
    self.insert_nortpp = v
}

func (self *Rtp_proxy_session) SetAfterCallerSdpChange(cb func(sippy_types.RtpProxyUpdateResult)) {
    self.caller.after_sdp_change = cb
}

func (self *Rtp_proxy_session) CalleeOrigin() *sippy_sdp.SdpOrigin {
    if self == nil {
        return nil
    }
    return self.callee.origin
}

func (self Rtp_proxy_session) SBindSupported() (bool, error) {
    rtp_proxy_client := self._rtp_proxy_client
    if rtp_proxy_client == nil {
        return true, errors.New("the session already deleted")
    }
    return rtp_proxy_client.SBindSupported(), nil
}

func (self Rtp_proxy_session) IsLocal() (bool, error) {
    rtp_proxy_client := self._rtp_proxy_client
    if rtp_proxy_client == nil {
        return true, errors.New("the session already deleted")
    }
    return rtp_proxy_client.IsLocal(), nil
}

func (self Rtp_proxy_session) TNotSupported() (bool, error) {
    rtp_proxy_client := self._rtp_proxy_client
    if rtp_proxy_client == nil {
        return true, errors.New("the session already deleted")
    }
    return rtp_proxy_client.TNotSupported(), nil
}

func (self Rtp_proxy_session) GetProxyAddress() (string, error) {
    rtp_proxy_client := self._rtp_proxy_client
    if rtp_proxy_client == nil {
        return "", errors.New("the session already deleted")
    }
    return rtp_proxy_client.GetProxyAddress(), nil
}
<|endoftext|>


<commit_before>package golb

import (
	"errors"
	"fmt"
	"io"
	"net"
	"os"
	"strings"
	"time"

	"github.com/satori/go.uuid"
)

func Listen() error {
	if config.Verbose {
		fmt.Printf("LOAD - - Loaded configuration from file: %s\n", confPath)
	}

	listener, err := net.Listen("tcp", config.IP+":"+config.Port)
	if err != nil {
		return fmt.Errorf("Failed to setup listener: %v", err)
	}
	host := config.IP
	if host == "" {
		host = "127.0.0.1"
	}

	if config.Verbose {
		fmt.Println("LOAD - - Kickstarting GC...")
	}
	go staleSessionsCleaner()

	fmt.Printf("LOAD - - Listening on %s:%s...\n", host, config.Port)

	for {
		conn, err := listener.Accept()
		id := uuid.NewV1().String()
		if err != nil || conn == nil {
			fmt.Fprintf(os.Stderr, "ERR  - - Failed to accept listener: %v\n", err)
			continue
		}

		if config.Verbose {
			fmt.Printf("INFO - %s - Accepted connection from %s\n", id, conn.RemoteAddr().String())
		}

		go func() {
			if config.Sticky {
				if session, isCached := getSession(stripPortFromAddr(conn.RemoteAddr().String())); isCached {
					if config.Verbose {
						fmt.Printf("INFO - %s - Client is sticked to %s\n", id, session.Upstream.Name)
					}
					err := forward(id, conn, session.Upstream)
					if err == nil {
						return
					}
				}
			}
			forwardWithStrategy(id, conn, 0)
		}()
	}
}

func forwardWithStrategy(id string, conn net.Conn, tries int) {
	if tries < len(config.Upstreams)*config.MaxCycles {
		switch config.Strategy {
		case "random":
			random(id, conn, tries)
		case "round-robin":
			roundRobin(id, conn, tries)
		}
	} else {
		if config.Verbose {
			fmt.Printf("WARN - %s - Max retry cycles reached, aborting\n", id)
		}
		conn.Close()
	}
}

func forward(id string, conn net.Conn, upstr upstream) error {
	client, err := net.DialTimeout("tcp", upstr.IP+":"+upstr.Port, time.Duration(config.Timeout)*time.Second)
	if err != nil {
		fmt.Fprintf(os.Stderr, "WARN - %s - Forwarding to %s (%s:%s) failed: %v\n", id, upstr.Name, upstr.IP, upstr.Port, err)
		return errors.New("")
	}

	if config.Verbose {
		fmt.Printf("INFO - %s - Forwarding to %s (%s:%s)\n", id, upstr.Name, upstr.IP, upstr.Port)
	}
	go func() {
		defer client.Close()
		defer conn.Close()
		io.Copy(client, conn)
	}()
	go func() {
		defer client.Close()
		defer conn.Close()
		io.Copy(conn, client)
	}()

	if config.Sticky {
		remoteIP := stripPortFromAddr(conn.RemoteAddr().String())
		_, isCached := getSession(remoteIP)
		updateSession(remoteIP, sessionDetails{upstr, time.Now()})
		if config.Verbose {
			keyword := "Sticked"
			if isCached {
				keyword = "Resticked"
			}
			fmt.Printf("INFO - %s - %s %s to %s\n", id, keyword, remoteIP, upstr.Name)
		}
	}

	return nil
}

func stripPortFromAddr(remoteAddr string) string {
	i := strings.LastIndex(remoteAddr, ":")
	if i != -1 {
		remoteAddr = remoteAddr[:i]
	}
	return remoteAddr
}
<commit_msg>GC only if sticky mode is enabled<commit_after>package golb

import (
	"errors"
	"fmt"
	"io"
	"net"
	"os"
	"strings"
	"time"

	"github.com/satori/go.uuid"
)

func Listen() error {
	if config.Verbose {
		fmt.Printf("LOAD - - Loaded configuration from file: %s\n", confPath)
	}

	listener, err := net.Listen("tcp", config.IP+":"+config.Port)
	if err != nil {
		return fmt.Errorf("Failed to setup listener: %v", err)
	}
	host := config.IP
	if host == "" {
		host = "127.0.0.1"
	}

	if config.Sticky {
		if config.Verbose {
			fmt.Println("LOAD - - Kickstarting GC...")
		}
		go staleSessionsCleaner()
	}

	fmt.Printf("LOAD - - Listening on %s:%s...\n", host, config.Port)

	for {
		conn, err := listener.Accept()
		id := uuid.NewV1().String()
		if err != nil || conn == nil {
			fmt.Fprintf(os.Stderr, "ERR  - - Failed to accept listener: %v\n", err)
			continue
		}

		if config.Verbose {
			fmt.Printf("INFO - %s - Accepted connection from %s\n", id, conn.RemoteAddr().String())
		}

		go func() {
			if config.Sticky {
				if session, isCached := getSession(stripPortFromAddr(conn.RemoteAddr().String())); isCached {
					if config.Verbose {
						fmt.Printf("INFO - %s - Client is sticked to %s\n", id, session.Upstream.Name)
					}
					err := forward(id, conn, session.Upstream)
					if err == nil {
						return
					}
				}
			}
			forwardWithStrategy(id, conn, 0)
		}()
	}
}

func forwardWithStrategy(id string, conn net.Conn, tries int) {
	if tries < len(config.Upstreams)*config.MaxCycles {
		switch config.Strategy {
		case "random":
			random(id, conn, tries)
		case "round-robin":
			roundRobin(id, conn, tries)
		}
	} else {
		if config.Verbose {
			fmt.Printf("WARN - %s - Max retry cycles reached, aborting\n", id)
		}
		conn.Close()
	}
}

func forward(id string, conn net.Conn, upstr upstream) error {
	client, err := net.DialTimeout("tcp", upstr.IP+":"+upstr.Port, time.Duration(config.Timeout)*time.Second)
	if err != nil {
		fmt.Fprintf(os.Stderr, "WARN - %s - Forwarding to %s (%s:%s) failed: %v\n", id, upstr.Name, upstr.IP, upstr.Port, err)
		return errors.New("")
	}

	if config.Verbose {
		fmt.Printf("INFO - %s - Forwarding to %s (%s:%s)\n", id, upstr.Name, upstr.IP, upstr.Port)
	}
	go func() {
		defer client.Close()
		defer conn.Close()
		io.Copy(client, conn)
	}()
	go func() {
		defer client.Close()
		defer conn.Close()
		io.Copy(conn, client)
	}()

	if config.Sticky {
		remoteIP := stripPortFromAddr(conn.RemoteAddr().String())
		_, isCached := getSession(remoteIP)
		updateSession(remoteIP, sessionDetails{upstr, time.Now()})
		if config.Verbose {
			keyword := "Sticked"
			if isCached {
				keyword = "Resticked"
			}
			fmt.Printf("INFO - %s - %s %s to %s\n", id, keyword, remoteIP, upstr.Name)
		}
	}

	return nil
}

func stripPortFromAddr(remoteAddr string) string {
	i := strings.LastIndex(remoteAddr, ":")
	if i != -1 {
		remoteAddr = remoteAddr[:i]
	}
	return remoteAddr
}
<|endoftext|>


<commit_before>// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package machineundertaker

import (
	"github.com/juju/errors"

	"github.com/juju/juju/api/base"
	"github.com/juju/juju/api/machineundertaker"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/worker"
	"github.com/juju/juju/worker/dependency"
)

// ManifoldConfig defines the machine undertaker's configuration and
// dependencies.
type ManifoldConfig struct {
	APICallerName string
	EnvironName   string

	NewWorker func(Facade, environs.Environ) (worker.Worker, error)
}

// Manifold returns a dependency.Manifold that runs a machine
// undertaker.
func Manifold(config ManifoldConfig) dependency.Manifold {
	return dependency.Manifold{
		Inputs: []string{config.APICallerName, config.EnvironName},
		Start: func(context dependency.Context) (worker.Worker, error) {
			var apiCaller base.APICaller
			if err := context.Get(config.APICallerName, &apiCaller); err != nil {
				return nil, errors.Trace(err)
			}
			var environ environs.Environ
			if err := context.Get(config.EnvironName, &environ); err != nil {
				return nil, errors.Trace(err)
			}
			api, err := machineundertaker.NewAPI(apiCaller)
			if err != nil {
				return nil, errors.Trace(err)
			}
			w, err := config.NewWorker(api, environ)
			if err != nil {
				return nil, errors.Trace(err)
			}
			return w, nil
		},
	}
}
<commit_msg>Update manifold to deal with new api signature<commit_after>// Copyright 2016 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package machineundertaker

import (
	"github.com/juju/errors"

	"github.com/juju/juju/api/base"
	"github.com/juju/juju/api/machineundertaker"
	"github.com/juju/juju/api/watcher"
	"github.com/juju/juju/environs"
	"github.com/juju/juju/worker"
	"github.com/juju/juju/worker/dependency"
)

// ManifoldConfig defines the machine undertaker's configuration and
// dependencies.
type ManifoldConfig struct {
	APICallerName string
	EnvironName   string

	NewWorker func(Facade, environs.Environ) (worker.Worker, error)
}

// Manifold returns a dependency.Manifold that runs a machine
// undertaker.
func Manifold(config ManifoldConfig) dependency.Manifold {
	return dependency.Manifold{
		Inputs: []string{config.APICallerName, config.EnvironName},
		Start: func(context dependency.Context) (worker.Worker, error) {
			var apiCaller base.APICaller
			if err := context.Get(config.APICallerName, &apiCaller); err != nil {
				return nil, errors.Trace(err)
			}
			var environ environs.Environ
			if err := context.Get(config.EnvironName, &environ); err != nil {
				return nil, errors.Trace(err)
			}
			api, err := machineundertaker.NewAPI(apiCaller, watcher.NewNotifyWatcher)
			if err != nil {
				return nil, errors.Trace(err)
			}
			w, err := config.NewWorker(api, environ)
			if err != nil {
				return nil, errors.Trace(err)
			}
			return w, nil
		},
	}
}
<|endoftext|>


<commit_before>package api

import (
	"github.com/alternaDev/go-firebase-verify"
	"github.com/gin-gonic/gin"
	"github.com/jkomoros/boardgame/server/api/users"
	"log"
	"math/rand"
	"net/http"
	"time"
)

const cookieName = "c"
const cookieLength = 64

const randomStringChars = "abcdefghijklmnopqrstuvwxyz0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

//authCookieHandler gets the JWT and the uid and the cookie. If the given uid
//is  already tied to the given cookie, it does nothing and returns success.
//If the cookie is tied to a different uid, it barfs. If there is no UID, but
//there is a cookie, it removes that row in the DB and Set-Cookie's to remove
//the cookie. If there is no cookie, it validates the JWT, and then creates a
//new cookie tyied to that uid (creating that user record if necessary), and
//Set-Cookie's it back.
func (s *Server) authCookieHandler(c *gin.Context) {
	if c.Request.Method != http.MethodPost {
		panic("This can only be called as a post.")
	}

	uid := c.PostForm("uid")
	token := c.PostForm("token")

	cookie, _ := c.Cookie(cookieName)

	log.Println("Auth Cookie Handler called", uid, token, cookie, "*")

	//If the user is already associated with that cookie it's a success, nothing more to do.

	if cookie != "" {
		userRecord := s.storage.GetUserByCookie(cookie)

		if userRecord != nil {
			if userRecord.Id == uid {
				c.JSON(http.StatusOK, gin.H{
					"Status":  "Success",
					"Message": "Cookie and uid already matched.",
				})
				return
			}
		}
	}

	if uid == "" && cookie != "" {
		//We must have an old cookie set. Clear it out.
		if err := s.storage.ConnectCookieToUser(cookie, nil); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  err.Error(),
			})
			return
		}

		//Delete the cookie on the client.

		//TODO: might need to set the domain in production.
		c.SetCookie(cookieName, "", int(time.Now().Add(time.Hour*10000*-1).Unix()), "", "", false, false)

		c.JSON(http.StatusOK, gin.H{
			"Status":  "Success",
			"Message": "Removed cookie for signed-out uid",
		})

		return

	}

	if cookie == "" && uid != "" {

		verifiedUid, err := firebase.VerifyIDToken(token, s.config.FirebaseProjectId)

		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  "Failed to verify jwt token: " + err.Error(),
			})
			return
		}

		if verifiedUid != uid {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  "The decoded jwt token doesn not match with the provided uid.",
			})
			return
		}

		user := s.storage.GetUserById(uid)

		//If we've never seen this Uid before, store it.
		if user == nil {

			user = &users.StorageRecord{
				Id: uid,
			}
			s.storage.UpdateUser(user)

		}

		cookie = randomString(cookieLength)

		if err := s.storage.ConnectCookieToUser(cookie, user); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  "Couldn't connect cookie to user: " + err.Error(),
			})
			return
		}

		//TODO: might need to set the domain in production
		c.SetCookie(cookieName, cookie, int(time.Now().Add(time.Hour*100).Unix()), "", "", false, false)

		c.JSON(http.StatusOK, gin.H{
			"Status":  "Success",
			"Message": "Created new cookie to point to uid",
		})
		return

	}

	c.JSON(http.StatusOK, gin.H{
		"Status": "Failure",
		"Error":  "Not Yet Implemented",
	})
}
<commit_msg>Throw out cookies if they are from an old database or invalid. Part of #71.<commit_after>package api

import (
	"github.com/alternaDev/go-firebase-verify"
	"github.com/gin-gonic/gin"
	"github.com/jkomoros/boardgame/server/api/users"
	"math/rand"
	"net/http"
	"time"
)

const cookieName = "c"
const cookieLength = 64

const randomStringChars = "abcdefghijklmnopqrstuvwxyz0123456789"

//randomString returns a random string of the given length.
func randomString(length int) string {
	var result = ""

	for len(result) < length {
		result += string(randomStringChars[rand.Intn(len(randomStringChars))])
	}

	return result
}

func (s *Server) unsetCookie(c *gin.Context, cookie string, message string) {
	//We must have an old cookie set. Clear it out.
	if err := s.storage.ConnectCookieToUser(cookie, nil); err != nil {
		c.JSON(http.StatusOK, gin.H{
			"Status": "Failure",
			"Error":  err.Error(),
		})
		return
	}

	//Delete the cookie on the client.

	//TODO: might need to set the domain in production.
	c.SetCookie(cookieName, "", int(time.Now().Add(time.Hour*10000*-1).Unix()), "", "", false, false)

	c.JSON(http.StatusOK, gin.H{
		"Status":  "Success",
		"Message": message,
	})

	return
}

//authCookieHandler gets the JWT and the uid and the cookie. If the given uid
//is  already tied to the given cookie, it does nothing and returns success.
//If the cookie is tied to a different uid, it barfs. If there is no UID, but
//there is a cookie, it removes that row in the DB and Set-Cookie's to remove
//the cookie. If there is no cookie, it validates the JWT, and then creates a
//new cookie tyied to that uid (creating that user record if necessary), and
//Set-Cookie's it back.
func (s *Server) authCookieHandler(c *gin.Context) {
	if c.Request.Method != http.MethodPost {
		panic("This can only be called as a post.")
	}

	uid := c.PostForm("uid")
	token := c.PostForm("token")

	cookie, _ := c.Cookie(cookieName)

	//If the user is already associated with that cookie it's a success, nothing more to do.

	if cookie != "" && uid != "" {

		userRecord := s.storage.GetUserByCookie(cookie)

		if userRecord == nil {
			//The cookie must be invalid
			s.unsetCookie(c, cookie, "Cookie pointed to an user that did not exist. Unsetting.")
			return
		} else {
			if userRecord.Id == uid {
				c.JSON(http.StatusOK, gin.H{
					"Status":  "Success",
					"Message": "Cookie and uid already matched.",
				})
				return
			} else {
				s.unsetCookie(c, cookie, "Cookie pointed to the wrong uid. Unsetting")
				return
			}
		}
	}

	if uid == "" && cookie != "" {
		s.unsetCookie(c, cookie, "Removed cookie for signed-out uid")
		return
	}

	if cookie == "" && uid != "" {

		verifiedUid, err := firebase.VerifyIDToken(token, s.config.FirebaseProjectId)

		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  "Failed to verify jwt token: " + err.Error(),
			})
			return
		}

		if verifiedUid != uid {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  "The decoded jwt token doesn not match with the provided uid.",
			})
			return
		}

		user := s.storage.GetUserById(uid)

		//If we've never seen this Uid before, store it.
		if user == nil {

			user = &users.StorageRecord{
				Id: uid,
			}
			s.storage.UpdateUser(user)

		}

		cookie = randomString(cookieLength)

		if err := s.storage.ConnectCookieToUser(cookie, user); err != nil {
			c.JSON(http.StatusOK, gin.H{
				"Status": "Failure",
				"Error":  "Couldn't connect cookie to user: " + err.Error(),
			})
			return
		}

		//TODO: might need to set the domain in production
		c.SetCookie(cookieName, cookie, int(time.Now().Add(time.Hour*100).Unix()), "", "", false, false)

		c.JSON(http.StatusOK, gin.H{
			"Status":  "Success",
			"Message": "Created new cookie to point to uid",
		})
		return

	}

	c.JSON(http.StatusOK, gin.H{
		"Status": "Failure",
		"Error":  "Not Yet Implemented",
	})
}
<|endoftext|>


<commit_before>// Copyright 2015 The Prometheus Authors
// Licensed 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.

// +build !noconntrack

package collector

import (
	"github.com/go-kit/kit/log"
	"github.com/prometheus/client_golang/prometheus"
)

type conntrackCollector struct {
	current *prometheus.Desc
	limit   *prometheus.Desc
	logger  log.Logger
}

func init() {
	registerCollector("conntrack", defaultEnabled, NewConntrackCollector)
}

// NewConntrackCollector returns a new Collector exposing conntrack stats.
func NewConntrackCollector(logger log.Logger) (Collector, error) {
	return &conntrackCollector{
		current: prometheus.NewDesc(
			prometheus.BuildFQName(namespace, "", "nf_conntrack_entries"),
			"Number of currently allocated flow entries for connection tracking.",
			nil, nil,
		),
		limit: prometheus.NewDesc(
			prometheus.BuildFQName(namespace, "", "nf_conntrack_entries_limit"),
			"Maximum size of connection tracking table.",
			nil, nil,
		),
		logger: logger,
	}, nil
}

func (c *conntrackCollector) Update(ch chan<- prometheus.Metric) error {
	value, err := readUintFromFile(procFilePath("sys/net/netfilter/nf_conntrack_count"))
	if err != nil {
		// Conntrack probably not loaded into the kernel.
		return err
	}
	ch <- prometheus.MustNewConstMetric(
		c.current, prometheus.GaugeValue, float64(value))

	value, err = readUintFromFile(procFilePath("sys/net/netfilter/nf_conntrack_max"))
	if err != nil {
		return err
	}
	ch <- prometheus.MustNewConstMetric(
		c.limit, prometheus.GaugeValue, float64(value))

	return nil
}
<commit_msg>Conntrack update: differentiate ErrNoData and other errors (#1323, #1723)<commit_after>// Copyright 2015 The Prometheus Authors
// Licensed 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.

// +build !noconntrack

package collector

import (
	"errors"
	"fmt"
	"os"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/prometheus/client_golang/prometheus"
)

type conntrackCollector struct {
	current *prometheus.Desc
	limit   *prometheus.Desc
	logger  log.Logger
}

func init() {
	registerCollector("conntrack", defaultEnabled, NewConntrackCollector)
}

// NewConntrackCollector returns a new Collector exposing conntrack stats.
func NewConntrackCollector(logger log.Logger) (Collector, error) {
	return &conntrackCollector{
		current: prometheus.NewDesc(
			prometheus.BuildFQName(namespace, "", "nf_conntrack_entries"),
			"Number of currently allocated flow entries for connection tracking.",
			nil, nil,
		),
		limit: prometheus.NewDesc(
			prometheus.BuildFQName(namespace, "", "nf_conntrack_entries_limit"),
			"Maximum size of connection tracking table.",
			nil, nil,
		),
		logger: logger,
	}, nil
}

func (c *conntrackCollector) Update(ch chan<- prometheus.Metric) error {
	value, err := readUintFromFile(procFilePath("sys/net/netfilter/nf_conntrack_count"))
	if err != nil {
		return c.handleErr(err)
	}
	ch <- prometheus.MustNewConstMetric(
		c.current, prometheus.GaugeValue, float64(value))

	value, err = readUintFromFile(procFilePath("sys/net/netfilter/nf_conntrack_max"))
	if err != nil {
		return c.handleErr(err)
	}
	ch <- prometheus.MustNewConstMetric(
		c.limit, prometheus.GaugeValue, float64(value))

	return nil
}

func (c *conntrackCollector) handleErr(err error) error {
	if errors.Is(err, os.ErrNotExist) {
		level.Debug(c.logger).Log("msg", "conntrack probably not loaded")
		return ErrNoData
	}
	return fmt.Errorf("failed to retrieve conntrack stats: %w", err)
}
<|endoftext|>


<commit_before>// Copyright 2018 The Ebiten Authors
//
// Licensed 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 shareable

import (
	"fmt"
	"image"
	"image/color"
	"runtime"
	"sync"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/graphicsutil"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/packing"
	"github.com/hajimehoshi/ebiten/internal/restorable"
)

type backend struct {
	restorable *restorable.Image

	// If page is nil, the backend is not shared.
	page *packing.Page
}

func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) {
	// If the region is allocated without any extention, it's fine.
	if n := b.page.Alloc(width, height); n != nil {
		return n, true
	}

	// Simulate the extending the page and calculate the appropriate page size.
	page := b.page.Clone()
	nExtended := 0
	for {
		if !page.Extend() {
			// The page can't be extended any more. Return as failure.
			return nil, false
		}
		nExtended++
		if n := page.Alloc(width, height); n != nil {
			break
		}
	}

	for i := 0; i < nExtended; i++ {
		b.page.Extend()
	}
	s := b.page.Size()
	newImg := restorable.NewImage(s, s, false)
	oldImg := b.restorable
	w, h := oldImg.Size()
	vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0)
	is := graphicsutil.QuadIndices()
	newImg.DrawImage(oldImg, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest)
	oldImg.Dispose()
	b.restorable = newImg

	n := b.page.Alloc(width, height)
	if n == nil {
		panic("not reached")
	}
	return n, true
}

var (
	// backendsM is a mutex for critical sections of the backend and packing.Node objects.
	backendsM sync.Mutex

	// theBackends is a set of actually shared images.
	theBackends = []*backend{}
)

type Image struct {
	width    int
	height   int
	disposed bool

	backend *backend

	node          *packing.Node
	countForShare int
}

func (i *Image) isShared() bool {
	return i.node != nil
}

func (i *Image) IsSharedForTesting() bool {
	backendsM.Lock()
	defer backendsM.Unlock()
	return i.isShared()
}

func (i *Image) ensureNotShared() {
	if i.backend == nil {
		i.allocate(false)
		return
	}

	if !i.isShared() {
		return
	}

	x, y, w, h := i.region()
	newImg := restorable.NewImage(w, h, false)
	vw, vh := i.backend.restorable.Size()
	vs := graphicsutil.QuadVertices(vw, vh, x, y, x+w, y+h, 1, 0, 0, 1, 0, 0)
	is := graphicsutil.QuadIndices()
	newImg.DrawImage(i.backend.restorable, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest)

	i.dispose(false)
	i.backend = &backend{
		restorable: newImg,
	}
}

func (i *Image) forceShared() {
	if i.backend == nil {
		i.allocate(false)
		return
	}

	if i.isShared() {
		return
	}

	if !i.shareable() {
		panic("not reached")
	}

	newI := NewImage(i.width, i.height)
	pixels := make([]byte, 4*i.width*i.height)
	for y := 0; y < i.height; y++ {
		for x := 0; x < i.width; x++ {
			c := i.at(x, y).(color.RGBA)
			pixels[4*(x+i.width*y)] = c.R
			pixels[4*(x+i.width*y)+1] = c.G
			pixels[4*(x+i.width*y)+2] = c.B
			pixels[4*(x+i.width*y)+3] = c.A
		}
	}
	newI.replacePixels(pixels)
	i.dispose(false)
	*i = *newI
	// TODO: This is a very tricky hack not to call Dispose twice for the same backend.
	// Avoid such hack.
	runtime.SetFinalizer(newI, nil)
}

func (i *Image) region() (x, y, width, height int) {
	if i.backend == nil {
		panic("not reached")
	}
	if !i.isShared() {
		w, h := i.backend.restorable.Size()
		return 0, 0, w, h
	}
	return i.node.Region()
}

func (i *Image) Size() (width, height int) {
	return i.width, i.height
}

func (i *Image) QuadVertices(sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32) []float32 {
	if i.backend == nil {
		i.allocate(true)
	}
	dx, dy, _, _ := i.region()
	w, h := i.backend.restorable.SizePowerOf2()
	return graphicsutil.QuadVertices(w, h, sx0+dx, sy0+dy, sx1+dx, sy1+dy, a, b, c, d, tx, ty)
}

const MaxCountForShare = 10

func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode opengl.CompositeMode, filter graphics.Filter) {
	backendsM.Lock()
	defer backendsM.Unlock()

	if img.disposed {
		panic("shareable: the drawing source image must not be disposed")
	}
	if i.disposed {
		panic("shareable: the drawing target image must not be disposed")
	}
	if img.backend == nil {
		img.allocate(true)
	}

	i.ensureNotShared()

	// Compare i and img after ensuring i is not shared, or
	// i and img might share the same texture even though i != img.
	if i.backend.restorable == img.backend.restorable {
		panic("shareable: Image.DrawImage: img must be different from the receiver")
	}

	i.backend.restorable.DrawImage(img.backend.restorable, vertices, indices, colorm, mode, filter)

	i.countForShare = 0
	if !img.isShared() && img.shareable() {
		img.countForShare++
		if img.countForShare >= MaxCountForShare {
			img.forceShared()
			img.countForShare = 0
		}
	}
}

func (i *Image) ReplacePixels(p []byte) {
	backendsM.Lock()
	defer backendsM.Unlock()
	i.replacePixels(p)
}

func (i *Image) replacePixels(p []byte) {
	if i.disposed {
		panic("shareable: the image must not be disposed")
	}
	if i.backend == nil {
		i.allocate(true)
	}

	x, y, w, h := i.region()
	if l := 4 * w * h; len(p) != l {
		panic(fmt.Sprintf("shareable: len(p) was %d but must be %d", len(p), l))
	}
	i.backend.restorable.ReplacePixels(p, x, y, w, h)
}

func (i *Image) At(x, y int) color.Color {
	backendsM.Lock()
	defer backendsM.Unlock()
	return i.at(x, y)
}

func (i *Image) at(x, y int) color.Color {
	if i.backend == nil {
		return color.RGBA{}
	}

	ox, oy, w, h := i.region()
	if x < 0 || y < 0 || x >= w || y >= h {
		return color.RGBA{}
	}

	return i.backend.restorable.At(x+ox, y+oy)
}

func (i *Image) Dispose() {
	backendsM.Lock()
	defer backendsM.Unlock()
	i.dispose(true)
}

func (i *Image) dispose(markDisposed bool) {
	defer func() {
		if markDisposed {
			i.disposed = true
		}
		i.backend = nil
		i.node = nil
		if markDisposed {
			runtime.SetFinalizer(i, nil)
		}
	}()

	if i.disposed {
		return
	}

	if i.backend == nil {
		// Not allocated yet.
		return
	}

	if !i.isShared() {
		i.backend.restorable.Dispose()
		return
	}

	i.backend.page.Free(i.node)
	if !i.backend.page.IsEmpty() {
		// As this part can be reused, this should be cleared explicitly.
		x0, y0, x1, y1 := i.region()
		i.backend.restorable.ReplacePixels(nil, x0, y0, x1, y1)
		return
	}

	i.backend.restorable.Dispose()
	index := -1
	for idx, sh := range theBackends {
		if sh == i.backend {
			index = idx
			break
		}
	}
	if index == -1 {
		panic("not reached")
	}
	theBackends = append(theBackends[:index], theBackends[index+1:]...)
}

func (i *Image) IsInvalidated() (bool, error) {
	backendsM.Lock()
	defer backendsM.Unlock()
	v, err := i.backend.restorable.IsInvalidated()
	return v, err
}

func NewImage(width, height int) *Image {
	// Actual allocation is done lazily.
	return &Image{
		width:  width,
		height: height,
	}
}

func (i *Image) shareable() bool {
	return i.width <= maxSize && i.height <= maxSize
}

func (i *Image) allocate(shareable bool) {
	if i.backend != nil {
		panic("not reached")
	}

	if !shareable || !i.shareable() {
		i.backend = &backend{
			restorable: restorable.NewImage(i.width, i.height, false),
		}
		runtime.SetFinalizer(i, (*Image).Dispose)
		return
	}

	for _, b := range theBackends {
		if n, ok := b.TryAlloc(i.width, i.height); ok {
			i.backend = b
			i.node = n
			runtime.SetFinalizer(i, (*Image).Dispose)
			return
		}
	}
	size := initSize
	for i.width > size || i.height > size {
		if size == maxSize {
			panic("not reached")
		}
		size *= 2
	}

	b := &backend{
		restorable: restorable.NewImage(size, size, false),
		page:       packing.NewPage(size, maxSize),
	}
	theBackends = append(theBackends, b)

	n := b.page.Alloc(i.width, i.height)
	if n == nil {
		panic("not reached")
	}
	i.backend = b
	i.node = n
	runtime.SetFinalizer(i, (*Image).Dispose)
	return
}

func NewVolatileImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewImage(width, height, true)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func NewScreenFramebufferImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewScreenFramebufferImage(width, height)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func InitializeGLState() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.InitializeGLState()
}

func ResolveStaleImages() {
	backendsM.Lock()
	defer backendsM.Unlock()
	restorable.ResolveStaleImages()
}

func IsRestoringEnabled() bool {
	// As IsRestoringEnabled is an immutable state, no need to lock here.
	return restorable.IsRestoringEnabled()
}

func Restore() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Restore()
}

func Images() []image.Image {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Images()
}

func Error() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Error()
}
<commit_msg>shareable: Refactoring: Add moveTo<commit_after>// Copyright 2018 The Ebiten Authors
//
// Licensed 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 shareable

import (
	"fmt"
	"image"
	"image/color"
	"runtime"
	"sync"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/graphicsutil"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/packing"
	"github.com/hajimehoshi/ebiten/internal/restorable"
)

type backend struct {
	restorable *restorable.Image

	// If page is nil, the backend is not shared.
	page *packing.Page
}

func (b *backend) TryAlloc(width, height int) (*packing.Node, bool) {
	// If the region is allocated without any extention, it's fine.
	if n := b.page.Alloc(width, height); n != nil {
		return n, true
	}

	// Simulate the extending the page and calculate the appropriate page size.
	page := b.page.Clone()
	nExtended := 0
	for {
		if !page.Extend() {
			// The page can't be extended any more. Return as failure.
			return nil, false
		}
		nExtended++
		if n := page.Alloc(width, height); n != nil {
			break
		}
	}

	for i := 0; i < nExtended; i++ {
		b.page.Extend()
	}
	s := b.page.Size()
	newImg := restorable.NewImage(s, s, false)
	oldImg := b.restorable
	w, h := oldImg.Size()
	vs := graphicsutil.QuadVertices(w, h, 0, 0, w, h, 1, 0, 0, 1, 0, 0)
	is := graphicsutil.QuadIndices()
	newImg.DrawImage(oldImg, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest)
	oldImg.Dispose()
	b.restorable = newImg

	n := b.page.Alloc(width, height)
	if n == nil {
		panic("not reached")
	}
	return n, true
}

var (
	// backendsM is a mutex for critical sections of the backend and packing.Node objects.
	backendsM sync.Mutex

	// theBackends is a set of actually shared images.
	theBackends = []*backend{}
)

type Image struct {
	width    int
	height   int
	disposed bool

	backend *backend

	node          *packing.Node
	countForShare int
}

func (i *Image) moveTo(dst *Image) {
	dst.dispose(false)
	*dst = *i

	// i is no longer available but Dispose must not be called
	// since i and dst have the same values like node.
	runtime.SetFinalizer(i, nil)
}

func (i *Image) isShared() bool {
	return i.node != nil
}

func (i *Image) IsSharedForTesting() bool {
	backendsM.Lock()
	defer backendsM.Unlock()
	return i.isShared()
}

func (i *Image) ensureNotShared() {
	if i.backend == nil {
		i.allocate(false)
		return
	}

	if !i.isShared() {
		return
	}

	x, y, w, h := i.region()
	newImg := restorable.NewImage(w, h, false)
	vw, vh := i.backend.restorable.Size()
	vs := graphicsutil.QuadVertices(vw, vh, x, y, x+w, y+h, 1, 0, 0, 1, 0, 0)
	is := graphicsutil.QuadIndices()
	newImg.DrawImage(i.backend.restorable, vs, is, nil, opengl.CompositeModeCopy, graphics.FilterNearest)

	i.dispose(false)
	i.backend = &backend{
		restorable: newImg,
	}
}

func (i *Image) forceShared() {
	if i.backend == nil {
		i.allocate(false)
		return
	}

	if i.isShared() {
		return
	}

	if !i.shareable() {
		panic("not reached")
	}

	newI := NewImage(i.width, i.height)
	pixels := make([]byte, 4*i.width*i.height)
	for y := 0; y < i.height; y++ {
		for x := 0; x < i.width; x++ {
			c := i.at(x, y).(color.RGBA)
			pixels[4*(x+i.width*y)] = c.R
			pixels[4*(x+i.width*y)+1] = c.G
			pixels[4*(x+i.width*y)+2] = c.B
			pixels[4*(x+i.width*y)+3] = c.A
		}
	}
	newI.replacePixels(pixels)
	newI.moveTo(i)
	i.countForShare = 0
}

func (i *Image) region() (x, y, width, height int) {
	if i.backend == nil {
		panic("not reached")
	}
	if !i.isShared() {
		w, h := i.backend.restorable.Size()
		return 0, 0, w, h
	}
	return i.node.Region()
}

func (i *Image) Size() (width, height int) {
	return i.width, i.height
}

func (i *Image) QuadVertices(sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32) []float32 {
	if i.backend == nil {
		i.allocate(true)
	}
	dx, dy, _, _ := i.region()
	w, h := i.backend.restorable.SizePowerOf2()
	return graphicsutil.QuadVertices(w, h, sx0+dx, sy0+dy, sx1+dx, sy1+dy, a, b, c, d, tx, ty)
}

const MaxCountForShare = 10

func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode opengl.CompositeMode, filter graphics.Filter) {
	backendsM.Lock()
	defer backendsM.Unlock()

	if img.disposed {
		panic("shareable: the drawing source image must not be disposed")
	}
	if i.disposed {
		panic("shareable: the drawing target image must not be disposed")
	}
	if img.backend == nil {
		img.allocate(true)
	}

	i.ensureNotShared()

	// Compare i and img after ensuring i is not shared, or
	// i and img might share the same texture even though i != img.
	if i.backend.restorable == img.backend.restorable {
		panic("shareable: Image.DrawImage: img must be different from the receiver")
	}

	i.backend.restorable.DrawImage(img.backend.restorable, vertices, indices, colorm, mode, filter)

	i.countForShare = 0
	if !img.isShared() && img.shareable() {
		img.countForShare++
		if img.countForShare >= MaxCountForShare {
			img.forceShared()
			img.countForShare = 0
		}
	}
}

func (i *Image) ReplacePixels(p []byte) {
	backendsM.Lock()
	defer backendsM.Unlock()
	i.replacePixels(p)
}

func (i *Image) replacePixels(p []byte) {
	if i.disposed {
		panic("shareable: the image must not be disposed")
	}
	if i.backend == nil {
		i.allocate(true)
	}

	x, y, w, h := i.region()
	if l := 4 * w * h; len(p) != l {
		panic(fmt.Sprintf("shareable: len(p) was %d but must be %d", len(p), l))
	}
	i.backend.restorable.ReplacePixels(p, x, y, w, h)
}

func (i *Image) At(x, y int) color.Color {
	backendsM.Lock()
	defer backendsM.Unlock()
	return i.at(x, y)
}

func (i *Image) at(x, y int) color.Color {
	if i.backend == nil {
		return color.RGBA{}
	}

	ox, oy, w, h := i.region()
	if x < 0 || y < 0 || x >= w || y >= h {
		return color.RGBA{}
	}

	return i.backend.restorable.At(x+ox, y+oy)
}

func (i *Image) Dispose() {
	backendsM.Lock()
	defer backendsM.Unlock()
	i.dispose(true)
}

func (i *Image) dispose(markDisposed bool) {
	defer func() {
		if markDisposed {
			i.disposed = true
		}
		i.backend = nil
		i.node = nil
		if markDisposed {
			runtime.SetFinalizer(i, nil)
		}
	}()

	if i.disposed {
		return
	}

	if i.backend == nil {
		// Not allocated yet.
		return
	}

	if !i.isShared() {
		i.backend.restorable.Dispose()
		return
	}

	i.backend.page.Free(i.node)
	if !i.backend.page.IsEmpty() {
		// As this part can be reused, this should be cleared explicitly.
		x0, y0, x1, y1 := i.region()
		i.backend.restorable.ReplacePixels(nil, x0, y0, x1, y1)
		return
	}

	i.backend.restorable.Dispose()
	index := -1
	for idx, sh := range theBackends {
		if sh == i.backend {
			index = idx
			break
		}
	}
	if index == -1 {
		panic("not reached")
	}
	theBackends = append(theBackends[:index], theBackends[index+1:]...)
}

func (i *Image) IsInvalidated() (bool, error) {
	backendsM.Lock()
	defer backendsM.Unlock()
	v, err := i.backend.restorable.IsInvalidated()
	return v, err
}

func NewImage(width, height int) *Image {
	// Actual allocation is done lazily.
	return &Image{
		width:  width,
		height: height,
	}
}

func (i *Image) shareable() bool {
	return i.width <= maxSize && i.height <= maxSize
}

func (i *Image) allocate(shareable bool) {
	if i.backend != nil {
		panic("not reached")
	}

	if !shareable || !i.shareable() {
		i.backend = &backend{
			restorable: restorable.NewImage(i.width, i.height, false),
		}
		runtime.SetFinalizer(i, (*Image).Dispose)
		return
	}

	for _, b := range theBackends {
		if n, ok := b.TryAlloc(i.width, i.height); ok {
			i.backend = b
			i.node = n
			runtime.SetFinalizer(i, (*Image).Dispose)
			return
		}
	}
	size := initSize
	for i.width > size || i.height > size {
		if size == maxSize {
			panic("not reached")
		}
		size *= 2
	}

	b := &backend{
		restorable: restorable.NewImage(size, size, false),
		page:       packing.NewPage(size, maxSize),
	}
	theBackends = append(theBackends, b)

	n := b.page.Alloc(i.width, i.height)
	if n == nil {
		panic("not reached")
	}
	i.backend = b
	i.node = n
	runtime.SetFinalizer(i, (*Image).Dispose)
	return
}

func NewVolatileImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewImage(width, height, true)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func NewScreenFramebufferImage(width, height int) *Image {
	backendsM.Lock()
	defer backendsM.Unlock()

	r := restorable.NewScreenFramebufferImage(width, height)
	i := &Image{
		width:  width,
		height: height,
		backend: &backend{
			restorable: r,
		},
	}
	runtime.SetFinalizer(i, (*Image).Dispose)
	return i
}

func InitializeGLState() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.InitializeGLState()
}

func ResolveStaleImages() {
	backendsM.Lock()
	defer backendsM.Unlock()
	restorable.ResolveStaleImages()
}

func IsRestoringEnabled() bool {
	// As IsRestoringEnabled is an immutable state, no need to lock here.
	return restorable.IsRestoringEnabled()
}

func Restore() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Restore()
}

func Images() []image.Image {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Images()
}

func Error() error {
	backendsM.Lock()
	defer backendsM.Unlock()
	return restorable.Error()
}
<|endoftext|>