<commit_before>package main

import (
	"testing"
)

func TestRandomCodePoint(t *testing.T) {
	var cp rune
	for _, b := range Blocks {
		cp = b.RandomCodePoint()
		if cp < b.start || cp > b.end {
			t.Fail()
		}
	}
}

func BenchmarkRandomCodePoint(b *testing.B) {
	testBlock := &UnicodeBlock{0x0000, 0x10ffff}
	for i := 0; i < b.N; i++ {
		testBlock.RandomCodePoint()
	}
}
<commit_msg>Add RandomBlock test and benchmark<commit_after>package main

import (
	"testing"
)

func TestRandomCodePoint(t *testing.T) {
	var cp rune
	for _, b := range Blocks {
		cp = b.RandomCodePoint()
		if cp < b.start || cp > b.end {
			t.Fail()
		}
	}
}

func TestRandomBlock(t *testing.T) {
	b := Blocks.RandomBlock()
	b.RandomCodePoint()
}

func BenchmarkRandomCodePoint(b *testing.B) {
	testBlock := &UnicodeBlock{0x0000, 0x10ffff}
	for i := 0; i < b.N; i++ {
		testBlock.RandomCodePoint()
	}
}

func BenchmarkRandomBlock(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Blocks.RandomBlock()
	}
}
<|endoftext|>


<commit_before>package periodic

import (
    "log"
    "net"
    "time"
    "sync"
    "strings"
    "container/list"
    "container/heap"
    "github.com/Lupino/periodic/driver"
    "github.com/Lupino/periodic/protocol"
)

type Sched struct {
    jobTimer   *time.Timer
    grabQueue  *GrabQueue
    procQueue  *list.List
    revertPQ   PriorityQueue
    revTimer   *time.Timer
    entryPoint string
    JobLocker  *sync.Mutex
    stats      map[string]*FuncStat
    FuncLocker *sync.Mutex
    driver     driver.StoreDriver
    jobPQ      map[string]*PriorityQueue
    PQLocker   *sync.Mutex
    timeout    time.Duration
    alive      bool
}

func NewSched(entryPoint string, driver driver.StoreDriver, timeout time.Duration) *Sched {
    sched := new(Sched)
    sched.jobTimer = time.NewTimer(1 * time.Hour)
    sched.revTimer = time.NewTimer(1 * time.Hour)
    sched.grabQueue = NewGrabQueue()
    sched.procQueue = list.New()
    sched.revertPQ = make(PriorityQueue, 0)
    heap.Init(&sched.revertPQ)
    sched.entryPoint = entryPoint
    sched.JobLocker = new(sync.Mutex)
    sched.PQLocker = new(sync.Mutex)
    sched.FuncLocker = new(sync.Mutex)
    sched.stats = make(map[string]*FuncStat)
    sched.driver = driver
    sched.jobPQ = make(map[string]*PriorityQueue)
    sched.timeout = timeout
    sched.alive = true
    return sched
}

func (sched *Sched) Serve() {
    parts := strings.SplitN(sched.entryPoint, "://", 2)
    if parts[0] == "unix" {
        sockCheck(parts[1])
    }
    sched.loadJobQueue()
    go sched.handleJobPQ()
    go sched.handleRevertPQ()
    listen, err := net.Listen(parts[0], parts[1])
    if err != nil {
        log.Fatal(err)
    }
    defer listen.Close()
    log.Printf("Periodic task system started on %s\n", sched.entryPoint)
    for {
        if !sched.alive {
            break
        }
        conn, err := listen.Accept()
        if err != nil {
            log.Fatal(err)
        }
        if sched.timeout > 0 {
            conn.SetDeadline(time.Now().Add(sched.timeout * time.Second))
        }
        sched.HandleConnection(conn)
    }
}

func (sched *Sched) NotifyJobTimer() {
    sched.jobTimer.Reset(time.Millisecond)
}

func (sched *Sched) NotifyRevertTimer() {
    sched.revTimer.Reset(time.Millisecond)
}

func (sched *Sched) HandleConnection(conn net.Conn) {
    c := Conn{Conn: conn}
    payload, err := c.Receive()
    if err != nil {
        return
    }
    switch protocol.ClientType(payload[0]) {
    case protocol.TYPE_CLIENT:
        client := NewClient(sched, c)
        go client.Handle()
        break
    case protocol.TYPE_WORKER:
        worker := NewWorker(sched, c)
        go worker.Handle()
        break
    default:
        log.Printf("Unsupport client %d\n", payload[0])
        c.Close()
        break
    }
}

func (sched *Sched) Done(jobId int64) {
    defer sched.NotifyJobTimer()
    defer sched.JobLocker.Unlock()
    sched.JobLocker.Lock()
    removeListJob(sched.procQueue, jobId)
    job, err := sched.driver.Get(jobId)
    if err == nil {
        sched.driver.Delete(jobId)
        sched.DecrStatJob(job)
        sched.DecrStatProc(job)
        sched.removeRevertPQ(job)
    }
    return
}

func (sched *Sched) isDoJob(job driver.Job) bool {
    for e := sched.procQueue.Front(); e != nil; e = e.Next() {
        chk := e.Value.(driver.Job)
        if chk.Id == job.Id {
            return true
        }
    }
    return false
}

func (sched *Sched) SubmitJob(grabItem GrabItem, job driver.Job) bool {
    defer sched.JobLocker.Unlock()
    sched.JobLocker.Lock()
    if job.Name == "" {
        sched.driver.Delete(job.Id)
        return true
    }
    if sched.isDoJob(job) {
        return true
    }
    if !grabItem.w.alive {
        return false
    }
    if err := grabItem.w.HandleDo(grabItem.msgId, job); err != nil {
        grabItem.w.alive = false
        return false
    }
    now := time.Now()
    current := int64(now.Unix())
    job.Status = driver.JOB_STATUS_PROC
    job.RunAt = current
    sched.driver.Save(&job)
    sched.IncrStatProc(job)
    sched.pushRevertPQ(job)
    sched.NotifyRevertTimer()
    sched.procQueue.PushBack(job)
    sched.grabQueue.Remove(grabItem)
    return true
}

func (sched *Sched) lessItem() (lessItem *Item) {
    defer sched.PQLocker.Unlock()
    sched.PQLocker.Lock()
    maybeItem := make(map[string]*Item)
    for Func, stat := range sched.stats {
        if stat.Worker == 0 {
            continue
        }
        pq, ok := sched.jobPQ[Func]
        if !ok || pq.Len() == 0 {
            continue
        }

        item := heap.Pop(pq).(*Item)

        maybeItem[Func] = item

    }

    if len(maybeItem) == 0 {
        return nil
    }

    var lessFunc string

    for Func, item := range maybeItem {
        if lessItem == nil {
            lessItem = item
            lessFunc = Func
            continue
        }
        if lessItem.priority > item.priority {
            lessItem = item
            lessFunc = Func
        }
    }

    for Func, item := range maybeItem {
        if Func == lessFunc {
            continue
        }
        pq := sched.jobPQ[Func]
        heap.Push(pq, item)
    }
    return
}

func (sched *Sched) handleJobPQ() {
    var current time.Time
    var timestamp int64
    for {
        if !sched.alive {
            break
        }
        if sched.grabQueue.Len() == 0 {
            sched.jobTimer.Reset(time.Minute)
            current =<-sched.jobTimer.C
            continue
        }

        lessItem := sched.lessItem()

        if lessItem == nil {
            sched.jobTimer.Reset(time.Minute)
            current =<-sched.jobTimer.C
            continue
        }

        schedJob, err := sched.driver.Get(lessItem.value)

        if err != nil {
            log.Printf("Error: Get job: %d %v\n", lessItem.value, err)
            continue
        }

        timestamp = int64(time.Now().Unix())

        if schedJob.SchedAt > timestamp {
            sched.jobTimer.Reset(time.Second * time.Duration(schedJob.SchedAt - timestamp))
            current =<-sched.jobTimer.C
            timestamp = int64(current.Unix())
            if schedJob.SchedAt > timestamp {
                sched.pushJobPQ(schedJob)
                continue
            }
        }

        grabItem, err := sched.grabQueue.Get(schedJob.Func)
        if err == nil {
            if !sched.SubmitJob(grabItem, schedJob) {
                sched.pushJobPQ(schedJob)
            }
        } else {
            sched.pushJobPQ(schedJob)
        }
    }
}

func (sched *Sched) handleRevertPQ() {
    var current time.Time
    var timestamp int64
    for {
        if !sched.alive {
            break
        }
        if sched.revertPQ.Len() == 0 {
            sched.revTimer.Reset(time.Minute)
            current =<-sched.revTimer.C
            continue
        }

        sched.PQLocker.Lock()
        item := heap.Pop(&sched.revertPQ).(*Item)
        sched.PQLocker.Unlock()

        if item == nil {
            sched.revTimer.Reset(time.Minute)
            current =<-sched.revTimer.C
            continue
        }

        revertJob, err := sched.driver.Get(item.value)

        if err != nil {
            log.Printf("Error: Get job: %d %v\n", item.value, err)
            continue
        }

        timestamp = int64(time.Now().Unix())

        if item.priority > timestamp {
            sched.revTimer.Reset(time.Second * time.Duration(item.priority - timestamp))
            current =<-sched.revTimer.C
            timestamp = int64(current.Unix())
            if item.priority > timestamp {
                sched.pushRevertPQ(revertJob)
                continue
            }
        }

        sched.DecrStatProc(revertJob)
        revertJob.Status = driver.JOB_STATUS_READY
        sched.driver.Save(&revertJob)
        sched.pushJobPQ(revertJob)
        removeListJob(sched.procQueue, revertJob.Id)
    }
}

func (sched *Sched) Fail(jobId int64) {
    defer sched.NotifyJobTimer()
    defer sched.JobLocker.Unlock()
    sched.JobLocker.Lock()
    removeListJob(sched.procQueue, jobId)
    job, _ := sched.driver.Get(jobId)
    sched.DecrStatProc(job)
    sched.removeRevertPQ(job)
    job.Status = driver.JOB_STATUS_READY
    sched.driver.Save(&job)
    sched.pushJobPQ(job)
    return
}

func (sched *Sched) getFuncStat(Func string) *FuncStat {
    defer sched.FuncLocker.Unlock()
    sched.FuncLocker.Lock()
    stat, ok := sched.stats[Func]
    if !ok {
        stat = new(FuncStat)
        sched.stats[Func] = stat
    }
    return stat
}

func (sched *Sched) IncrStatFunc(Func string) {
    stat := sched.getFuncStat(Func)
    stat.Worker.Incr()
}

func (sched *Sched) DecrStatFunc(Func string) {
    stat := sched.getFuncStat(Func)
    stat.Worker.Decr()
}

func (sched *Sched) IncrStatJob(job driver.Job) {
    stat := sched.getFuncStat(job.Func)
    stat.Job.Incr()
}

func (sched *Sched) DecrStatJob(job driver.Job) {
    stat := sched.getFuncStat(job.Func)
    stat.Job.Decr()
}

func (sched *Sched) IncrStatProc(job driver.Job) {
    stat := sched.getFuncStat(job.Func)
    if job.Status == driver.JOB_STATUS_PROC {
        stat.Processing.Incr()
    }
}

func (sched *Sched) DecrStatProc(job driver.Job) {
    stat := sched.getFuncStat(job.Func)
    if job.Status == driver.JOB_STATUS_PROC {
        stat.Processing.Decr()
    }
}

func (sched *Sched) SchedLater(jobId int64, delay int64) {
    defer sched.NotifyJobTimer()
    defer sched.JobLocker.Unlock()
    sched.JobLocker.Lock()
    removeListJob(sched.procQueue, jobId)
    job, _ := sched.driver.Get(jobId)
    sched.DecrStatProc(job)
    sched.removeRevertPQ(job)
    job.Status = driver.JOB_STATUS_READY
    var now = time.Now()
    job.SchedAt = int64(now.Unix()) + delay
    sched.driver.Save(&job)
    sched.pushJobPQ(job)
    return
}

func (sched *Sched) pushJobPQ(job driver.Job) bool {
    defer sched.PQLocker.Unlock()
    sched.PQLocker.Lock()
    if job.Status == driver.JOB_STATUS_READY {
        pq, ok := sched.jobPQ[job.Func]
        if !ok {
            pq1 := make(PriorityQueue, 0)
            pq = &pq1
            sched.jobPQ[job.Func] = pq
            heap.Init(pq)
        }
        item := &Item{
            value: job.Id,
            priority: job.SchedAt,
        }
        heap.Push(pq, item)
        return true
    }
    return false
}

func (sched *Sched) pushRevertPQ(job driver.Job) {
    defer sched.PQLocker.Unlock()
    sched.PQLocker.Lock()
    if job.Status == driver.JOB_STATUS_PROC && job.Timeout > 0 {
        runAt := job.RunAt
        if runAt == 0 {
            runAt = job.SchedAt
        }
        item := &Item{
            value: job.Id,
            priority: runAt + job.Timeout,
        }
        heap.Push(&sched.revertPQ, item)
    }
}

func (sched *Sched) removeRevertPQ(job driver.Job) {
    defer sched.PQLocker.Unlock()
    sched.PQLocker.Lock()
    if job.Status == driver.JOB_STATUS_PROC && job.Timeout > 0 {
        for _, item := range sched.revertPQ {
            if item.value == job.Id {
                heap.Remove(&sched.revertPQ, item.index)
                break
            }
        }
    }
}

func (sched *Sched) loadJobQueue() {
    updateQueue := make([]driver.Job, 0)
    removeQueue := make([]driver.Job, 0)
    var now = time.Now()
    current := int64(now.Unix())

    iter := sched.driver.NewIterator(nil)
    for {
        if !iter.Next() {
            break
        }
        job := iter.Value()
        if job.Name == "" {
            removeQueue = append(removeQueue, job)
            continue
        }
        sched.IncrStatJob(job)
        sched.pushJobPQ(job)
        runAt := job.RunAt
        if runAt < job.SchedAt {
            runAt = job.SchedAt
        }
        if runAt + job.Timeout < current {
            updateQueue = append(updateQueue, job)
        } else {
            sched.procQueue.PushBack(job)
            sched.IncrStatProc(job)
            sched.pushRevertPQ(job)
        }
    }

    iter.Close()

    for _, job := range updateQueue {
        job.Status = driver.JOB_STATUS_READY
        sched.driver.Save(&job)
    }

    for _, job := range removeQueue {
        sched.driver.Delete(job.Id)
    }
}

func (sched *Sched) Close() {
    sched.alive = false
    sched.driver.Close()
    log.Printf("Periodic task system shutdown\n")
}
<commit_msg>NotifyRevertTimer when job status change<commit_after>package periodic

import (
    "log"
    "net"
    "time"
    "sync"
    "strings"
    "container/list"
    "container/heap"
    "github.com/Lupino/periodic/driver"
    "github.com/Lupino/periodic/protocol"
)

type Sched struct {
    jobTimer   *time.Timer
    grabQueue  *GrabQueue
    procQueue  *list.List
    revertPQ   PriorityQueue
    revTimer   *time.Timer
    entryPoint string
    JobLocker  *sync.Mutex
    stats      map[string]*FuncStat
    FuncLocker *sync.Mutex
    driver     driver.StoreDriver
    jobPQ      map[string]*PriorityQueue
    PQLocker   *sync.Mutex
    timeout    time.Duration
    alive      bool
}

func NewSched(entryPoint string, driver driver.StoreDriver, timeout time.Duration) *Sched {
    sched := new(Sched)
    sched.jobTimer = time.NewTimer(1 * time.Hour)
    sched.revTimer = time.NewTimer(1 * time.Hour)
    sched.grabQueue = NewGrabQueue()
    sched.procQueue = list.New()
    sched.revertPQ = make(PriorityQueue, 0)
    heap.Init(&sched.revertPQ)
    sched.entryPoint = entryPoint
    sched.JobLocker = new(sync.Mutex)
    sched.PQLocker = new(sync.Mutex)
    sched.FuncLocker = new(sync.Mutex)
    sched.stats = make(map[string]*FuncStat)
    sched.driver = driver
    sched.jobPQ = make(map[string]*PriorityQueue)
    sched.timeout = timeout
    sched.alive = true
    return sched
}

func (sched *Sched) Serve() {
    parts := strings.SplitN(sched.entryPoint, "://", 2)
    if parts[0] == "unix" {
        sockCheck(parts[1])
    }
    sched.loadJobQueue()
    go sched.handleJobPQ()
    go sched.handleRevertPQ()
    listen, err := net.Listen(parts[0], parts[1])
    if err != nil {
        log.Fatal(err)
    }
    defer listen.Close()
    log.Printf("Periodic task system started on %s\n", sched.entryPoint)
    for {
        if !sched.alive {
            break
        }
        conn, err := listen.Accept()
        if err != nil {
            log.Fatal(err)
        }
        if sched.timeout > 0 {
            conn.SetDeadline(time.Now().Add(sched.timeout * time.Second))
        }
        sched.HandleConnection(conn)
    }
}

func (sched *Sched) NotifyJobTimer() {
    sched.jobTimer.Reset(time.Millisecond)
}

func (sched *Sched) NotifyRevertTimer() {
    sched.revTimer.Reset(time.Millisecond)
}

func (sched *Sched) HandleConnection(conn net.Conn) {
    c := Conn{Conn: conn}
    payload, err := c.Receive()
    if err != nil {
        return
    }
    switch protocol.ClientType(payload[0]) {
    case protocol.TYPE_CLIENT:
        client := NewClient(sched, c)
        go client.Handle()
        break
    case protocol.TYPE_WORKER:
        worker := NewWorker(sched, c)
        go worker.Handle()
        break
    default:
        log.Printf("Unsupport client %d\n", payload[0])
        c.Close()
        break
    }
}

func (sched *Sched) Done(jobId int64) {
    defer sched.NotifyJobTimer()
    defer sched.NotifyRevertTimer()
    defer sched.JobLocker.Unlock()
    sched.JobLocker.Lock()
    removeListJob(sched.procQueue, jobId)
    job, err := sched.driver.Get(jobId)
    if err == nil {
        sched.driver.Delete(jobId)
        sched.DecrStatJob(job)
        sched.DecrStatProc(job)
        sched.removeRevertPQ(job)
    }
    return
}

func (sched *Sched) isDoJob(job driver.Job) bool {
    for e := sched.procQueue.Front(); e != nil; e = e.Next() {
        chk := e.Value.(driver.Job)
        if chk.Id == job.Id {
            return true
        }
    }
    return false
}

func (sched *Sched) SubmitJob(grabItem GrabItem, job driver.Job) bool {
    defer sched.JobLocker.Unlock()
    sched.JobLocker.Lock()
    if job.Name == "" {
        sched.driver.Delete(job.Id)
        return true
    }
    if sched.isDoJob(job) {
        return true
    }
    if !grabItem.w.alive {
        return false
    }
    if err := grabItem.w.HandleDo(grabItem.msgId, job); err != nil {
        grabItem.w.alive = false
        return false
    }
    now := time.Now()
    current := int64(now.Unix())
    job.Status = driver.JOB_STATUS_PROC
    job.RunAt = current
    sched.driver.Save(&job)
    sched.IncrStatProc(job)
    sched.pushRevertPQ(job)
    sched.NotifyRevertTimer()
    sched.procQueue.PushBack(job)
    sched.grabQueue.Remove(grabItem)
    return true
}

func (sched *Sched) lessItem() (lessItem *Item) {
    defer sched.PQLocker.Unlock()
    sched.PQLocker.Lock()
    maybeItem := make(map[string]*Item)
    for Func, stat := range sched.stats {
        if stat.Worker == 0 {
            continue
        }
        pq, ok := sched.jobPQ[Func]
        if !ok || pq.Len() == 0 {
            continue
        }

        item := heap.Pop(pq).(*Item)

        maybeItem[Func] = item

    }

    if len(maybeItem) == 0 {
        return nil
    }

    var lessFunc string

    for Func, item := range maybeItem {
        if lessItem == nil {
            lessItem = item
            lessFunc = Func
            continue
        }
        if lessItem.priority > item.priority {
            lessItem = item
            lessFunc = Func
        }
    }

    for Func, item := range maybeItem {
        if Func == lessFunc {
            continue
        }
        pq := sched.jobPQ[Func]
        heap.Push(pq, item)
    }
    return
}

func (sched *Sched) handleJobPQ() {
    var current time.Time
    var timestamp int64
    for {
        if !sched.alive {
            break
        }
        if sched.grabQueue.Len() == 0 {
            sched.jobTimer.Reset(time.Minute)
            current =<-sched.jobTimer.C
            continue
        }

        lessItem := sched.lessItem()

        if lessItem == nil {
            sched.jobTimer.Reset(time.Minute)
            current =<-sched.jobTimer.C
            continue
        }

        schedJob, err := sched.driver.Get(lessItem.value)

        if err != nil {
            log.Printf("Error: Get job: %d %v\n", lessItem.value, err)
            continue
        }

        timestamp = int64(time.Now().Unix())

        if schedJob.SchedAt > timestamp {
            sched.jobTimer.Reset(time.Second * time.Duration(schedJob.SchedAt - timestamp))
            current =<-sched.jobTimer.C
            timestamp = int64(current.Unix())
            if schedJob.SchedAt > timestamp {
                sched.pushJobPQ(schedJob)
                continue
            }
        }

        grabItem, err := sched.grabQueue.Get(schedJob.Func)
        if err == nil {
            if !sched.SubmitJob(grabItem, schedJob) {
                sched.pushJobPQ(schedJob)
            }
        } else {
            sched.pushJobPQ(schedJob)
        }
    }
}

func (sched *Sched) handleRevertPQ() {
    var current time.Time
    var timestamp int64
    for {
        if !sched.alive {
            break
        }
        if sched.revertPQ.Len() == 0 {
            sched.revTimer.Reset(time.Minute)
            current =<-sched.revTimer.C
            continue
        }

        sched.PQLocker.Lock()
        item := heap.Pop(&sched.revertPQ).(*Item)
        sched.PQLocker.Unlock()

        if item == nil {
            sched.revTimer.Reset(time.Minute)
            current =<-sched.revTimer.C
            continue
        }

        revertJob, err := sched.driver.Get(item.value)

        if err != nil {
            log.Printf("Error: Get job: %d %v\n", item.value, err)
            continue
        }

        timestamp = int64(time.Now().Unix())

        if item.priority > timestamp {
            sched.revTimer.Reset(time.Second * time.Duration(item.priority - timestamp))
            current =<-sched.revTimer.C
            timestamp = int64(current.Unix())
            if item.priority > timestamp {
                sched.pushRevertPQ(revertJob)
                continue
            }
        }

        sched.DecrStatProc(revertJob)
        revertJob.Status = driver.JOB_STATUS_READY
        sched.driver.Save(&revertJob)
        sched.pushJobPQ(revertJob)
        removeListJob(sched.procQueue, revertJob.Id)
    }
}

func (sched *Sched) Fail(jobId int64) {
    defer sched.NotifyJobTimer()
    defer sched.NotifyRevertTimer()
    defer sched.JobLocker.Unlock()
    sched.JobLocker.Lock()
    removeListJob(sched.procQueue, jobId)
    job, _ := sched.driver.Get(jobId)
    sched.DecrStatProc(job)
    sched.removeRevertPQ(job)
    job.Status = driver.JOB_STATUS_READY
    sched.driver.Save(&job)
    sched.pushJobPQ(job)
    return
}

func (sched *Sched) getFuncStat(Func string) *FuncStat {
    defer sched.FuncLocker.Unlock()
    sched.FuncLocker.Lock()
    stat, ok := sched.stats[Func]
    if !ok {
        stat = new(FuncStat)
        sched.stats[Func] = stat
    }
    return stat
}

func (sched *Sched) IncrStatFunc(Func string) {
    stat := sched.getFuncStat(Func)
    stat.Worker.Incr()
}

func (sched *Sched) DecrStatFunc(Func string) {
    stat := sched.getFuncStat(Func)
    stat.Worker.Decr()
}

func (sched *Sched) IncrStatJob(job driver.Job) {
    stat := sched.getFuncStat(job.Func)
    stat.Job.Incr()
}

func (sched *Sched) DecrStatJob(job driver.Job) {
    stat := sched.getFuncStat(job.Func)
    stat.Job.Decr()
}

func (sched *Sched) IncrStatProc(job driver.Job) {
    stat := sched.getFuncStat(job.Func)
    if job.Status == driver.JOB_STATUS_PROC {
        stat.Processing.Incr()
    }
}

func (sched *Sched) DecrStatProc(job driver.Job) {
    stat := sched.getFuncStat(job.Func)
    if job.Status == driver.JOB_STATUS_PROC {
        stat.Processing.Decr()
    }
}

func (sched *Sched) SchedLater(jobId int64, delay int64) {
    defer sched.NotifyJobTimer()
    defer sched.NotifyRevertTimer()
    defer sched.JobLocker.Unlock()
    sched.JobLocker.Lock()
    removeListJob(sched.procQueue, jobId)
    job, _ := sched.driver.Get(jobId)
    sched.DecrStatProc(job)
    sched.removeRevertPQ(job)
    job.Status = driver.JOB_STATUS_READY
    var now = time.Now()
    job.SchedAt = int64(now.Unix()) + delay
    sched.driver.Save(&job)
    sched.pushJobPQ(job)
    return
}

func (sched *Sched) pushJobPQ(job driver.Job) bool {
    defer sched.PQLocker.Unlock()
    sched.PQLocker.Lock()
    if job.Status == driver.JOB_STATUS_READY {
        pq, ok := sched.jobPQ[job.Func]
        if !ok {
            pq1 := make(PriorityQueue, 0)
            pq = &pq1
            sched.jobPQ[job.Func] = pq
            heap.Init(pq)
        }
        item := &Item{
            value: job.Id,
            priority: job.SchedAt,
        }
        heap.Push(pq, item)
        return true
    }
    return false
}

func (sched *Sched) pushRevertPQ(job driver.Job) {
    defer sched.PQLocker.Unlock()
    sched.PQLocker.Lock()
    if job.Status == driver.JOB_STATUS_PROC && job.Timeout > 0 {
        runAt := job.RunAt
        if runAt == 0 {
            runAt = job.SchedAt
        }
        item := &Item{
            value: job.Id,
            priority: runAt + job.Timeout,
        }
        heap.Push(&sched.revertPQ, item)
    }
}

func (sched *Sched) removeRevertPQ(job driver.Job) {
    defer sched.PQLocker.Unlock()
    sched.PQLocker.Lock()
    if job.Status == driver.JOB_STATUS_PROC && job.Timeout > 0 {
        for _, item := range sched.revertPQ {
            if item.value == job.Id {
                heap.Remove(&sched.revertPQ, item.index)
                break
            }
        }
    }
}

func (sched *Sched) loadJobQueue() {
    updateQueue := make([]driver.Job, 0)
    removeQueue := make([]driver.Job, 0)
    var now = time.Now()
    current := int64(now.Unix())

    iter := sched.driver.NewIterator(nil)
    for {
        if !iter.Next() {
            break
        }
        job := iter.Value()
        if job.Name == "" {
            removeQueue = append(removeQueue, job)
            continue
        }
        sched.IncrStatJob(job)
        sched.pushJobPQ(job)
        runAt := job.RunAt
        if runAt < job.SchedAt {
            runAt = job.SchedAt
        }
        if runAt + job.Timeout < current {
            updateQueue = append(updateQueue, job)
        } else {
            sched.procQueue.PushBack(job)
            sched.IncrStatProc(job)
            sched.pushRevertPQ(job)
        }
    }

    iter.Close()

    for _, job := range updateQueue {
        job.Status = driver.JOB_STATUS_READY
        sched.driver.Save(&job)
    }

    for _, job := range removeQueue {
        sched.driver.Delete(job.Id)
    }
}

func (sched *Sched) Close() {
    sched.alive = false
    sched.driver.Close()
    log.Printf("Periodic task system shutdown\n")
}
<|endoftext|>


<commit_before>package main

import (
    //"bytes"
    "encoding/json"
    "errors"
    "flag"
    "fmt"
    //"golang.org/x/crypto/ssh"
    "log"
    "os"
    "os/signal"
    "strings"
    "syscall"
    "sync"
    //"time"

    gc "github.com/rthornton128/goncurses"
)

const _VERSION string = "0.1.0"
var _stdscr *gc.Window = nil

func sigHandler(ch *chan os.Signal) {
    sig := <-*ch
    gc.End()
    fmt.Println("Captured sig", sig)
    os.Exit(3)
}

func main() {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs,
                  syscall.SIGHUP,
                  syscall.SIGINT,
                  syscall.SIGQUIT,
                  syscall.SIGABRT,
                  syscall.SIGKILL,
                  syscall.SIGSEGV,
                  syscall.SIGTERM,
                  syscall.SIGSTOP)
    go sigHandler(&sigs)

    initLog()
    initGui()
    order := loadOrder()

    arr, err := parseSituation(&order.Situation)
    if (err != nil ) {
        fmt.Println(err)
        os.Exit(1)
    }

    tasks, err2 := parseExecution(&order.Execution)
    if (err2 != nil ) {
        fmt.Println(err2)
        os.Exit(1)
    }

    targets := make([]target, len(arr))
    channels := make([]chan string, len(arr))
    var wg sync.WaitGroup
    wg.Add(len(targets))
    for i := range arr {
        channels[i] = make(chan string, 10)
        if arr[i].Target.Prot == "SSH" {
            test := new(TargetSsh)
            target.New(test, arr[i], tasks)
            test.impl.ch = &channels[i]
            test.impl.wait = &wg
            targets[i] = test
        } else {
            test := new(TargetExec)
            target.New(test, arr[i], tasks)
            test.impl.ch = &channels[i]
            test.impl.wait = &wg
            targets[i] = test
        }
        go target.Find(targets[i])
    }
    wg.Wait()
    wg.Add(len(targets))
    for i := range targets {
        go target.Watch(targets[i])
    }
    ReportThread(targets)
    wg.Wait()
}

func initLog() {
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
}

func initGui() {
    var err error
    _stdscr, err = gc.Init()
    if err != nil {
        gc.End()
        log.Fatal(err)
    }
    defer gc.End()

    gc.Echo(false)
    gc.CBreak(true)
    gc.Cursor(0)
    gc.StartColor()

    gc.InitPair(gc.C_WHITE, gc.C_WHITE, gc.C_BLACK)
    gc.InitPair(gc.C_YELLOW, gc.C_YELLOW, gc.C_BLACK)
    gc.InitPair(gc.C_RED, gc.C_RED, gc.C_BLACK)
    gc.InitPair(gc.C_CYAN, gc.C_CYAN, gc.C_BLACK)
}

func getPrettyJson(v interface{}) string {
    buffer, err := json.MarshalIndent(v, "", "    ")
    if (err != nil) {

    }
    return string(buffer)
}

func loadOrder() Order {
    option := flag.String("order", "order.json", "file containing scouting operations order")
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "version %s\n", _VERSION)
        fmt.Fprintln(os.Stderr, "usage:")
        flag.PrintDefaults()
    }
    flag.Parse()

    file, _ := os.Open(*option)
    decoder := json.NewDecoder(file)
    order := Order{}
    err := decoder.Decode(&order)
    if err != nil {
        fmt.Println("error: ", err)
    } else {
        //log.Println(getPrettyJson(order))
    }
    //log.Println("Targets:", len(order.Situation.Targets))
    //log.Println("Tasks  :", len(order.Execution.Tasks))
    return order
}

func parseSituation(situation *Situation) (TargetArr, error) {
    size := len(situation.Targets)
    ret := make(TargetArr, size)
    var err error = nil
    definitions := situation.Definitions
    credentials := situation.Credentials

    for i, id := range situation.Targets {
        var exists bool
        var target Target
        if target, exists = definitions[id]; exists {
            ret[i].Target = target
        } else {
            err = errors.New("Target '" + id + "' is not found in definitions")
            break
        }

        var cred Credentials
        if cred, exists = credentials[target.Cred]; exists {
            ret[i].Credentials = cred
        } else {
            err = errors.New("Target '" + id + "' credentials '" + target.Cred + "' not found")
            break
        }
    }

    if size == 0 && err == nil {
        err = errors.New("No targets found")
    }

    return ret, err
}

func parseExecution(execution *Execution1) (TaskArr, error) {
    size := len(execution.Tasks)
    ret := make(TaskArr, size)
    var err error = nil
    tasks := execution.Tasks
    definitions := execution.Definitions

    var i int = 0
    for _, task := range tasks {
        var def Task
        var exists bool
        if def, exists = definitions[task.Task]; exists {
            if len(task.Vars) == len(def.Vars) {
                cmd := def.Task
                for j, param := range task.Vars {
                    cmd = strings.Replace(cmd, def.Vars[j], param, 1)
                }
                ret[i].Exec = task
                ret[i].Sys = def.Sys
                ret[i].Cmd = cmd
                ret[i].Ret = def.Type
                ret[i].Scale = task.Scale
                ret[i].Units = task.Units
            } else {
                err = errors.New("Task '" + task.Task + "' vars do not match definitions")
                break
            }
        } else {
            err = errors.New("Task '" + task.Task + "' is not found in definitions")
            break
        }

        i++
    }

    if size == 0 && err == nil {
        err = errors.New("No tasks found")
    } else {
        i = 0
        for _, task := range ret {
            if task.Exec.Active {
                ret[i] = task
                i++
            }
        }
        if i == 0 {
            err = errors.New("No active tasks found")
        } else {
            ret = ret[:i]
        }
    }

    return ret, err
}

//func findTargets(arr *TargetArr) []*ssh.Client {
//    size := len(*arr)
//    clients := make([]*ssh.Client, size)
//    targets := make([]*TargetSsh, size)
//    var wg sync.WaitGroup
//    wg.Add(size)
//
//    for i := range *arr {
//        targets[i] = new(TargetSsh)
//        targets[i].impl.conf = (*arr)[i]
//        targets[i].impl.wait = &wg
//        go target.Find(targets[i])
//    }
//
//    wg.Wait()
//    for i := range targets {
//        clients[i] = targets[i].client
//        //select {
//       //     /*case*/ clients[i] = <-channels[i]/*:*/
//        //    default:
//        //}
//    }
//
//    return clients
//}

//func watchTargets(targets []*ssh.Client, tasks *TaskArr, reports *[]database) {
//    var wg sync.WaitGroup
//    channels := make([]chan string, len(targets))
//
//    wg.Add(len(targets))
//    for i, target := range targets {
//        channels[i] = make(chan string, 10)
//        go observeTarget(target, (*tasks)[0].Cmd, &wg, &channels[i])
//    }
//    wg.Wait()
//
//    for i := range channels {
//        value := <-channels[i]
//        timeval := uint64(time.Now().UnixNano()) / uint64(time.Millisecond)
//
//        dp, _ := NewDataPoint(timeval, value)
//        Evaluate(&dp, &(*reports)[i])
//        //switch (*tasks)[0].Ret {
//        //    case "bool":
//        //    case "float64":
//        //    case "int64":
//        //    case "uint64":
//        //    default:
//        //        _stdscr.MovePrintf(10, 0, "Unknown return type: %s, %s", (*tasks)[0].Ret, value)
//        //        _stdscr.Refresh()
//        //}
//    }
//    reportTargets(reports)
//}

//func observeTarget(client *ssh.Client, cmd string, wg *sync.WaitGroup, ch *chan string) {
//    defer wg.Done()
//    session, err := client.NewSession()
//    if err != nil {
//        log.Fatal("Failed to create session: ", err)
//    }
//    defer session.Close()
//
//    var b bytes.Buffer
//    session.Stdout = &b
//    if err := session.Run(cmd); err != nil {
//        log.Fatal("Failed to run: " + err.Error())
//    }
//
//    val := strings.Trim(b.String(), " \n")
//    select {
//        case *ch <- val:
//        default:
//    }
//}

func reportTargets(reports *[]database) {
    var maxTicks int = 20
    var yScale uint64 = 50
    var total uint64 = 0
    _stdscr.Move(0,0)

    for i := range *reports {
        x := 0
        _stdscr.MovePrintf(i, x, "%-15s", (*reports)[i].target)

        x += 15
        _stdscr.MovePrintln(i, x, "[")

        x += 1
        ticks := uint64((*reports)[i].rate) / yScale

        for j := 0; j < maxTicks; j++ {
            if (j * 100 / maxTicks >= 66) {
                _stdscr.ColorOn(gc.C_RED)
            } else if (j * 100 / maxTicks >= 33) {
                _stdscr.ColorOn(gc.C_YELLOW)
            } else {
                _stdscr.ColorOn(gc.C_CYAN)
            }

            if int(ticks) > j {
                _stdscr.MovePrintln(i, x, "|")
            } else {
                _stdscr.MovePrintln(i, x, " ")
            }

            x++
        }

        _stdscr.ColorOff(gc.C_RED)
        _stdscr.ColorOff(gc.C_YELLOW)
        _stdscr.ColorOff(gc.C_CYAN)
        _stdscr.MovePrintf(i, x, "%6d %s] %s\n", uint64((*reports)[i].rate), (*reports)[i].units, (*reports)[i].task)

        total += uint64((*reports)[i].rate)
    }

    _stdscr.MovePrintf(len(*reports), 0,  "%-15s", "Total")
    _stdscr.MovePrintf(len(*reports), 36, "%6d Mbps\n", total)
    _stdscr.Refresh()
}
<commit_msg>Code cleanup<commit_after>package main

import (
    "encoding/json"
    "errors"
    "flag"
    "fmt"
    "log"
    "os"
    "os/signal"
    "strings"
    "syscall"
    "sync"

    gc "github.com/rthornton128/goncurses"
)

const _VERSION string = "0.1.0"
var _stdscr *gc.Window = nil

func sigHandler(ch *chan os.Signal) {
    sig := <-*ch
    gc.End()
    fmt.Println("Captured sig", sig)
    os.Exit(3)
}

func main() {
    sigs := make(chan os.Signal, 1)
    signal.Notify(sigs,
                  syscall.SIGHUP,
                  syscall.SIGINT,
                  syscall.SIGQUIT,
                  syscall.SIGABRT,
                  syscall.SIGKILL,
                  syscall.SIGSEGV,
                  syscall.SIGTERM,
                  syscall.SIGSTOP)
    go sigHandler(&sigs)

    initLog()
    initGui()
    order := loadOrder()

    arr, err := parseSituation(&order.Situation)
    if (err != nil ) {
        fmt.Println(err)
        os.Exit(1)
    }

    tasks, err2 := parseExecution(&order.Execution)
    if (err2 != nil ) {
        fmt.Println(err2)
        os.Exit(1)
    }

    targets := make([]target, len(arr))
    channels := make([]chan string, len(arr))
    var wg sync.WaitGroup
    for i := range arr {
        channels[i] = make(chan string, 10)
        if arr[i].Target.Prot == "SSH" {
            test := new(TargetSsh)
            target.New(test, arr[i], tasks)
            test.impl.ch = &channels[i]
            test.impl.wait = &wg
            targets[i] = test
        } else {
            test := new(TargetExec)
            target.New(test, arr[i], tasks)
            test.impl.ch = &channels[i]
            test.impl.wait = &wg
            targets[i] = test
        }
    }
    wg.Add(len(targets))
    for i := range targets {
        go target.Watch(targets[i])
    }
    ReportThread(targets)
    wg.Wait()
}

func initLog() {
    log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
}

func initGui() {
    var err error
    _stdscr, err = gc.Init()
    if err != nil {
        gc.End()
        log.Fatal(err)
    }
    defer gc.End()

    gc.Echo(false)
    gc.CBreak(true)
    gc.Cursor(0)
    gc.StartColor()

    gc.InitPair(gc.C_WHITE, gc.C_WHITE, gc.C_BLACK)
    gc.InitPair(gc.C_YELLOW, gc.C_YELLOW, gc.C_BLACK)
    gc.InitPair(gc.C_RED, gc.C_RED, gc.C_BLACK)
    gc.InitPair(gc.C_CYAN, gc.C_CYAN, gc.C_BLACK)
}

func getPrettyJson(v interface{}) string {
    buffer, err := json.MarshalIndent(v, "", "    ")
    if (err != nil) {

    }
    return string(buffer)
}

func loadOrder() Order {
    option := flag.String("order", "order.json", "file containing scouting operations order")
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "version %s\n", _VERSION)
        fmt.Fprintln(os.Stderr, "usage:")
        flag.PrintDefaults()
    }
    flag.Parse()

    file, _ := os.Open(*option)
    decoder := json.NewDecoder(file)
    order := Order{}
    err := decoder.Decode(&order)
    if err != nil {
        fmt.Println("error: ", err)
    } else {
        //log.Println(getPrettyJson(order))
    }
    //log.Println("Targets:", len(order.Situation.Targets))
    //log.Println("Tasks  :", len(order.Execution.Tasks))
    return order
}

func parseSituation(situation *Situation) (TargetArr, error) {
    size := len(situation.Targets)
    ret := make(TargetArr, size)
    var err error = nil
    definitions := situation.Definitions
    credentials := situation.Credentials

    for i, id := range situation.Targets {
        var exists bool
        var target Target
        if target, exists = definitions[id]; exists {
            ret[i].Target = target
        } else {
            err = errors.New("Target '" + id + "' is not found in definitions")
            break
        }

        var cred Credentials
        if cred, exists = credentials[target.Cred]; exists {
            ret[i].Credentials = cred
        } else {
            err = errors.New("Target '" + id + "' credentials '" + target.Cred + "' not found")
            break
        }
    }

    if size == 0 && err == nil {
        err = errors.New("No targets found")
    }

    return ret, err
}

func parseExecution(execution *Execution1) (TaskArr, error) {
    size := len(execution.Tasks)
    ret := make(TaskArr, size)
    var err error = nil
    tasks := execution.Tasks
    definitions := execution.Definitions

    var i int = 0
    for _, task := range tasks {
        var def Task
        var exists bool
        if def, exists = definitions[task.Task]; exists {
            if len(task.Vars) == len(def.Vars) {
                cmd := def.Task
                for j, param := range task.Vars {
                    cmd = strings.Replace(cmd, def.Vars[j], param, 1)
                }
                ret[i].Exec = task
                ret[i].Sys = def.Sys
                ret[i].Cmd = cmd
                ret[i].Ret = def.Type
                ret[i].Scale = task.Scale
                ret[i].Units = task.Units
            } else {
                err = errors.New("Task '" + task.Task + "' vars do not match definitions")
                break
            }
        } else {
            err = errors.New("Task '" + task.Task + "' is not found in definitions")
            break
        }

        i++
    }

    if size == 0 && err == nil {
        err = errors.New("No tasks found")
    } else {
        i = 0
        for _, task := range ret {
            if task.Exec.Active {
                ret[i] = task
                i++
            }
        }
        if i == 0 {
            err = errors.New("No active tasks found")
        } else {
            ret = ret[:i]
        }
    }

    return ret, err
}

func reportTargets(reports *[]database) {
    var maxTicks int = 20
    var yScale uint64 = 50
    var total uint64 = 0
    _stdscr.Move(0,0)

    for i := range *reports {
        x := 0
        _stdscr.MovePrintf(i, x, "%-15s", (*reports)[i].target)

        x += 15
        _stdscr.MovePrintln(i, x, "[")

        x += 1
        ticks := uint64((*reports)[i].rate) / yScale

        for j := 0; j < maxTicks; j++ {
            if (j * 100 / maxTicks >= 66) {
                _stdscr.ColorOn(gc.C_RED)
            } else if (j * 100 / maxTicks >= 33) {
                _stdscr.ColorOn(gc.C_YELLOW)
            } else {
                _stdscr.ColorOn(gc.C_CYAN)
            }

            if int(ticks) > j {
                _stdscr.MovePrintln(i, x, "|")
            } else {
                _stdscr.MovePrintln(i, x, " ")
            }

            x++
        }

        _stdscr.ColorOff(gc.C_RED)
        _stdscr.ColorOff(gc.C_YELLOW)
        _stdscr.ColorOff(gc.C_CYAN)
        _stdscr.MovePrintf(i, x, "%6d %s] %s\n", uint64((*reports)[i].rate), (*reports)[i].units, (*reports)[i].task)

        total += uint64((*reports)[i].rate)
    }

    _stdscr.MovePrintf(len(*reports), 0,  "%-15s", "Total")
    _stdscr.MovePrintf(len(*reports), 36, "%6d Mbps\n", total)
    _stdscr.Refresh()
}
<|endoftext|>


<commit_before>// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"log"
	"os"

	"github.com/google/go-cmp/cmp"
	"golang.org/x/vuln/vulncheck"
)

const usage = `test helper for examining the output of running govulncheck on k8s@v1.15.11.

Example usage: ./k8s [path to output file]
`

func main() {
	if len(os.Args) != 2 {
		log.Fatal("Incorrect number of expected command line arguments", usage)
	}
	out := os.Args[1]

	outJson, err := os.ReadFile(out)
	if err != nil {
		log.Fatal("Failed to read:", out)
	}

	var r vulncheck.Result
	if err := json.Unmarshal(outJson, &r); err != nil {
		log.Fatal("Failed to load json into vulncheck.Result:", err)
	}

	if len(r.Vulns) != 44 {
		log.Fatalf("want 44 vulns; got %d", len(r.Vulns))
	}

	type vuln struct {
		pkg    string
		symbol string
	}
	calledVulns := make(map[vuln]bool)
	for _, v := range r.Vulns {
		calledVulns[vuln{v.PkgPath, v.Symbol}] = true
	}

	want := map[vuln]bool{
		{"github.com/containernetworking/cni/pkg/invoke", "FindInPath"}:               true,
		{"github.com/evanphx/json-patch", "partialArray.add"}:                         true,
		{"github.com/opencontainers/selinux/go-selinux", "CurrentLabel"}:              true,
		{"github.com/opencontainers/selinux/go-selinux", "FileLabel"}:                 true,
		{"github.com/opencontainers/selinux/go-selinux", "GetEnabled"}:                true,
		{"github.com/opencontainers/selinux/go-selinux", "SetFileLabel"}:              true,
		{"github.com/opencontainers/selinux/go-selinux", "getSelinuxMountPoint"}:      true,
		{"github.com/opencontainers/selinux/go-selinux", "lgetxattr"}:                 true,
		{"github.com/opencontainers/selinux/go-selinux", "lsetxattr"}:                 true,
		{"github.com/opencontainers/selinux/go-selinux", "readCon"}:                   true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getEnabled"}:   true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getSELinuxfs"}: true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setEnable"}:    true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setSELinuxfs"}: true,
		{"golang.org/x/crypto/cryptobyte", "Builder.AddBytes"}:                        true,
		{"golang.org/x/crypto/cryptobyte", "Builder.AddUint16LengthPrefixed"}:         true,
		{"golang.org/x/crypto/cryptobyte", "Builder.Bytes"}:                           true,
		{"golang.org/x/crypto/cryptobyte", "Builder.add"}:                             true,
		{"golang.org/x/crypto/cryptobyte", "Builder.addLengthPrefixed"}:               true,
		{"golang.org/x/crypto/cryptobyte", "Builder.callContinuation"}:                true,
		{"golang.org/x/crypto/cryptobyte", "Builder.flushChild"}:                      true,
		{"golang.org/x/crypto/cryptobyte", "NewBuilder"}:                              true,
		{"golang.org/x/crypto/cryptobyte", "String.Empty"}:                            true,
		{"golang.org/x/crypto/cryptobyte", "String.PeekASN1Tag"}:                      true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadASN1"}:                         true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadAnyASN1"}:                      true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadBytes"}:                        true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadOptionalASN1"}:                 true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadUint16LengthPrefixed"}:         true,
		{"golang.org/x/crypto/cryptobyte", "String.Skip"}:                             true,
		{"golang.org/x/crypto/cryptobyte", "String.read"}:                             true,
		{"golang.org/x/crypto/cryptobyte", "String.readASN1"}:                         true,
		{"golang.org/x/crypto/cryptobyte", "String.readLengthPrefixed"}:               true,
		{"golang.org/x/crypto/cryptobyte", "String.readUnsigned"}:                     true,
		{"golang.org/x/crypto/salsa20/salsa", "XORKeyStream"}:                         true,
		{"golang.org/x/crypto/ssh", "NewPublicKey"}:                                   true,
		{"golang.org/x/crypto/ssh", "ed25519PublicKey.Verify"}:                        true,
		{"golang.org/x/crypto/ssh", "parseED25519"}:                                   true,
		{"golang.org/x/net/http/httpguts", "HeaderValuesContainsToken"}:               true,
		{"golang.org/x/net/http/httpguts", "headerValueContainsToken"}:                true,
		{"golang.org/x/net/http2", "Server.ServeConn"}:                                true,
		{"golang.org/x/net/http2", "serverConn.canonicalHeader"}:                      true,
		{"golang.org/x/text/encoding/unicode", "bomOverride.Transform"}:               true,
		{"golang.org/x/text/encoding/unicode", "utf16Decoder.Transform"}:              true,
	}

	if diff := cmp.Diff(want, calledVulns); diff != "" {
		log.Fatalf("reachable vulnerable symbols mismatch (-want, +got):\n%s", diff)
	}
}
<commit_msg>cmd/govulncheck/integration: address vuln db changes<commit_after>// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"log"
	"os"

	"github.com/google/go-cmp/cmp"
	"golang.org/x/vuln/vulncheck"
)

const usage = `test helper for examining the output of running govulncheck on k8s@v1.15.11.

Example usage: ./k8s [path to output file]
`

func main() {
	if len(os.Args) != 2 {
		log.Fatal("Incorrect number of expected command line arguments", usage)
	}
	out := os.Args[1]

	outJson, err := os.ReadFile(out)
	if err != nil {
		log.Fatal("Failed to read:", out)
	}

	var r vulncheck.Result
	if err := json.Unmarshal(outJson, &r); err != nil {
		log.Fatal("Failed to load json into vulncheck.Result:", err)
	}

	if len(r.Vulns) != 53 {
		log.Fatalf("want 53 vulns; got %d", len(r.Vulns))
	}

	type vuln struct {
		pkg    string
		symbol string
	}
	calledVulns := make(map[vuln]bool)
	for _, v := range r.Vulns {
		calledVulns[vuln{v.PkgPath, v.Symbol}] = true
	}

	want := map[vuln]bool{
		{"github.com/containernetworking/cni/pkg/invoke", "FindInPath"}:               true,
		{"github.com/evanphx/json-patch", "partialArray.add"}:                         true,
		{"github.com/opencontainers/selinux/go-selinux", "CurrentLabel"}:              true,
		{"github.com/opencontainers/selinux/go-selinux", "FileLabel"}:                 true,
		{"github.com/opencontainers/selinux/go-selinux", "GetEnabled"}:                true,
		{"github.com/opencontainers/selinux/go-selinux", "SetFileLabel"}:              true,
		{"github.com/opencontainers/selinux/go-selinux", "getSelinuxMountPoint"}:      true,
		{"github.com/opencontainers/selinux/go-selinux", "lgetxattr"}:                 true,
		{"github.com/opencontainers/selinux/go-selinux", "lsetxattr"}:                 true,
		{"github.com/opencontainers/selinux/go-selinux", "readCon"}:                   true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getEnabled"}:   true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.getSELinuxfs"}: true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setEnable"}:    true,
		{"github.com/opencontainers/selinux/go-selinux", "selinuxState.setSELinuxfs"}: true,
		{"golang.org/x/crypto/cryptobyte", "Builder.AddBytes"}:                        true,
		{"golang.org/x/crypto/cryptobyte", "Builder.AddUint16LengthPrefixed"}:         true,
		{"golang.org/x/crypto/cryptobyte", "Builder.Bytes"}:                           true,
		{"golang.org/x/crypto/cryptobyte", "Builder.add"}:                             true,
		{"golang.org/x/crypto/cryptobyte", "Builder.addLengthPrefixed"}:               true,
		{"golang.org/x/crypto/cryptobyte", "Builder.callContinuation"}:                true,
		{"golang.org/x/crypto/cryptobyte", "Builder.flushChild"}:                      true,
		{"golang.org/x/crypto/cryptobyte", "NewBuilder"}:                              true,
		{"golang.org/x/crypto/cryptobyte", "String.Empty"}:                            true,
		{"golang.org/x/crypto/cryptobyte", "String.PeekASN1Tag"}:                      true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadASN1"}:                         true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadAnyASN1"}:                      true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadBytes"}:                        true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadOptionalASN1"}:                 true,
		{"golang.org/x/crypto/cryptobyte", "String.ReadUint16LengthPrefixed"}:         true,
		{"golang.org/x/crypto/cryptobyte", "String.Skip"}:                             true,
		{"golang.org/x/crypto/cryptobyte", "String.read"}:                             true,
		{"golang.org/x/crypto/cryptobyte", "String.readASN1"}:                         true,
		{"golang.org/x/crypto/cryptobyte", "String.readLengthPrefixed"}:               true,
		{"golang.org/x/crypto/cryptobyte", "String.readUnsigned"}:                     true,
		{"golang.org/x/crypto/salsa20/salsa", "XORKeyStream"}:                         true,
		{"golang.org/x/crypto/ssh", "NewPublicKey"}:                                   true,
		{"golang.org/x/crypto/ssh", "ed25519PublicKey.Verify"}:                        true,
		{"golang.org/x/crypto/ssh", "parseED25519"}:                                   true,
		{"golang.org/x/net/http/httpguts", "HeaderValuesContainsToken"}:               true,
		{"golang.org/x/net/http/httpguts", "headerValueContainsToken"}:                true,
		{"golang.org/x/net/http2", "Server.ServeConn"}:                                true,
		{"golang.org/x/net/http2", "serverConn.canonicalHeader"}:                      true,
		{"golang.org/x/net/http2", "serverConn.goAway"}:                               true,
		{"golang.org/x/text/encoding/unicode", "bomOverride.Transform"}:               true,
		{"golang.org/x/text/encoding/unicode", "utf16Decoder.Transform"}:              true,
		{"net/http", "ListenAndServe"}:                                                true,
		{"net/http", "Server.ListenAndServe"}:                                         true,
		{"net/http", "Server.ListenAndServeTLS"}:                                      true,
		{"net/http", "Server.Serve"}:                                                  true,
		{"net/http", "Server.ServeTLS"}:                                               true,
		{"net/http", "http2Server.ServeConn"}:                                         true,
		{"net/http", "http2serverConn.goAway"}:                                        true,
	}

	if diff := cmp.Diff(want, calledVulns); diff != "" {
		log.Fatalf("reachable vulnerable symbols mismatch (-want, +got):\n%s", diff)
	}
}
<|endoftext|>


<commit_before><commit_msg>Added new params as report tags<commit_after><|endoftext|>


<commit_before>// Package bf offers facilities to test the satisfiability of generic boolean formula.
//
// SAT solvers usually expect as an input CNF formulas.
// A CNF, or Conjunctive Normal Form, is a set of clauses that must all be true, each clause
// being a set of potentially negated literals. For the clause to be true, at least one of
// these literals must be true.
//
// However, manually translating a given boolean formula to an equivalent CNF is tedious and error-prone.
// This package provides a set of logical connectors to define and solve generic logical formulas.
// Those formulas are then automatically translated to CNF and passed to the gophersat solver.
//
// For example, the following boolean formula:
//
// !(a & b) -> ((c | !d) & !(c & (e <-> !c)) & !(a xor b))
//
// Will be defined with the following code:
//
// f := Not(Implies(And(Var("a"), Var("b")), And(Or(Var("c"), Not(Var("d"))), Not(And(Var("c"), Eq(Var("e"), Not(Var("c"))))), Not(Xor(Var("a"), Var("b"))))))
//
// When calling `Solve(f)`, the following equivalent CNF will be generated:
//
// a & b & (!c | !x1) & (d | !x1) & (c | !x2) & (!e | !c | !x2) & (e | c | !x2) & (!a | !b | !x3) & (a | b | !x3) & (x1 | x2 | x3)
//
// Note that this formula is longer than the original one and that some variables were added to it.
// The translation is both polynomial in time and space.
// When fed this CNF as an input, gophersat then returns the following map:
//
// `map[a:true b:true c:false d:true e:false]`
//
// &{map[a:1 b:2 c:4 d:5 e:7] [[1] [2] [-4 -3] [5 -3] [4 -6] [-7 -4 -6] [7 4 -6] [-1 -2 -8] [1 2 -8] [3 6 8]]}
package bf
<commit_msg>improved documentation of bf package<commit_after>// Package bf offers facilities to test the satisfiability of generic boolean formula.
//
// SAT solvers usually expect as an input CNF formulas.
// A CNF, or Conjunctive Normal Form, is a set of clauses that must all be true, each clause
// being a set of potentially negated literals. For the clause to be true, at least one of
// these literals must be true.
//
// However, manually translating a given boolean formula to an equivalent CNF is tedious and error-prone.
// This package provides a set of logical connectors to define and solve generic logical formulas.
// Those formulas are then automatically translated to CNF and passed to the gophersat solver.
//
// For example, the following boolean formula:
//
//     ¬(a ∧ b) → ((c ∨ ¬d) ∧ ¬(c ∧ (e ↔ ¬c)) ∧ ¬(a ⊻ b))
//
// Will be defined with the following code:
//
//     f := Not(Implies(And(Var("a"), Var("b")), And(Or(Var("c"), Not(Var("d"))), Not(And(Var("c"), Eq(Var("e"), Not(Var("c"))))), Not(Xor(Var("a"), Var("b"))))))
//
// When calling `Solve(f)`, the following equivalent CNF will be generated:
//
//     a ∧ b ∧ (¬c ∨ ¬x1) ∧ (d ∨ ¬x1) ∧ (c ∨ ¬x2) ∧ (¬e ∨ ¬c ∨ ¬x2) ∧ (e ∨ c ∨ ¬x2) ∧ (¬a ∨ ¬b ∨ ¬x3) ∧ (a ∨ b ∨ ¬x3) ∧ (x1 ∨ x2 ∨ x3)
//
// Note that this formula is longer than the original one and that some variables were added to it.
// The translation is both polynomial in time and space.
// When fed this CNF as an input, gophersat then returns the following map:
//
//     map[a:true b:true c:false d:true e:false]
package bf
<|endoftext|>


<commit_before>// +build android

/*
Copyright © 2013 Andrea Fazzi <andrea.fazzi@alcacoop.it>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
“Software”), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package egl

/*
#include <stdlib.h>
#include <EGL/egl.h>
#include <EGL/eglplatform.h>
*/
import "C"
import "unsafe"

type (
	Enum              uint32
	Config            uintptr
	Context           uintptr
	Display           uintptr
	Surface           uintptr
	ClientBuffer      uintptr
	NativeDisplayType unsafe.Pointer
	NativeWindowType  uintptr
	NativePixmapType  uintptr
)
<commit_msg>Fix Android build again<commit_after>// +build android

/*
Copyright © 2013 Andrea Fazzi <andrea.fazzi@alcacoop.it>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
“Software”), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package egl

/*
#include <stdlib.h>
#include <EGL/egl.h>
#include <EGL/eglplatform.h>
*/
import "C"
import "unsafe"

type (
	Enum              uint32
	Config            uintptr
	Context           uintptr
	Display           uintptr
	Surface           uintptr
	ClientBuffer      uintptr
	NativeDisplayType unsafe.Pointer
	NativeWindowType  unsafe.Pointer
	NativePixmapType  unsafe.Pointer
)
<|endoftext|>


<commit_before>package compute

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
)

// PublicIPBlock represents an allocated block of public IPv4 addresses.
type PublicIPBlock struct {
	ID              string `json:"id"`
	NetworkDomainID string `json:"networkDomainId"`
	DataCenterID    string `json:"datacenterId"`
	BaseIP          string `json:"baseIp"`
	Size            int    `json:"size"`
	CreateTime      string `json:"createTime"`
	State           string `json:"state"`
}

// GetID returns the public IPv4 address block's Id.
func (block *PublicIPBlock) GetID() string {
	return block.ID
}

// GetResourceType returns the public IP block's resource type.
func (block *PublicIPBlock) GetResourceType() ResourceType {
	return ResourceTypePublicIPBlock
}

// GetName returns the public IPv4 address block's name.
func (block *PublicIPBlock) GetName() string {
	return fmt.Sprintf("%s+%d", block.BaseIP, block.Size)
}

// GetState returns the network block's current state.
func (block *PublicIPBlock) GetState() string {
	return block.State
}

// IsDeleted determines whether the public IPv4 address block has been deleted (is nil).
func (block *PublicIPBlock) IsDeleted() bool {
	return block == nil
}

var _ Resource = &PublicIPBlock{}

// PublicIPBlocks represents a page of PublicIPBlock results.
type PublicIPBlocks struct {
	Blocks []PublicIPBlock `json:"publicIpBlock"`

	PagedResult
}

// ReservedPublicIP represents a public IPv4 address reserved for NAT or a VIP.
type ReservedPublicIP struct {
	IPBlockID       string `json:"ipBlockId"`
	DataCenterID    string `json:"datacenterId"`
	NetworkDomainID string `json:"networkDomainId"`
	Address         string `json:"value"`
}

// ReservedPublicIPs represents a page of ReservedPublicIP results.
type ReservedPublicIPs struct {
	IPs []ReservedPublicIP `json:"ip"`

	PagedResult
}

// Request body for adding a public IPv4 address block.
type addPublicAddressBlock struct {
	NetworkDomainID string `json:"networkDomainId"`
}

// Request body for removing a public IPv4 address block.
type removePublicAddressBlock struct {
	IPBlockID string `json:"id"`
}

// GetPublicIPBlock retrieves the public IPv4 address block with the specified Id.
// Returns nil if no IPv4 address block is found with the specified Id.
func (client *Client) GetPublicIPBlock(id string) (block *PublicIPBlock, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/network/publicIpBlock/%s", organizationID, id)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponseV2

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		if apiResponse.ResponseCode == ResponseCodeResourceNotFound {
			return nil, nil // Not an error, but was not found.
		}

		return nil, apiResponse.ToError("Request to retrieve public IPv4 address block failed with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	block = &PublicIPBlock{}
	err = json.Unmarshal(responseBody, block)
	if err != nil {
		return nil, err
	}

	return block, nil
}

// ListPublicIPBlocks retrieves all blocks of public IPv4 addresses that have been allocated to the specified network domain.
func (client *Client) ListPublicIPBlocks(networkDomainID string, paging *Paging) (blocks *PublicIPBlocks, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/network/publicIpBlock?networkDomainId=%s&%s",
		organizationID,
		networkDomainID,
		paging.EnsurePaging().toQueryParameters(),
	)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}

	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponseV2

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		return nil, apiResponse.ToError("Request to list public IPv4 address blocks failed with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	blocks = &PublicIPBlocks{}
	err = json.Unmarshal(responseBody, blocks)

	return blocks, err
}

// AddPublicIPBlock adds a new block of public IPv4 addresses to the specified network domain.
func (client *Client) AddPublicIPBlock(networkDomainID string) (blockID string, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return "", err
	}

	requestURI := fmt.Sprintf("%s/network/addPublicIpBlock", organizationID)
	request, err := client.newRequestV22(requestURI, http.MethodPost,
		&addPublicAddressBlock{networkDomainID},
	)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return "", err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return "", err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return "", apiResponse.ToError("Request to add IPv4 address block to network domain '%s' failed with unexpected status code %d (%s): %s", networkDomainID, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	// Expected: "info" { "name": "ipBlockId", "value": "the-Id-of-the-new-IP-block" }
	ipBlockIDMessage := apiResponse.GetFieldMessage("ipBlockId")
	if ipBlockIDMessage == nil {
		return "", apiResponse.ToError("Received an unexpected response (missing 'ipBlockId') with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return *ipBlockIDMessage, nil
}

// RemovePublicIPBlock removes the specified block of public IPv4 addresses from its network domain.
// This operation is synchronous.
func (client *Client) RemovePublicIPBlock(id string) error {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return err
	}

	requestURI := fmt.Sprintf("%s/network/removePublicIpBlock", organizationID)
	request, err := client.newRequestV22(requestURI, http.MethodPost,
		&removePublicAddressBlock{id},
	)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return apiResponse.ToError("Request to remove IPv4 address block '%s' failed with unexpected status code %d (%s): %s", id, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return nil
}

// ListReservedPublicIPAddresses retrieves all public IPv4 addresses in the specified network domain that have been reserved.
func (client *Client) ListReservedPublicIPAddresses(networkDomainID string, paging *Paging) (reservedPublicIPs *ReservedPublicIPs, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/network/reservedPublicIpv4Address?networkDomainId=%s&%s",
		organizationID,
		networkDomainID,
		paging.EnsurePaging().toQueryParameters(),
	)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}

	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponseV2

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		return nil, apiResponse.ToError("Request to list reserved public IPv4 addresses failed with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	reservedPublicIPs = &ReservedPublicIPs{}
	err = json.Unmarshal(responseBody, reservedPublicIPs)

	return reservedPublicIPs, err
}

// GetAvailablePublicIPAddresses retrieves all public IPv4 addresses in the specified network domain that are available for use.
//
// Returns a map of IP block IDs, keyed by public IP address.
func (client *Client) GetAvailablePublicIPAddresses(networkDomainID string) (availableIPs map[string]string, err error) {
	availableIPs = make(map[string]string)

	// Public IPs are allocated in blocks.
	page := DefaultPaging()
	for {
		var publicIPBlocks *PublicIPBlocks
		publicIPBlocks, err = client.ListPublicIPBlocks(networkDomainID, page)
		if err != nil {
			return
		}
		if publicIPBlocks.IsEmpty() {
			break // We're done
		}

		var blockAddresses []string
		for _, block := range publicIPBlocks.Blocks {
			blockAddresses, err = calculateBlockAddresses(block)
			if err != nil {
				return
			}

			for _, address := range blockAddresses {
				availableIPs[address] = block.ID
			}
		}

		page.Next()
	}

	// Some of those IPs may be reserved for other NAT rules or VIPs.
	page.First()
	for {
		var reservedIPs *ReservedPublicIPs
		reservedIPs, err = client.ListReservedPublicIPAddresses(networkDomainID, page)
		if err != nil {
			return
		}
		if reservedIPs.IsEmpty() {
			break // We're done
		}

		for _, reservedIP := range reservedIPs.IPs {
			delete(availableIPs, reservedIP.Address)
		}

		page.Next()
	}

	return
}

func calculateBlockAddresses(block PublicIPBlock) ([]string, error) {
	addresses := make([]string, block.Size)

	baseAddressComponents := strings.Split(block.BaseIP, ".")
	if len(baseAddressComponents) != 4 {
		return addresses, fmt.Errorf("Invalid base IP address '%s'.", block.BaseIP)
	}
	baseOctet, err := strconv.Atoi(baseAddressComponents[3])
	if err != nil {
		return addresses, err
	}

	for index := range addresses {
		// Increment the last octet to determine the next address in the block.
		baseAddressComponents[3] = strconv.Itoa(baseOctet + index)
		addresses[index] = strings.Join(baseAddressComponents, ".")
	}

	return addresses, nil
}
<commit_msg>Escape query parameters for public IP block request URIs (#7).<commit_after>package compute

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"strings"
)

// PublicIPBlock represents an allocated block of public IPv4 addresses.
type PublicIPBlock struct {
	ID              string `json:"id"`
	NetworkDomainID string `json:"networkDomainId"`
	DataCenterID    string `json:"datacenterId"`
	BaseIP          string `json:"baseIp"`
	Size            int    `json:"size"`
	CreateTime      string `json:"createTime"`
	State           string `json:"state"`
}

// GetID returns the public IPv4 address block's Id.
func (block *PublicIPBlock) GetID() string {
	return block.ID
}

// GetResourceType returns the public IP block's resource type.
func (block *PublicIPBlock) GetResourceType() ResourceType {
	return ResourceTypePublicIPBlock
}

// GetName returns the public IPv4 address block's name.
func (block *PublicIPBlock) GetName() string {
	return fmt.Sprintf("%s+%d", block.BaseIP, block.Size)
}

// GetState returns the network block's current state.
func (block *PublicIPBlock) GetState() string {
	return block.State
}

// IsDeleted determines whether the public IPv4 address block has been deleted (is nil).
func (block *PublicIPBlock) IsDeleted() bool {
	return block == nil
}

var _ Resource = &PublicIPBlock{}

// PublicIPBlocks represents a page of PublicIPBlock results.
type PublicIPBlocks struct {
	Blocks []PublicIPBlock `json:"publicIpBlock"`

	PagedResult
}

// ReservedPublicIP represents a public IPv4 address reserved for NAT or a VIP.
type ReservedPublicIP struct {
	IPBlockID       string `json:"ipBlockId"`
	DataCenterID    string `json:"datacenterId"`
	NetworkDomainID string `json:"networkDomainId"`
	Address         string `json:"value"`
}

// ReservedPublicIPs represents a page of ReservedPublicIP results.
type ReservedPublicIPs struct {
	IPs []ReservedPublicIP `json:"ip"`

	PagedResult
}

// Request body for adding a public IPv4 address block.
type addPublicAddressBlock struct {
	NetworkDomainID string `json:"networkDomainId"`
}

// Request body for removing a public IPv4 address block.
type removePublicAddressBlock struct {
	IPBlockID string `json:"id"`
}

// GetPublicIPBlock retrieves the public IPv4 address block with the specified Id.
// Returns nil if no IPv4 address block is found with the specified Id.
func (client *Client) GetPublicIPBlock(id string) (block *PublicIPBlock, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/network/publicIpBlock/%s",
		url.QueryEscape(organizationID),
		url.QueryEscape(id),
	)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponseV2

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		if apiResponse.ResponseCode == ResponseCodeResourceNotFound {
			return nil, nil // Not an error, but was not found.
		}

		return nil, apiResponse.ToError("Request to retrieve public IPv4 address block failed with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	block = &PublicIPBlock{}
	err = json.Unmarshal(responseBody, block)
	if err != nil {
		return nil, err
	}

	return block, nil
}

// ListPublicIPBlocks retrieves all blocks of public IPv4 addresses that have been allocated to the specified network domain.
func (client *Client) ListPublicIPBlocks(networkDomainID string, paging *Paging) (blocks *PublicIPBlocks, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/network/publicIpBlock?networkDomainId=%s&%s",
		url.QueryEscape(organizationID),
		url.QueryEscape(networkDomainID),
		paging.EnsurePaging().toQueryParameters(),
	)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}

	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponseV2

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		return nil, apiResponse.ToError("Request to list public IPv4 address blocks failed with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	blocks = &PublicIPBlocks{}
	err = json.Unmarshal(responseBody, blocks)

	return blocks, err
}

// AddPublicIPBlock adds a new block of public IPv4 addresses to the specified network domain.
func (client *Client) AddPublicIPBlock(networkDomainID string) (blockID string, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return "", err
	}

	requestURI := fmt.Sprintf("%s/network/addPublicIpBlock",
		url.QueryEscape(organizationID),
	)
	request, err := client.newRequestV22(requestURI, http.MethodPost,
		&addPublicAddressBlock{networkDomainID},
	)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return "", err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return "", err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return "", apiResponse.ToError("Request to add IPv4 address block to network domain '%s' failed with unexpected status code %d (%s): %s", networkDomainID, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	// Expected: "info" { "name": "ipBlockId", "value": "the-Id-of-the-new-IP-block" }
	ipBlockIDMessage := apiResponse.GetFieldMessage("ipBlockId")
	if ipBlockIDMessage == nil {
		return "", apiResponse.ToError("Received an unexpected response (missing 'ipBlockId') with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return *ipBlockIDMessage, nil
}

// RemovePublicIPBlock removes the specified block of public IPv4 addresses from its network domain.
// This operation is synchronous.
func (client *Client) RemovePublicIPBlock(id string) error {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return err
	}

	requestURI := fmt.Sprintf("%s/network/removePublicIpBlock",
		url.QueryEscape(organizationID),
	)
	request, err := client.newRequestV22(requestURI, http.MethodPost,
		&removePublicAddressBlock{id},
	)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return apiResponse.ToError("Request to remove IPv4 address block '%s' failed with unexpected status code %d (%s): %s", id, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return nil
}

// ListReservedPublicIPAddresses retrieves all public IPv4 addresses in the specified network domain that have been reserved.
func (client *Client) ListReservedPublicIPAddresses(networkDomainID string, paging *Paging) (reservedPublicIPs *ReservedPublicIPs, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/network/reservedPublicIpv4Address?networkDomainId=%s&%s",
		url.QueryEscape(organizationID),
		url.QueryEscape(networkDomainID),
		paging.EnsurePaging().toQueryParameters(),
	)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}

	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponseV2

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		return nil, apiResponse.ToError("Request to list reserved public IPv4 addresses failed with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	reservedPublicIPs = &ReservedPublicIPs{}
	err = json.Unmarshal(responseBody, reservedPublicIPs)

	return reservedPublicIPs, err
}

// GetAvailablePublicIPAddresses retrieves all public IPv4 addresses in the specified network domain that are available for use.
//
// Returns a map of IP block IDs, keyed by public IP address.
func (client *Client) GetAvailablePublicIPAddresses(networkDomainID string) (availableIPs map[string]string, err error) {
	availableIPs = make(map[string]string)

	// Public IPs are allocated in blocks.
	page := DefaultPaging()
	for {
		var publicIPBlocks *PublicIPBlocks
		publicIPBlocks, err = client.ListPublicIPBlocks(networkDomainID, page)
		if err != nil {
			return
		}
		if publicIPBlocks.IsEmpty() {
			break // We're done
		}

		var blockAddresses []string
		for _, block := range publicIPBlocks.Blocks {
			blockAddresses, err = calculateBlockAddresses(block)
			if err != nil {
				return
			}

			for _, address := range blockAddresses {
				availableIPs[address] = block.ID
			}
		}

		page.Next()
	}

	// Some of those IPs may be reserved for other NAT rules or VIPs.
	page.First()
	for {
		var reservedIPs *ReservedPublicIPs
		reservedIPs, err = client.ListReservedPublicIPAddresses(networkDomainID, page)
		if err != nil {
			return
		}
		if reservedIPs.IsEmpty() {
			break // We're done
		}

		for _, reservedIP := range reservedIPs.IPs {
			delete(availableIPs, reservedIP.Address)
		}

		page.Next()
	}

	return
}

func calculateBlockAddresses(block PublicIPBlock) ([]string, error) {
	addresses := make([]string, block.Size)

	baseAddressComponents := strings.Split(block.BaseIP, ".")
	if len(baseAddressComponents) != 4 {
		return addresses, fmt.Errorf("Invalid base IP address '%s'.", block.BaseIP)
	}
	baseOctet, err := strconv.Atoi(baseAddressComponents[3])
	if err != nil {
		return addresses, err
	}

	for index := range addresses {
		// Increment the last octet to determine the next address in the block.
		baseAddressComponents[3] = strconv.Itoa(baseOctet + index)
		addresses[index] = strings.Join(baseAddressComponents, ".")
	}

	return addresses, nil
}
<|endoftext|>


<commit_before>package air

import (
	"encoding/json"
	"encoding/xml"
	"errors"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

// binder is used to provide a `bind()` method for an `Air` instance
// for binds a HTTP request body into privided type.
type binder struct {
	air *Air
}

// newBinder returns a new instance of `binder`.
func newBinder(a *Air) *binder {
	return &binder{
		air: a,
	}
}

// bind binds the HTTP request body into provided type i based on
// "Content-Type" header.
func (b *binder) bind(i interface{}, c *Context) (err error) {
	req := c.Request
	if req.Method() == GET {
		if err = b.bindData(i, c.QueryParams()); err != nil {
			err = NewHTTPError(http.StatusBadRequest, err.Error())
		}
		return
	}
	ctype := req.Header.Get(HeaderContentType)
	if req.Body() == nil {
		err = NewHTTPError(http.StatusBadRequest, "Request Body Can't Be Empty")
		return
	}
	err = ErrUnsupportedMediaType
	switch {
	case strings.HasPrefix(ctype, MIMEApplicationJSON):
		if err = json.NewDecoder(req.Body()).Decode(i); err != nil {
			err = NewHTTPError(http.StatusBadRequest, err.Error())
		}
	case strings.HasPrefix(ctype, MIMEApplicationXML):
		if err = xml.NewDecoder(req.Body()).Decode(i); err != nil {
			err = NewHTTPError(http.StatusBadRequest, err.Error())
		}
	case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
		if err = b.bindData(i, req.FormParams()); err != nil {
			err = NewHTTPError(http.StatusBadRequest, err.Error())
		}
	}
	return
}

// bindData binds the data into a type ptr.
func (b *binder) bindData(ptr interface{}, data map[string][]string) error {
	typ := reflect.TypeOf(ptr).Elem()
	val := reflect.ValueOf(ptr).Elem()

	if typ.Kind() != reflect.Struct {
		return errors.New("Binding Element Must Be A Struct")
	}

	for i := 0; i < typ.NumField(); i++ {
		typeField := typ.Field(i)
		structField := val.Field(i)
		if !structField.CanSet() {
			continue
		}
		structFieldKind := structField.Kind()
		inputFieldName := typeField.Tag.Get("form")

		if inputFieldName == "" {
			inputFieldName = typeField.Name
			// If "form" tag is nil, we inspect if the field is a struct.
			if structFieldKind == reflect.Struct {
				err := b.bindData(structField.Addr().Interface(), data)
				if err != nil {
					return err
				}
				continue
			}
		}
		inputValue, exists := data[inputFieldName]
		if !exists {
			continue
		}

		numElems := len(inputValue)
		if structFieldKind == reflect.Slice && numElems > 0 {
			sliceOf := structField.Type().Elem().Kind()
			slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
			for i := 0; i < numElems; i++ {
				if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil {
					return err
				}
			}
			val.Field(i).Set(slice)
		} else {
			if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
				return err
			}
		}
	}
	return nil
}

// setWithProperType sets the val into a structField with a proper valueKind.
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
	switch valueKind {
	case reflect.Int:
		return setIntField(val, 0, structField)
	case reflect.Int8:
		return setIntField(val, 8, structField)
	case reflect.Int16:
		return setIntField(val, 16, structField)
	case reflect.Int32:
		return setIntField(val, 32, structField)
	case reflect.Int64:
		return setIntField(val, 64, structField)
	case reflect.Uint:
		return setUintField(val, 0, structField)
	case reflect.Uint8:
		return setUintField(val, 8, structField)
	case reflect.Uint16:
		return setUintField(val, 16, structField)
	case reflect.Uint32:
		return setUintField(val, 32, structField)
	case reflect.Uint64:
		return setUintField(val, 64, structField)
	case reflect.Bool:
		return setBoolField(val, structField)
	case reflect.Float32:
		return setFloatField(val, 32, structField)
	case reflect.Float64:
		return setFloatField(val, 64, structField)
	case reflect.String:
		structField.SetString(val)
	default:
		return errors.New("Unknown Type")
	}
	return nil
}

// setIntField sets the value into a field with a provided bitSize.
func setIntField(value string, bitSize int, field reflect.Value) error {
	if value == "" {
		value = "0"
	}
	intVal, err := strconv.ParseInt(value, 10, bitSize)
	if err == nil {
		field.SetInt(intVal)
	}
	return err
}

// setUintField sets the value into a field with a provided bitSize.
func setUintField(value string, bitSize int, field reflect.Value) error {
	if value == "" {
		value = "0"
	}
	uintVal, err := strconv.ParseUint(value, 10, bitSize)
	if err == nil {
		field.SetUint(uintVal)
	}
	return err
}

// setBoolField sets the value into a field.
func setBoolField(value string, field reflect.Value) error {
	if value == "" {
		value = "false"
	}
	boolVal, err := strconv.ParseBool(value)
	if err == nil {
		field.SetBool(boolVal)
	}
	return err
}

// setFloatField sets the value into a field with a provided bitSize.
func setFloatField(value string, bitSize int, field reflect.Value) error {
	if value == "" {
		value = "0.0"
	}
	floatVal, err := strconv.ParseFloat(value, bitSize)
	if err == nil {
		field.SetFloat(floatVal)
	}
	return err
}
<commit_msg>refactor: remove `binder#air` and `newBinder()`<commit_after>package air

import (
	"encoding/json"
	"encoding/xml"
	"errors"
	"net/http"
	"reflect"
	"strconv"
	"strings"
)

// binder is used to provide a `bind()` method for an `Air` instance
// for binds a HTTP request body into privided type.
type binder struct{}

// bind binds the HTTP request body into provided type i based on
// "Content-Type" header.
func (b *binder) bind(i interface{}, c *Context) (err error) {
	req := c.Request
	if req.Method() == GET {
		if err = b.bindData(i, c.QueryParams()); err != nil {
			err = NewHTTPError(http.StatusBadRequest, err.Error())
		}
		return
	}
	ctype := req.Header.Get(HeaderContentType)
	if req.Body() == nil {
		err = NewHTTPError(http.StatusBadRequest, "Request Body Can't Be Empty")
		return
	}
	err = ErrUnsupportedMediaType
	switch {
	case strings.HasPrefix(ctype, MIMEApplicationJSON):
		if err = json.NewDecoder(req.Body()).Decode(i); err != nil {
			err = NewHTTPError(http.StatusBadRequest, err.Error())
		}
	case strings.HasPrefix(ctype, MIMEApplicationXML):
		if err = xml.NewDecoder(req.Body()).Decode(i); err != nil {
			err = NewHTTPError(http.StatusBadRequest, err.Error())
		}
	case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
		if err = b.bindData(i, req.FormParams()); err != nil {
			err = NewHTTPError(http.StatusBadRequest, err.Error())
		}
	}
	return
}

// bindData binds the data into a type ptr.
func (b *binder) bindData(ptr interface{}, data map[string][]string) error {
	typ := reflect.TypeOf(ptr).Elem()
	val := reflect.ValueOf(ptr).Elem()

	if typ.Kind() != reflect.Struct {
		return errors.New("Binding Element Must Be A Struct")
	}

	for i := 0; i < typ.NumField(); i++ {
		typeField := typ.Field(i)
		structField := val.Field(i)
		if !structField.CanSet() {
			continue
		}
		structFieldKind := structField.Kind()
		inputFieldName := typeField.Tag.Get("form")

		if inputFieldName == "" {
			inputFieldName = typeField.Name
			// If "form" tag is nil, we inspect if the field is a struct.
			if structFieldKind == reflect.Struct {
				err := b.bindData(structField.Addr().Interface(), data)
				if err != nil {
					return err
				}
				continue
			}
		}
		inputValue, exists := data[inputFieldName]
		if !exists {
			continue
		}

		numElems := len(inputValue)
		if structFieldKind == reflect.Slice && numElems > 0 {
			sliceOf := structField.Type().Elem().Kind()
			slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
			for i := 0; i < numElems; i++ {
				if err := setWithProperType(sliceOf, inputValue[i], slice.Index(i)); err != nil {
					return err
				}
			}
			val.Field(i).Set(slice)
		} else {
			if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
				return err
			}
		}
	}
	return nil
}

// setWithProperType sets the val into a structField with a proper valueKind.
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value) error {
	switch valueKind {
	case reflect.Int:
		return setIntField(val, 0, structField)
	case reflect.Int8:
		return setIntField(val, 8, structField)
	case reflect.Int16:
		return setIntField(val, 16, structField)
	case reflect.Int32:
		return setIntField(val, 32, structField)
	case reflect.Int64:
		return setIntField(val, 64, structField)
	case reflect.Uint:
		return setUintField(val, 0, structField)
	case reflect.Uint8:
		return setUintField(val, 8, structField)
	case reflect.Uint16:
		return setUintField(val, 16, structField)
	case reflect.Uint32:
		return setUintField(val, 32, structField)
	case reflect.Uint64:
		return setUintField(val, 64, structField)
	case reflect.Bool:
		return setBoolField(val, structField)
	case reflect.Float32:
		return setFloatField(val, 32, structField)
	case reflect.Float64:
		return setFloatField(val, 64, structField)
	case reflect.String:
		structField.SetString(val)
	default:
		return errors.New("Unknown Type")
	}
	return nil
}

// setIntField sets the value into a field with a provided bitSize.
func setIntField(value string, bitSize int, field reflect.Value) error {
	if value == "" {
		value = "0"
	}
	intVal, err := strconv.ParseInt(value, 10, bitSize)
	if err == nil {
		field.SetInt(intVal)
	}
	return err
}

// setUintField sets the value into a field with a provided bitSize.
func setUintField(value string, bitSize int, field reflect.Value) error {
	if value == "" {
		value = "0"
	}
	uintVal, err := strconv.ParseUint(value, 10, bitSize)
	if err == nil {
		field.SetUint(uintVal)
	}
	return err
}

// setBoolField sets the value into a field.
func setBoolField(value string, field reflect.Value) error {
	if value == "" {
		value = "false"
	}
	boolVal, err := strconv.ParseBool(value)
	if err == nil {
		field.SetBool(boolVal)
	}
	return err
}

// setFloatField sets the value into a field with a provided bitSize.
func setFloatField(value string, bitSize int, field reflect.Value) error {
	if value == "" {
		value = "0.0"
	}
	floatVal, err := strconv.ParseFloat(value, bitSize)
	if err == nil {
		field.SetFloat(floatVal)
	}
	return err
}
<|endoftext|>


<commit_before>// Copyright 2014 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package ini

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"strings"
	"unicode"
)

type SyntaxError struct {
	Line int
	Msg  string
}

func (s *SyntaxError) Error() string {
	return fmt.Sprintf("unicode/ini，在第%d行发生语法错误：%v", s.Line, s.Msg)
}

// 几种元素类型
const (
	Undefined = iota // 未定义，初始状态
	Element
	Section
	Comment
	EOF // 已经读取完毕。
)

type Token struct {
	Type  int
	Value string
	Key   string
}

func (t *Token) reset() {
	t.Type = Undefined
	t.Value = t.Value[:0]
	t.Key = t.Key[:0]
}

// 复制一个新的Token
func (t *Token) Copy() *Token {
	return &Token{
		Type:  t.Type,
		Value: t.Value,
		Key:   t.Key,
	}
}

// ini数据的读取操作类。
// 注释只支持以`#`,`;`开头的行，不支持行尾注释；
//
// 读取的内容，Element元素的首尾空格将被去除
// 但注释不会作此处理，包括换行符都将原样输出。
type Reader struct {
	reader *bufio.Reader
	eof    bool // 已经读取完毕
	line   int  // 当前正在处理的行数。
	token  *Token
}

// 从一个io.Reader初始化Reader
func NewReader(r io.Reader) *Reader {
	return &Reader{reader: bufio.NewReader(r), token: &Token{}}
}

// 从一个[]byte初始化Reader
func NewReaderBytes(data []byte) *Reader {
	return NewReader(bytes.NewReader(data))
}

// 从一个字符串初始化Reader
func NewReaderString(str string) *Reader {
	return NewReader(strings.NewReader(str))
}

// 返回下一个Token，当内容读取完毕之后，将返回Type值为EOF的Token
//
// 返回的Token变量，在下将调用Token()时，数据会被重置，若需要保存
// Token的数据，可使用Token.Copy()函数复制一份。
func (r *Reader) Token() (*Token, error) {
	r.token.reset()

START:
	if r.eof {
		r.token.Type = EOF
		return r.token, nil
	}

	// 读取新行
	buffer, err := r.reader.ReadString('\n')
	r.line++
	if err != nil {
		// 真的发生错误了
		if err != io.EOF {
			return nil, err
		}

		// 读取完毕
		r.eof = true
		if len(buffer) == 0 {
			r.token.Type = EOF
			return r.token, nil
		}
	}

	buffer = strings.TrimLeftFunc(buffer, unicode.IsSpace)
	if len(buffer) == 0 {
		goto START
	}

	return r.parseLine(buffer)
}

// 分析一行字符串，并返回结果。
func (r *Reader) parseLine(line string) (*Token, error) {
	switch line[0] {
	case '[': // section
		line = strings.TrimRightFunc(line, unicode.IsSpace) // 防止]之后有空格
		if line[len(line)-1] != ']' {
			return nil, r.newSyntaxError("section名称没有以]作为结尾")
		}

		r.token.Type = Section
		r.token.Value = line[1 : len(line)-1]
		return r.token, nil
	case '#', ';': // comment
		r.token.Type = Comment
		r.token.Value = line[1:]
		return r.token, nil
	default: // element
		pos := strings.IndexRune(line, '=')
		if pos < 0 {
			return nil, r.newSyntaxError("表达式中未找到`=`符号")
		}

		r.token.Type = Element
		r.token.Key = strings.TrimRightFunc(line[:pos], unicode.IsSpace)
		r.token.Value = strings.TrimSpace(line[pos+1:])
		return r.token, nil
	}

	r.token.Type = EOF
	return r.token, nil
}

// 返回一个语法错误的error
func (r *Reader) newSyntaxError(msg string) error {
	return &SyntaxError{
		Msg:  msg,
		Line: r.line,
	}
}

// 将ini转换成map[string]interface{}返回。
//
// 没有与之相对就的MarshalMap，因为map是无序的，若一个map带了section，则转换结
// 果未必是正确的。
//
// 若section参数不为空，则表示只返回section的内容，若没有对应内容，则返回空值
func UnmarshalMap(data []byte, section string) (map[string]interface{}, error) {
	if len(data) == 0 {
		return nil, &SyntaxError{Msg: "没有内容", Line: 0}
	}

	m := make(map[string]interface{})
	currSection := m

	sectionFlag := false // 是否指定了section值
	if section != "" {
		sectionFlag = true
		currSection = nil
	}

	r := NewReaderBytes(data)
	for {
		token, err := r.Token()
		if err != nil {
			return nil, err
		}

		switch token.Type {
		case Comment:
			continue
		case EOF:
			return m, nil
		case Element:
			if currSection != nil {
				currSection[token.Key] = token.Value
			}
		case Section:
			if sectionFlag {
				if section == token.Value {
					currSection = m
				} else {
					currSection = nil
				}
			} else {
				currSection = make(map[string]interface{})
				m[token.Value] = currSection
			}
		default:
			return nil, errors.New("未知的元素类型")
		}
	}
	return m, nil
}
<commit_msg>unmarshal完成一半<commit_after>// Copyright 2014 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package ini

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"github.com/caixw/lib.go/conv"
	"io"
	"reflect"
	"strings"
	"unicode"
)

type SyntaxError struct {
	Line int
	Msg  string
}

func (s *SyntaxError) Error() string {
	return fmt.Sprintf("unicode/ini，在第%d行发生语法错误：%v", s.Line, s.Msg)
}

// 几种元素类型
const (
	Undefined = iota // 未定义，初始状态
	Element
	Section
	Comment
	EOF // 已经读取完毕。
)

type Token struct {
	Type  int
	Value string
	Key   string
}

func (t *Token) reset() {
	t.Type = Undefined
	t.Value = t.Value[:0]
	t.Key = t.Key[:0]
}

// 复制一个新的Token
func (t *Token) Copy() *Token {
	return &Token{
		Type:  t.Type,
		Value: t.Value,
		Key:   t.Key,
	}
}

// ini数据的读取操作类。
// 注释只支持以`#`,`;`开头的行，不支持行尾注释；
//
// 读取的内容，Element元素的首尾空格将被去除
// 但注释不会作此处理，包括换行符都将原样输出。
type Reader struct {
	reader *bufio.Reader
	eof    bool // 已经读取完毕
	line   int  // 当前正在处理的行数。
	token  *Token
}

// 从一个io.Reader初始化Reader
func NewReader(r io.Reader) *Reader {
	return &Reader{reader: bufio.NewReader(r), token: &Token{}}
}

// 从一个[]byte初始化Reader
func NewReaderBytes(data []byte) *Reader {
	return NewReader(bytes.NewReader(data))
}

// 从一个字符串初始化Reader
func NewReaderString(str string) *Reader {
	return NewReader(strings.NewReader(str))
}

// 返回下一个Token，当内容读取完毕之后，将返回Type值为EOF的Token
//
// 返回的Token变量，在下将调用Token()时，数据会被重置，若需要保存
// Token的数据，可使用Token.Copy()函数复制一份。
func (r *Reader) Token() (*Token, error) {
	r.token.reset()

START:
	if r.eof {
		r.token.Type = EOF
		return r.token, nil
	}

	// 读取新行
	buffer, err := r.reader.ReadString('\n')
	r.line++
	if err != nil {
		// 真的发生错误了
		if err != io.EOF {
			return nil, err
		}

		// 读取完毕
		r.eof = true
		if len(buffer) == 0 {
			r.token.Type = EOF
			return r.token, nil
		}
	}

	buffer = strings.TrimLeftFunc(buffer, unicode.IsSpace)
	if len(buffer) == 0 {
		goto START
	}

	return r.parseLine(buffer)
}

// 分析一行字符串，并返回结果。
func (r *Reader) parseLine(line string) (*Token, error) {
	switch line[0] {
	case '[': // section
		line = strings.TrimRightFunc(line, unicode.IsSpace) // 防止]之后有空格
		if line[len(line)-1] != ']' {
			return nil, r.newSyntaxError("section名称没有以]作为结尾")
		}

		r.token.Type = Section
		r.token.Value = line[1 : len(line)-1]
		return r.token, nil
	case '#', ';': // comment
		r.token.Type = Comment
		r.token.Value = line[1:]
		return r.token, nil
	default: // element
		pos := strings.IndexRune(line, '=')
		if pos < 0 {
			return nil, r.newSyntaxError("表达式中未找到`=`符号")
		}

		r.token.Type = Element
		r.token.Key = strings.TrimRightFunc(line[:pos], unicode.IsSpace)
		r.token.Value = strings.TrimSpace(line[pos+1:])
		return r.token, nil
	}

	r.token.Type = EOF
	return r.token, nil
}

// 返回一个语法错误的error
func (r *Reader) newSyntaxError(msg string) error {
	return &SyntaxError{
		Msg:  msg,
		Line: r.line,
	}
}

// 将ini转换成map[string]interface{}返回。
//
// 没有与之相对就的MarshalMap，因为map是无序的，若一个map带了section，则转换结
// 果未必是正确的。
//
// 若section参数不为空，则表示只返回section的内容，若没有对应内容，则返回空值
func UnmarshalMap(data []byte, section string) (map[string]interface{}, error) {
	if len(data) == 0 {
		return nil, &SyntaxError{Msg: "没有内容", Line: 0}
	}

	m := make(map[string]interface{})
	currSection := m

	sectionFlag := false // 是否指定了section值
	if section != "" {
		sectionFlag = true
		currSection = nil
	}

	r := NewReaderBytes(data)
	for {
		token, err := r.Token()
		if err != nil {
			return nil, err
		}

		switch token.Type {
		case Comment:
			continue
		case EOF:
			return m, nil
		case Element:
			if currSection != nil {
				currSection[token.Key] = token.Value
			}
		case Section:
			if sectionFlag {
				if section == token.Value {
					currSection = m
				} else {
					currSection = nil
				}
			} else {
				currSection = make(map[string]interface{})
				m[token.Value] = currSection
			}
		default:
			return nil, errors.New("未知的元素类型")
		}
	}
	return m, nil
}

// 将data转换成struct
func Unmarshal(data []byte, v interface{}) error {
	return unmarshal(NewReaderBytes(data), v)
}

func unmarshal(r *Reader, curr reflect.Value, parent interface{}) error {
	if curr.Kind() == reflect.Ptr {
		curr = curr.Elem()
	}

	k := curr.Kind()

	if k != reflect.Map && k != reflect.Struct {
		return fmt.Errorf("无效的目标类型[%v]", k)
	}

	for {
		token, err := r.Token()
		if err != nil {
			return err
		}

		switch token.Type {
		case EOF:
			return nil
		case Element:
			if k == reflect.Map {
				if err := setMapElement(curr, token); err != nil {
					return err
				}
			} else {
				if err := setStructElement(curr, token); err != nil {
					return err
				}
			}
		case Section:
			if parent == nil {
				parent = curr.Interface()
			}

			// find section
		}
	}
}

var mapType = reflect.TypeOf(map[string]interface{}{})

// 将一个token的元素保存到map中。
//
// 供unmarshal函数调用，需要确保v的类型为reflect.Map(指针都不行)，Token.Type为Element
func setMapElement(v reflect.Value, token *Token) error {
	if v.IsValid() {
		return errors.New("无效的map值")
	}

	if !v.CanSet() {
		return errors.New("无法使用指针的指针")
	}

	if v.IsNil() { // map是个nil，则给它初始化。
		v = reflect.MakeMap(mapType)
	}

	v.SetMapIndex(reflect.ValueOf(token.Key), reflect.ValueOf(token.Value))

	return nil
}

func setStructElement(v reflect.Value, token *Token) error {
	if v.IsValid() {
		return errors.New("无效的map值")
	}

	if !v.CanSet() {
		return errors.New("无法使用指针的指针")
	}

	if v.IsNil() { // 若为nil，则给它初始化。
		v = reflect.New(v.Type())
	}

	f := v.FieldByName(token.Key)
	if !f.CanSet() {
		return errors.New("无法设置值")
	}

	// todo conv.conv(...)
	if f.Kind() == reflect.String {
		f.SetString(token.Value)
		return nil
	}

	return nil
}
<|endoftext|>